diff --git a/Admin/Hooks/Cli.php b/Admin/Hooks/Cli.php
new file mode 100644
index 0000000..3d371ab
--- /dev/null
+++ b/Admin/Hooks/Cli.php
@@ -0,0 +1,16 @@
+ [
+ 'callback' => ['\Modules\Workflow\Controller\ApiController:hookWorkflowChangeState'],
+ ],
+ "POST:Billing:bill-update" => [
+ 'callback' => ['\Modules\Workflow\Controller\ApiController:hookWorkflowChangeState'],
+ ],
+
+ "POST:Billing:bill_element-create" => [
+ 'callback' => ['\Modules\Workflow\Controller\ApiController:hookWorkflowChangeState'],
+ ],
+ "POST:Billing:bill_element-update" => [
+ 'callback' => ['\Modules\Workflow\Controller\ApiController:hookWorkflowChangeState'],
+ ],
+];
diff --git a/Admin/Install/Workflow.install.json b/Admin/Install/Workflow.install.json
index 4a752dd..79604d3 100755
--- a/Admin/Install/Workflow.install.json
+++ b/Admin/Install/Workflow.install.json
@@ -1,32 +1,38 @@
{
+ "templates": [
+ {
+ "name": "Billing workflow",
+ "path": "/Modules/Billing/Admin/Install/Workflow/bill"
+ }
+ ],
"triggers": [
- "PRE:Module:Billing:bill-create",
- "POST:Module:Billing:bill-create",
- "PRE:Module:Billing:bill-update",
- "POST:Module:Billing:bill-update",
- "PRE:Module:Billing:bill-delete",
- "POST:Module:Billing:bill-delete",
+ "PRE:Billing:bill-create",
+ "POST:Billing:bill-create",
+ "PRE:Billing:bill-update",
+ "POST:Billing:bill-update",
+ "PRE:Billing:bill-delete",
+ "POST:Billing:bill-delete",
- "PRE:Module:Billing:bill_element-create",
- "POST:Module:Billing:bill_element-create",
- "PRE:Module:Billing:bill_element-update",
- "POST:Module:Billing:bill_element-update",
- "PRE:Module:Billing:bill_element-delete",
- "POST:Module:Billing:bill_element-delete",
+ "PRE:Billing:bill_element-create",
+ "POST:Billing:bill_element-create",
+ "PRE:Billing:bill_element-update",
+ "POST:Billing:bill_element-update",
+ "PRE:Billing:bill_element-delete",
+ "POST:Billing:bill_element-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: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: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: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"
],
"actions": {
"1005100001": {
diff --git a/Admin/Install/Workflow/bill/WorkflowController.php b/Admin/Install/Workflow/bill/WorkflowController.php
new file mode 100644
index 0000000..fb00a19
--- /dev/null
+++ b/Admin/Install/Workflow/bill/WorkflowController.php
@@ -0,0 +1,319 @@
+createdBy = new NullAccount($request->header->account);
+ $instance->title = $template->name . ': ' . ($request->getDataString('title') ?? '');
+ $instance->template = new NullWorkflowTemplate($request->getData('template') ?? 0);
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getInstanceListFromRequest(RequestAbstract $request) : array
+ {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createTemplateViewFromRequest(View $view, RequestAbstract $request, ResponseAbstract $response) : void
+ {
+ $includes = 'Modules/Media/Files/';
+ $tpl = $view->data['template']->source->findFile('template-profile.tpl.php');
+
+ $start = \stripos($tpl->getPath(), $includes);
+
+ $view->setTemplate('/' . \substr($tpl->getPath(), $start, -8));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createInstanceViewFromRequest(View $view, RequestAbstract $request, ResponseAbstract $response) : void
+ {
+ $includes = 'Modules/Media/Files/';
+ $tpl = $view->data['template']->source->findFile('instance-profile.tpl.php');
+
+ $start = \stripos($tpl->getPath(), $includes);
+
+ $view->setTemplate('/' . \substr($tpl->getPath(), $start, -8));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function apiChangeState(RequestAbstract $request, ResponseAbstract $response, $data = []) : void
+ {
+ // @bug what if we create two tasks for the same element (due to edits) before it got closed
+ // In that case the automatic task closing will only effect the most recent task
+ // Maybe check task existence first and close it before creating a new task
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ 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
+ {
+ $old = null;
+
+ /** @var Bill $bill */
+ $bill = null;
+ if ($type === StringUtils::intHash(BillMapper::class)) {
+ $bill = $new;
+ } else if ($type === StringUtils::intHash(BillElementMapper::class)) {
+ /** @var BillElement $element */
+ $element = $new;
+ $bill = BillMapper::get()
+ ->where('id', $element->bill->id)
+ ->executeGet();
+ }
+
+ if ($bill->status !== BillStatus::DONE) {
+ return null;
+ }
+
+ $instance = WorkflowInstanceAbstractMapper::get()
+ ->with('steps')
+ ->with('template')
+ ->where('template/module', 'Billing')
+ ->where('template/type', $bill->client != null ? WorkflowType::SALES_BILL : WorkflowType::PURCHASE_BILL)
+ ->where('ref', $bill->id)
+ ->executeGet();
+
+ $instance->template = $template;
+ $old = clone $instance;
+
+ if ($type === StringUtils::intHash(BillMapper::class)) {
+ $actionType = $this->hookChangeBillState($instance, $bill);
+ } else if ($type === StringUtils::intHash(BillElementMapper::class)) {
+ $actionType = $this->hookChangeBillElementState($instance, $bill);
+ }
+
+ if ($actionType < 0) {
+ return null;
+ }
+
+ // $actionType === 0 -> create/update instance but don't create a task
+ if ($instance->id === 0) {
+ // Create new instance if none exist for this element (ref)
+ $instance->template = $template;
+ $instance->ref = $element->bill->id;
+
+ WorkflowInstanceAbstractMapper::create()
+ ->execute($instance);
+
+ $oldSerialized = '';
+ } else {
+ WorkflowInstanceAbstractMapper::update()
+ ->execute($instance);
+
+ $oldSerialized = $old->jsonSerialize();
+ }
+
+ $audit = new Audit(
+ new NullAccount($account),
+ $oldSerialized,
+ $instance->jsonSerialize(),
+ StringUtils::intHash(WorkflowInstanceAbstract::class),
+ $trigger,
+ $module,
+ $ref,
+ $content,
+ (int) \ip2long($ip ?? '127.0.0.1')
+ );
+ AuditMapper::create()->execute($audit);
+
+ if ($actionType === 0) {
+ return null;
+ }
+
+ // Create task for users
+ // @bug We are currently searching the groups by static category. This is wrong
+ // Workflows could be different per company (multiple levels) -> we need to be able to define the group differently
+ // Maybe we define the group that does a specific step as a setting either in the settings table or settings.json file of the workflow
+ $groups = GroupMapper::findReadPermission(
+ $this->app->unitId,
+ 'Billing',
+ PermissionCategory::SALES_INVOICE,
+ $bill->id
+ );
+
+ $task = new Task();
+ $task->title = '';
+ $task->description = '';
+ $task->descriptionRaw = '';
+ $task->createdBy = new NullAccount($account);
+ $task->status = TaskStatus::OPEN;
+ $task->type = TaskType::SINGLE;
+ $task->redirect = '{/base}/workflow/instance/view?{?}&id=' . $instance->id;
+ $task->unit = $bill->unit;
+ $task->priority = TaskPriority::HIGH;
+
+ $element = new TaskElement();
+ $element->createdBy = $task->createdBy;
+ $element->due = $task->due;
+ $element->priority = $task->priority;
+ $element->status = TaskStatus::OPEN;
+
+ foreach ($groups as $group) {
+ $element->addTo(new NullGroup($group));
+ }
+
+ $task->addElement($element);
+
+ TaskMapper::create()->execute($task);
+
+ // TaskStep data:
+ /*
+ [
+ {
+ 'task_id' => 0,
+ 'element_id' => 0,
+ 'level' => 1, // sometimes multiple levels exist per approval (e.g. price until x approved by cso, then cfo, then ceo)
+ 'approved' => true,
+
+ 'max_price' => ,
+ 'max_price' => ,
+ }
+ ];
+ */
+ // @todo steps should have notes for communication (maybe use task elements for that). This way people can communicate
+ // Maybe create notifications whenever a new note is created?
+
+ return $instance;
+ }
+
+ private function hookChangeBillState(WorkflowInstanceAbstract $instance, Bill $bill) : bool {
+ // if all states approved -> set to approved
+ // careful, if bill create -> check if elements are approved
+
+ $instance->data_int = WorkflowStepStatusEnum::BILL_APPROVAL;
+
+ $foundStep = null;
+
+ // Find matching step (if exists)
+ foreach ($instance->steps as $step) {
+
+ }
+
+ // @bug It could be multiple steps that we need to handle
+ // Create new step if not existing
+ if ($foundStep !== null) {
+ $foundStep = new WorkflowStep();
+ $instance->steps[] = $foundStep;
+ }
+
+ // new bill
+ // check credit limit
+ // check payment term
+ // check payment approval
+ // check total price
+ // check total gross profit
+ // check correctness
+
+ return false;
+ }
+
+ private function hookChangeBillElementState(WorkflowInstanceAbstract $instance, Bill $bill) : bool {
+ // if element within range -> set to approved (unless element exists and is not approved)
+
+ // new element
+ // quantity changed
+ // price changed
+
+ // check price
+ // check quantity
+ // check gross profit
+
+ return false;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function apiHandleState(RequestAbstract $request, ResponseAbstract $response, $data = []) : void
+ {
+ if (($data['trigger'] ?? null) === 'PRE:Billing-print') {
+
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createInstanceDbModel(WorkflowInstanceAbstract $instance) : void
+ {
+ }
+}
diff --git a/Admin/Install/Workflow/bill/WorkflowState.php b/Admin/Install/Workflow/bill/WorkflowState.php
new file mode 100644
index 0000000..e69de29
diff --git a/Admin/Install/Workflow/bill/instance-list.tpl.php b/Admin/Install/Workflow/bill/instance-list.tpl.php
new file mode 100644
index 0000000..9fdd481
--- /dev/null
+++ b/Admin/Install/Workflow/bill/instance-list.tpl.php
@@ -0,0 +1,49 @@
+data['instances'];
+
+$previous = empty($instances) ? 'workflows/instance/list' : '{/base}/workflows/instance/list?{?}&offset=' . \reset($instances)->id . '&ptype=p';
+$next = empty($instances) ? 'workflows/instance/list' : '{/base}/workflows/instance/list?{?}&offset=' . \end($instances)->id . '&ptype=n';
+
+echo $this->data['nav']->render();
+?>
+
+
+
+ = $this->getHtml('Instances'); ?>download
+
+
+
+
+
diff --git a/Admin/Install/Workflow/bill/instance-profile.tpl.php b/Admin/Install/Workflow/bill/instance-profile.tpl.php
new file mode 100644
index 0000000..029b5e5
--- /dev/null
+++ b/Admin/Install/Workflow/bill/instance-profile.tpl.php
@@ -0,0 +1,54 @@
+data['nav']->render();
+?>
+
+
+
+ -
+
-
+
-
+
-
+
-
+
+
+
+
request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
+
+
+
+ // create colored list (grey, red, green) of the different steps
+ // grey = no action needed
+ // yellow = action needed
+ // green = approved
+ // red = declined
+
+
+
+
+
request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
+
+
+
diff --git a/Admin/Install/Workflow/bill/lang.php b/Admin/Install/Workflow/bill/lang.php
new file mode 100644
index 0000000..28853ba
--- /dev/null
+++ b/Admin/Install/Workflow/bill/lang.php
@@ -0,0 +1,18 @@
+ [
+ 'Overview' => 'Overview',
+ 'Bill' => 'Bill',
+ 'CreditLimit' => 'Credit Limit',
+ 'PaymentTerm' => 'Payment Term',
+ 'Elements' => 'Elements',
+ ],
+ 'de' => [
+ 'Overview' => 'Übersicht',
+ 'Bill' => 'Beleg',
+ 'CreditLimit' => 'Kreditlimit',
+ 'PaymentTerm' => 'Zahlungsbedingung',
+ 'Elements' => 'Belegzeilen',
+ ],
+];
diff --git a/Admin/Install/Workflow/bill/settings.json b/Admin/Install/Workflow/bill/settings.json
new file mode 100644
index 0000000..e69de29
diff --git a/Admin/Install/Workflow/bill/settings.tpl.php b/Admin/Install/Workflow/bill/settings.tpl.php
new file mode 100644
index 0000000..e69de29
diff --git a/Admin/Install/Workflow/bill/template-profile.tpl.php b/Admin/Install/Workflow/bill/template-profile.tpl.php
new file mode 100644
index 0000000..a52a6bb
--- /dev/null
+++ b/Admin/Install/Workflow/bill/template-profile.tpl.php
@@ -0,0 +1,131 @@
+data['template'];
+$media = $template->source->getSources();
+
+$lang = include $template->source->findFile('lang.php')->getAbsolutePath();
+
+echo $this->data['nav']->render(); ?>
+
+
+
+
+ -
+
-
+
-
+
-
+
+
+
+
request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
+
+
+
request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
+
+
+
+
+
+
+ flowchart TB;
+ CREATE_BILL[Create bill]-->LOCKED{Is locked?};
+ LOCKED-->|TRUE|CREATE_APPROVAL_TASK[Accounting approval task];
+ LOCKED-->|FALSE|PRINTABLE;
+ CREATE_APPROVAL_TASK-->ACCOUNTING_APPROVAL{Is ok?};
+ ACCOUNTING_APPROVAL-->|FALSE|ACCOUNTING_NOT_APPROVED[Inform OP];
+ ACCOUNTING_APPROVAL-->|TRUE|PRINTABLE;
+ CREATE_BILL-->CREATE_CHECK_TASK[Invoice validation task];
+ CREATE_CHECK_TASK-->BILL_CHECK{Is correct?};
+ BILL_CHECK-->|TRUE|CHECK_PRICES{High discounts?};
+ CHECK_PRICES-->|FALSE|PRINTABLE;
+ BILL_CHECK-->|FALSE|INFO_WRITER[Inform OP];
+ CHECK_PRICES-->|TRUE|CREATE_SALES_APPROVAL_TASK[Sales approval task];
+ CREATE_SALES_APPROVAL_TASK-->SALES_APPROVAL{Is ok?};
+ SALES_APPROVAL-->|TRUE|CHECK_PRICES_ESCALATED{Over limit?};
+ SALES_APPROVAL-->|FALSE|SALES_NOT_APPROVED[Inform OP];
+ CHECK_PRICES_ESCALATED-->|TRUE|CREATE_CFO_PRICE_APPROVAL[CFO approval task];
+ CHECK_PRICES_ESCALATED-->|FALSE|PRINTABLE;
+ CREATE_CFO_PRICE_APPROVAL-->CFO_APPROVAL{Is ok?};
+ CFO_APPROVAL-->|TRUE|PRINTABLE[Mark printable];
+ CFO_APPROVAL-->|FALSE|CFO_NOT_APPROVED[Inform OP + Sales];
+
+ CLICK_PRINT[Click print]-->IS_APPROVED{Is approved};
+ IS_APPROVED-->|TRUE|PRINT[Print];
+ IS_APPROVED-->|FALSE|PRINT_ERROR[Show print error];
+
+ UPDATE_BILL[Update bill]-->CHECK_THREASHOLDS{Change above threshold};
+ CHECK_THREASHOLDS-->|TRUE|OPEN_TASKS[Update & re-open tasks];
+
+
+
+
+
+
+
+
request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
+
+
+
request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
+
+
+
+
+ = $this->getHtml('Media'); ?>download
+
+
+
+ |
+ | = $this->getHtml('Name'); ?>
+ | = $this->getHtml('Type'); ?>
+ |
+ extension === 'collection'
+ ? UriFactory::build('{/base}/media/list?path=' . \rtrim($file->getVirtualPath(), '/') . '/' . $file->name)
+ : UriFactory::build('{/base}/media/view?id=' . $file->id
+ . '&path={?path}' . (
+ $file->id === 0
+ ? '/' . $file->name
+ : ''
+ )
+ );
+
+ ?>
+
+ |
+ | = $file->name; ?>
+ | = $file->extension; ?>
+
+ |
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/Install/Workflow/bill/workflow.json b/Admin/Install/Workflow/bill/workflow.json
new file mode 100644
index 0000000..bc893cf
--- /dev/null
+++ b/Admin/Install/Workflow/bill/workflow.json
@@ -0,0 +1,12 @@
+[
+ {
+ "name": "IS_APPROVED",
+ "title": "Is approved?",
+ "hooks": [
+ ],
+ "if": {
+ "isApproved": true,
+ "action": "print"
+ }
+ }
+]
\ No newline at end of file
diff --git a/Admin/Install/db.json b/Admin/Install/db.json
index c6d51d5..67b76ae 100755
--- a/Admin/Install/db.json
+++ b/Admin/Install/db.json
@@ -907,6 +907,11 @@
"null": false,
"foreignTable": "unit",
"foreignKey": "unit_id"
+ },
+ "billing_bill_approval": {
+ "name": "billing_bill_approval",
+ "type": "INT",
+ "null": false
}
}
},
@@ -1278,6 +1283,11 @@
"type": "INT",
"default": null,
"null": true
+ },
+ "billing_bill_element_approval": {
+ "name": "billing_bill_element_approval",
+ "type": "INT",
+ "null": false
}
}
},
diff --git a/Controller/ApiAttributeController.php b/Controller/ApiAttributeController.php
index c65058d..9fdc553 100755
--- a/Controller/ApiAttributeController.php
+++ b/Controller/ApiAttributeController.php
@@ -243,7 +243,7 @@ final class ApiAttributeController extends Controller
->with('type')
->with('type/defaults')
->with('value')
- ->where('id', (int) $request->getData('id'))
+ ->where('id', $request->getDataInt('id') ?? 0)
->execute();
$new = $this->updateAttributeFromRequest($request, clone $old);
@@ -291,7 +291,7 @@ final class ApiAttributeController extends Controller
$billAttribute = BillAttributeMapper::get()
->with('type')
- ->where('id', (int) $request->getData('id'))
+ ->where('id', $request->getDataInt('id') ?? 0)
->execute();
if ($billAttribute->type->isRequired) {
@@ -327,7 +327,7 @@ final class ApiAttributeController extends Controller
}
/** @var BaseStringL11n $old */
- $old = BillAttributeTypeL11nMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $old = BillAttributeTypeL11nMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
$new = $this->updateAttributeTypeL11nFromRequest($request, clone $old);
$this->updateModel($request->header->account, $old, $new, BillAttributeTypeL11nMapper::class, 'bill_attribute_type_l11n', $request->getOrigin());
@@ -357,7 +357,7 @@ final class ApiAttributeController extends Controller
}
/** @var BaseStringL11n $billAttributeTypeL11n */
- $billAttributeTypeL11n = BillAttributeTypeL11nMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $billAttributeTypeL11n = BillAttributeTypeL11nMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
$this->deleteModel($request->header->account, $billAttributeTypeL11n, BillAttributeTypeL11nMapper::class, 'bill_attribute_type_l11n', $request->getOrigin());
$this->createStandardDeleteResponse($request, $response, $billAttributeTypeL11n);
}
@@ -385,7 +385,7 @@ final class ApiAttributeController extends Controller
}
/** @var AttributeType $old */
- $old = BillAttributeTypeMapper::get()->with('defaults')->where('id', (int) $request->getData('id'))->execute();
+ $old = BillAttributeTypeMapper::get()->with('defaults')->where('id', $request->getDataInt('id') ?? 0)->execute();
$new = $this->updateAttributeTypeFromRequest($request, clone $old);
$this->updateModel($request->header->account, $old, $new, BillAttributeTypeMapper::class, 'bill_attribute_type', $request->getOrigin());
@@ -417,7 +417,7 @@ final class ApiAttributeController extends Controller
}
/** @var AttributeType $billAttributeType */
- $billAttributeType = BillAttributeTypeMapper::get()->with('defaults')->where('id', (int) $request->getData('id'))->execute();
+ $billAttributeType = BillAttributeTypeMapper::get()->with('defaults')->where('id', $request->getDataInt('id') ?? 0)->execute();
$this->deleteModel($request->header->account, $billAttributeType, BillAttributeTypeMapper::class, 'bill_attribute_type', $request->getOrigin());
$this->createStandardDeleteResponse($request, $response, $billAttributeType);
}
@@ -445,7 +445,7 @@ final class ApiAttributeController extends Controller
}
/** @var AttributeValue $old */
- $old = BillAttributeValueMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $old = BillAttributeValueMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
/** @var \Modules\Attribute\Models\Attribute $attr */
$attr = BillAttributeMapper::get()
@@ -485,7 +485,7 @@ final class ApiAttributeController extends Controller
// }
// /** @var \Modules\Billing\Models\BillAttributeValue $billAttributeValue */
- // $billAttributeValue = BillAttributeValueMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ // $billAttributeValue = BillAttributeValueMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
// $this->deleteModel($request->header->account, $billAttributeValue, BillAttributeValueMapper::class, 'bill_attribute_value', $request->getOrigin());
// $this->createStandardDeleteResponse($request, $response, $billAttributeValue);
}
@@ -513,7 +513,7 @@ final class ApiAttributeController extends Controller
}
/** @var BaseStringL11n $old */
- $old = BillAttributeValueL11nMapper::get()->where('id', (int) $request->getData('id'));
+ $old = BillAttributeValueL11nMapper::get()->where('id', $request->getDataInt('id') ?? 0);
$new = $this->updateAttributeValueL11nFromRequest($request, clone $old);
$this->updateModel($request->header->account, $old, $new, BillAttributeValueL11nMapper::class, 'bill_attribute_value_l11n', $request->getOrigin());
@@ -543,7 +543,7 @@ final class ApiAttributeController extends Controller
}
/** @var BaseStringL11n $billAttributeValueL11n */
- $billAttributeValueL11n = BillAttributeValueL11nMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $billAttributeValueL11n = BillAttributeValueL11nMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
$this->deleteModel($request->header->account, $billAttributeValueL11n, BillAttributeValueL11nMapper::class, 'bill_attribute_value_l11n', $request->getOrigin());
$this->createStandardDeleteResponse($request, $response, $billAttributeValueL11n);
}
diff --git a/Controller/ApiBillController.php b/Controller/ApiBillController.php
index fbebea3..43ad3d6 100755
--- a/Controller/ApiBillController.php
+++ b/Controller/ApiBillController.php
@@ -64,6 +64,10 @@ use phpOMS\Model\Message\FormValidation;
use phpOMS\Stdlib\Base\FloatInt;
use phpOMS\System\MimeType;
use phpOMS\Views\View;
+use Modules\Billing\Models\WorkflowType;
+use Modules\Workflow\Models\WorkflowTemplateMapper;
+use Modules\Workflow\Models\WorkflowTemplateStatus;
+use phpOMS\Message\Http\HttpResponse;
/**
* Billing class.
@@ -278,7 +282,7 @@ final class ApiBillController extends Controller
return;
}
- $this->app->eventManager->triggerSimilar('PRE:Module:' . self::NAME . '-bill-finalize', '', [
+ $this->app->eventManager->triggerSimilar('PRE:' . self::NAME . '-bill-finalize', '', [
$request->header->account,
null, $new,
null, self::NAME . '-bill-finalize',
@@ -317,7 +321,7 @@ final class ApiBillController extends Controller
}
/** @var \Modules\Billing\Models\Bill $old */
- $old = BillMapper::get()->where('id', (int) $request->getData('bill'))->execute();
+ $old = BillMapper::get()->where('id', $request->getDataInt('bill') ?? 0)->execute();
// @feature Allow to update internal statistical fields
// Example: Referral account
@@ -482,7 +486,7 @@ final class ApiBillController extends Controller
$bill->accSection = empty($temp = $bill->client->getAttribute('section')->value->id) ? null : $temp;
$bill->accGroup = empty($temp = $bill->client->getAttribute('client_group')->value->id) ? null : $temp;
$bill->accType = empty($temp = $bill->client->getAttribute('client_type')->value->id) ? null : $temp;
- $bill->rep = $request->hasData('rep') ? new NullSalesRep((int) $request->getData('rep')) : $account->rep;
+ $bill->rep = $request->hasData('rep') ? new NullSalesRep($request->getDataInt('re ?? 0p')) : $account->rep;
} else {
$bill->supplier = $account;
$bill->accTaxCode = empty($temp = $bill->supplier->getAttribute('purchase_tax_code')->value->id) ? null : $temp;
@@ -602,7 +606,7 @@ final class ApiBillController extends Controller
$attr->value = $attrValue;
$container = $request->hasData('container')
- ? new NullContainer((int) $request->getData('container'))
+ ? new NullContainer($request->getDataInt('co ?? 0ntainer'))
: null;
$attr = new NullAttribute();
@@ -723,7 +727,7 @@ final class ApiBillController extends Controller
->with('attributes')
->with('attributes/type')
->with('attributes/value')
- ->where('id', (int) $request->getData('client'))
+ ->where('id', $request->getDataInt('client') ?? 0)
->where('attributes/type/name', [
'segment', 'section', 'client_group', 'client_type',
'sales_tax_code',
@@ -740,7 +744,7 @@ final class ApiBillController extends Controller
->with('attributes')
->with('attributes/type')
->with('attributes/value')
- ->where('id', (int) $request->getData('supplier'))
+ ->where('id', $request->getDataInt('supplier') ?? 0)
->where('attributes/type/name', [
'purchase_tax_code',
], 'IN')
@@ -797,7 +801,7 @@ final class ApiBillController extends Controller
}
/** @var \Modules\Billing\Models\Bill $bill */
- $bill = BillMapper::get()->where('id', (int) $request->getData('ref'))->execute();
+ $bill = BillMapper::get()->where('id', $request->getDataInt('ref') ?? 0)->execute();
$path = $this->createBillDir($bill);
$uploaded = new NullCollection();
@@ -864,10 +868,10 @@ final class ApiBillController extends Controller
}
/** @var \Modules\Media\Models\Media $media */
- $media = MediaMapper::get()->where('id', (int) $request->getData('media'))->execute();
+ $media = MediaMapper::get()->where('id', $request->getDataInt('media') ?? 0)->execute();
/** @var \Modules\Billing\Models\Bill $bill */
- $bill = BillMapper::get()->where('id', (int) $request->getData('bill'))->execute();
+ $bill = BillMapper::get()->where('id', $request->getDataInt('bill') ?? 0)->execute();
// Cannot delete system generated bill
if (\stripos($media->name, $bill->number) !== false) {
@@ -1593,7 +1597,7 @@ final class ApiBillController extends Controller
}
/** @var \Modules\Billing\Models\Bill $bill */
- $bill = BillMapper::get()->where('id', (int) $request->getData('ref'))->execute();
+ $bill = BillMapper::get()->where('id', $request->getDataInt('ref') ?? 0)->execute();
$request->setData('virtualpath', $this->createBillDir($bill), true);
$this->app->moduleManager->get('Editor', 'Api')->apiEditorCreate($request, $response, $data);
@@ -1659,7 +1663,7 @@ final class ApiBillController extends Controller
}
/** @var \Modules\Billing\Models\Bill $old */
- $old = BillMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $old = BillMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
// @todo check if bill can be deleted
// @todo adjust stock transfer
@@ -1730,7 +1734,7 @@ final class ApiBillController extends Controller
/** @var BillElement $old */
$old = BillElementMapper::get()
->with('bill')
- ->where('id', (int) $request->getData('id'))
+ ->where('id', $request->getDataInt('id') ?? 0)
->execute();
if ($old->bill->status === BillStatus::ARCHIVED) {
@@ -1813,7 +1817,7 @@ final class ApiBillController extends Controller
// @todo handle transactions and bill update
/** @var \Modules\Billing\Models\BillElement $billElement */
- $billElement = BillElementMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $billElement = BillElementMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
$this->deleteModel($request->header->account, $billElement, BillElementMapper::class, 'bill_element', $request->getOrigin());
$this->createStandardDeleteResponse($request, $response, $billElement);
}
diff --git a/Controller/ApiBillTypeController.php b/Controller/ApiBillTypeController.php
index f588a1e..3f0aa31 100755
--- a/Controller/ApiBillTypeController.php
+++ b/Controller/ApiBillTypeController.php
@@ -208,7 +208,7 @@ final class ApiBillTypeController extends Controller
}
/** @var BillType $old */
- $old = BillTypeMapper::get()->where('id', (int) $request->getData('id'));
+ $old = BillTypeMapper::get()->where('id', $request->getDataInt('id') ?? 0);
$new = $this->updateBillTypeFromRequest($request, clone $old);
$this->updateModel($request->header->account, $old, $new, BillTypeMapper::class, 'bill_type', $request->getOrigin());
@@ -285,7 +285,7 @@ final class ApiBillTypeController extends Controller
}
/** @var \Modules\Billing\Models\BillType $billType */
- $billType = BillTypeMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $billType = BillTypeMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
$this->deleteModel($request->header->account, $billType, BillTypeMapper::class, 'bill_type', $request->getOrigin());
$this->createStandardDeleteResponse($request, $response, $billType);
}
@@ -332,7 +332,7 @@ final class ApiBillTypeController extends Controller
}
/** @var BaseStringL11n $old */
- $old = BillTypeL11nMapper::get()->where('id', (int) $request->getData('id'));
+ $old = BillTypeL11nMapper::get()->where('id', $request->getDataInt('id') ?? 0);
$new = $this->updateBillTypeL11nFromRequest($request, clone $old);
$this->updateModel($request->header->account, $old, $new, BillTypeL11nMapper::class, 'bill_type_l11n', $request->getOrigin());
@@ -400,7 +400,7 @@ final class ApiBillTypeController extends Controller
}
/** @var BaseStringL11n $billTypeL11n */
- $billTypeL11n = BillTypeL11nMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $billTypeL11n = BillTypeL11nMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
$this->deleteModel($request->header->account, $billTypeL11n, BillTypeL11nMapper::class, 'bill_type_l11n', $request->getOrigin());
$this->createStandardDeleteResponse($request, $response, $billTypeL11n);
}
diff --git a/Controller/ApiPriceController.php b/Controller/ApiPriceController.php
index 3f4c6c5..74843b7 100755
--- a/Controller/ApiPriceController.php
+++ b/Controller/ApiPriceController.php
@@ -486,7 +486,7 @@ final class ApiPriceController extends Controller
}
/** @var \Modules\Billing\Models\Price\Price $old */
- $old = PriceMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $old = PriceMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
$new = $this->updatePriceFromRequest($request, clone $old);
$this->app->cachePool->get()->delete(
@@ -605,7 +605,7 @@ final class ApiPriceController extends Controller
}
/** @var \Modules\Billing\Models\Price\Price $price */
- $price = PriceMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $price = PriceMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
if ($price->name === 'default') {
// default price cannot be deleted
diff --git a/Controller/ApiPurchaseController.php b/Controller/ApiPurchaseController.php
index ed59422..9eb94d3 100755
--- a/Controller/ApiPurchaseController.php
+++ b/Controller/ApiPurchaseController.php
@@ -143,6 +143,9 @@ final class ApiPurchaseController extends Controller
}
*/
+ // @question How do we allow to manually assign a bill to a person/group for approval?
+ // Possible solution: create general module called Approval for all kinds of approvals
+
$documents = $files;
foreach ($documents as $file) {
@@ -255,7 +258,7 @@ final class ApiPurchaseController extends Controller
}
$bill = BillMapper::get()
- ->where('id', (int) $request->getData('id'))
+ ->where('id', $request->getDataInt('id') ?? 0)
->execute();
// After a bill is "closed" its values shouldn't change
diff --git a/Controller/ApiTaxController.php b/Controller/ApiTaxController.php
index f267b4b..b5a1763 100755
--- a/Controller/ApiTaxController.php
+++ b/Controller/ApiTaxController.php
@@ -304,7 +304,7 @@ final class ApiTaxController extends Controller
}
/** @var \Modules\Billing\Models\Tax\TaxCombination $old */
- $old = TaxCombinationMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $old = TaxCombinationMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
$new = $this->updateTaxCombinationFromRequest($request, clone $old);
$this->updateModel($request->header->account, $old, $new, TaxCombinationMapper::class, 'tax_combination', $request->getOrigin());
@@ -463,7 +463,7 @@ final class ApiTaxController extends Controller
}
/** @var \Modules\Billing\Models\Tax\TaxCombination $taxCombination */
- $taxCombination = TaxCombinationMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $taxCombination = TaxCombinationMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
$this->deleteModel($request->header->account, $taxCombination, TaxCombinationMapper::class, 'tax_combination', $request->getOrigin());
$this->createStandardDeleteResponse($request, $response, $taxCombination);
}
diff --git a/Controller/BackendController.php b/Controller/BackendController.php
index 7303d70..1419be7 100755
--- a/Controller/BackendController.php
+++ b/Controller/BackendController.php
@@ -246,7 +246,7 @@ final class BackendController extends Controller
->with('files')
->with('files/tags')
->with('notes')
- ->where('id', (int) $request->getData('id'))
+ ->where('id', $request->getDataInt('id') ?? 0)
->execute();
$view->data['billtypes'] = BillTypeMapper::getAll()
@@ -442,7 +442,7 @@ final class BackendController extends Controller
->with('files')
->with('files/tags')
->with('notes')
- ->where('id', (int) $request->getData('id'))
+ ->where('id', $request->getDataInt('id') ?? 0)
->execute();
$view->data['billtypes'] = BillTypeMapper::getAll()
@@ -537,7 +537,7 @@ final class BackendController extends Controller
$view->setTemplate('/Modules/Billing/Theme/Backend/purchase-bill');
$view->data['nav'] = $this->app->moduleManager->get('Navigation')->createNavigationMid(1005106001, $request, $response);
- $bill = BillMapper::get()->where('id', (int) $request->getData('id'))->execute();
+ $bill = BillMapper::get()->where('id', $request->getDataInt('id') ?? 0)->execute();
$view->data['bill'] = $bill;
$view->data['media-upload'] = new \Modules\Media\Theme\Backend\Components\Upload\BaseView($this->app->l11nManager, $request, $response);
@@ -647,7 +647,7 @@ final class BackendController extends Controller
->with('files')
->with('files/tags')
->with('notes')
- ->where('id', (int) $request->getData('id'))
+ ->where('id', $request->getDataInt('id') ?? 0)
->execute();
$tags = TagMapper::getAll()
@@ -713,7 +713,7 @@ final class BackendController extends Controller
$view->data['type'] = PaymentTermMapper::get()
->with('l11n')
- ->where('id', (int) $request->getData('id'))
+ ->where('id', $request->getDataInt('id') ?? 0)
->where('l11n/language', $response->header->l11n->language)
->execute();
@@ -775,7 +775,7 @@ final class BackendController extends Controller
$view->data['type'] = ShippingTermMapper::get()
->with('l11n')
- ->where('id', (int) $request->getData('id'))
+ ->where('id', $request->getDataInt('id') ?? 0)
->where('l11n/language', $response->header->l11n->language)
->execute();
@@ -887,7 +887,7 @@ final class BackendController extends Controller
->with('supplierCode')
->with('itemCode')
->with('taxCode')
- ->where('id', (int) $request->getData('id'))
+ ->where('id', $request->getDataInt('id') ?? 0)
->execute();
$view->data['client_codes'] = ClientAttributeTypeMapper::get()
diff --git a/Controller/CliController.php b/Controller/CliController.php
index f3cf993..5293b65 100755
--- a/Controller/CliController.php
+++ b/Controller/CliController.php
@@ -14,6 +14,7 @@ declare(strict_types=1);
namespace Modules\Billing\Controller;
+use Modules\Admin\Models\AccountMapper;
use Modules\Billing\Models\BillElement;
use Modules\Billing\Models\BillElementMapper;
use Modules\Billing\Models\BillMapper;
@@ -29,6 +30,7 @@ use Modules\SupplierManagement\Models\NullSupplier;
use Modules\SupplierManagement\Models\Supplier;
use Modules\SupplierManagement\Models\SupplierMapper;
use Modules\Tag\Models\TagMapper;
+use phpOMS\Account\AccountStatus;
use phpOMS\Contract\RenderableInterface;
use phpOMS\Localization\ISO4217CharEnum;
use phpOMS\Localization\ISO4217DecimalEnum;
@@ -121,12 +123,22 @@ final class CliController extends Controller
$identifiers = \json_decode($identifierContent, true);
/* Supplier */
+ // @performance Do we really want to select all the attributes below or only after we have found a suitable supplier
+ // We don't need these attributes initially, only once we found a matching supplier
+
+ // @performance We can't select all suppliers in one go, we probably need to iterate in chunks
+
+ // @bug We are missing the payment information here used in the matchSupplier() function
+
+ // @performance Could it be better to first perform some parsing of the bill to get the payment information and find the supplier based on that first?
+ // If we find a supplier this would be much faster, if it doesn't we can still do the brute force below
+
/** @var \Modules\SupplierManagement\Models\Supplier[] $suppliers */
$suppliers = SupplierMapper::getAll()
->with('account')
->with('mainAddress')
->with('attributes/type')
- ->where('attributes/type/name', ['bill_match_pattern', 'bill_date_format'], 'IN')
+ ->where('attributes/type/name', ['bill_match_pattern', 'bill_date_format', 'bill_approval'], 'IN')
->executeGetArray();
$bill->supplier = $this->matchSupplier($content, $suppliers);
@@ -482,6 +494,18 @@ final class CliController extends Controller
$billResponse = new HttpResponse();
$this->app->moduleManager->get('Billing', 'ApiBill')->apiBillPdfArchiveCreate($request, $billResponse);
+ if ($bill->supplier->id !== 0) {
+ // @question Do we want to also create a notification for the people in the default group
+
+ /*
+ $approvalAccounts = AccountMapper::getAll()
+ ->with('groups')
+ ->where('status', AccountStatus::ACTIVE)
+ ->where('groups/name', $bill->supplier->getAttribute('bill_approval')->value->valueStr)
+ ->executeGetArray();
+ */
+ }
+
return $view;
}
diff --git a/Models/Bill.php b/Models/Bill.php
index da0bf9e..b7713e5 100755
--- a/Models/Bill.php
+++ b/Models/Bill.php
@@ -19,6 +19,8 @@ use Modules\Admin\Models\NullAccount;
use Modules\ClientManagement\Models\Client;
use Modules\Sales\Models\SalesRep;
use Modules\SupplierManagement\Models\Supplier;
+use Modules\Workflow\Models\NullWorkflowStep;
+use Modules\Workflow\Models\WorkflowStep;
use phpOMS\Localization\BaseStringL11nType;
use phpOMS\Localization\ISO4217CharEnum;
use phpOMS\Localization\ISO639x1Enum;
@@ -57,6 +59,8 @@ class Bill implements \JsonSerializable
public int $unit = 0;
+ public WorkflowStep $approval;
+
public int $source = 0;
/**
@@ -418,6 +422,7 @@ class Bill implements \JsonSerializable
public ?string $fiAccount = null;
// @todo Implement reason for bill (especially useful for credit notes, warehouse bookings)
+ // @todo Implement internal notes for bill
/**
* Constructor.
@@ -438,6 +443,8 @@ class Bill implements \JsonSerializable
$this->createdBy = new NullAccount();
$this->referral = new NullAccount();
$this->type = new NullBillType();
+
+ $this->approval = new NullWorkflowStep();
}
/**
diff --git a/Models/BillElement.php b/Models/BillElement.php
index 9fd2426..0c19c5c 100755
--- a/Models/BillElement.php
+++ b/Models/BillElement.php
@@ -18,6 +18,8 @@ use Modules\Billing\Models\Tax\TaxCombination;
use Modules\ItemManagement\Models\Container;
use Modules\ItemManagement\Models\Item;
use Modules\ItemManagement\Models\NullItem;
+use Modules\Workflow\Models\NullWorkflowStep;
+use Modules\Workflow\Models\WorkflowStep;
use phpOMS\Localization\ISO4217DecimalEnum;
use phpOMS\Stdlib\Base\FloatInt;
use phpOMS\Stdlib\Base\SmartDateTime;
@@ -42,6 +44,8 @@ class BillElement implements \JsonSerializable
public int $order = 0;
+ public WorkflowStep $approval;
+
public ?Item $item = null;
public ?Container $container = null;
@@ -201,6 +205,8 @@ class BillElement implements \JsonSerializable
$this->taxP = new FloatInt();
$this->taxR = new FloatInt();
+
+ $this->approval = new NullWorkflowStep();
}
/**
diff --git a/Models/BillElementMapper.php b/Models/BillElementMapper.php
index ad3300b..f4fab42 100755
--- a/Models/BillElementMapper.php
+++ b/Models/BillElementMapper.php
@@ -17,6 +17,7 @@ namespace Modules\Billing\Models;
use Modules\Billing\Models\Tax\TaxCombinationMapper;
use Modules\ItemManagement\Models\ContainerMapper;
use Modules\ItemManagement\Models\ItemMapper;
+use Modules\Workflow\Models\WorkflowStepMapper;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
/**
@@ -80,6 +81,8 @@ final class BillElementMapper extends DataMapperFactory
'billing_bill_element_fiaccount' => ['name' => 'billing_bill_element_fiaccount', 'type' => 'string', 'internal' => 'fiAccount'],
'billing_bill_element_costcenter' => ['name' => 'billing_bill_element_costcenter', 'type' => 'string', 'internal' => 'costcenter'],
'billing_bill_element_costobject' => ['name' => 'billing_bill_element_costobject', 'type' => 'string', 'internal' => 'costobject'],
+
+ 'billing_bill_element_approval' => ['name' => 'billing_bill_element_approval', 'type' => 'int', 'internal' => 'approval'],
];
/**
@@ -118,6 +121,10 @@ final class BillElementMapper extends DataMapperFactory
'mapper' => TaxCombinationMapper::class,
'external' => 'billing_bill_element_tax_combination',
],
+ 'approval' => [
+ 'mapper' => WorkflowStepMapper::class,
+ 'external' => 'billing_bill_element_approval',
+ ],
];
/**
diff --git a/Models/BillMapper.php b/Models/BillMapper.php
index 6961b48..b47e1b1 100755
--- a/Models/BillMapper.php
+++ b/Models/BillMapper.php
@@ -21,6 +21,7 @@ use Modules\Editor\Models\EditorDocMapper;
use Modules\Media\Models\MediaMapper;
use Modules\Sales\Models\SalesRepMapper;
use Modules\SupplierManagement\Models\SupplierMapper;
+use Modules\Workflow\Models\WorkflowStepMapper;
use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
/**
@@ -100,6 +101,7 @@ class BillMapper extends DataMapperFactory
'billing_bill_performance_date' => ['name' => 'billing_bill_performance_date', 'type' => 'DateTime', 'internal' => 'performanceDate', 'readonly' => true],
'billing_bill_created_at' => ['name' => 'billing_bill_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true],
'billing_bill_unit' => ['name' => 'billing_bill_unit', 'type' => 'int', 'internal' => 'unit'],
+ 'billing_bill_approval' => ['name' => 'billing_bill_approval', 'type' => 'int', 'internal' => 'approval'],
];
/**
@@ -162,6 +164,10 @@ class BillMapper extends DataMapperFactory
'mapper' => ShippingTermMapper::class,
'external' => 'shippingTerms',
],
+ 'approval' => [
+ 'mapper' => WorkflowStepMapper::class,
+ 'external' => 'approval',
+ ],
];
/**
diff --git a/Models/BillStatus.php b/Models/BillStatus.php
index 2c58a48..fe9cd11 100755
--- a/Models/BillStatus.php
+++ b/Models/BillStatus.php
@@ -32,6 +32,8 @@ abstract class BillStatus extends Enum
public const DELETED = 4;
+ // Bill is completed and ready to be finalized
+ // This means the bill can now be approved (if the workflow is defined that way)
public const DONE = 8;
public const DRAFT = 16;
diff --git a/Models/WorkflowStepStatusEnum.php b/Models/WorkflowStepStatusEnum.php
new file mode 100644
index 0000000..49a5507
--- /dev/null
+++ b/Models/WorkflowStepStatusEnum.php
@@ -0,0 +1,61 @@
+data['media'] ?? [];
/** @var \Modules\Billing\Models\Bill $bill */
-$bill = $this->getData('bill') ?? new NullBill();
+$bill = $this->data['bill'] ?? new NullBill();
$elements = $bill->elements;
$billTypes = $this->data['billtypes'] ?? [];
diff --git a/Theme/Cli/bill-parsed.tpl.php b/Theme/Cli/bill-parsed.tpl.php
index 5dc0650..29d01a6 100755
--- a/Theme/Cli/bill-parsed.tpl.php
+++ b/Theme/Cli/bill-parsed.tpl.php
@@ -1,3 +1,3 @@
getData('bill') ?? null, \JSON_PRETTY_PRINT);
+echo \json_encode($this->data['bill'] ?? null, \JSON_PRETTY_PRINT);
diff --git a/info.json b/info.json
index 56ba1c9..c391eb0 100755
--- a/info.json
+++ b/info.json
@@ -23,7 +23,8 @@
"Calendar": "1.0.0",
"ItemManagement": "1.0.0",
"ClientManagement": "1.0.0",
- "SupplierManagement": "1.0.0"
+ "SupplierManagement": "1.0.0",
+ "Workflow": "1.0.0"
},
"providing": {
"Admin": "*",