Commit cfa6b2b3 authored by Marcello Pivanti's avatar Marcello Pivanti
Browse files

Initial Commit

parents
Amos Workflow
========
The module Workflow is used in order to manage workflow status transitions on records useing a workflow.
Amos Workflow is based on:
- raoul2000/yii2-workflow
- cornernote/yii2-workflow-manager
Installation
------------
1. The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
Either run
```bash
composer require elitedivision/amos-workflow
```
or add this row
```
"elitedivision/amos-workflow": "dev-master"
```
to the require section of your `composer.json` file.
2. Add module to your modules config in backend:
```php
'modules' => [
'workflow' => [
'class' => 'elitedivision\amos\workflow\AmosWorkflow',
],
],
```
3. Apply migrations
a. amos-workflow migrations
```bash
php yii migrate/up --migrationPath=@vendor/elitedivision/amos-workflow/src/migrations
```
or add this row to your migrations config in console:
```php
return [
.
.
.
'@vendor/elitedivision/amos-workflow/src/migrations',
.
.
.
];
```
Configuration
------------
//TODO translate-fix this section
Ogni cambio di stato del workflow viene intercettato come evento e viene scritta un record di log nella tabella workflow_transition_log (vedere *WorkflowLogFunctionsBehavior*).
**Define model workflow**
Il workflow classico in amos4 comprende gli stati:
* bozza /draft
* da validare/richiesta pubblicazione
* validato/pubblicato
Per automatizzare/standardizzare alcune operazioni è stata creata in amos-core l'interfaccia WorkflowModelInterface che è bene implementare, insieme ad estendere NotifyRecord per beneficiare ad esempio delle mail automatiche alla richiesta di validazione del modello.
Il workflow del model viene definito tramite il popolamento delle tabelle:
* sw_workflow:
definizione id del workflow, per convenzione in amos 4 è il nome del model seguito da 'Workflow' es. 'NewsWorkflow'.
Definizione dello stato iniziale di default, eventualmente modificabile da interfaccia o tramite migration. Es initial_status_id = 'DRAFT'
* sw_status:
Definizione degli stati del workflow. Lo stato verrà salvato (come convenzione) nel campo status del model concatendo Workflow_id / status_id ad esempio 'NewsWorkflow/BOZZA'
* sw_transition:
definizione delle transizioni, ossia di tutti i possibili passaggi di stato.
IMPORTANTE: l'errore non esiste la transizione tra stato A e stato B che potreste riscontrare, se avete definito la transizione è in realtà dovuto alla mancanza del permesso dell'utente salvare il modello in quello stato.
* sw_metadata:
per ogni stato del workflow definisce label sui bottoni di cambio stato, testi dei popup, classi css, ecc.
E' possibile anche commentare il passaggio di stato e salvare tale nota/commento nei log di passaggio di stato.
To enable workflow event behavior, insert in your model behavior array eg.
```php
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
.
.
.
'workflow' => [
'class' => SimpleWorkflowBehavior::className(),
'defaultWorkflowId' => self::NEWS_WORKFLOW,
'propagateErrorsToModel' => true
]
];
}
```
Widgets
-----------
Amos Workflow provides three Widgets:
* **WorkflowTransitionButtonsWidget**
* **WorkflowTransitionStateDescriptorWidget**
//TODO explain/example of use for new widgets above
* **WorkflowTransitionWidget** *elitedivision\amos\workflow\widgets\WorkflowTransitionWidget*
Draws a section containing model current status and the buttons with possible status to change starting from the current one (reading from sw_metadata).
***Comment / notes on status change***
If needed, it is possible to show a popup to insert comment/notes on status change; to enable the functionality add in sw_metadata for the transition final status the metadata: key ='comment', value = 1
It is possible to use a global parameter to hide all transition widgets, if the model workflows are bypassed.
Insert between your application backend params array:
```php
return [
.
.
.
'hideWorkflowTransitionWidget' => true
.
.
.
];
```
example of use in a form:
```php
<?= WorkflowTransitionWidget::widget([
'form' => $form,
'model' => $model,
'workflowId' => ShowcaseProject::SHOWCASEPROJECT_WORKFLOW,
'classDivIcon' => 'pull-left',
'classDivMessage' => 'pull-left message',
'viewWidgetOnNewRecord' => true,
'translationCategory' => 'amosshowcaseprojects'
]); ?>
```
{
"name": "arter/amos-workflow",
"description": "Sistema AMOS per il workflow",
"keywords": [
"amos",
"yii2",
"workflow"
],
"homepage": "http://www.elitedivision.it/",
"type": "component",
"license": "BSD-3-Clause",
"support": {
"issues": "http://www.example.com/",
"forum": "http://www.example.com/forum/",
"wiki": "http://www.example.com/wiki/",
"source": "https://github.com/elitedivision/amos-workflow"
},
"minimum-stability": "stable",
"require": {
"php": ">=5.4.0",
"arter/amos-core": "^1.9.50",
"raoul2000/yii2-workflow": "^1.1.0",
"cornernote/yii2-workflow-manager": "^1.0.1"
},
"extra": {
"asset-installer-paths": {
"npm-asset-library": "vendor/npm",
"bower-asset-library": "vendor/bower"
}
},
"autoload": {
"psr-4": {
"elitedivision\\amos\\workflow\\": "src"
}
}
}
\ No newline at end of file
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
*
* @package elitedivision\amos\workflow
* @category CategoryName
* @author Elite Division S.r.l.
*/
namespace elitedivision\amos\workflow;
use elitedivision\amos\core\module\Module;
use elitedivision\amos\core\module\ModuleInterface;
use elitedivision\amos\core\record\Record;
use elitedivision\amos\workflow\components\events\SimpleWorkFlowEventsListener;
use raoul2000\workflow\base\SimpleWorkflowBehavior;
use Yii;
use yii\base\Event;
/**
* Class AmosWorkflow
* @package elitedivision\amos\workflow
*/
class AmosWorkflow extends Module implements ModuleInterface
{
public static $CONFIG_FOLDER = 'config';
/**
* @var string|boolean the layout that should be applied for views within this module. This refers to a view name
* relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]]
* will be taken. If this is false, layout will be disabled within this module.
*/
public $layout = 'main';
public $name = 'Workflow';
public static function getModuleName()
{
return "workflow";
}
public function init()
{
parent::init();
// initialize the module with the configuration loaded from config.php
Yii::configure($this, require(__DIR__ . DIRECTORY_SEPARATOR . self::$CONFIG_FOLDER . DIRECTORY_SEPARATOR . 'config.php'));
Event::on(Record::className(), SimpleWorkflowBehavior::EVENT_AFTER_CHANGE_STATUS, [SimpleWorkFlowEventsListener::className(), 'afterChangeStatus']);
}
public function getWidgetIcons()
{
return [];
}
public function getWidgetGraphics()
{
return [];
}
}
\ No newline at end of file
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
*
* @package elitedivision\amos\workflow\behaviors
* @category CategoryName
* @author Elite Division S.r.l.
*/
namespace elitedivision\amos\workflow\behaviors;
use elitedivision\amos\core\record\Record;
use elitedivision\amos\workflow\components\events\SimpleWorkFlowEventsListener;
use elitedivision\amos\workflow\models\WorkflowTransitionsLog;
use yii\base\Behavior;
use yii\helpers\Json;
/**
* Class WorkflowLogFunctionsBehavior
* @package elitedivision\amos\workflow\behaviors
*/
class WorkflowLogFunctionsBehavior extends Behavior
{
/**
* @var string $changeStatusComment - attribute for model to send comment data on status change
*/
public $changeStatusComment;
public function getStatusLastUpdateTime($status)
{
/** @var Record $owner */
$owner = $this->owner;
return SimpleWorkFlowEventsListener::getLastUpdateTime($owner->className(), $owner->primaryKey, $status);
}
public function getStatusLastUpdateUser($status)
{
/** @var Record $owner */
$owner = $this->owner;
return SimpleWorkFlowEventsListener::getLastUpdateUser($owner->className(), $owner->primaryKey, $status);
}
public function getStatusFirstUpdateUser($status)
{
/** @var Record $owner */
$owner = $this->owner;
return SimpleWorkFlowEventsListener::getFirstUpdateUser($owner->className(), $owner->primaryKey, $status);
}
/**
* @param $status string, the status of SimpleWorkflowBehavior
* @return null|number (user)
*/
public function getStatusLastUpdateComment($status)
{
/** @var Record $owner */
$owner = $this->owner;
$class = $owner->className();
$primaryKey = $owner->primaryKey;
$primaryKeyJson = Json::encode($primaryKey);
$logList = WorkflowTransitionsLog::find()->andWhere([
'classname' => $class,
'owner_primary_key' => $primaryKeyJson,
'end_status' => $status
])->orderBy('created_at DESC')->all();
if (count($logList) >= 1) {
/** @var WorkflowTransitionsLog $wtl */
$wtl = reset($logList);
return isset($wtl->comment) ? $wtl->comment : null;
}
return null;
}
public function getStatusUpdateHistory()
{
/** @var Record $owner */
$owner = $this->owner;
return SimpleWorkFlowEventsListener::getUpdateHistory($owner->className(), $owner->primaryKey);
}
}
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
*
* @package elitedivision\amos\workflow
* @category CategoryName
* @author Elite Division S.r.l.
*/
namespace elitedivision\amos\workflow\components\events;
use elitedivision\amos\workflow\models\WorkflowTransitionsLog;
use elitedivision\amos\core\record\Record;
use raoul2000\workflow\events\WorkflowEvent;
use yii\base\Component;
use yii\db\ActiveQuery;
use yii\helpers\Json;
class SimpleWorkFlowEventsListener extends Component
{
/**
* @param $class string the classname on entity
* @param $primaryKey mixed number|array
* @param $status string, the status of SimpleWorkflowBehavior
* @return null|string (datetime)
*/
public static function getLastUpdateTime($class, $primaryKey, $status)
{
$primaryKeyJson = Json::encode($primaryKey);
$logList = WorkflowTransitionsLog::find()->andWhere([
'classname' => $class,
'owner_primary_key' => $primaryKeyJson,
'end_status' => $status
])->orderBy('created_at DESC')->all();
if (count($logList) >= 1) {
/** @var WorkflowTransitionsLog $wtl */
$wtl = reset($logList);
return isset($wtl->created_at) ? $wtl->created_at : null;
}
return null;
}
/**
* @param $class string the classname on entity
* @param $primaryKey mixed number|array
* @param $status string, the status of SimpleWorkflowBehavior
* @return null|number (user)
*/
public static function getLastUpdateUser($class, $primaryKey, $status)
{
$primaryKeyJson = Json::encode($primaryKey);
$logList = WorkflowTransitionsLog::find()->andWhere([
'classname' => $class,
'owner_primary_key' => $primaryKeyJson,
'end_status' => $status
])->orderBy('created_at DESC')->all();
if (count($logList) >= 1) {
/** @var WorkflowTransitionsLog $wtl */
$wtl = reset($logList);
return isset($wtl->created_by) ? $wtl->created_by : null;
}
return null;
}
/**
* @param $class string the classname on entity
* @param $primaryKey mixed number|array
* @param $status string, the status of SimpleWorkflowBehavior
* @return null|number (user)
*/
public static function getFirstUpdateUser($class, $primaryKey, $status)
{
$primaryKeyJson = Json::encode($primaryKey);
$logList = WorkflowTransitionsLog::find()->andWhere([
'classname' => $class,
'owner_primary_key' => $primaryKeyJson,
'end_status' => $status
])->orderBy('created_at ASC')->all();
if (count($logList) >= 1) {
/** @var WorkflowTransitionsLog $wtl */
$wtl = reset($logList);
return isset($wtl->created_by) ? $wtl->created_by : null;
}
return null;
}
/**
* @param $class string the classname on entity
* @param $primaryKey mixed number|array
* @param $status string, the status of SimpleWorkflowBehavior
* @return array WorkflowTransitionsLog[]
*/
public static function getUpdateHistory($class, $primaryKey)
{
$primaryKeyJson = Json::encode($primaryKey);
/** @var ActiveQuery $logListQ */
$logListQ = WorkflowTransitionsLog::find()->andWhere([
'classname' => $class,
'owner_primary_key' => $primaryKeyJson
])->orderBy('created_at ASC');
$logList = $logListQ->all();
if (count($logList) > 0) {
return is_array($logList) ? $logList : [];
}
return [];
}
/**
* @param $event WorkflowEvent
* @return bool
*/
public static function afterChangeStatus($event)
{
/** @var Record $owner */
$owner = $event->sender->owner;
if (!empty($owner)) {
$post = [];
$startStatus = $event->getStartStatus();
$endStatus = $event->getEndStatus();
$workflowTransitionLog = new WorkflowTransitionsLog;
//check if any comment has been typed on status change
if (isset($_POST[$owner->formName()])) {
$post = $_POST[$owner->formName()];
}
if (count($post)) {
if (array_key_exists('changeStatusComment', $post)) {
$comment = $post['changeStatusComment'];
if(!empty($comment)) {
$workflowTransitionLog->comment = $comment;
}
}
}
$workflowTransitionLog->classname = $owner->className();
$workflowTransitionLog->owner_primary_key = Json::encode($owner->getPrimaryKey());
if (!empty($startStatus)) {
$workflowTransitionLog->start_status = $startStatus->getId();
}
$workflowTransitionLog->end_status = $endStatus->getId();
$workflowTransitionLog->save(false);
}
return true;
}
}
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
*
* @package elitedivision\amos\workflow
* @category CategoryName
* @author Elite Division S.r.l.
*/
return [
];
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
*
* @package elitedivision\amos\workflow\migrations
* @category CategoryName
* @author Elite Division S.r.l.
*/
use yii\db\Migration;
class m170519_134656_create_workflow_transitions_log extends Migration
{
const TABLE = '{{%amos_workflow_transitions_log}}';
public function safeUp()
{
$tableName = $this->db->getSchema()->getRawTableName(self::TABLE);
if ($this->db->schema->getTableSchema(self::TABLE, true) === null) {
try {
$this->createTable(self::TABLE, [
'id' => $this->primaryKey(),
'classname' => $this->text(255)->defaultValue(null)->comment('Name of the class'),
'owner_primary_key' => $this->text(255)->defaultValue(null)->comment('Primary key'),
'start_status' => $this->text(255)->defaultValue(null)->comment('Start status'),
'end_status' => $this->text(255)->defaultValue(null)->comment('End status'),
'created_at' => $this->dateTime()->null()->defaultValue(null),
'updated_at' => $this->dateTime()->null()->defaultValue(null),
'deleted_at' => $this->dateTime()->null()->defaultValue(null),
'created_by' => $this->integer()->null()->defaultValue(null),
'updated_by' => $this->integer()->null()->defaultValue(null),
'deleted_by' => $this->integer()->null()->defaultValue(null),
], $this->db->driverName === 'mysql' ? 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB AUTO_INCREMENT=1' : null);
} catch (Exception $e) {
echo "Error on creation " . $tableName . "\n";
echo $e->getMessage() . "\n";
return false;
}
} else {
echo "Table already exist " . $tableName . "\n";
}
return true;
}
public function safeDown()
{
try {
// $this->execute("SET FOREIGN_KEY_CHECKS = 0;");
$this->dropTable(self::TABLE);
// $this->execute("SET FOREIGN_KEY_CHECKS = 1;");
} catch (Exception $e) {
echo "Error on drop table " . self::TABLE . "\n";
echo $e->getMessage() . "\n";
return false;
}
return true;
}
}
\ No newline at end of file
<?php
/**
* Art-ER Attrattività, ricerca e territorio dell’Emilia-Romagna
* OPEN 2.0
*
*
* @package elitedivision\amos\workflow\migrations
* @category CategoryName
* @author Elite Division S.r.l.
*/
use yii\db\Migration;
/**
* Class m180301_103841_add_change_status_workflow_log_comment
*/
class m180301_103841_add_change_status_workflow_log_comment extends Migration
{
const TABLE = '{{%amos_workflow_transitions_log}}';
/**
* @inheritdoc
*/
public function safeUp()
{
$this->addColumn(self::TABLE, 'comment', $this->text()->defaultValue(null)->after('end_status')->comment('Change status comment'));
return true;
}
/**
* @inheritdoc
*/
public function safeDown()
{
$this->dropColumn(self::TABLE, 'comment');
return true;
}
}