crash backup
Some checks failed
Image optimization / general_image_workflow (push) Has been cancelled
CI / general_module_workflow_php (push) Has been cancelled
CI / general_module_workflow_js (push) Has been cancelled

This commit is contained in:
Dennis Eichhorn 2025-03-21 02:48:21 +00:00
parent 0fe6db40b6
commit 6ee722b038
19 changed files with 745 additions and 61 deletions

View File

@ -1,18 +1,18 @@
{
"triggers": [
"PRE:Module:Workflow:workflow_template-create",
"POST:Module:Workflow:workflow_template-create",
"PRE:Module:Workflow:workflow_template-update",
"POST:Module:Workflow:workflow_template-update",
"PRE:Module:Workflow:workflow_template-delete",
"POST:Module:Workflow:workflow_template-delete",
"PRE:Workflow:workflow_template-create",
"POST:Workflow:workflow_template-create",
"PRE:Workflow:workflow_template-update",
"POST:Workflow:workflow_template-update",
"PRE:Workflow:workflow_template-delete",
"POST:Workflow:workflow_template-delete",
"PRE:Module:Workflow:workflow_instance-create",
"POST:Module:Workflow:workflow_instance-create",
"PRE:Module:Workflow:workflow_instance-update",
"POST:Module:Workflow:workflow_instance-update",
"PRE:Module:Workflow:workflow_instance-delete",
"POST:Module:Workflow:workflow_instance-delete"
"PRE:Workflow:workflow_instance-create",
"POST:Workflow:workflow_instance-create",
"PRE:Workflow:workflow_instance-update",
"POST:Workflow:workflow_instance-update",
"PRE:Workflow:workflow_instance-delete",
"POST:Workflow:workflow_instance-delete"
],
"actions": {
"1005500001": {
@ -270,9 +270,9 @@
"default": null,
"pattern": null,
"examples": [
"PRE:Module:Billing:bill-create",
"PRE:Module:Billing:bill-update.*",
"PRE:Module:Billing:bill-update-{$id}"
"PRE:Billing:bill-create",
"PRE:Billing:bill-update.*",
"PRE:Billing:bill-update-{$id}"
],
"required": true,
"title": {

View File

@ -15,6 +15,11 @@
"default": null,
"null": true
},
"workflow_template_type": {
"name": "workflow_template_type",
"type": "INT",
"null": false
},
"workflow_template_status": {
"name": "workflow_template_status",
"type": "INT",
@ -38,6 +43,14 @@
"default": null,
"null": true
},
"workflow_template_module": {
"name": "workflow_template_module",
"type": "VARCHAR(190)",
"null": true,
"default": null,
"foreignTable": "module",
"foreignKey": "module_id"
},
"workflow_template_media": {
"name": "workflow_template_media",
"type": "INT",
@ -73,8 +86,7 @@
"workflow_instance_title": {
"name": "workflow_instance_title",
"type": "VARCHAR(255)",
"default": null,
"null": true
"null": false
},
"workflow_instance_status": {
"name": "workflow_instance_status",
@ -86,6 +98,16 @@
"type": "TEXT",
"null": false
},
"workflow_instance_int": {
"name": "workflow_instance_int",
"type": "INT",
"null": false
},
"workflow_instance_ref": {
"name": "workflow_instance_ref",
"type": "INT",
"null": false
},
"workflow_instance_template": {
"name": "workflow_instance_template",
"type": "INT",
@ -112,5 +134,64 @@
"foreignKey": "account_id"
}
}
},
"workflow_step": {
"name": "workflow_step",
"fields": {
"workflow_step_id": {
"name": "workflow_step_id",
"type": "INT",
"null": false,
"primary": true,
"autoincrement": true
},
"workflow_step_order": {
"name": "workflow_step_order",
"type": "INT",
"null": false
},
"workflow_step_status": {
"name": "workflow_step_status",
"type": "INT",
"null": false
},
"workflow_step_comment": {
"name": "workflow_step_comment",
"type": "TEXT",
"null": false
},
"workflow_step_data": {
"name": "workflow_step_data",
"type": "TEXT",
"null": false
},
"workflow_step_instance": {
"name": "workflow_step_instance",
"type": "INT",
"null": false,
"foreignTable": "workflow_instance",
"foreignKey": "workflow_instance_id"
},
"workflow_step_media": {
"name": "workflow_step_media",
"type": "INT",
"null": true,
"default": null,
"foreignTable": "media",
"foreignKey": "media_id"
},
"workflow_step_created_at": {
"name": "workflow_step_created_at",
"type": "DATETIME",
"null": false
},
"workflow_step_created_by": {
"name": "workflow_step_created_by",
"type": "INT",
"null": false,
"foreignTable": "account",
"foreignKey": "account_id"
}
}
}
}

View File

@ -104,39 +104,16 @@ final class Installer extends InstallerAbstract
self::createTriggers($apiApp, $workflowData['triggers'] ?? []);
self::createActions($apiApp, $workflowData['actions'] ?? []);
self::createWorkflows($apiApp, $workflowData['workflows'] ?? []);
// Workflows possible to also define in the ui
self::installWorkflow($apiApp, $workflowData['workflows'] ?? []);
// Workflows only programmable
self::installWorkflow($apiApp, $workflowData['templates'] ?? []);
return [];
}
/**
* Create a workflow template
*
* @param ApplicationAbstract $app Application
* @param array $data Workflow schemas
*
* @return void
*
* @since 1.0.0
*/
private static function createWorkflows(ApplicationAbstract $app, array $data) : void
{
/** @var \Modules\Workflow\Controller\ApiController $module */
$module = $app->moduleManager->get('Workflow');
foreach ($data as $name => $workflow) {
$response = new HttpResponse();
$request = new HttpRequest();
$request->header->account = 1;
$request->setData('name', $name);
$request->setData('schema', \json_encode($workflow));
$module->apiWorkflowTemplateCreate($request, $response);
}
}
/**
* Install trigger.
*
@ -216,12 +193,23 @@ final class Installer extends InstallerAbstract
/** @var \Modules\Workflow\Controller\ApiController $module */
$module = $app->moduleManager->get('Workflow');
foreach ($data as $template) {
foreach ($data as $id => $template) {
$response = new HttpResponse();
$request = new HttpRequest();
$request->header->account = 1;
$request->setData('name', $template['name']);
if (($temlate['name'] ?? null) !== null) {
$request->setData('name', $template['name']);
} else {
$request->setData('name', $id);
$request->setData('schema', \json_encode($template));
}
if (($template['path'] ?? null) == null) {
$module->apiWorkflowTemplateCreate($request, $response);
continue;
}
$tempPath = __DIR__ . '/../../../temp/';

View File

@ -16,7 +16,42 @@ omsApp.Modules.Workflow = class {
bind (id)
{
mermaid.initialize({ startOnLoad: true });
const mermaidElements = document.querySelectorAll('.mermaid');
if (mermaidElements.length === 0) return; // Exit if no .mermaid elements are found
mermaidElements.forEach((mermaidElement) => {
const observer = new MutationObserver((mutationsList, observer) => {
if (mermaidElement.offsetParent !== null) {
initializeMermaid();
observer.disconnect();
}
});
observer.observe(mermaidElement, {
attributes: true,
attributeFilter: ['style', 'class'],
});
});
function initializeMermaid() {
if (typeof mermaid !== 'undefined') {
mermaid.run({
querySelector: '.mermaid',
postRenderCallback: (id) => {
const svgs = d3.selectAll('.mermaid svg');
svgs.each(function () {
const svg = d3.select(this);
svg.html('<g>' + svg.html() + '</g>');
const inner = svg.select('g');
const zoom = d3.zoom().on('zoom', function (event) {
inner.attr('transform', event.transform);
});
svg.call(zoom);
});
},
});
}
}
};
bindElement (chart)

View File

@ -23,6 +23,7 @@ use Modules\Workflow\Models\WorkflowInstanceAbstract;
use Modules\Workflow\Models\WorkflowInstanceAbstractMapper;
use Modules\Workflow\Models\WorkflowTemplate;
use Modules\Workflow\Models\WorkflowTemplateMapper;
use Modules\Workflow\Models\WorkflowTemplateStatus;
use phpOMS\Account\PermissionType;
use phpOMS\Autoloader;
use phpOMS\DataStorage\Database\Schema\Builder as SchemaBuilder;
@ -98,7 +99,7 @@ final class ApiController extends Controller
->with('template/source')
->with('template/source/sources')
->with('createdBy')
->where('id', (int) $request->getData('id'))
->where('id', $request->getDataInt('id') ?? 0)
->execute();
$accountId = $request->header->account;
@ -165,7 +166,7 @@ final class ApiController extends Controller
private function setWorkflowResponseHeader(View $view, string $name, RequestAbstract $request, ResponseAbstract $response) : void
{
/** @var array{lang?:\Modules\Media\Models\Media, cfg?:\Modules\Media\Models\Media, excel?:\Modules\Media\Models\Media, word?:\Modules\Media\Models\Media, powerpoint?:\Modules\Media\Models\Media, pdf?:\Modules\Media\Models\Media, csv?:\Modules\Media\Models\Media, json?:\Modules\Media\Models\Media, template?:\Modules\Media\Models\Media, css?:array<string, \Modules\Media\Models\Media>, js?:array<string, \Modules\Media\Models\Media>, db?:array<string, \Modules\Media\Models\Media>, other?:array<string, \Modules\Media\Models\Media>} $tcoll */
$tcoll = $view->getData('tcoll') ?? [];
$tcoll = $view->data['tcoll'] ?? [];
switch ($request->getData('type')) {
case 'pdf':
@ -669,7 +670,7 @@ final class ApiController extends Controller
$template = WorkflowTemplateMapper::get()
->with('source')
->with('source/sources')
->where('id', (int) $request->getData('template'))
->where('id', $request->getDataInt('template'))
->execute();
$instance = $this->createInstanceFromRequest($request, $template);
@ -739,4 +740,143 @@ final class ApiController extends Controller
public function apiWorkflowImport(HttpRequest $request, HttpResponse $response, mixed $data = null) : void
{
}
/**
* Api method to handle the bill workflow
*
* @param int $account Account who created the model
* @param mixed $old Old value
* @param mixed $new New value (unused, should be null)
* @param int $type Module model type
* @param string $trigger What triggered this log?
* @param string $module Module name
* @param string $ref Reference to other model
* @param string $content Message
* @param string $ip Ip
*
* @return mixed
*
* @since 1.0.0
*/
public function hookWorkflowChangeState(
int $account,
mixed $old,
mixed $new,
?int $type = null,
string $trigger = '',
?string $module = null,
?string $ref = null,
?string $content = null,
?string $ip = null
) : mixed
{
$template = WorkflowTemplateMapper::get()
->with('source')
->with('source/sources')
->where('module', $module)
->where('type', $type ?? 0)
->executeGet();
if ($template->id === 0) {
$template = WorkflowTemplateMapper::get()
->with('source')
->with('source/sources')
->where('module', $module)
->executeGet();
}
if ($template->id === 0 || $template->status != WorkflowTemplateStatus::ACTIVE) {
return null;
}
require_once $template->source->findFile('WorkflowController.php')->getPath();
/** @var WorkflowControllerInterface $controller */
$controller = new \Modules\Workflow\Controller\WorkflowController($this->app, $template);
return $controller->hookChangeState(
$template,
$account,
$old,
$new,
$type,
$trigger,
$module,
$ref,
$content,
$ip
);
}
/**
* Api method to handle the bill workflow
*
* @param int $account Account who created the model
* @param mixed $old Old value
* @param mixed $new New value (unused, should be null)
* @param int $type Module model type
* @param string $trigger What triggered this log?
* @param string $module Module name
* @param string $ref Reference to other model
* @param string $content Message
* @param string $ip Ip
*
* @return mixed
*
* @since 1.0.0
*/
public function apiWorkflowHandleState(
int $account,
mixed $old,
mixed $new,
?int $type = null,
string $trigger = '',
?string $module = null,
?string $ref = null,
?string $content = null,
?string $ip = null
) : mixed
{
$template = WorkflowTemplateMapper::get()
->with('source')
->with('source/sources')
->where('module', $module)
->where('type', $type ?? 0)
->executeGet();
if ($template->id === 0) {
$template = WorkflowTemplateMapper::get()
->with('source')
->with('source/sources')
->where('module', $module)
->executeGet();
}
if ($template->id === 0 || $template->status != WorkflowTemplateStatus::ACTIVE) {
return null;
}
require_once $template->source->findFile('WorkflowController.php')->getPath();
$response = new HttpResponse();
$request = new HttpRequest();
$request->header->account = $account;
/** @var WorkflowControllerInterface $controller */
$controller = new \Modules\Workflow\Controller\WorkflowController($this->app, $template);
return $controller->apiHandleState(
$request,
$response,
[
'template' => $template,
'old' => $old,
'new' => $new,
'type' => $type,
'trigger' => $trigger,
'module' => $module,
'ref' => $ref,
'content' => $content,
'ip' => $ip
]
);
}
}

View File

@ -103,6 +103,7 @@ final class BackendController extends Controller
$head = $response->data['Content']->head;
$nonce = $this->app->appSettings->getOption('script-nonce');
$head->addAsset(AssetType::JSLATE, 'Resources/d3/d3.min.js?v=' . $this->app->version, ['nonce' => $nonce]);
$head->addAsset(AssetType::JSLATE, 'Resources/mermaid/mermaid.min.js?v=' . $this->app->version, ['nonce' => $nonce]);
$head->addAsset(AssetType::JSLATE, 'Modules/Workflow/Controller.js?v=' . self::VERSION, ['nonce' => $nonce, 'type' => 'module']);
@ -114,7 +115,7 @@ final class BackendController extends Controller
->with('source')
->with('source/sources')
->with('createdBy')
->where('id', (int) $request->getData('id'))
->where('id', $request->getDataInt('id') ?? 0)
->execute();
$view->data['template'] = $template;
@ -217,7 +218,7 @@ final class BackendController extends Controller
$view = new View($this->app->l11nManager, $request, $response);
$view->data['instance'] = WorkflowInstanceAbstractMapper::get()
->where('id', (int) $request->getData('id'))
->where('id', $request->getDataInt('id') ?? 0)
->execute();
if ($view->data['instance']->id === 0) {

View File

@ -49,9 +49,15 @@ final class CliController extends Controller
*/
public function runWorkflowFromHook(mixed ...$data) : void
{
// @performance This seems incredibly bad.
// We are loading always ALL workflows to find the correct one
/** @var \Modules\Workflow\Models\WorkflowTemplate[] $workflows */
$workflows = WorkflowTemplateMapper::getAll()->where('status', WorkflowStatus::ACTIVE)->executeGetArray();
$workflows = WorkflowTemplateMapper::getAll()
->where('status', WorkflowStatus::ACTIVE)
->executeGetArray();
foreach ($workflows as $workflow) {
// @todo This isn't even implemented (see getHooks function, which is empty)
$hooks = $workflow->getHooks();
foreach ($hooks as $hook) {

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
["PRE:Module:Workflow:workflow_template-create","POST:Module:Workflow:workflow_template-create","PRE:Module:Workflow:workflow_template-update","POST:Module:Workflow:workflow_template-update","PRE:Module:Workflow:workflow_template-delete","POST:Module:Workflow:workflow_template-delete","PRE:Module:Workflow:workflow_instance-create","POST:Module:Workflow:workflow_instance-create","PRE:Module:Workflow:workflow_instance-update","POST:Module:Workflow:workflow_instance-update","PRE:Module:Workflow:workflow_instance-delete","POST:Module:Workflow:workflow_instance-delete","PRE:Module:Billing:bill_media-create","POST:Module:Billing:bill_media-create","PRE:Module:Billing:bill_media-update","POST:Module:Billing:bill_media-update","PRE:Module:Billing:bill_media-delete","POST:Module:Billing:bill_media-delete","PRE:Module:Billing:bill_note-create","POST:Module:Billing:bill_note-create","PRE:Module:Billing:bill_note-update","POST:Module:Billing:bill_note-update","PRE:Module:Billing:bill_note-delete","POST:Module:Billing:bill_note-delete"]
["PRE:Workflow:workflow_template-create","POST:Workflow:workflow_template-create","PRE:Workflow:workflow_template-update","POST:Workflow:workflow_template-update","PRE:Workflow:workflow_template-delete","POST:Workflow:workflow_template-delete","PRE:Workflow:workflow_instance-create","POST:Workflow:workflow_instance-create","PRE:Workflow:workflow_instance-update","POST:Workflow:workflow_instance-update","PRE:Workflow:workflow_instance-delete","POST:Workflow:workflow_instance-delete","PRE:Billing:bill_media-create","POST:Billing:bill_media-create","PRE:Billing:bill_media-update","POST:Billing:bill_media-update","PRE:Billing:bill_media-delete","POST:Billing:bill_media-delete","PRE:Billing:bill_note-create","POST:Billing:bill_note-create","PRE:Billing:bill_note-update","POST:Billing:bill_note-update","PRE:Billing:bill_note-delete","POST:Billing:bill_note-delete"]

View File

@ -14,9 +14,6 @@ A `States.php` file contains all workflow states. This is especially important i
The `Workflow.php` file is the heart of every workflow. This file is responsible for executing state driven actions and it can also be seen as the API for a workflow. All workflow related actions will be forwarded to this file and can be handled inside including database queries.
##
1. Workflow gets installed with a trigger (either hook or cron job)
2. Trigger is fired
3. Hook loads CliApplication::installWorkflowTemplate with template ID, action ID and Hook
@ -24,3 +21,22 @@ The `Workflow.php` file is the heart of every workflow. This file is responsible
5. Installer calls CliApplication::runWorkflow with template, action ID, Hook, instance, and element for every element on this level.
6. runWorkflow executes the element by calling the respective function
7. runWorkflow calls itself with all child elements
Results can be stored in the workflow_instance_data field or in a workflow_step_data. The step data is if you want to store information for a specific step of a workflow instance.
## Install file explanations
### Triggers
Which internal event triggers exists. This allows workflows to bind actions to any of these possible triggers.
### Actions
These are the workflow actions that the module exposes. These can be used to create your own workflow by chaining multiple actions together
## Workflow definition
1. You can define workflows based on the above mentioned triggers and actions
2. You can create programmed workflows using hooks which then trigger a workflow
3. You can create "forms" that function as workflows ()

View File

@ -0,0 +1,46 @@
<?php
/**
* Jingga
*
* PHP Version 8.2
*
* @package Modules\Workflow\Models
* @copyright Dennis Eichhorn
* @license OMS License 2.2
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Workflow\Models;
/**
* Null model
*
* @package Modules\Workflow\Models
* @license OMS License 2.2
* @link https://jingga.app
* @since 1.0.0
*/
final class NullWorkflowStep extends WorkflowStep
{
/**
* Constructor
*
* @param int $id Model id
*
* @since 1.0.0
*/
public function __construct(int $id = 0)
{
$this->id = $id;
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return ['id' => $this->id];
}
}

View File

@ -64,6 +64,52 @@ interface WorkflowControllerInterface
*/
public function apiChangeState(RequestAbstract $request, ResponseAbstract $response, $data = null) : void;
/**
* Change workflow instance state based on a hook
*
* @param WorkflowTemplate $template Workflow template
* @param int $account Account who created the model
* @param mixed $old Old value
* @param mixed $new New value (unused, should be null)
* @param int $type Module model type
* @param string $trigger What triggered this log?
* @param string $module Module name
* @param string $ref Reference to other model
* @param string $content Message
* @param string $ip Ip
*
* @return mixed
*
* @since 1.0.0
*/
public function hookChangeState(
WorkflowTemplate $template,
int $account,
mixed $old,
mixed $new,
?int $type = null,
string $trigger = '',
?string $module = null,
?string $ref = null,
?string $content = null,
?string $ip = null
) : mixed;
/**
* Handle workflow instance state
*
* This can be used to determine if a certain action is allowed or not based on the current state
*
* @param RequestAbstract $request Request
* @param ResponseAbstract $response Response
* @param null|mixed $data Data
*
* @return void
*
* @since 1.0.0
*/
public function apiHandleState(RequestAbstract $request, ResponseAbstract $response, $data = null) : void;
/**
* Store instance model in the database
*

View File

@ -25,7 +25,7 @@ use Modules\Admin\Models\NullAccount;
* @link https://jingga.app
* @since 1.0.0
*/
class WorkflowInstanceAbstract
class WorkflowInstanceAbstract implements \JsonSerializable
{
/**
* ID.
@ -51,6 +51,22 @@ class WorkflowInstanceAbstract
*/
public string $data = '';
/**
* Instance data.
*
* @var int
* @since 1.0.0
*/
public int $data_int = 0;
/**
* Reference.
*
* @var int
* @since 1.0.0
*/
public int $ref = 0;
/**
* Instance status.
*
@ -67,6 +83,14 @@ class WorkflowInstanceAbstract
*/
public WorkflowTemplate $template;
/**
* Workflow steps.
*
* @var WorkflowStep[]
* @since 1.0.0
*/
public array $steps = [];
/**
* Creator.
*
@ -102,4 +126,27 @@ class WorkflowInstanceAbstract
$this->createdBy = new NullAccount();
$this->createdAt = new \DateTimeImmutable('now');
}
/**
* {@inheritdoc}
*/
public function toArray() : array
{
return [
'id' => $this->id,
'title' => $this->title,
'createdAt' => $this->createdAt,
'data' => $this->data,
'data_int' => $this->data_int,
'ref' => $this->ref,
];
}
/**
* {@inheritdoc}
*/
public function jsonSerialize() : mixed
{
return $this->toArray();
}
}

View File

@ -41,6 +41,8 @@ final class WorkflowInstanceAbstractMapper extends DataMapperFactory
'workflow_instance_title' => ['name' => 'workflow_instance_title', 'type' => 'string', 'internal' => 'title'],
'workflow_instance_status' => ['name' => 'workflow_instance_status', 'type' => 'int', 'internal' => 'status'],
'workflow_instance_data' => ['name' => 'workflow_instance_data', 'type' => 'string', 'internal' => 'data'],
'workflow_instance_int' => ['name' => 'workflow_instance_int', 'type' => 'int', 'internal' => 'data_int'],
'workflow_instance_ref' => ['name' => 'workflow_instance_ref', 'type' => 'int', 'internal' => 'ref'],
'workflow_instance_template' => ['name' => 'workflow_instance_template', 'type' => 'int', 'internal' => 'template'],
'workflow_instance_created_at' => ['name' => 'workflow_instance_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true],
'workflow_instance_created_by' => ['name' => 'workflow_instance_created_by', 'type' => 'int', 'internal' => 'createdBy', 'readonly' => true],
@ -63,6 +65,21 @@ final class WorkflowInstanceAbstractMapper extends DataMapperFactory
],
];
/**
* Has many relation.
*
* @var array<string, array{mapper:class-string, table:string, self?:?string, external?:?string, column?:string}>
* @since 1.0.0
*/
public const HAS_MANY = [
'steps' => [
'mapper' => WorkflowStepMapper::class,
'table' => 'workflow_step',
'self' => 'workflow_step_instance',
'external' => null,
],
];
/**
* Primary table.
*

126
Models/WorkflowStep.php Normal file
View File

@ -0,0 +1,126 @@
<?php
/**
* Jingga
*
* PHP Version 8.2
*
* @package Modules\Workflow\Models
* @copyright Dennis Eichhorn
* @license OMS License 2.2
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Workflow\Models;
use Modules\Admin\Models\Account;
use Modules\Admin\Models\NullAccount;
use Modules\Media\Models\Collection;
use Modules\Media\Models\NullCollection;
/**
* Workflow template class.
*
* @package Modules\Workflow\Models
* @license OMS License 2.2
* @link https://jingga.app
* @since 1.0.0
*/
class WorkflowStep
{
/**
* ID.
*
* @var int
* @since 1.0.0
*/
public int $id = 0;
/**
* Order.
*
* @var int
* @since 1.0.0
*/
public int $order = 0;
/**
* Comment.
*
* @var string
* @since 1.0.0
*/
public string $comment = '';
/**
* Data.
*
* @var string
* @since 1.0.0
*/
public string $data = '';
/**
* Status.
*
* @var int
* @since 1.0.0
*/
public int $status = 0;
/**
* Creator.
*
* @var Account
* @since 1.0.0
*/
public Account $createdBy;
/**
* Created.
*
* @var \DateTimeImmutable
* @since 1.0.0
*/
public \DateTimeImmutable $createdAt;
/**
* Instance.
*
* @var WorkflowInstanceAbstract
* @since 1.0.0
*/
public WorkflowInstanceAbstract $instance;
/**
* Media.
*
* @var null|Collection
* @since 1.0.0
*/
public ?Collection $media = null;
/**
* Constructor.
*
* @since 1.0.0
*/
public function __construct()
{
$this->createdBy = new NullAccount();
$this->createdAt = new \DateTimeImmutable('now');
$this->instance = new NullWorkflowInstanceAbstract();
}
/**
* Get hooks
*
* @return array
* @since 1.0.0
*/
public function getHooks() : array
{
return [];
}
}

View File

@ -0,0 +1,107 @@
<?php
/**
* Jingga
*
* PHP Version 8.2
*
* @package Modules\Workflow\Models
* @copyright Dennis Eichhorn
* @license OMS License 2.2
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Workflow\Models;
use Modules\Admin\Models\AccountMapper;
use Modules\Admin\Models\ModuleMapper;
use Modules\Media\Models\CollectionMapper;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
/**
* WorkflowStep mapper class.
*
* @package Modules\Workflow\Models
* @license OMS License 2.2
* @link https://jingga.app
* @since 1.0.0
*
* @template T of WorkflowStep
* @extends DataMapperFactory<T>
*/
final class WorkflowStepMapper extends DataMapperFactory
{
/**
* Columns.
*
* @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
* @since 1.0.0
*/
public const COLUMNS = [
'workflow_step_id' => ['name' => 'workflow_step_id', 'type' => 'int', 'internal' => 'id'],
'workflow_step_status' => ['name' => 'workflow_step_status', 'type' => 'int', 'internal' => 'status'],
'workflow_step_order' => ['name' => 'workflow_step_order', 'type' => 'int', 'internal' => 'order'],
'workflow_step_type' => ['name' => 'workflow_step_type', 'type' => 'int', 'internal' => 'type'],
'workflow_step_comment' => ['name' => 'workflow_step_comment', 'type' => 'string', 'internal' => 'comment'],
'workflow_step_data' => ['name' => 'workflow_step_data', 'type' => 'string', 'internal' => 'data'],
'workflow_step_instance' => ['name' => 'workflow_step_instance', 'type' => 'Json', 'internal' => 'instance'],
'workflow_step_media' => ['name' => 'workflow_step_media', 'type' => 'int', 'internal' => 'media'],
'workflow_step_created_at' => ['name' => 'workflow_step_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true],
'workflow_step_created_by' => ['name' => 'workflow_step_created_by', 'type' => 'int', 'internal' => 'createdBy', 'readonly' => true],
];
/**
* Has one relation.
*
* @var array<string, array{mapper:class-string, external:string, by?:string, column?:string, conditional?:bool}>
* @since 1.0.0
*/
public const OWNS_ONE = [
'media' => [
'mapper' => CollectionMapper::class,
'external' => 'workflow_step_media',
],
];
/**
* Belongs to.
*
* @var array<string, array{mapper:class-string, external:string, column?:string, by?:string}>
* @since 1.0.0
*/
public const BELONGS_TO = [
'createdBy' => [
'mapper' => AccountMapper::class,
'external' => 'workflow_step_created_by',
],
'instance' => [
'mapper' => ModuleMapper::class,
'external' => 'workflow_step_instance',
],
];
/**
* Primary table.
*
* @var string
* @since 1.0.0
*/
public const TABLE = 'workflow_step';
/**
* Created at.
*
* @var string
* @since 1.0.0
*/
public const CREATED_AT = 'workflow_step_created_at';
/**
* Primary field name.
*
* @var string
* @since 1.0.0
*/
public const PRIMARYFIELD = 'workflow_step_id';
}

View File

@ -15,7 +15,9 @@ declare(strict_types=1);
namespace Modules\Workflow\Models;
use Modules\Admin\Models\Account;
use Modules\Admin\Models\Module;
use Modules\Admin\Models\NullAccount;
use Modules\Admin\Models\NullModule;
use Modules\Media\Models\Collection;
use Modules\Media\Models\NullCollection;
@ -45,6 +47,17 @@ class WorkflowTemplate
*/
public string $name = '';
/**
* Type.
*
* This is only for internal usage per module.
* Modules may use this to internally handle workflows
*
* @var int
* @since 1.0.0
*/
public int $type = 0;
/**
* Description.
*
@ -85,6 +98,14 @@ class WorkflowTemplate
*/
public \DateTimeImmutable $createdAt;
/**
* Module.
*
* @var null|Module
* @since 1.0.0
*/
public ?Module $module = null;
/**
* Template source.
*

View File

@ -15,6 +15,7 @@ declare(strict_types=1);
namespace Modules\Workflow\Models;
use Modules\Admin\Models\AccountMapper;
use Modules\Admin\Models\ModuleMapper;
use Modules\Media\Models\CollectionMapper;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
@ -40,10 +41,12 @@ final class WorkflowTemplateMapper extends DataMapperFactory
public const COLUMNS = [
'workflow_template_id' => ['name' => 'workflow_template_id', 'type' => 'int', 'internal' => 'id'],
'workflow_template_status' => ['name' => 'workflow_template_status', 'type' => 'int', 'internal' => 'status'],
'workflow_template_type' => ['name' => 'workflow_template_type', 'type' => 'int', 'internal' => 'type'],
'workflow_template_name' => ['name' => 'workflow_template_name', 'type' => 'string', 'internal' => 'name'],
'workflow_template_desc' => ['name' => 'workflow_template_desc', 'type' => 'string', 'internal' => 'description'],
'workflow_template_descRaw' => ['name' => 'workflow_template_descRaw', 'type' => 'string', 'internal' => 'descriptionRaw'],
'workflow_template_schema' => ['name' => 'workflow_template_schema', 'type' => 'Json', 'internal' => 'schema'],
'workflow_template_module' => ['name' => 'workflow_template_module', 'type' => 'string', 'internal' => 'module'],
'workflow_template_media' => ['name' => 'workflow_template_media', 'type' => 'int', 'internal' => 'source'],
'workflow_template_created_at' => ['name' => 'workflow_template_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true],
'workflow_template_created_by' => ['name' => 'workflow_template_created_by', 'type' => 'int', 'internal' => 'createdBy', 'readonly' => true],
@ -73,6 +76,10 @@ final class WorkflowTemplateMapper extends DataMapperFactory
'mapper' => AccountMapper::class,
'external' => 'workflow_template_created_by',
],
'module' => [
'mapper' => ModuleMapper::class,
'external' => 'workflow_template_module',
],
];
/**

View File

@ -27,7 +27,7 @@ $accountDir = $account->id . ' ' . $account->login;
/** @var \Modules\Media\Models\Collection[] */
$collections = $this->data['collections'];
$mediaPath = \urldecode($this->getData('path') ?? '/');
$mediaPath = \urldecode($this->data['path'] ?? '/');
$previous = empty($templates) ? 'workflow/template/list' : '{/base}/workflow/template/list?{?}&offset=' . \reset($templates)->id . '&ptype=p';
$next = empty($templates) ? 'workflow/template/list' : '{/base}/workflow/template/list?{?}&offset=' . \end($templates)->id . '&ptype=n';