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

This commit is contained in:
Dennis Eichhorn 2025-03-21 02:48:17 +00:00
parent 0647f6da2b
commit b36936274d
31 changed files with 864 additions and 67 deletions

16
Admin/Hooks/Cli.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/**
* Jingga
*
* PHP Version 8.2
*
* @package Modules\Billing
* @copyright Dennis Eichhorn
* @license OMS License 2.2
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
return [
];

29
Admin/Hooks/Web/Api.php Normal file
View File

@ -0,0 +1,29 @@
<?php
/**
* Jingga
*
* PHP Version 8.2
*
* @package Modules\Admin
* @copyright Dennis Eichhorn
* @license OMS License 2.2
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
return [
"POST:Billing:bill-create" => [
'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'],
],
];

View File

@ -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": {

View File

@ -0,0 +1,319 @@
<?php
/**
* Jingga
*
* PHP Version 8.2
*
* @package Modules\Workflow\Controller
* @copyright Dennis Eichhorn
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Workflow\Controller;
use Modules\Admin\Models\GroupMapper;
use Modules\Admin\Models\NullAccount;
use Modules\Admin\Models\NullGroup;
use Modules\Auditor\Models\Audit;
use Modules\Auditor\Models\AuditMapper;
use Modules\Billing\Models\Bill;
use Modules\Billing\Models\BillElementMapper;
use Modules\Billing\Models\BillMapper;
use Modules\Billing\Models\BillStatus;
use Modules\Billing\Models\PermissionCategory;
use Modules\Billing\Models\WorkflowStepStatusEnum;
use Modules\Billing\Models\WorkflowType;
use Modules\Tasks\Models\Task;
use Modules\Tasks\Models\TaskElement;
use Modules\Tasks\Models\TaskMapper;
use Modules\Tasks\Models\TaskPriority;
use Modules\Tasks\Models\TaskStatus;
use Modules\Tasks\Models\TaskType;
use Modules\Workflow\Models\NullWorkflowTemplate;
use Modules\Workflow\Models\WorkflowControllerInterface;
use Modules\Workflow\Models\WorkflowInstanceAbstract;
use Modules\Workflow\Models\WorkflowTemplate;
use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\Views\View;
use Modules\Workflow\Models\WorkflowInstanceAbstractMapper;
use Modules\Workflow\Models\WorkflowStep;
use phpOMS\Utils\StringUtils;
/**
* OMS export class
*
* @package Modules\Workflow\Controller
* @license OMS License 2.0
* @link https://jingga.app
* @since 1.0.0
*/
final class WorkflowController implements WorkflowControllerInterface
{
/**
* {@inheritdoc}
*/
public function createInstanceFromRequest(RequestAbstract $request, WorkflowTemplate $template) : WorkflowInstanceAbstract
{
$instance = new WorkflowInstanceAbstract();
$instance->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
{
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Jingga
*
* PHP Version 8.2
*
* @package Modules\Workflow
* @copyright Dennis Eichhorn
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
use phpOMS\Uri\UriFactory;
$instances = $this->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();
?>
<div class="row">
<div class="col-xs-12">
<section class="portlet">
<div class="portlet-head"><?= $this->getHtml('Instances'); ?><i class="g-icon download btn end-xs">download</i></div>
<div class="slider">
<table id="helperList" class="default sticky">
<thead>
<tr>
<td><label class="checkbox" for="helperList-0">
<input type="checkbox" id="helperList-0" name="helperselect">
<span class="checkmark"></span>
</label>
<td>
</tr>
<tbody>
</table>
</div>
<!--
<div class="portlet-foot">
<a tabindex="0" class="button" href="<?= UriFactory::build($previous); ?>"><?= $this->getHtml('Previous', '0', '0'); ?></a>
<a tabindex="0" class="button" href="<?= UriFactory::build($next); ?>"><?= $this->getHtml('Next', '0', '0'); ?></a>
</div>
-->
</section>
</div>
</div>

View File

@ -0,0 +1,54 @@
<?php
/**
* Jingga
*
* PHP Version 8.2
*
* @package Modules\Workflow
* @copyright Dennis Eichhorn
* @license OMS License 2.0
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
echo $this->data['nav']->render();
?>
<div class="tabview tab-2">
<div class="box">
<ul class="tab-links">
<li><label for="c-tab-1"><?= $this->getHtml('Overview'); ?></label>
<li><label for="c-tab-1"><?= $this->getHtml('Bill'); ?></label>
<li><label for="c-tab-1"><?= $this->getHtml('CreditLimit'); ?></label>
<li><label for="c-tab-1"><?= $this->getHtml('PaymentTerm'); ?></label>
<li><label for="c-tab-2"><?= $this->getHtml('Elements'); ?></label>
</ul>
</div>
<div class="tab-content">
<input type="radio" id="c-tab-1" name="tabular-2"<?= empty($this->request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
<div class="tab">
<div class="row">
<div class="col-xs-12">
// create colored list (grey, red, green) of the different steps
// grey = no action needed
// yellow = action needed
// green = approved
// red = declined
</div>
</div>
</div>
<input type="radio" id="c-tab-1" name="tabular-2"<?= empty($this->request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
<div class="tab">
<div class="row">
<div class="col-xs-12">
<section class="portlet">
<div class="portlet-head">Profile</div>
<div class="portlet-body"></div>
<div class="portlet-foot"></div>
</section>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
return [
'en' => [
'Overview' => 'Overview',
'Bill' => 'Bill',
'CreditLimit' => 'Credit Limit',
'PaymentTerm' => 'Payment Term',
'Elements' => 'Elements',
],
'de' => [
'Overview' => 'Übersicht',
'Bill' => 'Beleg',
'CreditLimit' => 'Kreditlimit',
'PaymentTerm' => 'Zahlungsbedingung',
'Elements' => 'Belegzeilen',
],
];

View File

@ -0,0 +1,131 @@
<?php
use phpOMS\Uri\UriFactory;
/** @var \Modules\Workflow\Models\WorkflowTemplate $template */
$template = $this->data['template'];
$media = $template->source->getSources();
$lang = include $template->source->findFile('lang.php')->getAbsolutePath();
echo $this->data['nav']->render(); ?>
<div id="iSettings" class="tabview tab-2 url-rewrite">
<div class="box">
<ul class="tab-links">
<li><label for="c-tab-1"><?= $this->getHtml('General'); ?></label>
<li><label for="c-tab-2"><?= $this->getHtml('Workflow'); ?></label>
<li><label for="c-tab-3"><?= $this->getHtml('Settings'); ?></label>
<li><label for="c-tab-4"><?= $this->getHtml('Files'); ?></label>
</ul>
</div>
<div class="tab-content">
<input type="radio" id="c-tab-1" name="tabular-2"<?= empty($this->request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
<div class="tab">
<div class="row">
<div class="col-xs-12 col-md-6">
<section class="portlet">
<form id="iGeneralSettings" action="<?= UriFactory::build('{/api}admin/settings/general?csrf={$CSRF}'); ?>" method="post">
<div class="portlet-head"><?= $this->getHtml('General'); ?></div>
<div class="portlet-body">
<div class="form-group">
<label for="iOname"><?= $this->getHtml('Name'); ?></label>
<input type="text" value="<?= $template->name; ?>">
</div>
<div class="form-group">
<label for="iOname"><?= $this->getHtml('Description'); ?></label>
<textarea></textarea>
</div>
</div>
</form>
</section>
</div>
</div>
</div>
<input type="radio" id="c-tab-2" name="tabular-2"<?= empty($this->request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
<div class="tab">
<div class="row">
<div class="col-xs-12">
<section class="portlet">
<div class="portlet-body">
<div class="mermaid">
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];
</div>
</div>
</section>
</div>
</div>
</div>
<input type="radio" id="c-tab-3" name="tabular-2"<?= empty($this->request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
<div class="tab">
<div class="row">
</div>
</div>
<input type="radio" id="c-tab-4" name="tabular-2"<?= empty($this->request->uri->fragment) || $this->request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>>
<div class="tab">
<div class="row">
<div class="col-xs-12 col-md-6">
<section class="portlet">
<div class="portlet-head"><?= $this->getHtml('Media'); ?><i class="g-icon download btn end-xs">download</i></div>
<table class="default sticky" id="invoice-item-list">
<thead>
<tr>
<td>
<td class="wf-100"><?= $this->getHtml('Name'); ?>
<td><?= $this->getHtml('Type'); ?>
<tbody>
<?php foreach ($media as $file) :
$url = $file->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
: ''
)
);
?>
<tr data-href="<?= $url; ?>">
<td>
<td><a href="<?= $url; ?>"><?= $file->name; ?></a>
<td><a href="<?= $url; ?>"><?= $file->extension; ?></a>
<?php endforeach; ?>
</table>
</section>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,12 @@
[
{
"name": "IS_APPROVED",
"title": "Is approved?",
"hooks": [
],
"if": {
"isApproved": true,
"action": "print"
}
}
]

View File

@ -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
}
}
},

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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()

