From 2d5608e41f6014efd019e837d3ccb017753ad152 Mon Sep 17 00:00:00 2001 From: Dennis Eichhorn Date: Tue, 2 Jan 2024 23:34:17 +0000 Subject: [PATCH] update --- Admin/Install/db.json | 234 +++++++- Admin/Routes/Web/Api.php | 132 +++++ Admin/Routes/Web/Backend.php | 19 +- Controller/ApiAttributeController.php | 524 ++++++++++++++++++ Controller/ApiController.php | 2 +- Controller/BackendController.php | 106 +++- Docs/Dev/en/SUMMARY.md | 3 + Docs/Dev/en/structure.md | 5 + Docs/Dev/img/er.png | Bin 0 -> 52305 bytes Docs/introduction.md | 1 + Models/AmountGroup.php | 17 + Models/AmountGroupMapper.php | 2 +- .../InvestmentObjectAttributeMapper.php | 86 +++ ...nvestmentObjectAttributeTypeL11nMapper.php | 69 +++ .../InvestmentObjectAttributeTypeMapper.php | 94 ++++ ...vestmentObjectAttributeValueL11nMapper.php | 69 +++ .../InvestmentObjectAttributeValueMapper.php | 90 +++ Models/Investment.php | 2 - Models/InvestmentMapper.php | 5 +- Models/InvestmentObject.php | 22 +- Models/InvestmentObjectMapper.php | 7 + Models/NullAmount.php | 46 ++ Models/NullAmountGroup.php | 46 ++ Models/PermissionCategory.php | 2 +- Theme/Backend/Lang/de.lang.php | 16 + Theme/Backend/Lang/en.lang.php | 18 +- .../Backend/investment-object-profile.tpl.php | 67 +++ Theme/Backend/investment-profile.tpl.php | 246 ++++++++ 28 files changed, 1905 insertions(+), 25 deletions(-) create mode 100644 Admin/Routes/Web/Api.php create mode 100644 Controller/ApiAttributeController.php create mode 100644 Docs/Dev/en/SUMMARY.md create mode 100644 Docs/Dev/en/structure.md create mode 100644 Docs/Dev/img/er.png create mode 100644 Docs/introduction.md create mode 100644 Models/Attribute/InvestmentObjectAttributeMapper.php create mode 100644 Models/Attribute/InvestmentObjectAttributeTypeL11nMapper.php create mode 100644 Models/Attribute/InvestmentObjectAttributeTypeMapper.php create mode 100644 Models/Attribute/InvestmentObjectAttributeValueL11nMapper.php create mode 100644 Models/Attribute/InvestmentObjectAttributeValueMapper.php create mode 100644 Models/NullAmount.php create mode 100644 Models/NullAmountGroup.php create mode 100644 Theme/Backend/investment-object-profile.tpl.php create mode 100644 Theme/Backend/investment-profile.tpl.php diff --git a/Admin/Install/db.json b/Admin/Install/db.json index a4a8174..26bef02 100755 --- a/Admin/Install/db.json +++ b/Admin/Install/db.json @@ -80,11 +80,6 @@ "foreignTable": "investmgmt_investment_type", "foreignKey": "investmgmt_investment_type_id" }, - "investmgmt_investment_depreciation_type": { - "name": "investmgmt_investment_depreciation_type", - "type": "TINYINT", - "null": false - }, "investmgmt_investment_created_by": { "name": "investmgmt_investment_created_by", "type": "INT", @@ -232,6 +227,235 @@ } } }, + "investmgmt_attr_type": { + "name": "investmgmt_attr_type", + "fields": { + "investmgmt_attr_type_id": { + "name": "investmgmt_attr_type_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "investmgmt_attr_type_name": { + "name": "investmgmt_attr_type_name", + "type": "VARCHAR(255)", + "null": false, + "unique": true + }, + "investmgmt_attr_type_datatype": { + "name": "investmgmt_attr_type_datatype", + "type": "INT(11)", + "null": false + }, + "investmgmt_attr_type_fields": { + "name": "investmgmt_attr_type_fields", + "type": "INT(11)", + "null": false + }, + "investmgmt_attr_type_custom": { + "name": "investmgmt_attr_type_custom", + "type": "TINYINT(1)", + "null": false + }, + "investmgmt_attr_type_required": { + "description": "Every item must have this attribute type if set to true.", + "name": "investmgmt_attr_type_required", + "type": "TINYINT(1)", + "null": false + }, + "investmgmt_attr_type_pattern": { + "description": "This is a regex validation pattern.", + "name": "investmgmt_attr_type_pattern", + "type": "VARCHAR(255)", + "null": false + } + } + }, + "investmgmt_attr_type_l11n": { + "name": "investmgmt_attr_type_l11n", + "fields": { + "investmgmt_attr_type_l11n_id": { + "name": "investmgmt_attr_type_l11n_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "investmgmt_attr_type_l11n_title": { + "name": "investmgmt_attr_type_l11n_title", + "type": "VARCHAR(255)", + "null": false + }, + "investmgmt_attr_type_l11n_type": { + "name": "investmgmt_attr_type_l11n_type", + "type": "INT(11)", + "null": false, + "foreignTable": "investmgmt_attr_type", + "foreignKey": "investmgmt_attr_type_id" + }, + "investmgmt_attr_type_l11n_lang": { + "name": "investmgmt_attr_type_l11n_lang", + "type": "VARCHAR(2)", + "null": false, + "foreignTable": "language", + "foreignKey": "language_639_1" + } + } + }, + "investmgmt_attr_value": { + "name": "investmgmt_attr_value", + "fields": { + "investmgmt_attr_value_id": { + "name": "investmgmt_attr_value_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "investmgmt_attr_value_default": { + "name": "investmgmt_attr_value_default", + "type": "TINYINT(1)", + "null": false + }, + "investmgmt_attr_value_valueStr": { + "name": "investmgmt_attr_value_valueStr", + "type": "VARCHAR(255)", + "null": true, + "default": null + }, + "investmgmt_attr_value_valueInt": { + "name": "investmgmt_attr_value_valueInt", + "type": "INT(11)", + "null": true, + "default": null + }, + "investmgmt_attr_value_valueDec": { + "name": "investmgmt_attr_value_valueDec", + "type": "DECIMAL(19,5)", + "null": true, + "default": null + }, + "investmgmt_attr_value_valueDat": { + "name": "investmgmt_attr_value_valueDat", + "type": "DATETIME", + "null": true, + "default": null + }, + "investmgmt_attr_value_unit": { + "name": "investmgmt_attr_value_unit", + "type": "VARCHAR(255)", + "null": false + }, + "investmgmt_attr_value_deptype": { + "name": "investmgmt_attr_value_deptype", + "type": "INT(11)", + "null": true, + "default": null, + "foreignTable": "investmgmt_attr_type", + "foreignKey": "investmgmt_attr_type_id" + }, + "investmgmt_attr_value_depvalue": { + "name": "investmgmt_attr_value_depvalue", + "type": "INT(11)", + "null": true, + "default": null, + "foreignTable": "investmgmt_attr_value", + "foreignKey": "investmgmt_attr_value_id" + } + } + }, + "investmgmt_attr_value_l11n": { + "name": "investmgmt_attr_value_l11n", + "fields": { + "investmgmt_attr_value_l11n_id": { + "name": "investmgmt_attr_value_l11n_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "investmgmt_attr_value_l11n_title": { + "name": "investmgmt_attr_value_l11n_title", + "type": "VARCHAR(255)", + "null": false + }, + "investmgmt_attr_value_l11n_value": { + "name": "investmgmt_attr_value_l11n_value", + "type": "INT(11)", + "null": false, + "foreignTable": "investmgmt_attr_value", + "foreignKey": "investmgmt_attr_value_id" + }, + "investmgmt_attr_value_l11n_lang": { + "name": "investmgmt_attr_value_l11n_lang", + "type": "VARCHAR(2)", + "null": false, + "foreignTable": "language", + "foreignKey": "language_639_1" + } + } + }, + "investmgmt_option_attr_default": { + "name": "investmgmt_option_attr_default", + "fields": { + "investmgmt_option_attr_default_id": { + "name": "investmgmt_option_attr_default_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "investmgmt_option_attr_default_type": { + "name": "investmgmt_option_attr_default_type", + "type": "INT(11)", + "null": false, + "foreignTable": "investmgmt_attr_type", + "foreignKey": "investmgmt_attr_type_id" + }, + "investmgmt_option_attr_default_value": { + "name": "investmgmt_option_attr_default_value", + "type": "INT(11)", + "null": false, + "foreignTable": "investmgmt_attr_value", + "foreignKey": "investmgmt_attr_value_id" + } + } + }, + "investmgmt_option_attr": { + "name": "investmgmt_option_attr", + "fields": { + "investmgmt_option_attr_id": { + "name": "investmgmt_option_attr_id", + "type": "INT", + "null": false, + "primary": true, + "autoincrement": true + }, + "investmgmt_option_attr_option": { + "name": "investmgmt_option_attr_option", + "type": "INT(11)", + "null": false, + "foreignTable": "investmgmt_option", + "foreignKey": "investmgmt_option_id" + }, + "investmgmt_option_attr_type": { + "name": "investmgmt_option_attr_type", + "type": "INT(11)", + "null": false, + "foreignTable": "investmgmt_attr_type", + "foreignKey": "investmgmt_attr_type_id" + }, + "investmgmt_option_attr_value": { + "name": "investmgmt_option_attr_value", + "type": "INT(11)", + "null": true, + "default": null, + "foreignTable": "investmgmt_attr_value", + "foreignKey": "investmgmt_attr_value_id" + } + } + }, "investmgmt_option_media": { "name": "investmgmt_option_media", "fields": { diff --git a/Admin/Routes/Web/Api.php b/Admin/Routes/Web/Api.php new file mode 100644 index 0000000..44fef03 --- /dev/null +++ b/Admin/Routes/Web/Api.php @@ -0,0 +1,132 @@ + [ + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiController:apiInvestmentFind', + 'verb' => RouteVerb::GET, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + ], + '^.*/finance/investment/attribute$' => [ + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiAttributeController:apiInvestmentAttributeCreate', + 'verb' => RouteVerb::PUT, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiAttributeController:apiInvestmentAttributeUpdate', + 'verb' => RouteVerb::SET, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + ], + '^.*/finance/investment/attribute/type$' => [ + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiAttributeController:apiInvestmentAttributeTypeCreate', + 'verb' => RouteVerb::PUT, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiAttributeController:apiInvestmentAttributeTypeUpdate', + 'verb' => RouteVerb::SET, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + ], + '^.*/finance/investment/attribute/type/l11n$' => [ + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiAttributeController:apiInvestmentAttributeTypeL11nCreate', + 'verb' => RouteVerb::PUT, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiAttributeController:apiInvestmentAttributeTypeL11nUpdate', + 'verb' => RouteVerb::SET, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + ], + '^.*/finance/investment/attribute/value$' => [ + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiAttributeController:apiInvestmentAttributeValueCreate', + 'verb' => RouteVerb::PUT, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiAttributeController:apiInvestmentAttributeValueUpdate', + 'verb' => RouteVerb::SET, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + ], + '^.*/finance/investment/attribute/value$' => [ + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiAttributeController:apiInvestmentAttributeValueL11nCreate', + 'verb' => RouteVerb::PUT, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + [ + 'dest' => '\Modules\InvestmentManagement\Controller\ApiAttributeController:apiInvestmentAttributeValueL11nUpdate', + 'verb' => RouteVerb::SET, + 'permission' => [ + 'module' => ApiController::NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + ], +]; diff --git a/Admin/Routes/Web/Backend.php b/Admin/Routes/Web/Backend.php index af696f0..5a5797d 100755 --- a/Admin/Routes/Web/Backend.php +++ b/Admin/Routes/Web/Backend.php @@ -17,9 +17,9 @@ return [ ], ], ], - '^.*/finance/investment/single.*$' => [ + '^.*/finance/investment/profile.*$' => [ [ - 'dest' => '\Modules\InvestmentManagement\Controller\BackendController:viewInvestmentSingle', + 'dest' => '\Modules\InvestmentManagement\Controller\BackendController:viewInvestmentProfile', 'verb' => RouteVerb::GET, 'permission' => [ 'module' => BackendController::MODULE_NAME, @@ -39,6 +39,17 @@ return [ ], ], ], + '^.*/finance/investment/object.*$' => [ + [ + 'dest' => '\Modules\InvestmentManagement\Controller\BackendController:viewInvestmentObjectProfile', + 'verb' => RouteVerb::GET, + 'permission' => [ + 'module' => BackendController::MODULE_NAME, + 'type' => PermissionType::READ, + 'state' => PermissionCategory::INVESTMENT, + ], + ], + ], '^.*/private/investment/list.*$' => [ [ @@ -51,9 +62,9 @@ return [ ], ], ], - '^.*/private/investment/single.*$' => [ + '^.*/private/investment/profile.*$' => [ [ - 'dest' => '\Modules\InvestmentManagement\Controller\BackendController:viewInvestmentSingle', + 'dest' => '\Modules\InvestmentManagement\Controller\BackendController:viewInvestmentProfile', 'verb' => RouteVerb::GET, 'permission' => [ 'module' => BackendController::MODULE_NAME, diff --git a/Controller/ApiAttributeController.php b/Controller/ApiAttributeController.php new file mode 100644 index 0000000..e25d875 --- /dev/null +++ b/Controller/ApiAttributeController.php @@ -0,0 +1,524 @@ +validateAttributeCreate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidCreateResponse($request, $response, $val); + + return; + } + + $type = InvestmentObjectAttributeTypeMapper::get()->with('defaults')->where('id', (int) $request->getData('type'))->execute(); + $attribute = $this->createAttributeFromRequest($request, $type); + $this->createModel($request->header->account, $attribute, InvestmentObjectAttributeMapper::class, 'attribute', $request->getOrigin()); + $this->createStandardCreateResponse($request, $response, $attribute); + } + + /** + * Api method to create option attribute l11n + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeTypeL11nCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeTypeL11nCreate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidCreateResponse($request, $response, $val); + + return; + } + + $attrL11n = $this->createAttributeTypeL11nFromRequest($request); + $this->createModel($request->header->account, $attrL11n, InvestmentObjectAttributeTypeL11nMapper::class, 'attr_type_l11n', $request->getOrigin()); + $this->createStandardCreateResponse($request, $response, $attrL11n); + } + + /** + * Api method to create option attribute type + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeTypeCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeTypeCreate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidCreateResponse($request, $response, $val); + + return; + } + + $attrType = $this->createAttributeTypeFromRequest($request); + $this->createModel($request->header->account, $attrType, InvestmentObjectAttributeTypeMapper::class, 'attr_type', $request->getOrigin()); + $this->createStandardCreateResponse($request, $response, $attrType); + } + + /** + * Api method to create option attribute value + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeValueCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeValueCreate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidCreateResponse($request, $response, $val); + + return; + } + + /** @var \Modules\Attribute\Models\AttributeType $type */ + $type = InvestmentObjectAttributeTypeMapper::get() + ->where('id', $request->getDataInt('type') ?? 0) + ->execute(); + + $attrValue = $this->createAttributeValueFromRequest($request, $type); + $this->createModel($request->header->account, $attrValue, InvestmentObjectAttributeValueMapper::class, 'attr_value', $request->getOrigin()); + + if ($attrValue->isDefault) { + $this->createModelRelation( + $request->header->account, + (int) $request->getData('type'), + $attrValue->id, + InvestmentObjectAttributeTypeMapper::class, 'defaults', '', $request->getOrigin() + ); + } + + $this->createStandardCreateResponse($request, $response, $attrValue); + } + + /** + * Api method to create option attribute l11n + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeValueL11nCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeValueL11nCreate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidCreateResponse($request, $response, $val); + + return; + } + + $attrL11n = $this->createAttributeValueL11nFromRequest($request); + $this->createModel($request->header->account, $attrL11n, InvestmentObjectAttributeValueL11nMapper::class, 'attr_value_l11n', $request->getOrigin()); + $this->createStandardCreateResponse($request, $response, $attrL11n); + } + + /** + * Api method to update InvestmentObjectAttribute + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeUpdate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeUpdate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidUpdateResponse($request, $response, $val); + + return; + } + + /** @var Attribute $old */ + $old = InvestmentObjectAttributeMapper::get() + ->with('type') + ->with('type/defaults') + ->with('value') + ->where('id', (int) $request->getData('id')) + ->execute(); + + $new = $this->updateAttributeFromRequest($request, clone $old); + + if ($new->id === 0) { + // Set response header to invalid request because of invalid data + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidUpdateResponse($request, $response, $new); + + return; + } + + $this->updateModel($request->header->account, $old, $new, InvestmentObjectAttributeMapper::class, 'option_attribute', $request->getOrigin()); + + if ($new->value->getValue() !== $old->value->getValue() + && $new->type->custom + ) { + $this->updateModel($request->header->account, $old->value, $new->value, InvestmentObjectAttributeValueMapper::class, 'attribute_value', $request->getOrigin()); + } + + $this->createStandardUpdateResponse($request, $response, $new); + } + + /** + * Api method to delete InvestmentObjectAttribute + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeDelete(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeDelete($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidDeleteResponse($request, $response, $val); + + return; + } + + $optionAttribute = InvestmentObjectAttributeMapper::get() + ->with('type') + ->where('id', (int) $request->getData('id')) + ->execute(); + + if ($optionAttribute->type->isRequired) { + $this->createInvalidDeleteResponse($request, $response, []); + + return; + } + + $this->deleteModel($request->header->account, $optionAttribute, InvestmentObjectAttributeMapper::class, 'option_attribute', $request->getOrigin()); + $this->createStandardDeleteResponse($request, $response, $optionAttribute); + } + + /** + * Api method to update InvestmentObjectAttributeTypeL11n + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeTypeL11nUpdate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeTypeL11nUpdate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidUpdateResponse($request, $response, $val); + + return; + } + + /** @var BaseStringL11n $old */ + $old = InvestmentObjectAttributeTypeL11nMapper::get()->where('id', (int) $request->getData('id'))->execute(); + $new = $this->updateAttributeTypeL11nFromRequest($request, clone $old); + + $this->updateModel($request->header->account, $old, $new, InvestmentObjectAttributeTypeL11nMapper::class, 'option_attribute_type_l11n', $request->getOrigin()); + $this->createStandardUpdateResponse($request, $response, $new); + } + + /** + * Api method to delete InvestmentObjectAttributeTypeL11n + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeTypeL11nDelete(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeTypeL11nDelete($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidDeleteResponse($request, $response, $val); + + return; + } + + /** @var BaseStringL11n $optionAttributeTypeL11n */ + $optionAttributeTypeL11n = InvestmentObjectAttributeTypeL11nMapper::get()->where('id', (int) $request->getData('id'))->execute(); + $this->deleteModel($request->header->account, $optionAttributeTypeL11n, InvestmentObjectAttributeTypeL11nMapper::class, 'option_attribute_type_l11n', $request->getOrigin()); + $this->createStandardDeleteResponse($request, $response, $optionAttributeTypeL11n); + } + + /** + * Api method to update InvestmentObjectAttributeType + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeTypeUpdate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeTypeUpdate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidUpdateResponse($request, $response, $val); + + return; + } + + /** @var AttributeType $old */ + $old = InvestmentObjectAttributeTypeMapper::get()->with('defaults')->where('id', (int) $request->getData('id'))->execute(); + $new = $this->updateAttributeTypeFromRequest($request, clone $old); + + $this->updateModel($request->header->account, $old, $new, InvestmentObjectAttributeTypeMapper::class, 'option_attribute_type', $request->getOrigin()); + $this->createStandardUpdateResponse($request, $response, $new); + } + + /** + * Api method to delete InvestmentObjectAttributeType + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @todo Implement API function + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeTypeDelete(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeTypeDelete($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidDeleteResponse($request, $response, $val); + + return; + } + + /** @var AttributeType $optionAttributeType */ + $optionAttributeType = InvestmentObjectAttributeTypeMapper::get()->with('defaults')->where('id', (int) $request->getData('id'))->execute(); + $this->deleteModel($request->header->account, $optionAttributeType, InvestmentObjectAttributeTypeMapper::class, 'option_attribute_type', $request->getOrigin()); + $this->createStandardDeleteResponse($request, $response, $optionAttributeType); + } + + /** + * Api method to update InvestmentObjectAttributeValue + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeValueUpdate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeValueUpdate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidUpdateResponse($request, $response, $val); + + return; + } + + /** @var AttributeValue $old */ + $old = InvestmentObjectAttributeValueMapper::get()->where('id', (int) $request->getData('id'))->execute(); + + /** @var \Modules\Attribute\Models\Attribute $attr */ + $attr = InvestmentObjectAttributeMapper::get() + ->with('type') + ->where('id', $request->getDataInt('attribute') ?? 0) + ->execute(); + + $new = $this->updateAttributeValueFromRequest($request, clone $old, $attr); + + $this->updateModel($request->header->account, $old, $new, InvestmentObjectAttributeValueMapper::class, 'option_attribute_value', $request->getOrigin()); + $this->createStandardUpdateResponse($request, $response, $new); + } + + /** + * Api method to delete InvestmentObjectAttributeValue + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeValueDelete(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + // @todo I don't think values can be deleted? Only Attributes + // However, It should be possible to remove UNUSED default values + // either here or other function? + // if (!empty($val = $this->validateAttributeValueDelete($request))) { + // $response->header->status = RequestStatusCode::R_400; + // $this->createInvalidDeleteResponse($request, $response, $val); + + // return; + // } + + // /** @var \Modules\InvestmentManagement\Models\InvestmentObjectAttributeValue $optionAttributeValue */ + // $optionAttributeValue = InvestmentObjectAttributeValueMapper::get()->where('id', (int) $request->getData('id'))->execute(); + // $this->deleteModel($request->header->account, $optionAttributeValue, InvestmentObjectAttributeValueMapper::class, 'option_attribute_value', $request->getOrigin()); + // $this->createStandardDeleteResponse($request, $response, $optionAttributeValue); + } + + /** + * Api method to update InvestmentObjectAttributeValueL11n + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeValueL11nUpdate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeValueL11nUpdate($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidUpdateResponse($request, $response, $val); + + return; + } + + /** @var BaseStringL11n $old */ + $old = InvestmentObjectAttributeValueL11nMapper::get()->where('id', (int) $request->getData('id')); + $new = $this->updateAttributeValueL11nFromRequest($request, clone $old); + + $this->updateModel($request->header->account, $old, $new, InvestmentObjectAttributeValueL11nMapper::class, 'option_attribute_value_l11n', $request->getOrigin()); + $this->createStandardUpdateResponse($request, $response, $new); + } + + /** + * Api method to delete InvestmentObjectAttributeValueL11n + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return void + * + * @api + * + * @since 1.0.0 + */ + public function apiInvestmentObjectAttributeValueL11nDelete(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void + { + if (!empty($val = $this->validateAttributeValueL11nDelete($request))) { + $response->header->status = RequestStatusCode::R_400; + $this->createInvalidDeleteResponse($request, $response, $val); + + return; + } + + /** @var BaseStringL11n $optionAttributeValueL11n */ + $optionAttributeValueL11n = InvestmentObjectAttributeValueL11nMapper::get()->where('id', (int) $request->getData('id'))->execute(); + $this->deleteModel($request->header->account, $optionAttributeValueL11n, InvestmentObjectAttributeValueL11nMapper::class, 'option_attribute_value_l11n', $request->getOrigin()); + $this->createStandardDeleteResponse($request, $response, $optionAttributeValueL11n); + } +} diff --git a/Controller/ApiController.php b/Controller/ApiController.php index 910b653..904a02e 100644 --- a/Controller/ApiController.php +++ b/Controller/ApiController.php @@ -478,7 +478,7 @@ final class ApiController extends Controller $investment->investment = (int) $request->getData('investment'); $investment->parent = $request->getDataInt('parent'); $investment->supplier = $request->getDataInt('supplier'); - $investment->supplierName = $request->getDataString('supplierName') ?? ''; + $investment->supplierName = $request->getDataString('suppliername') ?? ''; $investment->item = $request->getDataInt('item'); // @todo reconsider the following lines. This seems rather complicated. diff --git a/Controller/BackendController.php b/Controller/BackendController.php index 61a2d13..8ad8713 100755 --- a/Controller/BackendController.php +++ b/Controller/BackendController.php @@ -14,8 +14,16 @@ declare(strict_types=1); namespace Modules\InvestmentManagement\Controller; +use Modules\Admin\Models\LocalizationMapper; +use Modules\Admin\Models\SettingsEnum; use Modules\InvestmentManagement\Models\InvestmentMapper; +use Modules\InvestmentManagement\Models\InvestmentObjectMapper; +use Modules\InvestmentManagement\Models\InvestmentTypeMapper; +use Modules\Media\Models\MediaMapper; +use Modules\Media\Models\MediaTypeMapper; +use Modules\Organization\Models\UnitMapper; use phpOMS\Contract\RenderableInterface; +use phpOMS\DataStorage\Database\Query\Builder; use phpOMS\Message\RequestAbstract; use phpOMS\Message\ResponseAbstract; use phpOMS\Views\View; @@ -31,7 +39,7 @@ use phpOMS\Views\View; final class BackendController extends Controller { /** - * Routing end-point for application behaviour. + * Routing end-point for application behavior. * * @param RequestAbstract $request Request * @param ResponseAbstract $response Response @@ -59,7 +67,7 @@ final class BackendController extends Controller } /** - * Routing end-point for application behaviour. + * Routing end-point for application behavior. * * @param RequestAbstract $request Request * @param ResponseAbstract $response Response @@ -70,17 +78,105 @@ final class BackendController extends Controller * @since 1.0.0 * @codeCoverageIgnore */ - public function viewInvestmentSingle(RequestAbstract $request, ResponseAbstract $response, $data = null) : RenderableInterface + public function viewInvestmentObjectProfile(RequestAbstract $request, ResponseAbstract $response, $data = null) : RenderableInterface { $view = new View($this->app->l11nManager, $request, $response); - $view->setTemplate('/Modules/InvestmentManagement/Theme/Backend/investment-create'); + $view->setTemplate('/Modules/InvestmentManagement/Theme/Backend/investment-object-profile'); $view->data['nav'] = $this->app->moduleManager->get('Navigation')->createNavigationMid(1007101001, $request, $response); + $object = InvestmentObjectMapper::get() + ->with('files') + ->with('notes') + ->with('amountGroups') + ->with('amountGroups/type') + ->with('amountGroups/amounts') + ->where('id', (int) $request->getData('id')) + ->execute(); + + $view->data['object'] = $object; + + /** @var \Model\Setting $settings */ + $settings = $this->app->appSettings->get(null, [ + SettingsEnum::DEFAULT_LOCALIZATION, + ]); + + $view->data['attributeView'] = new \Modules\Attribute\Theme\Backend\Components\AttributeView($this->app->l11nManager, $request, $response); + $view->data['attributeView']->data['defaultlocalization'] = LocalizationMapper::get()->where('id', (int) $settings->id)->execute(); + + $view->data['media-upload'] = new \Modules\Media\Theme\Backend\Components\Upload\BaseView($this->app->l11nManager, $request, $response); + return $view; } /** - * Routing end-point for application behaviour. + * Routing end-point for application behavior. + * + * @param RequestAbstract $request Request + * @param ResponseAbstract $response Response + * @param array $data Generic data + * + * @return RenderableInterface + * + * @since 1.0.0 + * @codeCoverageIgnore + */ + public function viewInvestmentProfile(RequestAbstract $request, ResponseAbstract $response, $data = null) : RenderableInterface + { + $view = new View($this->app->l11nManager, $request, $response); + $view->setTemplate('/Modules/InvestmentManagement/Theme/Backend/investment-profile'); + $view->data['nav'] = $this->app->moduleManager->get('Navigation')->createNavigationMid(1007101001, $request, $response); + + $investment = InvestmentMapper::get() + ->with('notes') + ->with('files') + ->with('supplier') + ->with('supplier/account') + ->with('item') + ->with('createdBy') + ->with('options') + ->with('options/files') + ->with('options/notes') + ->with('options/amountGroups') + ->with('options/amountGroups/type') + ->with('options/amountGroups/amounts') + ->with('options/attributes') + ->with('options/attributes/type') + ->with('options/attributes/type/l11n') + ->with('options/attributes/value') + ->where('id', (int) $request->getData('id')) + ->where('options/attributes/type/l11n/language', $response->header->l11n->language) + ->execute(); + + $view->data['investment'] = $investment; + + /** @var \Model\Setting $settings */ + $settings = $this->app->appSettings->get(null, [ + SettingsEnum::DEFAULT_LOCALIZATION, + ]); + + $view->data['attributeView'] = new \Modules\Attribute\Theme\Backend\Components\AttributeView($this->app->l11nManager, $request, $response); + $view->data['attributeView']->data['defaultlocalization'] = LocalizationMapper::get()->where('id', (int) $settings->id)->execute(); + + $investmentTypes = InvestmentTypeMapper::getAll() + ->with('l11n') + ->where('l11n/language', $response->header->l11n->language) + ->execute(); + + $view->data['types'] = $investmentTypes; + + $units = UnitMapper::getAll() + ->execute(); + + $view->data['units'] = $units; + + $view->data['media-upload'] = new \Modules\Media\Theme\Backend\Components\Upload\BaseView($this->app->l11nManager, $request, $response); + $view->data['note'] = new \Modules\Editor\Theme\Backend\Components\Note\BaseView($this->app->l11nManager, $request, $response); + + return $view; + } + + /** + * Routing end-point for application behavior. * * @param RequestAbstract $request Request * @param ResponseAbstract $response Response diff --git a/Docs/Dev/en/SUMMARY.md b/Docs/Dev/en/SUMMARY.md new file mode 100644 index 0000000..8a97952 --- /dev/null +++ b/Docs/Dev/en/SUMMARY.md @@ -0,0 +1,3 @@ +# Developer Content + +* [Structure]({%}&page=Dev/structure) diff --git a/Docs/Dev/en/structure.md b/Docs/Dev/en/structure.md new file mode 100644 index 0000000..f917d9f --- /dev/null +++ b/Docs/Dev/en/structure.md @@ -0,0 +1,5 @@ +# Structure + +## ER + +![ER](Modules/InvestmentManagement/Docs/Dev/img/er.png) \ No newline at end of file diff --git a/Docs/Dev/img/er.png b/Docs/Dev/img/er.png new file mode 100644 index 0000000000000000000000000000000000000000..294597737f9f23e15f2448066cda20320ef5ecde GIT binary patch literal 52305 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 literal 0 HcmV?d00001 diff --git a/Docs/introduction.md b/Docs/introduction.md new file mode 100644 index 0000000..e10b99d --- /dev/null +++ b/Docs/introduction.md @@ -0,0 +1 @@ +# Introduction diff --git a/Models/AmountGroup.php b/Models/AmountGroup.php index eb07610..59a29a6 100644 --- a/Models/AmountGroup.php +++ b/Models/AmountGroup.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace Modules\InvestmentManagement\Models; use phpOMS\Localization\BaseStringL11nType; +use phpOMS\Stdlib\Base\FloatInt; /** * Costs/Earnings. @@ -30,6 +31,12 @@ class AmountGroup public string $name = ''; + /** + * Amounts + * + * @var Amount[] + * @since 1.0.0 + */ public array $amounts = []; public BaseStringL11nType $type; @@ -45,4 +52,14 @@ class AmountGroup { $this->type = new BaseStringL11nType(); } + + public function sum() : FloatInt + { + $sum = new FloatInt(); + foreach ($this->amounts as $value) { + $sum->add($value->amount); + } + + return $sum; + } } diff --git a/Models/AmountGroupMapper.php b/Models/AmountGroupMapper.php index a8209fe..0f1b13a 100644 --- a/Models/AmountGroupMapper.php +++ b/Models/AmountGroupMapper.php @@ -63,7 +63,7 @@ final class AmountGroupMapper extends DataMapperFactory */ public const HAS_MANY = [ 'amounts' => [ - 'mapper' => self::class, + 'mapper' => AmountMapper::class, 'table' => 'investmgmt_amount', 'self' => 'investmgmt_amount_group', 'external' => null, diff --git a/Models/Attribute/InvestmentObjectAttributeMapper.php b/Models/Attribute/InvestmentObjectAttributeMapper.php new file mode 100644 index 0000000..15f33ef --- /dev/null +++ b/Models/Attribute/InvestmentObjectAttributeMapper.php @@ -0,0 +1,86 @@ + + */ +final class InvestmentObjectAttributeMapper extends DataMapperFactory +{ + /** + * Columns. + * + * @var array + * @since 1.0.0 + */ + public const COLUMNS = [ + 'investmgmt_option_attr_id' => ['name' => 'investmgmt_option_attr_id', 'type' => 'int', 'internal' => 'id'], + 'investmgmt_option_attr_option' => ['name' => 'investmgmt_option_attr_option', 'type' => 'int', 'internal' => 'ref'], + 'investmgmt_option_attr_type' => ['name' => 'investmgmt_option_attr_type', 'type' => 'int', 'internal' => 'type'], + 'investmgmt_option_attr_value' => ['name' => 'investmgmt_option_attr_value', 'type' => 'int', 'internal' => 'value'], + ]; + + /** + * Has one relation. + * + * @var array + * @since 1.0.0 + */ + public const OWNS_ONE = [ + 'type' => [ + 'mapper' => InvestmentObjectAttributeTypeMapper::class, + 'external' => 'investmgmt_option_attr_type', + ], + 'value' => [ + 'mapper' => InvestmentObjectAttributeValueMapper::class, + 'external' => 'investmgmt_option_attr_value', + ], + ]; + + /** + * Model to use by the mapper. + * + * @var class-string + * @since 1.0.0 + */ + public const MODEL = Attribute::class; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'investmgmt_option_attr'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD = 'investmgmt_option_attr_id'; +} diff --git a/Models/Attribute/InvestmentObjectAttributeTypeL11nMapper.php b/Models/Attribute/InvestmentObjectAttributeTypeL11nMapper.php new file mode 100644 index 0000000..2eda610 --- /dev/null +++ b/Models/Attribute/InvestmentObjectAttributeTypeL11nMapper.php @@ -0,0 +1,69 @@ + + */ +final class InvestmentObjectAttributeTypeL11nMapper extends DataMapperFactory +{ + /** + * Columns. + * + * @var array + * @since 1.0.0 + */ + public const COLUMNS = [ + 'investmgmt_attr_type_l11n_id' => ['name' => 'investmgmt_attr_type_l11n_id', 'type' => 'int', 'internal' => 'id'], + 'investmgmt_attr_type_l11n_title' => ['name' => 'investmgmt_attr_type_l11n_title', 'type' => 'string', 'internal' => 'content', 'autocomplete' => true], + 'investmgmt_attr_type_l11n_type' => ['name' => 'investmgmt_attr_type_l11n_type', 'type' => 'int', 'internal' => 'ref'], + 'investmgmt_attr_type_l11n_lang' => ['name' => 'investmgmt_attr_type_l11n_lang', 'type' => 'string', 'internal' => 'language'], + ]; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'investmgmt_attr_type_l11n'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD = 'investmgmt_attr_type_l11n_id'; + + /** + * Model to use by the mapper. + * + * @var class-string + * @since 1.0.0 + */ + public const MODEL = BaseStringL11n::class; +} diff --git a/Models/Attribute/InvestmentObjectAttributeTypeMapper.php b/Models/Attribute/InvestmentObjectAttributeTypeMapper.php new file mode 100644 index 0000000..00eef59 --- /dev/null +++ b/Models/Attribute/InvestmentObjectAttributeTypeMapper.php @@ -0,0 +1,94 @@ + + */ +final class InvestmentObjectAttributeTypeMapper extends DataMapperFactory +{ + /** + * Columns. + * + * @var array + * @since 1.0.0 + */ + public const COLUMNS = [ + 'investmgmt_attr_type_id' => ['name' => 'investmgmt_attr_type_id', 'type' => 'int', 'internal' => 'id'], + 'investmgmt_attr_type_name' => ['name' => 'investmgmt_attr_type_name', 'type' => 'string', 'internal' => 'name', 'autocomplete' => true], + 'investmgmt_attr_type_datatype' => ['name' => 'investmgmt_attr_type_datatype', 'type' => 'int', 'internal' => 'datatype'], + 'investmgmt_attr_type_fields' => ['name' => 'investmgmt_attr_type_fields', 'type' => 'int', 'internal' => 'fields'], + 'investmgmt_attr_type_custom' => ['name' => 'investmgmt_attr_type_custom', 'type' => 'bool', 'internal' => 'custom'], + 'investmgmt_attr_type_pattern' => ['name' => 'investmgmt_attr_type_pattern', 'type' => 'string', 'internal' => 'validationPattern'], + 'investmgmt_attr_type_required' => ['name' => 'investmgmt_attr_type_required', 'type' => 'bool', 'internal' => 'isRequired'], + ]; + + /** + * Has many relation. + * + * @var array + * @since 1.0.0 + */ + public const HAS_MANY = [ + 'l11n' => [ + 'mapper' => InvestmentObjectAttributeTypeL11nMapper::class, + 'table' => 'investmgmt_attr_type_l11n', + 'self' => 'investmgmt_attr_type_l11n_type', + 'column' => 'content', + 'external' => null, + ], + 'defaults' => [ + 'mapper' => InvestmentObjectAttributeValueMapper::class, + 'table' => 'investmgmt_option_attr_default', + 'self' => 'investmgmt_option_attr_default_type', + 'external' => 'investmgmt_option_attr_default_value', + ], + ]; + + /** + * Model to use by the mapper. + * + * @var class-string + * @since 1.0.0 + */ + public const MODEL = AttributeType::class; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'investmgmt_attr_type'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD = 'investmgmt_attr_type_id'; +} diff --git a/Models/Attribute/InvestmentObjectAttributeValueL11nMapper.php b/Models/Attribute/InvestmentObjectAttributeValueL11nMapper.php new file mode 100644 index 0000000..2653c58 --- /dev/null +++ b/Models/Attribute/InvestmentObjectAttributeValueL11nMapper.php @@ -0,0 +1,69 @@ + + */ +final class InvestmentObjectAttributeValueL11nMapper extends DataMapperFactory +{ + /** + * Columns. + * + * @var array + * @since 1.0.0 + */ + public const COLUMNS = [ + 'investmgmt_attr_value_l11n_id' => ['name' => 'investmgmt_attr_value_l11n_id', 'type' => 'int', 'internal' => 'id'], + 'investmgmt_attr_value_l11n_title' => ['name' => 'investmgmt_attr_value_l11n_title', 'type' => 'string', 'internal' => 'content', 'autocomplete' => true], + 'investmgmt_attr_value_l11n_value' => ['name' => 'investmgmt_attr_value_l11n_value', 'type' => 'int', 'internal' => 'ref'], + 'investmgmt_attr_value_l11n_lang' => ['name' => 'investmgmt_attr_value_l11n_lang', 'type' => 'string', 'internal' => 'language'], + ]; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'investmgmt_attr_value_l11n'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD = 'investmgmt_attr_value_l11n_id'; + + /** + * Model to use by the mapper. + * + * @var class-string + * @since 1.0.0 + */ + public const MODEL = BaseStringL11n::class; +} diff --git a/Models/Attribute/InvestmentObjectAttributeValueMapper.php b/Models/Attribute/InvestmentObjectAttributeValueMapper.php new file mode 100644 index 0000000..80ba9a7 --- /dev/null +++ b/Models/Attribute/InvestmentObjectAttributeValueMapper.php @@ -0,0 +1,90 @@ + + */ +final class InvestmentObjectAttributeValueMapper extends DataMapperFactory +{ + /** + * Columns. + * + * @var array + * @since 1.0.0 + */ + public const COLUMNS = [ + 'investmgmt_attr_value_id' => ['name' => 'investmgmt_attr_value_id', 'type' => 'int', 'internal' => 'id'], + 'investmgmt_attr_value_default' => ['name' => 'investmgmt_attr_value_default', 'type' => 'bool', 'internal' => 'isDefault'], + 'investmgmt_attr_value_valueStr' => ['name' => 'investmgmt_attr_value_valueStr', 'type' => 'string', 'internal' => 'valueStr'], + 'investmgmt_attr_value_valueInt' => ['name' => 'investmgmt_attr_value_valueInt', 'type' => 'int', 'internal' => 'valueInt'], + 'investmgmt_attr_value_valueDec' => ['name' => 'investmgmt_attr_value_valueDec', 'type' => 'float', 'internal' => 'valueDec'], + 'investmgmt_attr_value_valueDat' => ['name' => 'investmgmt_attr_value_valueDat', 'type' => 'DateTime', 'internal' => 'valueDat'], + 'investmgmt_attr_value_unit' => ['name' => 'investmgmt_attr_value_unit', 'type' => 'string', 'internal' => 'unit'], + 'investmgmt_attr_value_deptype' => ['name' => 'investmgmt_attr_value_deptype', 'type' => 'int', 'internal' => 'dependingAttributeType'], + 'investmgmt_attr_value_depvalue' => ['name' => 'investmgmt_attr_value_depvalue', 'type' => 'int', 'internal' => 'dependingAttributeValue'], + ]; + + /** + * Has many relation. + * + * @var array + * @since 1.0.0 + */ + public const HAS_MANY = [ + 'l11n' => [ + 'mapper' => InvestmentObjectAttributeValueL11nMapper::class, + 'table' => 'investmgmt_attr_value_l11n', + 'self' => 'investmgmt_attr_value_l11n_value', + 'column' => 'content', + 'external' => null, + ], + ]; + + /** + * Model to use by the mapper. + * + * @var class-string + * @since 1.0.0 + */ + public const MODEL = AttributeValue::class; + + /** + * Primary table. + * + * @var string + * @since 1.0.0 + */ + public const TABLE = 'investmgmt_attr_value'; + + /** + * Primary field name. + * + * @var string + * @since 1.0.0 + */ + public const PRIMARYFIELD = 'investmgmt_attr_value_id'; +} diff --git a/Models/Investment.php b/Models/Investment.php index a3fa781..36eca0c 100644 --- a/Models/Investment.php +++ b/Models/Investment.php @@ -35,8 +35,6 @@ class Investment public string $description = ''; - public int $depreciationType = DepreciationType::NONE; - public int $status = InvestmentStatus::DRAFT; public ?BaseStringL11nType $type = null; diff --git a/Models/InvestmentMapper.php b/Models/InvestmentMapper.php index 047819f..aaf31c8 100644 --- a/Models/InvestmentMapper.php +++ b/Models/InvestmentMapper.php @@ -43,7 +43,6 @@ final class InvestmentMapper extends DataMapperFactory 'investmgmt_investment_name' => ['name' => 'investmgmt_investment_name', 'type' => 'string', 'internal' => 'name'], 'investmgmt_investment_description' => ['name' => 'investmgmt_investment_description', 'type' => 'string', 'internal' => 'description'], 'investmgmt_investment_status' => ['name' => 'investmgmt_investment_status', 'type' => 'int', 'internal' => 'status'], - 'investmgmt_investment_depreciation_type' => ['name' => 'investmgmt_investment_depreciation_type', 'type' => 'int', 'internal' => 'depreciationType'], 'investmgmt_investment_unit' => ['name' => 'investmgmt_investment_unit', 'type' => 'int', 'internal' => 'unit'], 'investmgmt_investment_created_by' => ['name' => 'investmgmt_investment_created_by', 'type' => 'int', 'internal' => 'createdBy', 'readonly' => true], 'investmgmt_investment_performance' => ['name' => 'investmgmt_investment_performance', 'type' => 'DateTime', 'internal' => 'performanceDate'], @@ -84,8 +83,8 @@ final class InvestmentMapper extends DataMapperFactory ], 'options' => [ 'mapper' => InvestmentObjectMapper::class, - 'table' => 'investmgmt_investment_option', - 'self' => 'investmgmt_investment_option_investment', + 'table' => 'investmgmt_option', + 'self' => 'investmgmt_option_investment', 'external' => null, ], ]; diff --git a/Models/InvestmentObject.php b/Models/InvestmentObject.php index 9a64bb7..3f7f566 100644 --- a/Models/InvestmentObject.php +++ b/Models/InvestmentObject.php @@ -14,6 +14,9 @@ declare(strict_types=1); namespace Modules\InvestmentManagement\Models; +use Modules\ItemManagement\Models\Item; +use Modules\SupplierManagement\Models\Supplier; + /** * Investment object. * @@ -30,7 +33,7 @@ class InvestmentObject public string $description = ''; - public ?int $supplier = null; + public ?Supplier $supplier = null; public string $supplierName = ''; @@ -38,6 +41,9 @@ class InvestmentObject /** * Costs / Revenue + * + * @var AmountGroup[] + * @since 1.0.0 */ public array $amountGroups = []; @@ -49,8 +55,20 @@ class InvestmentObject public int $investment = 0; - public ?int $item = null; + public ?Item $item = null; + + public function getAmountByTypeName(string $type) : AmountGroup + { + foreach ($this->amountGroups as $group) { + if ($group->type->title === $type) { + return $group; + } + } + + return new NullAmountGroup(); + } use \Modules\Media\Models\MediaListTrait; use \Modules\Editor\Models\EditorDocListTrait; + use \Modules\Attribute\Models\AttributeHolderTrait; } diff --git a/Models/InvestmentObjectMapper.php b/Models/InvestmentObjectMapper.php index 86a71ed..f10c557 100644 --- a/Models/InvestmentObjectMapper.php +++ b/Models/InvestmentObjectMapper.php @@ -15,6 +15,7 @@ declare(strict_types=1); namespace Modules\InvestmentManagement\Models; use Modules\Editor\Models\EditorDocMapper; +use Modules\InvestmentManagement\Models\Attribute\InvestmentObjectAttributeMapper; use Modules\ItemManagement\Models\ItemMapper; use Modules\Media\Models\MediaMapper; use Modules\SupplierManagement\Models\SupplierMapper; @@ -93,6 +94,12 @@ final class InvestmentObjectMapper extends DataMapperFactory 'self' => 'investmgmt_amount_group_option', 'external' => null, ], + 'attributes' => [ + 'mapper' => InvestmentObjectAttributeMapper::class, + 'table' => 'investmgmt_option_attr', + 'self' => 'investmgmt_option_attr_item', + 'external' => null, + ], ]; /** diff --git a/Models/NullAmount.php b/Models/NullAmount.php new file mode 100644 index 0000000..60a05aa --- /dev/null +++ b/Models/NullAmount.php @@ -0,0 +1,46 @@ +id = $id; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return ['id' => $this->id]; + } +} diff --git a/Models/NullAmountGroup.php b/Models/NullAmountGroup.php new file mode 100644 index 0000000..dffa6b5 --- /dev/null +++ b/Models/NullAmountGroup.php @@ -0,0 +1,46 @@ +id = $id; + } + + /** + * {@inheritdoc} + */ + public function jsonSerialize() : mixed + { + return ['id' => $this->id]; + } +} diff --git a/Models/PermissionCategory.php b/Models/PermissionCategory.php index 6bd9289..a9ce99f 100755 --- a/Models/PermissionCategory.php +++ b/Models/PermissionCategory.php @@ -17,7 +17,7 @@ namespace Modules\InvestmentManagement\Models; use phpOMS\Stdlib\Base\Enum; /** - * Permision state enum. + * Permission category enum. * * @package Modules\InvestmentManagement\Models * @license OMS License 2.0 diff --git a/Theme/Backend/Lang/de.lang.php b/Theme/Backend/Lang/de.lang.php index 84e04a4..e80adf8 100644 --- a/Theme/Backend/Lang/de.lang.php +++ b/Theme/Backend/Lang/de.lang.php @@ -23,4 +23,20 @@ return ['InvestmentManagement' => [ 'Name' => 'Name', 'Status' => 'Status', 'Title' => 'Titel', + 'Files' => 'Dateien', + 'Notes' => 'Notizen', + 'Options' => 'Optionen', + 'Option' => 'Option', + 'Description' => 'Beschreibung', + 'Link' => 'Link', + 'Amounts' => 'Beträge', + 'Amount' => 'Betrag', + 'Quantity' => 'Menge', + 'Attributes' => 'Attribute', + 'Type' => 'Typ', + 'Purchase' => 'Kauf', + 'Price' => 'Preis', + 'Supplier' => 'Lieferant', + 'Approved' => 'Genehmigt', + 'Approve' => 'Genehmigen', ]]; diff --git a/Theme/Backend/Lang/en.lang.php b/Theme/Backend/Lang/en.lang.php index 7fc003a..44578f8 100755 --- a/Theme/Backend/Lang/en.lang.php +++ b/Theme/Backend/Lang/en.lang.php @@ -22,5 +22,21 @@ return ['InvestmentManagement' => [ 'Investments' => 'Investments', 'Name' => 'Name', 'Status' => 'Status', - 'Title' => 'Titel', + 'Title' => 'Title', + 'Files' => 'Files', + 'Notes' => 'Notes', + 'Options' => 'Options', + 'Option' => 'Option', + 'Description' => 'Description', + 'Link' => 'Link', + 'Amounts' => 'Amounts', + 'Amount' => 'Amount', + 'Quantity' => 'Quantity', + 'Attributes' => 'Attributes', + 'Type' => 'Type', + 'Purchase' => 'Purchase', + 'Price' => 'Price', + 'Supplier' => 'Supplier', + 'Approved' => 'Approved', + 'Approve' => 'Approve', ]]; diff --git a/Theme/Backend/investment-object-profile.tpl.php b/Theme/Backend/investment-object-profile.tpl.php new file mode 100644 index 0000000..e3b836a --- /dev/null +++ b/Theme/Backend/investment-object-profile.tpl.php @@ -0,0 +1,67 @@ +data['object'] ?? null; + +$attributeView = $this->data['attributeView']; +$languages = ISO639Enum::getConstants(); + +echo $this->data['nav']->render(); ?> +
+
+ +
+
+ request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>> +
+
+ + request->uri->fragment === 'c-tab-2' ? ' checked' : ''; ?>> +
+
+ render( + $object->attributes, + $this->data['attributeTypes'] ?? [], + $this->data['units'] ?? [], + '{/api}finance/investment/object', + $object->id + ); + ?> +
+
+ + request->uri->fragment === 'c-tab-3' ? ' checked' : ''; ?>> +
+
+ + request->uri->fragment === 'c-tab-4' ? ' checked' : ''; ?>> +
+ data['media-upload']->render('object-file', 'files', '', $object->files); ?> +
+ + request->uri->fragment === 'c-tab-5' ? ' checked' : ''; ?>> +
+ data['note']->render('object-note', 'notes', $object->notes); ?> +
+
+
\ No newline at end of file diff --git a/Theme/Backend/investment-profile.tpl.php b/Theme/Backend/investment-profile.tpl.php new file mode 100644 index 0000000..d70e0d9 --- /dev/null +++ b/Theme/Backend/investment-profile.tpl.php @@ -0,0 +1,246 @@ +data['investment'] ?? null; +$investmentStatus = InvestmentStatus::getConstants(); +$files = $investment->files; +$investmentTypes = $this->data['types'] ?? []; + +echo $this->data['nav']->render(); ?> +
+
+ +
+
+ request->uri->fragment === 'c-tab-1' ? ' checked' : ''; ?>> +
+
+
+
+
getHtml('Investment'); ?>
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ id === 0) : ?> + + + + +
+
+
+ +
+
+
+ getHtml('Options'); ?> + download +
+
+ + + + + options as $option) : ?> + +
+ getHtml('Name'); ?> + getHtml('Supplier'); ?> + getHtml('Price'); ?> + getHtml('Link'); ?> +
approved) : ?>check + printHtml($option->name); ?> + supplier === null + ? $this->printHtml($option->supplierName) + : $option->supplier->account->name1; + ?> + getCurrency($option->getAmountByTypeName('costs')->sum(), '', 'medium'); ?> + link)) : ?> + printHtml($option->link); ?> + + +
+
+
+
+
+
+ + request->uri->fragment === 'c-tab-2' ? ' checked' : ''; ?>> +
+ data['media-upload']->render('investment-file', 'files', '', $investment->files); ?> +
+ + request->uri->fragment === 'c-tab-3' ? ' checked' : ''; ?>> +
+ data['note']->render('investment-notes', '', $investment->notes); ?> +
+ + request->uri->fragment === 'c-tab-4' ? ' checked' : ''; ?>> +
+
+ options as $option) : + if ($option->parent !== null) { + continue; + } + + ++$count; + ?> +
+
+
getHtml('Option'); ?>
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + +
+ +
+ + + + + attributes as $attribute) : ?> + +
getHtml('Attributes'); ?> +
+ + attributes)) : ?> +
getHtml('Empty', '0', '0'); ?> + +
+
+ +
+ + + + + amountGroups as $group) : ?> + +
getHtml('Amounts'); ?> +
getCurrency($group->sum(), '', 'medium'); ?> + + files)) : ?> +
getHtml('Empty', '0', '0'); ?> + +
+
+ +
+ + + + + files as $file) : ?> + +
getHtml('Files'); ?> +
+ + files)) : ?> +
getHtml('Empty', '0', '0'); ?> + +
+
+ +
+ + + + + notes as $note) : ?> + +
getHtml('Notes'); ?> +
+ + notes)) : ?> +
getHtml('Empty', '0', '0'); ?> + +
+
+
+ +
+
+ +
+
+ +
+