From 6ee722b038555e0b3ca24e002d2f66accc6ca399 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Fri, 21 Mar 2025 02:48:21 +0000 Subject: [PATCH] crash backup --- Admin/Install/Workflow.install.json | 30 ++-- Admin/Install/db.json | 85 ++++++++++- Admin/Installer.php | 48 +++--- Controller.js | 37 ++++- Controller/ApiController.php | 146 ++++++++++++++++++- Controller/BackendController.php | 5 +- Controller/CliController.php | 8 +- Definitions/actions.json | 2 +- Definitions/triggers.json | 2 +- Docs/workflow_components.md | 22 ++- Models/NullWorkflowStep.php | 46 ++++++ Models/WorkflowControllerInterface.php | 46 ++++++ Models/WorkflowInstanceAbstract.php | 49 ++++++- Models/WorkflowInstanceAbstractMapper.php | 17 +++ Models/WorkflowStep.php | 126 ++++++++++++++++ Models/WorkflowStepMapper.php | 107 ++++++++++++++ Models/WorkflowTemplate.php | 21 +++ Models/WorkflowTemplateMapper.php | 7 + Theme/Backend/workflow-template-list.tpl.php | 2 +- 19 files changed, 745 insertions(+), 61 deletions(-) create mode 100644 Models/NullWorkflowStep.php create mode 100644 Models/WorkflowStep.php create mode 100644 Models/WorkflowStepMapper.php diff --git a/Admin/Install/Workflow.install.json b/Admin/Install/Workflow.install.json index 436a7f8..38e7036 100755 --- a/Admin/Install/Workflow.install.json +++ b/Admin/Install/Workflow.install.json @@ -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": { diff --git a/Admin/Install/db.json b/Admin/Install/db.json index c80f2c1..9cb3882 100755 --- a/Admin/Install/db.json +++ b/Admin/Install/db.json @@ -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" + } + } } } \ No newline at end of file diff --git a/Admin/Installer.php b/Admin/Installer.php index 8fb68f9..aef60eb 100755 --- a/Admin/Installer.php +++ b/Admin/Installer.php @@ -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/'; diff --git a/Controller.js b/Controller.js index f5f379f..9353b5f 100755 --- a/Controller.js +++ b/Controller.js @@ -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('' + svg.html() + ''); + const inner = svg.select('g'); + const zoom = d3.zoom().on('zoom', function (event) { + inner.attr('transform', event.transform); + }); + svg.call(zoom); + }); + }, + }); + } + } }; bindElement (chart) diff --git a/Controller/ApiController.php b/Controller/ApiController.php index 8f42294..b72eba2 100755 --- a/Controller/ApiController.php +++ b/Controller/ApiController.php @@ -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, js?:array, db?:array, other?:array} $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 + ] + ); + } } diff --git a/Controller/BackendController.php b/Controller/BackendController.php index 7a1e7db..5640a5a 100755 --- a/Controller/BackendController.php +++ b/Controller/BackendController.php @@ -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) { diff --git a/Controller/CliController.php b/Controller/CliController.php index d1a3a46..d7dd8ef 100755 --- a/Controller/CliController.php +++ b/Controller/CliController.php @@ -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) { diff --git a/Definitions/actions.json b/Definitions/actions.json index bd7a055..e2d0f2a 100644 --- a/Definitions/actions.json +++ b/Definitions/actions.json @@ -1 +1 @@ -{"1005500001":{"name":"If","description":{"en":"Check condition","de":"\u00dcberpr\u00fcfe Bedingung"},"function_type":"Api","function":"apiValidateCondition","module":"Workflow","inputs":["field_name","field_value","field_value_type","field_comparison","{*}"],"outputs":["true|false"],"settings":{"field_name":{"type":"input","subtype":"text","default":"*","pattern":null,"examples":[],"required":true,"title":{"en":"Field name","de":"Feld name"},"description":{"en":"","de":""}},"field_value":{"type":"input","subtype":"text","default":"*","pattern":null,"examples":[],"required":true,"title":{"en":"Field value","de":"Feld value"},"description":{"en":"","de":""}},"field_comparison":{"type":"select","subtype":null,"default":"=","pattern":null,"examples":[],"required":true,"title":{"en":"Field comparison","de":"Feld comparison"},"description":{"en":"","de":""},"options":[{"value":"=","text":{"":"="}},{"value":"!=","text":{"":"!="}},{"value":">","text":{"":">"}},{"value":"<","text":{"":"<"}},{"value":">=","text":{"":">="}},{"value":"<=","text":{"":"<="}}]},"field_value_type":{"type":"select","subtype":null,"default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"Field value type","de":"Feld value type"},"description":{"en":"","de":""},"options":[{"value":"int","text":{"en":"Integer","de":"Ganze Zahl"}},{"value":"float","text":{"en":"Decimal number","den":"Dezimalzahl"}},{"value":"datetime","text":{"en":"Date\/Time","de":"Datum\/Zeit"}},{"value":"string","text":{"en":"Text","de":"Text"}},{"value":"bool","text":{"en":"True\/False","de":"Wahr\/Falsch"}}]}}},"1005500002":{"name":"Adapter","description":{"en":"Takes input data and forwards the data to another action. Check the outputs and inputs of the actions to create the correct mapping.","de":"\u00dcbernimmt Eingangsdaten und leitet diese an eine andere Aktion weiter. \u00dcberpr\u00fcfe die Ausgaben und Eingaben der jeweiligen Aktionen um die korrekten Verkn\u00fcpfungen zu erstellen."},"function_type":"Api","function":"apiAdapter","module":"Workflow","inputs":["map","{*}"],"outputs":["{*}"],"settings":{"map":{"type":"textarea","subtype":null,"default":null,"pattern":null,"examples":["output1 -> input1\noutput1.name -> input2"],"required":true,"title":{"en":"Adapter","de":"Adapter"},"description":{"en":"","de":""}}}},"1005500003":{"name":"Listen Trigger","description":{"en":"Listens to trigger","de":"Wartet auf Trigger"},"function_type":"Api","function":"apiListenToTrigger","module":"Workflow","inputs":["type","trigger","delete","{*}"],"outputs":["{*}"],"settings":{"type":{"type":"select","subtype":null,"default":"1","pattern":null,"examples":[],"required":true,"title":{"en":"Type","de":"Type"},"options":[{"value":"1","text":{"en":"Existing Trigger","de":"Bestehender Trigger"}},{"value":"2","text":{"en":"New Trigger","de":"Neuer Trigger"}}],"description":{"en":"","de":""}},"trigger":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":["PRE:Module:Billing:bill-create","PRE:Module:Billing:bill-update.*","PRE:Module:Billing:bill-update-{$id}"],"required":true,"title":{"en":"Trigger","de":"Trigger"},"description":{"en":"","de":""}},"delete":{"type":"select","subtype":null,"default":"1","pattern":null,"examples":[],"required":true,"title":{"en":"Type","de":"Type"},"options":[{"value":"1","text":{"en":"Never","de":"Niemals"}},{"value":"2","text":{"en":"After trigger execution","de":"Nach Triggerausf\u00fchrung"}},{"value":"3","text":{"en":"After workflow execution","de":"Nach Workflowausf\u00fchrung"}}],"description":{"en":"","de":""}}}},"1005500004":{"name":"Run Trigger","description":{"en":"Takes input data and forwards the data to another action. Check the outputs and inputs of the actions to create the correct mapping.","de":"\u00dcbernimmt Eingangsdaten und leitet diese an eine andere Aktion weiter. \u00dcberpr\u00fcfe die Ausgaben und Eingaben der jeweiligen Aktionen um die korrekten Verkn\u00fcpfungen zu erstellen."},"function_type":"Api","function":"apiRun","module":"Workflow","inputs":["map","{*}"],"outputs":["{*}"],"settings":{"map":{"type":"textarea","subtype":null,"default":null,"pattern":null,"examples":["output1 -> input1\noutput1.name -> input2"],"required":true,"title":{"en":"Adapter","de":"Adapter"},"description":{"en":"","de":""}}}},"1005500005":{"name":"Timed Trigger (Job\/Task)","description":{"en":"Timed trigger (Job\/Task)","de":"Zeitgesteuerter Trigger (Job\/Task)"},"function_type":"Api","function":"","module":"Workflow","function_install":{"module":"Workflow","function":"installTimedTrigger"},"inputs":["interval","{*}"],"outputs":["{*}"],"settings":{"interval":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"Interval","de":"Interval"},"description":{"en":"","de":""}}}},"1005500006":{"name":"Cli Action","description":{"en":"Cli action","de":"Konsolenbefehl"},"function_type":"Api","function":"","module":"Workflow","inputs":["cmd","{*}"],"outputs":["{*}"],"settings":{"cmd":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"Command","de":"Befehl"},"description":{"en":"","de":""}}}},"1005500007":{"name":"Workflow Script","description":{"en":"Workflow script","de":"Workflow Script"},"function_type":"Api","function":"","module":"Workflow","inputs":["{*}"],"outputs":["{*}"],"settings":[]},"1006200001":{"name":"Create blockchain","description":{"en":"Create audit blockchain","de":"Erstelle Audit Blockchain"},"function_type":"Cli","function":"cliGenerateBlockchain","module":"Auditor","inputs":[],"outputs":[],"settings":[]},"1000700001":{"name":"Error report","description":{"en":"Send error report","de":"Versende Fehlerbericht"},"function_type":"Cli","function":"cliLogReport","module":"Monitoring","inputs":["email","{*}"],"outputs":["resources[]","{*}"],"settings":{"email":{"type":"input","subtype":"text","default":null,"title":{"en":"Email","de":"Email"}}}},"1001200001":{"name":"Send Email","description":{"en":"Send Email","de":"Versende Email"},"function_type":"Api","function":"apiSendEmail","module":"Messages","inputs":["from","to","bcc","message","attachments","{*}"],"outputs":["message","message.getTo()","message.getFrom()","message.getBcc()","message.message","message.getAttachments()","message.getStatus()","{*}"],"settings":{"from":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"From","de":"Absender"},"description":{"en":"","de":""}},"to":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"Receiver","de":"Empf\u00e4nger"},"description":{"en":"","de":""}},"bcc":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"BCC","de":"BCC"},"description":{"en":"","de":""}},"message":{"type":"textarea","subtype":null,"default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"Message","de":"Nachricht"},"description":{"en":"","de":""}},"attachments":{"type":"input","subtype":"text","default":null,"pattern":"\/(^\\*$)|(^(\\d+)(,\\s*\\d+)*$)\/","examples":[],"required":false,"title":{"en":"Attachments","de":"Anh\u00e4nge"},"description":{"en":"","de":""}}}},"1005100001":{"name":"Find Subscripctions","description":{"en":"Finds subscriptions","de":"Findet Abonnements"},"function_type":"Api","function":"apiSubscriptionFind","module":"Billing","inputs":["date_start","date_end","client[]","payment_type","{*}"],"outputs":["subscription[]","{*}"],"settings":{"date_start":{"type":"input","subtype":"datetime","default":"now","required":true,"title":{"en":"Start","de":"Start"}},"date_end":{"type":"input","subtype":"datetime","default":null,"required":false,"title":{"en":"End","de":"End"}},"client":{"type":"input","subtype":"text","default":"*","pattern":"\/(^\\*$)|(^(\\d+)(,\\s*\\d+)*$)\/","examples":["*","12,654,789"],"required":true,"title":{"en":"Client ID","de":"Kundennummer"},"description":{"en":"The client IDs of the clients. Use * to get all clients or a comma separated list to specify specific clients.","de":"Die Kundennummern. Benutzen Sie * um alle Kunden auszuw\u00e4hlen oder eine durch Kommas getrennte Liste von mehreren Kundennummern."}},"payment_type":{"type":"select","subtype":null,"default":"cc","required":true,"title":{"en":"Payment type","de":"Zahlungsart"},"options":[{"value":"cc","text":{"en":"Credit Card","de":"Kreditkarte"}}]}}},"1005100002":{"name":"Invoice for Subscription","description":{"en":"Creates invoices for subscriptions","de":"Erstellt Rechnungen f\u00fcr Abonnements"},"function_type":"Api","function":"apiInvoiceFromSubscriptionCreate","module":"Billing","inputs":["subscription[]","{*}"],"outputs":["invoice[]","{*}"],"settings":{"subscription":{"type":"input","subtype":"text","default":"*","pattern":"\/(^\\*$)|(^(\\d+)(,\\s*\\d+)*$)\/","examples":["*","12,654,789"],"required":true,"title":{"en":"Subscription ID","de":"Abonnement ID"},"description":{"en":"The subscription IDs of the subscriptions. Use * to get all subscriptions or a comma separated list to specify specific subscriptions.","de":"Die Abonnement ID. Benutzen Sie * um alle Abonnements auszuw\u00e4hlen oder eine durch Kommas getrennte Liste von mehreren Abonnement IDs."}}}},"1008000001":{"name":"Check resources","description":{"en":"Check resources","de":"Ueberpruefe Ressourcen"},"function_type":"Api","function":"apiCheckResources","module":"OnlineResourceWatcher","inputs":["unit","{*}"],"outputs":["resources[]","{*}"],"settings":{"unit":{"type":"input","subtype":"number","default":null,"title":{"en":"Unit","de":"Unit"}}}},"1008000002":{"name":"Inform users","description":{"en":"Inform users about changes","de":"Informiere Nutzer \u00fcber \u00c4nderungen"},"function_type":"Api","function":"informUsers","module":"OnlineResourceWatcher","inputs":["unit","{*}"],"outputs":["resources[]","{*}"],"settings":{"unit":{"type":"input","subtype":"number","default":null,"title":{"en":"Unit","de":"Unit"}}}}} \ No newline at end of file +{"1005500001":{"name":"If","description":{"en":"Check condition","de":"\u00dcberpr\u00fcfe Bedingung"},"function_type":"Api","function":"apiValidateCondition","module":"Workflow","inputs":["field_name","field_value","field_value_type","field_comparison","{*}"],"outputs":["true|false"],"settings":{"field_name":{"type":"input","subtype":"text","default":"*","pattern":null,"examples":[],"required":true,"title":{"en":"Field name","de":"Feld name"},"description":{"en":"","de":""}},"field_value":{"type":"input","subtype":"text","default":"*","pattern":null,"examples":[],"required":true,"title":{"en":"Field value","de":"Feld value"},"description":{"en":"","de":""}},"field_comparison":{"type":"select","subtype":null,"default":"=","pattern":null,"examples":[],"required":true,"title":{"en":"Field comparison","de":"Feld comparison"},"description":{"en":"","de":""},"options":[{"value":"=","text":{"":"="}},{"value":"!=","text":{"":"!="}},{"value":">","text":{"":">"}},{"value":"<","text":{"":"<"}},{"value":">=","text":{"":">="}},{"value":"<=","text":{"":"<="}}]},"field_value_type":{"type":"select","subtype":null,"default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"Field value type","de":"Feld value type"},"description":{"en":"","de":""},"options":[{"value":"int","text":{"en":"Integer","de":"Ganze Zahl"}},{"value":"float","text":{"en":"Decimal number","den":"Dezimalzahl"}},{"value":"datetime","text":{"en":"Date\/Time","de":"Datum\/Zeit"}},{"value":"string","text":{"en":"Text","de":"Text"}},{"value":"bool","text":{"en":"True\/False","de":"Wahr\/Falsch"}}]}}},"1005500002":{"name":"Adapter","description":{"en":"Takes input data and forwards the data to another action. Check the outputs and inputs of the actions to create the correct mapping.","de":"\u00dcbernimmt Eingangsdaten und leitet diese an eine andere Aktion weiter. \u00dcberpr\u00fcfe die Ausgaben und Eingaben der jeweiligen Aktionen um die korrekten Verkn\u00fcpfungen zu erstellen."},"function_type":"Api","function":"apiAdapter","module":"Workflow","inputs":["map","{*}"],"outputs":["{*}"],"settings":{"map":{"type":"textarea","subtype":null,"default":null,"pattern":null,"examples":["output1 -> input1\noutput1.name -> input2"],"required":true,"title":{"en":"Adapter","de":"Adapter"},"description":{"en":"","de":""}}}},"1005500003":{"name":"Listen Trigger","description":{"en":"Listens to trigger","de":"Wartet auf Trigger"},"function_type":"Api","function":"apiListenToTrigger","module":"Workflow","inputs":["type","trigger","delete","{*}"],"outputs":["{*}"],"settings":{"type":{"type":"select","subtype":null,"default":"1","pattern":null,"examples":[],"required":true,"title":{"en":"Type","de":"Type"},"options":[{"value":"1","text":{"en":"Existing Trigger","de":"Bestehender Trigger"}},{"value":"2","text":{"en":"New Trigger","de":"Neuer Trigger"}}],"description":{"en":"","de":""}},"trigger":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":["PRE:Billing:bill-create","PRE:Billing:bill-update.*","PRE:Billing:bill-update-{$id}"],"required":true,"title":{"en":"Trigger","de":"Trigger"},"description":{"en":"","de":""}},"delete":{"type":"select","subtype":null,"default":"1","pattern":null,"examples":[],"required":true,"title":{"en":"Type","de":"Type"},"options":[{"value":"1","text":{"en":"Never","de":"Niemals"}},{"value":"2","text":{"en":"After trigger execution","de":"Nach Triggerausf\u00fchrung"}},{"value":"3","text":{"en":"After workflow execution","de":"Nach Workflowausf\u00fchrung"}}],"description":{"en":"","de":""}}}},"1005500004":{"name":"Run Trigger","description":{"en":"Takes input data and forwards the data to another action. Check the outputs and inputs of the actions to create the correct mapping.","de":"\u00dcbernimmt Eingangsdaten und leitet diese an eine andere Aktion weiter. \u00dcberpr\u00fcfe die Ausgaben und Eingaben der jeweiligen Aktionen um die korrekten Verkn\u00fcpfungen zu erstellen."},"function_type":"Api","function":"apiRun","module":"Workflow","inputs":["map","{*}"],"outputs":["{*}"],"settings":{"map":{"type":"textarea","subtype":null,"default":null,"pattern":null,"examples":["output1 -> input1\noutput1.name -> input2"],"required":true,"title":{"en":"Adapter","de":"Adapter"},"description":{"en":"","de":""}}}},"1005500005":{"name":"Timed Trigger (Job\/Task)","description":{"en":"Timed trigger (Job\/Task)","de":"Zeitgesteuerter Trigger (Job\/Task)"},"function_type":"Api","function":"","module":"Workflow","function_install":{"module":"Workflow","function":"installTimedTrigger"},"inputs":["interval","{*}"],"outputs":["{*}"],"settings":{"interval":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"Interval","de":"Interval"},"description":{"en":"","de":""}}}},"1005500006":{"name":"Cli Action","description":{"en":"Cli action","de":"Konsolenbefehl"},"function_type":"Api","function":"","module":"Workflow","inputs":["cmd","{*}"],"outputs":["{*}"],"settings":{"cmd":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"Command","de":"Befehl"},"description":{"en":"","de":""}}}},"1005500007":{"name":"Workflow Script","description":{"en":"Workflow script","de":"Workflow Script"},"function_type":"Api","function":"","module":"Workflow","inputs":["{*}"],"outputs":["{*}"],"settings":[]},"1006200001":{"name":"Create blockchain","description":{"en":"Create audit blockchain","de":"Erstelle Audit Blockchain"},"function_type":"Cli","function":"cliGenerateBlockchain","module":"Auditor","inputs":[],"outputs":[],"settings":[]},"1000700001":{"name":"Error report","description":{"en":"Send error report","de":"Versende Fehlerbericht"},"function_type":"Cli","function":"cliLogReport","module":"Monitoring","inputs":["email","{*}"],"outputs":["resources[]","{*}"],"settings":{"email":{"type":"input","subtype":"text","default":null,"title":{"en":"Email","de":"Email"}}}},"1001200001":{"name":"Send Email","description":{"en":"Send Email","de":"Versende Email"},"function_type":"Api","function":"apiSendEmail","module":"Messages","inputs":["from","to","bcc","message","attachments","{*}"],"outputs":["message","message.getTo()","message.getFrom()","message.getBcc()","message.message","message.getAttachments()","message.getStatus()","{*}"],"settings":{"from":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"From","de":"Absender"},"description":{"en":"","de":""}},"to":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"Receiver","de":"Empf\u00e4nger"},"description":{"en":"","de":""}},"bcc":{"type":"input","subtype":"text","default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"BCC","de":"BCC"},"description":{"en":"","de":""}},"message":{"type":"textarea","subtype":null,"default":null,"pattern":null,"examples":[],"required":true,"title":{"en":"Message","de":"Nachricht"},"description":{"en":"","de":""}},"attachments":{"type":"input","subtype":"text","default":null,"pattern":"\/(^\\*$)|(^(\\d+)(,\\s*\\d+)*$)\/","examples":[],"required":false,"title":{"en":"Attachments","de":"Anh\u00e4nge"},"description":{"en":"","de":""}}}},"1005100001":{"name":"Find Subscripctions","description":{"en":"Finds subscriptions","de":"Findet Abonnements"},"function_type":"Api","function":"apiSubscriptionFind","module":"Billing","inputs":["date_start","date_end","client[]","payment_type","{*}"],"outputs":["subscription[]","{*}"],"settings":{"date_start":{"type":"input","subtype":"datetime","default":"now","required":true,"title":{"en":"Start","de":"Start"}},"date_end":{"type":"input","subtype":"datetime","default":null,"required":false,"title":{"en":"End","de":"End"}},"client":{"type":"input","subtype":"text","default":"*","pattern":"\/(^\\*$)|(^(\\d+)(,\\s*\\d+)*$)\/","examples":["*","12,654,789"],"required":true,"title":{"en":"Client ID","de":"Kundennummer"},"description":{"en":"The client IDs of the clients. Use * to get all clients or a comma separated list to specify specific clients.","de":"Die Kundennummern. Benutzen Sie * um alle Kunden auszuw\u00e4hlen oder eine durch Kommas getrennte Liste von mehreren Kundennummern."}},"payment_type":{"type":"select","subtype":null,"default":"cc","required":true,"title":{"en":"Payment type","de":"Zahlungsart"},"options":[{"value":"cc","text":{"en":"Credit Card","de":"Kreditkarte"}}]}}},"1005100002":{"name":"Invoice for Subscription","description":{"en":"Creates invoices for subscriptions","de":"Erstellt Rechnungen f\u00fcr Abonnements"},"function_type":"Api","function":"apiInvoiceFromSubscriptionCreate","module":"Billing","inputs":["subscription[]","{*}"],"outputs":["invoice[]","{*}"],"settings":{"subscription":{"type":"input","subtype":"text","default":"*","pattern":"\/(^\\*$)|(^(\\d+)(,\\s*\\d+)*$)\/","examples":["*","12,654,789"],"required":true,"title":{"en":"Subscription ID","de":"Abonnement ID"},"description":{"en":"The subscription IDs of the subscriptions. Use * to get all subscriptions or a comma separated list to specify specific subscriptions.","de":"Die Abonnement ID. Benutzen Sie * um alle Abonnements auszuw\u00e4hlen oder eine durch Kommas getrennte Liste von mehreren Abonnement IDs."}}}},"1008000001":{"name":"Check resources","description":{"en":"Check resources","de":"Ueberpruefe Ressourcen"},"function_type":"Api","function":"apiCheckResources","module":"OnlineResourceWatcher","inputs":["unit","{*}"],"outputs":["resources[]","{*}"],"settings":{"unit":{"type":"input","subtype":"number","default":null,"title":{"en":"Unit","de":"Unit"}}}},"1008000002":{"name":"Inform users","description":{"en":"Inform users about changes","de":"Informiere Nutzer \u00fcber \u00c4nderungen"},"function_type":"Api","function":"informUsers","module":"OnlineResourceWatcher","inputs":["unit","{*}"],"outputs":["resources[]","{*}"],"settings":{"unit":{"type":"input","subtype":"number","default":null,"title":{"en":"Unit","de":"Unit"}}}}} \ No newline at end of file diff --git a/Definitions/triggers.json b/Definitions/triggers.json index 7d20102..d18bc10 100644 --- a/Definitions/triggers.json +++ b/Definitions/triggers.json @@ -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"] \ No newline at end of file +["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"] \ No newline at end of file diff --git a/Docs/workflow_components.md b/Docs/workflow_components.md index 68fb803..bb0184d 100755 --- a/Docs/workflow_components.md +++ b/Docs/workflow_components.md @@ -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 () + diff --git a/Models/NullWorkflowStep.php b/Models/NullWorkflowStep.php new file mode 100644 index 0000000..fc58878 --- /dev/null +++ b/Models/NullWorkflowStep.php @@ -0,0 +1,46 @@ +id = $id; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return ['id' => $this->id]; + } +} diff --git a/Models/WorkflowControllerInterface.php b/Models/WorkflowControllerInterface.php index 036f680..a5f89d0 100755 --- a/Models/WorkflowControllerInterface.php +++ b/Models/WorkflowControllerInterface.php @@ -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 * diff --git a/Models/WorkflowInstanceAbstract.php b/Models/WorkflowInstanceAbstract.php index add0e97..32cf2c3 100755 --- a/Models/WorkflowInstanceAbstract.php +++ b/Models/WorkflowInstanceAbstract.php @@ -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(); + } } diff --git a/Models/WorkflowInstanceAbstractMapper.php b/Models/WorkflowInstanceAbstractMapper.php index 7b12f45..15ae531 100755 --- a/Models/WorkflowInstanceAbstractMapper.php +++ b/Models/WorkflowInstanceAbstractMapper.php @@ -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 + * @since 1.0.0 + */ + public const HAS_MANY = [ + 'steps' => [ + 'mapper' => WorkflowStepMapper::class, + 'table' => 'workflow_step', + 'self' => 'workflow_step_instance', + 'external' => null, + ], + ]; + /** * Primary table. * diff --git a/Models/WorkflowStep.php b/Models/WorkflowStep.php new file mode 100644 index 0000000..6c94f1c --- /dev/null +++ b/Models/WorkflowStep.php @@ -0,0 +1,126 @@ +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 []; + } +} diff --git a/Models/WorkflowStepMapper.php b/Models/WorkflowStepMapper.php new file mode 100644 index 0000000..d207af5 --- /dev/null +++ b/Models/WorkflowStepMapper.php @@ -0,0 +1,107 @@ + + */ +final class WorkflowStepMapper extends DataMapperFactory +{ + /** + * Columns. + * + * @var 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 + * @since 1.0.0 + */ + public const OWNS_ONE = [ + 'media' => [ + 'mapper' => CollectionMapper::class, + 'external' => 'workflow_step_media', + ], + ]; + + /** + * Belongs to. + * + * @var array + * @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'; +} diff --git a/Models/WorkflowTemplate.php b/Models/WorkflowTemplate.php index 5fbb1fc..f453088 100755 --- a/Models/WorkflowTemplate.php +++ b/Models/WorkflowTemplate.php @@ -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. * diff --git a/Models/WorkflowTemplateMapper.php b/Models/WorkflowTemplateMapper.php index 2095b79..5d41f04 100755 --- a/Models/WorkflowTemplateMapper.php +++ b/Models/WorkflowTemplateMapper.php @@ -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', + ], ]; /** diff --git a/Theme/Backend/workflow-template-list.tpl.php b/Theme/Backend/workflow-template-list.tpl.php index db8b197..b8716c9 100755 --- a/Theme/Backend/workflow-template-list.tpl.php +++ b/Theme/Backend/workflow-template-list.tpl.php @@ -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';