View File

@ -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;
}

View File

@ -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();
}
/**

View File

@ -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();
}
/**

View File

@ -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',
],
];
/**

View File

@ -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',
],
];
/**

View File

@ -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;

View File

@ -0,0 +1,61 @@
<?php
/**
* Jingga
*
* PHP Version 8.2
*
* @package Modules\Billing\Models
* @copyright Dennis Eichhorn
* @license OMS License 2.2
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models;
use phpOMS\Stdlib\Base\Enum;
/**
* Workflow type enum.
*
* @package Modules\Billing\Models
* @license OMS License 2.2
* @link https://jingga.app
* @since 1.0.0
*/
abstract class WorkflowStepStatusEnum extends Enum
{
// The price/profit of the element needs approval
public const BILL_ELEMENT_PRICE_APPROVAL = 1 << 0;
// Large quantity of element is large and needs to be approved
public const BILL_ELEMENT_QUANTITY_APPROVAL = 1 << 1;
// Large quantity of element is large and needs to be approved
public const BILL_ELEMENT_PROFIT_APPROVAL = 1 << 2;
// The element is completely approved
public const BILL_ELEMENT_APPROVAL = 1 << 3;
// The bill got checked for correctness
public const BILL_CORRECTNESS_APPROVAL = 1 << 4;
// The total price is approved (e.g. large orders)
public const BILL_PRICE_APPROVAL = 1 << 5;
// The profit of the bill is below a certain threshold
public const BILL_PROFIT_APPROVAL = 1 << 6;
// The payment term is worse than the default term of the customer
public const BILL_PAYMENT_TERM_APPROVAL = 1 << 7;
// Approval despite credit limit reached
public const BILL_CREDIT_LIMIT_APPROVAL = 1 << 8;
// Allows bill to be paid
public const BILL_PAYMENT_APPROVAL = 1 << 9;
// The bill is completely approved
public const BILL_APPROVAL = 1 << 10;
}

32
Models/WorkflowType.php Normal file
View File

@ -0,0 +1,32 @@
<?php
/**
* Jingga
*
* PHP Version 8.2
*
* @package Modules\Billing\Models
* @copyright Dennis Eichhorn
* @license OMS License 2.2
* @version 1.0.0
* @link https://jingga.app
*/
declare(strict_types=1);
namespace Modules\Billing\Models;
use phpOMS\Stdlib\Base\Enum;
/**
* Workflow type enum.
*
* @package Modules\Billing\Models
* @license OMS License 2.2
* @link https://jingga.app
* @since 1.0.0
*/
abstract class WorkflowType extends Enum
{
public const SALES_BILL = 1;
public const PURCHASE_BILL = 2;
}

View File

@ -31,7 +31,7 @@ $currencies = ISO4217Enum::getConstants();
$media = $this->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'] ?? [];

View File

@ -1,3 +1,3 @@
<?php declare(strict_types=1);
echo \json_encode($this->getData('bill') ?? null, \JSON_PRETTY_PRINT);
echo \json_encode($this->data['bill'] ?? null, \JSON_PRETTY_PRINT);

View File

@ -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": "*",