diff --git a/Admin/Install/Navigation.install.json b/Admin/Install/Navigation.install.json
index 7193faf..c4bc50d 100755
--- a/Admin/Install/Navigation.install.json
+++ b/Admin/Install/Navigation.install.json
@@ -1,4 +1,66 @@
[
+ {
+ "id": 1004801001,
+ "pid": "/",
+ "type": 2,
+ "subtype": 0,
+ "name": "ItemManagement",
+ "uri": null,
+ "target": "self",
+ "icon": "fa fa-usd",
+ "order": 20,
+ "from": "ItemManagement",
+ "permission": { "permission": 2, "type": null, "element": null },
+ "parent": 0,
+ "children": [
+ {
+ "id": 1004802001,
+ "pid": "/",
+ "type": 2,
+ "subtype": 1,
+ "name": "Attributes",
+ "uri": "{/prefix}item/attribute/type?{?}",
+ "target": "self",
+ "icon": null,
+ "order": 15,
+ "from": "Sales",
+ "permission": { "permission": 2, "type": null, "element": null },
+ "parent": 1004801001,
+ "children": [
+ {
+ "id": 1004802002,
+ "pid": "/item",
+ "type": 3,
+ "subtype": 1,
+ "name": "Types",
+ "uri": "{/prefix}item/attribute/type?{?}",
+ "target": "self",
+ "icon": null,
+ "order": 15,
+ "from": "Sales",
+ "permission": { "permission": 2, "type": null, "element": null },
+ "parent": 1004802001,
+ "children": []
+ },
+ {
+ "id": 1004802003,
+ "pid": "/item",
+ "type": 3,
+ "subtype": 1,
+ "name": "Values",
+ "uri": "{/prefix}item/attribute/value?{?}",
+ "target": "self",
+ "icon": null,
+ "order": 15,
+ "from": "Sales",
+ "permission": { "permission": 2, "type": null, "element": null },
+ "parent": 1004802001,
+ "children": []
+ }
+ ]
+ }
+ ]
+ },
{
"id": 1004805001,
"pid": "/",
@@ -246,5 +308,20 @@
"children": []
}
]
+ },
+ {
+ "id": 1004809001,
+ "pid": "/sales/analysis",
+ "type": 3,
+ "subtype": 1,
+ "name": "Article",
+ "uri": "{/prefix}sales/analysis/item",
+ "target": "self",
+ "icon": null,
+ "order": 4,
+ "from": "ItemManagement",
+ "permission": { "permission": 2, "type": null, "element": null },
+ "parent": 1001602001,
+ "children": []
}
]
diff --git a/Admin/Install/db.json b/Admin/Install/db.json
index ff0497b..994ff68 100755
--- a/Admin/Install/db.json
+++ b/Admin/Install/db.json
@@ -114,6 +114,18 @@
"name": "itemmgmt_attr_type_custom",
"type": "TINYINT(1)",
"null": false
+ },
+ "itemmgmt_attr_type_required": {
+ "description": "Every item must have this attribute type if set to true.",
+ "name": "itemmgmt_attr_type_required",
+ "type": "TINYINT(1)",
+ "null": false
+ },
+ "itemmgmt_attr_type_pattern": {
+ "description": "This is a regex validation pattern.",
+ "name": "itemmgmt_attr_type_pattern",
+ "type": "VARCHAR(255)",
+ "null": false
}
}
},
diff --git a/Admin/Routes/Web/Backend.php b/Admin/Routes/Web/Backend.php
index d0068c8..4a53334 100755
--- a/Admin/Routes/Web/Backend.php
+++ b/Admin/Routes/Web/Backend.php
@@ -6,6 +6,50 @@ use phpOMS\Account\PermissionType;
use phpOMS\Router\RouteVerb;
return [
+ '^.*/item/attribute/type.*$' => [
+ [
+ 'dest' => '\Modules\ItemManagement\Controller\BackendController:viewItemManagementAttributeTypes',
+ 'verb' => RouteVerb::GET,
+ 'permission' => [
+ 'module' => BackendController::MODULE_NAME,
+ 'type' => PermissionType::READ,
+ 'state' => PermissionState::ATTRIBUTE,
+ ],
+ ],
+ ],
+ '^.*/item/attribute/value.*$' => [
+ [
+ 'dest' => '\Modules\ItemManagement\Controller\BackendController:viewItemManagementAttributeValues',
+ 'verb' => RouteVerb::GET,
+ 'permission' => [
+ 'module' => BackendController::MODULE_NAME,
+ 'type' => PermissionType::READ,
+ 'state' => PermissionState::ATTRIBUTE,
+ ],
+ ],
+ ],
+ '^.*/item/attribute/type.*$' => [
+ [
+ 'dest' => '\Modules\ItemManagement\Controller\BackendController:viewItemManagementAttributeTypes',
+ 'verb' => RouteVerb::GET,
+ 'permission' => [
+ 'module' => BackendController::MODULE_NAME,
+ 'type' => PermissionType::READ,
+ 'state' => PermissionState::ATTRIBUTE,
+ ],
+ ],
+ ],
+ '^.*/item/attribute/value.*$' => [
+ [
+ 'dest' => '\Modules\ItemManagement\Controller\BackendController:viewItemManagementAttributeValues',
+ 'verb' => RouteVerb::GET,
+ 'permission' => [
+ 'module' => BackendController::MODULE_NAME,
+ 'type' => PermissionType::READ,
+ 'state' => PermissionState::ATTRIBUTE,
+ ],
+ ],
+ ],
'^.*/sales/item/list.*$' => [
[
'dest' => '\Modules\ItemManagement\Controller\BackendController:viewItemManagementSalesList',
@@ -105,4 +149,15 @@ return [
],
],
],
+ '^.*/sales/analysis/item(\?.*|$)$' => [
+ [
+ 'dest' => '\Modules\ItemManagement\Controller\BackendController:viewItemAnalysis',
+ 'verb' => RouteVerb::GET,
+ 'permission' => [
+ 'module' => BackendController::MODULE_NAME,
+ 'type' => PermissionType::READ,
+ 'state' => PermissionState::SALES_ITEM,
+ ],
+ ],
+ ],
];
diff --git a/Controller/BackendController.php b/Controller/BackendController.php
index 25b6f8b..e039222 100755
--- a/Controller/BackendController.php
+++ b/Controller/BackendController.php
@@ -15,11 +15,14 @@ declare(strict_types=1);
namespace Modules\ItemManagement\Controller;
use Model\SettingsEnum;
+use Modules\Media\Models\Media;
use Modules\Admin\Models\LocalizationMapper;
use Modules\Billing\Models\BillTypeL11n;
use Modules\Billing\Models\SalesBillMapper;
use Modules\ItemManagement\Models\ItemAttributeMapper;
+use Modules\ItemManagement\Models\ItemAttributeTypeMapper;
use Modules\ItemManagement\Models\ItemL11nMapper;
+use Modules\ItemManagement\Models\ItemL11nType;
use Modules\ItemManagement\Models\ItemMapper;
use phpOMS\Asset\AssetType;
use phpOMS\Contract\RenderableInterface;
@@ -28,6 +31,7 @@ use phpOMS\Message\RequestAbstract;
use phpOMS\Message\ResponseAbstract;
use phpOMS\Stdlib\Base\SmartDateTime;
use phpOMS\Views\View;
+use Modules\ItemManagement\Models\ItemAttributeValueMapper;
/**
* ItemManagement controller class.
@@ -39,6 +43,111 @@ use phpOMS\Views\View;
*/
final class BackendController extends Controller
{
+
+ /**
+ * Routing end-point for application behaviour.
+ *
+ * @param RequestAbstract $request Request
+ * @param ResponseAbstract $response Response
+ * @param mixed $data Generic data
+ *
+ * @return RenderableInterface
+ *
+ * @since 1.0.0
+ * @codeCoverageIgnore
+ */
+ public function viewItemManagementAttributeTypes(RequestAbstract $request, ResponseAbstract $response, $data = null) : RenderableInterface
+ {
+ $view = new View($this->app->l11nManager, $request, $response);
+ $view->setTemplate('/Modules/ItemManagement/Theme/Backend/attribute-type-list');
+ $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1004801001, $request, $response));
+
+ $attributes = ItemAttributeTypeMapper::with('language', $response->getLanguage())
+ ::getAll();
+
+ $view->addData('attributes', $attributes);
+
+ return $view;
+ }
+
+ /**
+ * Routing end-point for application behaviour.
+ *
+ * @param RequestAbstract $request Request
+ * @param ResponseAbstract $response Response
+ * @param mixed $data Generic data
+ *
+ * @return RenderableInterface
+ *
+ * @since 1.0.0
+ * @codeCoverageIgnore
+ */
+ public function viewItemManagementAttributeValues(RequestAbstract $request, ResponseAbstract $response, $data = null) : RenderableInterface
+ {
+ $view = new View($this->app->l11nManager, $request, $response);
+ $view->setTemplate('/Modules/ItemManagement/Theme/Backend/attribute-value-list');
+ $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1004801001, $request, $response));
+
+ $attributes = ItemAttributeValueMapper::with('language', $response->getLanguage())
+ ::getAll();
+
+ $view->addData('attributes', $attributes);
+
+ return $view;
+ }
+
+ /**
+ * Routing end-point for application behaviour.
+ *
+ * @param RequestAbstract $request Request
+ * @param ResponseAbstract $response Response
+ * @param mixed $data Generic data
+ *
+ * @return RenderableInterface
+ *
+ * @since 1.0.0
+ * @codeCoverageIgnore
+ */
+ public function viewItemManagementAttributeType(RequestAbstract $request, ResponseAbstract $response, $data = null) : RenderableInterface
+ {
+ $view = new View($this->app->l11nManager, $request, $response);
+ $view->setTemplate('/Modules/ItemManagement/Theme/Backend/attribute-type');
+ $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1004801001, $request, $response));
+
+ $attribute = ItemAttributeTypeMapper::with('language', $response->getLanguage())
+ ::get((int) $request->getData('id'));
+
+ $view->addData('attribute', $attribute);
+
+ return $view;
+ }
+
+ /**
+ * Routing end-point for application behaviour.
+ *
+ * @param RequestAbstract $request Request
+ * @param ResponseAbstract $response Response
+ * @param mixed $data Generic data
+ *
+ * @return RenderableInterface
+ *
+ * @since 1.0.0
+ * @codeCoverageIgnore
+ */
+ public function viewItemManagementAttributeValue(RequestAbstract $request, ResponseAbstract $response, $data = null) : RenderableInterface
+ {
+ $view = new View($this->app->l11nManager, $request, $response);
+ $view->setTemplate('/Modules/ItemManagement/Theme/Backend/attribute-value');
+ $view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1004801001, $request, $response));
+
+ $attribute = ItemAttributeValueMapper::with('language', $response->getLanguage())
+ ::get((int) $request->getData('id'));
+
+ $view->addData('attribute', $attribute);
+
+ return $view;
+ }
+
/**
* Routing end-point for application behaviour.
*
@@ -57,7 +166,13 @@ final class BackendController extends Controller
$view->setTemplate('/Modules/ItemManagement/Theme/Backend/sales-item-list');
$view->addData('nav', $this->app->moduleManager->get('Navigation')->createNavigationMid(1004805001, $request, $response));
- $items = ItemMapper::with('language', $response->getLanguage())::getAfterPivot(0, null, 25);
+ $items = ItemMapper::with('language', $response->getLanguage())
+ ::with('type', 'backend_image', models: [Media::class]) // @todo: it would be nicer if I coult say files:type or files/type and remove the models parameter?
+ ::with('notes', models: null)
+ ::with('attributes', models: null)
+ ::with('title', ['name1', 'name2', 'name3'], comparison: 'in', models: [ItemL11nType::class]) // @todo: profile, why does this have almost no impact on the sql performance?
+ ::getAfterPivot(0, null, 25);
+
$view->addData('items', $items);
return $view;
@@ -181,12 +296,12 @@ final class BackendController extends Controller
* @param ResponseAbstract $response Response
* @param mixed $data Generic data
*
- * @return RenderableInterface
+ * @return View
*
* @since 1.0.0
* @codeCoverageIgnore
*/
- public function viewItemManagementSalesItem(RequestAbstract $request, ResponseAbstract $response, $data = null) : RenderableInterface
+ public function viewItemManagementSalesItem(RequestAbstract $request, ResponseAbstract $response, $data = null) : View
{
$head = $response->get('Content')->getData('head');
$head->addAsset(AssetType::CSS, 'Resources/chartjs/Chartjs/chart.css');
@@ -226,6 +341,7 @@ final class BackendController extends Controller
// @todo: why is the conditional array necessary, shouldn't the mapper realize when it mustn't use the conditional (when the field doesn't exist in the mapper)
$newestInvoices = SalesBillMapper::with('language', $response->getLanguage(), [BillTypeL11n::class])::getNewestItemInvoices($item->getId(), 5);
$topCustomers = SalesBillMapper::getItemTopCustomers($item->getId(), new SmartDateTime('Y-01-01'), new SmartDateTime('now'), 5);
+ $allInvoices = SalesBillMapper::getItemBills($item->getId(), new SmartDateTime('Y-01-01'), new SmartDateTime('now'));
$regionSales = SalesBillMapper::getItemRegionSales($item->getId(), new SmartDateTime('Y-01-01'), new SmartDateTime('now'));
$countrySales = SalesBillMapper::getItemCountrySales($item->getId(), new SmartDateTime('Y-01-01'), new SmartDateTime('now'), 5);
$monthlySalesCosts = SalesBillMapper::getItemMonthlySalesCosts($item->getId(), (new SmartDateTime('now'))->createModify(-1), new SmartDateTime('now'));
@@ -235,6 +351,7 @@ final class BackendController extends Controller
$avg = new Money();
$lastOrder = null;
$newestInvoices = [];
+ $allInvoices = [];
$topCustomers = [];
$regionSales = [];
$countrySales = [];
@@ -246,6 +363,7 @@ final class BackendController extends Controller
$view->addData('avg', $avg);
$view->addData('lastOrder', $lastOrder);
$view->addData('newestInvoices', $newestInvoices);
+ $view->addData('allInvoices', $allInvoices);
$view->addData('topCustomers', $topCustomers);
$view->addData('regionSales', $regionSales);
$view->addData('countrySales', $countrySales);
diff --git a/Models/ItemAttributeType.php b/Models/ItemAttributeType.php
index 9f7a9fd..fe1e935 100755
--- a/Models/ItemAttributeType.php
+++ b/Models/ItemAttributeType.php
@@ -41,7 +41,7 @@ class ItemAttributeType implements \JsonSerializable, ArrayableInterface
* @var string
* @since 1.0.0
*/
- protected string $name = ''; // @todo: currently not filled, should be used as identifier or if not required removed (at the moment it seems like it is useless?!)
+ public string $name = ''; // @todo: currently not filled, should be used as identifier or if not required removed (at the moment it seems like it is useless?!)
/**
* Which field data type is required (string, int, ...) in the value
@@ -59,12 +59,16 @@ class ItemAttributeType implements \JsonSerializable, ArrayableInterface
*/
protected bool $custom = false;
+ public string $validationPattern = '';
+
+ public bool $isRequired = false;
+
/**
* Localization
*
- * @var int|int[]|ItemAttributeTypeL11n|ItemAttributeTypeL11n[]
+ * @var ItemAttributeTypeL11n
*/
- protected $l11n = 0;
+ protected string | ItemAttributeTypeL11n $l11n;
/**
* Possible default attribute values
diff --git a/Models/ItemAttributeTypeL11n.php b/Models/ItemAttributeTypeL11n.php
index 558392d..9cc06d4 100755
--- a/Models/ItemAttributeTypeL11n.php
+++ b/Models/ItemAttributeTypeL11n.php
@@ -38,12 +38,12 @@ class ItemAttributeTypeL11n implements \JsonSerializable, ArrayableInterface
/**
* Item ID.
*
- * @var int|ItemL11nType
+ * @var int|ItemAttributeType
* @since 1.0.0
*/
protected int |
-ItemL11nType $type = 0;
+ItemAttributeType $type = 0;
/**
* Language.
@@ -64,13 +64,13 @@ ItemL11nType $type = 0;
/**
* Constructor.
*
- * @param int|ItemL11nType $type Attribute type
+ * @param int|ItemAttributeType $type Attribute type
* @param string $title Localized title
* @param string $language Language
*
* @since 1.0.0
*/
- public function __construct(int | ItemL11nType $type = 0, string $title = '', string $language = ISO639x1Enum::_EN)
+ public function __construct(int | ItemAttributeType $type = 0, string $title = '', string $language = ISO639x1Enum::_EN)
{
$this->type = $type;
$this->title = $title;
@@ -92,11 +92,11 @@ ItemL11nType $type = 0;
/**
* Get attribute type
*
- * @return int|ItemL11nType
+ * @return int|ItemAttributeType
*
* @since 1.0.0
*/
- public function getType()
+ public function getType() : int | ItemAttributeType
{
return $this->type;
}
diff --git a/Models/ItemAttributeTypeMapper.php b/Models/ItemAttributeTypeMapper.php
index 6be4170..813de7c 100755
--- a/Models/ItemAttributeTypeMapper.php
+++ b/Models/ItemAttributeTypeMapper.php
@@ -37,6 +37,8 @@ final class ItemAttributeTypeMapper extends DataMapperAbstract
'itemmgmt_attr_type_name' => ['name' => 'itemmgmt_attr_type_name', 'type' => 'string', 'internal' => 'name', 'autocomplete' => true],
'itemmgmt_attr_type_fields' => ['name' => 'itemmgmt_attr_type_fields', 'type' => 'int', 'internal' => 'fields'],
'itemmgmt_attr_type_custom' => ['name' => 'itemmgmt_attr_type_custom', 'type' => 'bool', 'internal' => 'custom'],
+ 'itemmgmt_attr_type_pattern' => ['name' => 'itemmgmt_attr_type_pattern', 'type' => 'bool', 'internal' => 'validationPattern'],
+ 'itemmgmt_attr_type_required' => ['name' => 'itemmgmt_attr_type_required', 'type' => 'bool', 'internal' => 'isRequired'],
];
/**
diff --git a/Models/PermissionState.php b/Models/PermissionState.php
index 141a1cf..555e873 100755
--- a/Models/PermissionState.php
+++ b/Models/PermissionState.php
@@ -31,4 +31,6 @@ abstract class PermissionState extends Enum
public const PURCHASE_ITEM = 2;
public const STOCK_ITEM = 3;
+
+ public const ATTRIBUTE = 4;
}
diff --git a/Theme/Backend/Lang/Navigation.en.lang.php b/Theme/Backend/Lang/Navigation.en.lang.php
index beb3bd9..a20bad2 100755
--- a/Theme/Backend/Lang/Navigation.en.lang.php
+++ b/Theme/Backend/Lang/Navigation.en.lang.php
@@ -14,6 +14,10 @@ declare(strict_types=1);
return ['Navigation' => [
'Analyze' => 'Analyze',
+ 'Attributes' => 'Attributes',
+ 'Types' => 'Types',
+ 'Values' => 'Values',
'Create' => 'Create',
'List' => 'List',
+ 'ItemManagement' => 'Item Management',
]];
diff --git a/Theme/Backend/attribute-type-list.tpl.php b/Theme/Backend/attribute-type-list.tpl.php
new file mode 100644
index 0000000..d89d5db
--- /dev/null
+++ b/Theme/Backend/attribute-type-list.tpl.php
@@ -0,0 +1,70 @@
+getData('attributes');
+
+echo $this->getData('nav')->render(); ?>
+
+
+
+
+ = $this->getHtml('AttributeTypes'); ?>
+
+
+
+
diff --git a/Theme/Backend/attribute-type.tpl.php b/Theme/Backend/attribute-type.tpl.php
new file mode 100644
index 0000000..e69de29
diff --git a/Theme/Backend/attribute-value-list.tpl.php b/Theme/Backend/attribute-value-list.tpl.php
new file mode 100644
index 0000000..e69de29
diff --git a/Theme/Backend/attribute-value.tpl.php b/Theme/Backend/attribute-value.tpl.php
new file mode 100644
index 0000000..e69de29
diff --git a/Theme/Backend/sales-item-profile.tpl.php b/Theme/Backend/sales-item-profile.tpl.php
index 960195a..fc63cea 100755
--- a/Theme/Backend/sales-item-profile.tpl.php
+++ b/Theme/Backend/sales-item-profile.tpl.php
@@ -29,6 +29,7 @@ $notes = $item->getNotes();
$files = $item->getFiles();
$newestInvoices = $this->getData('newestInvoices') ?? [];
+$allInvoices = $this->getData('allInvoices') ?? [];
$topCustomers = $this->getData('topCustomers') ?? [];
$regionSales = $this->getData('regionSales') ?? [];
$countrySales = $this->getData('countrySales') ?? [];
@@ -56,7 +57,8 @@ echo $this->getData('nav')->render();
-
+
+
@@ -168,8 +170,8 @@ echo $this->getData('nav')->render();
$url = UriFactory::build('{/prefix}editor/single?{?}&id=' . $note->getId());
?>
- | = $note->title; ?>
- | = $note->createdAt->format('Y-m-d'); ?>
+ | = $this->printHtml($note->title); ?>
+ | = $this->printHtml($note->createdAt->format('Y-m-d')); ?>
@@ -189,9 +191,9 @@ echo $this->getData('nav')->render();
$url = UriFactory::build('{/prefix}media/single?{?}&id=' . $file->getId());
?>
|
- | = $file->name; ?>
- | = $file->extension; ?>
- | = $file->createdAt->format('Y-m-d'); ?>
+ | = $this->printHtml($file->name); ?>
+ | = $this->printHtml($file->extension); ?>
+ | = $this->printHtml($file->createdAt->format('Y-m-d')); ?>
@@ -215,11 +217,11 @@ echo $this->getData('nav')->render();
$url = UriFactory::build('{/prefix}sales/bill?{?}&id=' . $invoice->getId());
?>
|
- | = $invoice->getNumber(); ?>
- | = $invoice->type->getL11n(); ?>
- | = $invoice->billTo; ?>
- | = $invoice->net->getCurrency(); ?>
- | = $invoice->createdAt->format('Y-m-d'); ?>
+ | = $this->printHtml($invoice->getNumber()); ?>
+ | = $this->printHtml($invoice->type->getL11n()); ?>
+ | = $this->printHtml($invoice->billTo); ?>
+ | = $this->printHtml($invoice->net->getCurrency()); ?>
+ | = $this->printHtml($invoice->createdAt->format('Y-m-d')); ?>
@@ -513,7 +515,7 @@ echo $this->getData('nav')->render();
|
|
|
- |
+ |
|
@@ -557,7 +559,7 @@ echo $this->getData('nav')->render();
- = $this->getHtml('Customer'); ?>
+ = $this->getHtml('Pricing'); ?>
-
- = $this->getHtml('Prices'); ?>
-
-
- |
- | = $this->getHtml('ID', '0', '0'); ?>
- | = $this->getHtml('Name'); ?>
- |
- $value) : ++$c;
- $url = UriFactory::build('{/prefix}admin/group/settings?{?}&id=' . $value->getId()); ?>
-
- |
- | = $value->getId(); ?>
- | = $this->printHtml($value->name); ?>
-
-
+
+ = $this->getHtml('Prices'); ?>
+
+
- | = $this->getHtml('Empty', '0', '0'); ?>
-
- |
+
+ | = $this->getHtml('ID', '0', '0'); ?>
+ | = $this->getHtml('Name'); ?>
+ |
+ $value) : ++$c;
+ $url = UriFactory::build('{/prefix}admin/group/settings?{?}&id=' . $value->getId()); ?>
+
+ |
+ | = $value->getId(); ?>
+ | = $this->printHtml($value->name); ?>
+
+
+ |
+ | = $this->getHtml('Empty', '0', '0'); ?>
+
+ | |
+
@@ -900,6 +904,37 @@ echo $this->getData('nav')->render();
+
+
+
+
+ = $this->getHtml('RecentInvoices'); ?>
+
+
+
+
+
+
+
|