From b216a987125bb82099dddd16c4ce420c807870f8 Mon Sep 17 00:00:00 2001 From: Gloomy Date: Mon, 18 Aug 2025 18:35:33 +0000 Subject: [PATCH] new fix --- hesabixCore/composer.json | 1 + hesabixCore/composer.lock | 308 ++- .../migrations/Version20250113000000.php | 40 + .../migrations/Version20250113000001.php | 31 + .../migrations/Version20250113000002.php | 40 + .../migrations/Version20250815143325.php | 101 + .../migrations/Version20250816171207.php | 47 + ...11113332.php => Version20250816185111.php} | 6 +- .../migrations/Version20250816185556.php | 35 + .../migrations/Version20250818042052.php | 35 + .../migrations/Version20250818042232.php | 35 + hesabixCore/src/Cog/PersonService.php | 5 +- .../src/Controller/ApprovalController.php | 426 +++ .../src/Controller/BusinessController.php | 16 + .../Controller/ImportWorkflowController.php | 601 ++++- .../Plugins/PlugWarrantyController.php | 646 +++-- .../src/Controller/PublicController.php | 400 +++ hesabixCore/src/Controller/SellController.php | 67 +- .../src/Controller/StoreroomController.php | 240 +- hesabixCore/src/Entity/Business.php | 56 + hesabixCore/src/Entity/HesabdariDoc.php | 44 + hesabixCore/src/Entity/HesabdariRow.php | 2 + hesabixCore/src/Entity/ImportWorkflowItem.php | 16 + hesabixCore/src/Entity/Permission.php | 44 +- hesabixCore/src/Entity/Person.php | 12 +- hesabixCore/src/Entity/PlugWarrantySerial.php | 295 +-- hesabixCore/src/Entity/StoreroomTicket.php | 71 +- .../PlugWarrantySerialRepository.php | 11 +- hesabixCore/src/Service/Explore.php | 13 +- hesabixCore/src/Service/FileStorage.php | 42 + ..._4837DCA8-0915-4C7B-9BE1-70C87FF4385A.jpeg | Bin 0 -> 112180 bytes ....93976672_Screenshot_2025-03-30_233821.png | Bin 0 -> 2082 bytes ....56036064_Screenshot_2025-03-30_233821.png | Bin 0 -> 2082 bytes .../st_68a1b350a9b8c3.34236142_NDr4WkNHDI.pdf | Bin 0 -> 19375 bytes webUI/package.json | 1 + .../src/components/common/ApprovalManager.vue | 268 ++ .../src/components/common/ApprovalStatus.vue | 163 ++ .../import-workflow/ImportWorkflowCustoms.vue | 8 +- .../ImportWorkflowDocuments.vue | 58 +- .../import-workflow/ImportWorkflowItems.vue | 85 +- .../ImportWorkflowPayments.vue | 5 +- .../ImportWorkflowShipping.vue | 8 +- .../import-workflow/ImportWorkflowStages.vue | 18 +- .../plugins/warranty/BulkImportDialog.vue | 249 +- .../plugins/warranty/SerialDialog.vue | 505 ++-- .../plugins/warranty/SerialViewDialog.vue | 162 +- webUI/src/router/index.ts | 34 +- webUI/src/utils/approvalUtils.js | 146 ++ webUI/src/views/acc/persons/card.vue | 44 +- webUI/src/views/acc/persons/insert.vue | 24 +- .../acc/plugins/import-workflow/list.vue | 12 +- .../acc/plugins/import-workflow/view.vue | 19 +- .../acc/plugins/warranty/WarrantyPlugin.vue | 641 +++-- webUI/src/views/acc/sell/list.vue | 125 +- webUI/src/views/acc/settings/bussiness.vue | 215 +- webUI/src/views/acc/storeroom/io/sell.vue | 931 +++++-- .../src/views/acc/storeroom/io/ticketList.vue | 235 +- webUI/src/views/acc/storeroom/io/view.vue | 181 +- webUI/src/views/public/PublicLayout.vue | 25 + webUI/src/views/public/WarrantyActivation.vue | 2291 +++++++++++++++++ 60 files changed, 8593 insertions(+), 1546 deletions(-) create mode 100644 hesabixCore/migrations/Version20250113000000.php create mode 100644 hesabixCore/migrations/Version20250113000001.php create mode 100644 hesabixCore/migrations/Version20250113000002.php create mode 100644 hesabixCore/migrations/Version20250815143325.php create mode 100644 hesabixCore/migrations/Version20250816171207.php rename hesabixCore/migrations/{Version20250811113332.php => Version20250816185111.php} (76%) create mode 100644 hesabixCore/migrations/Version20250816185556.php create mode 100644 hesabixCore/migrations/Version20250818042052.php create mode 100644 hesabixCore/migrations/Version20250818042232.php create mode 100644 hesabixCore/src/Controller/ApprovalController.php create mode 100644 hesabixCore/src/Controller/PublicController.php create mode 100644 hesabixCore/src/Service/FileStorage.php create mode 100644 public_html/uploads/storeroom/st_6899dc09b4eb05.86469834_4837DCA8-0915-4C7B-9BE1-70C87FF4385A.jpeg create mode 100644 public_html/uploads/storeroom/st_68a1ad4e1bb6c5.93976672_Screenshot_2025-03-30_233821.png create mode 100644 public_html/uploads/storeroom/st_68a1af08b47979.56036064_Screenshot_2025-03-30_233821.png create mode 100644 public_html/uploads/storeroom/st_68a1b350a9b8c3.34236142_NDr4WkNHDI.pdf create mode 100644 webUI/src/components/common/ApprovalManager.vue create mode 100644 webUI/src/components/common/ApprovalStatus.vue create mode 100644 webUI/src/utils/approvalUtils.js create mode 100644 webUI/src/views/public/PublicLayout.vue create mode 100644 webUI/src/views/public/WarrantyActivation.vue diff --git a/hesabixCore/composer.json b/hesabixCore/composer.json index 66aedb9..f44927d 100644 --- a/hesabixCore/composer.json +++ b/hesabixCore/composer.json @@ -16,6 +16,7 @@ "doctrine/orm": "^3.2", "dompdf/dompdf": "^3.0", "melipayamak/php": "^1.0", + "morilog/jalali": "*", "mpdf/mpdf": "^8.2", "nelmio/api-doc-bundle": "^4.35", "nelmio/cors-bundle": "^2.5", diff --git a/hesabixCore/composer.lock b/hesabixCore/composer.lock index fa625a3..c53e754 100644 --- a/hesabixCore/composer.lock +++ b/hesabixCore/composer.lock @@ -4,8 +4,75 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "43db0ad2bb94569ed6d44cabf503210e", + "content-hash": "01b5daf5a6fd011b4eb616e0e4ae18fe", "packages": [ + { + "name": "beberlei/assert", + "version": "v3.3.3", + "source": { + "type": "git", + "url": "https://github.com/beberlei/assert.git", + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-simplexml": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "*", + "phpstan/phpstan": "*", + "phpunit/phpunit": ">=6.0.0", + "yoast/phpunit-polyfills": "^0.1.0" + }, + "suggest": { + "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" + }, + "type": "library", + "autoload": { + "files": [ + "lib/Assert/functions.php" + ], + "psr-4": { + "Assert\\": "lib/Assert" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de", + "role": "Lead Developer" + }, + { + "name": "Richard Quadling", + "email": "rquadling@gmail.com", + "role": "Collaborator" + } + ], + "description": "Thin assertion library for input validation in business models.", + "keywords": [ + "assert", + "assertion", + "validation" + ], + "support": { + "issues": "https://github.com/beberlei/assert/issues", + "source": "https://github.com/beberlei/assert/tree/v3.3.3" + }, + "time": "2024-07-15T13:18:35+00:00" + }, { "name": "brick/math", "version": "0.12.3", @@ -66,6 +133,75 @@ ], "time": "2025-02-28T13:11:00+00:00" }, + { + "name": "carbonphp/carbon-doctrine-types", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/dbal": "<4.0.0 || >=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, { "name": "composer/pcre", "version": "3.3.2", @@ -2297,6 +2433,71 @@ ], "time": "2025-03-24T10:02:05+00:00" }, + { + "name": "morilog/jalali", + "version": "v3.4.2", + "source": { + "type": "git", + "url": "https://github.com/morilog/jalali.git", + "reference": "f475f4db7bd540c6abc01126e46824c897ed1e03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/morilog/jalali/zipball/f475f4db7bd540c6abc01126e46824c897ed1e03", + "reference": "f475f4db7bd540c6abc01126e46824c897ed1e03", + "shasum": "" + }, + "require": { + "beberlei/assert": "^3.0", + "nesbot/carbon": "^1.21 || ^2.0 || ^3.0", + "php": "^7.0 | ^8.0" + }, + "require-dev": { + "phpunit/phpunit": ">4.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Morilog\\Jalali\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Milad Rey", + "email": "miladr@gmail.com" + }, + { + "name": "Morteza Parvini", + "email": "m.parvini@outlook.com" + } + ], + "description": "This Package helps developers to easily work with Jalali (Shamsi or Iranian) dates in PHP applications, based on Jalali (Shamsi) DateTime class.", + "keywords": [ + "Jalali", + "date", + "datetime", + "laravel", + "morilog" + ], + "support": { + "issues": "https://github.com/morilog/jalali/issues", + "source": "https://github.com/morilog/jalali/tree/v3.4.2" + }, + "funding": [ + { + "url": "https://issuehunt.io/r/morilog", + "type": "issuehunt" + } + ], + "time": "2024-05-09T08:44:51+00:00" + }, { "name": "mpdf/mpdf", "version": "v8.2.5", @@ -2714,6 +2915,111 @@ }, "time": "2024-06-24T21:25:28+00:00" }, + { + "name": "nesbot/carbon", + "version": "3.10.2", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24", + "reference": "76b5c07b8a9d2025ed1610e14cef1f3fd6ad2c24", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^3.75.0", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.17", + "phpunit/phpunit": "^10.5.46", + "squizlabs/php_codesniffer": "^3.13.0" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2025-08-02T09:36:06+00:00" + }, { "name": "nikic/php-parser", "version": "v5.4.0", diff --git a/hesabixCore/migrations/Version20250113000000.php b/hesabixCore/migrations/Version20250113000000.php new file mode 100644 index 0000000..73e672a --- /dev/null +++ b/hesabixCore/migrations/Version20250113000000.php @@ -0,0 +1,40 @@ +addSql('ALTER TABLE hesabdari_doc ADD is_preview TINYINT(1) DEFAULT NULL'); + $this->addSql('ALTER TABLE hesabdari_doc ADD is_approved TINYINT(1) DEFAULT NULL'); + $this->addSql('ALTER TABLE hesabdari_doc ADD approved_by_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE hesabdari_doc ADD CONSTRAINT FK_HESABDARI_DOC_APPROVED_BY FOREIGN KEY (approved_by_id) REFERENCES user (id)'); + $this->addSql('CREATE INDEX IDX_HESABDARI_DOC_APPROVED_BY ON hesabdari_doc (approved_by_id)'); + + // Set default values for existing documents + $this->addSql('UPDATE hesabdari_doc SET is_preview = 0, is_approved = 1 WHERE is_preview IS NULL'); + } + + public function down(Schema $schema): void + { + // Remove approval fields from HesabdariDoc table + $this->addSql('ALTER TABLE hesabdari_doc DROP FOREIGN KEY FK_HESABDARI_DOC_APPROVED_BY'); + $this->addSql('DROP INDEX IDX_HESABDARI_DOC_APPROVED_BY ON hesabdari_doc'); + $this->addSql('ALTER TABLE hesabdari_doc DROP is_preview, DROP is_approved, DROP approved_by_id'); + } +} diff --git a/hesabixCore/migrations/Version20250113000001.php b/hesabixCore/migrations/Version20250113000001.php new file mode 100644 index 0000000..bb87e08 --- /dev/null +++ b/hesabixCore/migrations/Version20250113000001.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE storeroom_ticket DROP status'); + } + + public function down(Schema $schema): void + { + // Add status field back to storeroom_ticket table + $this->addSql('ALTER TABLE storeroom_ticket ADD status VARCHAR(50) DEFAULT NULL'); + } +} diff --git a/hesabixCore/migrations/Version20250113000002.php b/hesabixCore/migrations/Version20250113000002.php new file mode 100644 index 0000000..794b897 --- /dev/null +++ b/hesabixCore/migrations/Version20250113000002.php @@ -0,0 +1,40 @@ +addSql('ALTER TABLE storeroom_ticket ADD is_preview TINYINT(1) DEFAULT NULL'); + $this->addSql('ALTER TABLE storeroom_ticket ADD is_approved TINYINT(1) DEFAULT NULL'); + $this->addSql('ALTER TABLE storeroom_ticket ADD approved_by_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE storeroom_ticket ADD CONSTRAINT FK_STOREROOM_TICKET_APPROVED_BY FOREIGN KEY (approved_by_id) REFERENCES user (id)'); + $this->addSql('CREATE INDEX IDX_STOREROOM_TICKET_APPROVED_BY ON storeroom_ticket (approved_by_id)'); + + // Set default values for existing tickets + $this->addSql('UPDATE storeroom_ticket SET is_preview = 0, is_approved = 1 WHERE is_preview IS NULL'); + } + + public function down(Schema $schema): void + { + // Remove approval fields from storeroom_ticket table + $this->addSql('ALTER TABLE storeroom_ticket DROP FOREIGN KEY FK_STOREROOM_TICKET_APPROVED_BY'); + $this->addSql('DROP INDEX IDX_STOREROOM_TICKET_APPROVED_BY ON storeroom_ticket'); + $this->addSql('ALTER TABLE storeroom_ticket DROP is_preview, DROP is_approved, DROP approved_by_id'); + } +} diff --git a/hesabixCore/migrations/Version20250815143325.php b/hesabixCore/migrations/Version20250815143325.php new file mode 100644 index 0000000..3008029 --- /dev/null +++ b/hesabixCore/migrations/Version20250815143325.php @@ -0,0 +1,101 @@ +addSql(<<<'SQL' + ALTER TABLE hesabdari_row DROP FOREIGN KEY FK_83B2C6EC2D234F6A + SQL); + $this->addSql(<<<'SQL' + DROP INDEX IDX_83B2C6EC2D234F6A ON hesabdari_row + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE hesabdari_row DROP is_preview, DROP is_approved, DROP approved_by_id + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial DROP FOREIGN KEY FK_1A5DC26F4D9866B8 + SQL); + $this->addSql(<<<'SQL' + DROP INDEX IDX_1A5DC26F4D9866B8 ON plug_warranty_serial + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial ADD allocated_to_document_id INT DEFAULT NULL, ADD allocated_at DATETIME DEFAULT NULL, ADD bound_to_item_id INT DEFAULT NULL, ADD bound_at DATETIME DEFAULT NULL, DROP used, DROP used_at, CHANGE date_submit date_submit DATETIME NOT NULL, CHANGE description description LONGTEXT DEFAULT NULL, CHANGE warranty_start_date warranty_start_date DATETIME DEFAULT NULL, CHANGE warranty_end_date warranty_end_date DATETIME DEFAULT NULL, CHANGE status status VARCHAR(20) NOT NULL, CHANGE notes notes LONGTEXT DEFAULT NULL, CHANGE used_ticket_code void_reason VARCHAR(255) DEFAULT NULL, CHANGE bid_id business_id INT NOT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial ADD CONSTRAINT FK_1A5DC26FA89DB457 FOREIGN KEY (business_id) REFERENCES business (id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_1A5DC26FA89DB457 ON plug_warranty_serial (business_id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX idx_status_product ON plug_warranty_serial (status, commodity_id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX idx_alloc_doc ON plug_warranty_serial (allocated_to_document_id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial RENAME INDEX uniq_1a5dc26fd948ee2 TO uniq_warranty_serial + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE storeroom_ticket RENAME INDEX idx_storeroom_ticket_approved_by TO IDX_9B4CC0F72D234F6A + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE hesabdari_row ADD is_preview TINYINT(1) DEFAULT NULL, ADD is_approved TINYINT(1) DEFAULT NULL, ADD approved_by_id INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE hesabdari_row ADD CONSTRAINT FK_83B2C6EC2D234F6A FOREIGN KEY (approved_by_id) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE NO ACTION + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_83B2C6EC2D234F6A ON hesabdari_row (approved_by_id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial DROP FOREIGN KEY FK_1A5DC26FA89DB457 + SQL); + $this->addSql(<<<'SQL' + DROP INDEX IDX_1A5DC26FA89DB457 ON plug_warranty_serial + SQL); + $this->addSql(<<<'SQL' + DROP INDEX idx_status_product ON plug_warranty_serial + SQL); + $this->addSql(<<<'SQL' + DROP INDEX idx_alloc_doc ON plug_warranty_serial + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial ADD used TINYINT(1) DEFAULT NULL, ADD used_at VARCHAR(50) DEFAULT NULL, DROP allocated_to_document_id, DROP allocated_at, DROP bound_to_item_id, DROP bound_at, CHANGE date_submit date_submit VARCHAR(25) NOT NULL, CHANGE description description VARCHAR(255) DEFAULT NULL, CHANGE warranty_start_date warranty_start_date VARCHAR(25) DEFAULT NULL, CHANGE warranty_end_date warranty_end_date VARCHAR(25) DEFAULT NULL, CHANGE status status VARCHAR(50) DEFAULT NULL, CHANGE notes notes VARCHAR(255) DEFAULT NULL, CHANGE business_id bid_id INT NOT NULL, CHANGE void_reason used_ticket_code VARCHAR(255) DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial ADD CONSTRAINT FK_1A5DC26F4D9866B8 FOREIGN KEY (bid_id) REFERENCES business (id) ON UPDATE NO ACTION ON DELETE NO ACTION + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_1A5DC26F4D9866B8 ON plug_warranty_serial (bid_id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial RENAME INDEX uniq_warranty_serial TO UNIQ_1A5DC26FD948EE2 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE storeroom_ticket RENAME INDEX idx_9b4cc0f72d234f6a TO IDX_STOREROOM_TICKET_APPROVED_BY + SQL); + } +} diff --git a/hesabixCore/migrations/Version20250816171207.php b/hesabixCore/migrations/Version20250816171207.php new file mode 100644 index 0000000..5bfe61d --- /dev/null +++ b/hesabixCore/migrations/Version20250816171207.php @@ -0,0 +1,47 @@ +addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial ADD commodity_serial VARCHAR(255) DEFAULT NULL, ADD buyer_id INT DEFAULT NULL + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial ADD CONSTRAINT FK_1A5DC26F6C755722 FOREIGN KEY (buyer_id) REFERENCES person (id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_1A5DC26F6C755722 ON plug_warranty_serial (buyer_id) + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial DROP FOREIGN KEY FK_1A5DC26F6C755722 + SQL); + $this->addSql(<<<'SQL' + DROP INDEX IDX_1A5DC26F6C755722 ON plug_warranty_serial + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial DROP commodity_serial, DROP buyer_id + SQL); + } +} diff --git a/hesabixCore/migrations/Version20250811113332.php b/hesabixCore/migrations/Version20250816185111.php similarity index 76% rename from hesabixCore/migrations/Version20250811113332.php rename to hesabixCore/migrations/Version20250816185111.php index b46c836..9274184 100644 --- a/hesabixCore/migrations/Version20250811113332.php +++ b/hesabixCore/migrations/Version20250816185111.php @@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20250811113332 extends AbstractMigration +final class Version20250816185111 extends AbstractMigration { public function getDescription(): string { @@ -21,7 +21,7 @@ final class Version20250811113332 extends AbstractMigration { // this up() migration is auto-generated, please modify it to your needs $this->addSql(<<<'SQL' - ALTER TABLE person ADD require_two_step TINYINT(1) DEFAULT NULL + ALTER TABLE plug_warranty_serial ADD activation VARCHAR(20) NOT NULL SQL); } @@ -29,7 +29,7 @@ final class Version20250811113332 extends AbstractMigration { // this down() migration is auto-generated, please modify it to your needs $this->addSql(<<<'SQL' - ALTER TABLE person DROP require_two_step + ALTER TABLE plug_warranty_serial DROP activation SQL); } } diff --git a/hesabixCore/migrations/Version20250816185556.php b/hesabixCore/migrations/Version20250816185556.php new file mode 100644 index 0000000..d6ea780 --- /dev/null +++ b/hesabixCore/migrations/Version20250816185556.php @@ -0,0 +1,35 @@ +addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial ADD activation_at VARCHAR(20) NOT NULL + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial DROP activation_at + SQL); + } +} diff --git a/hesabixCore/migrations/Version20250818042052.php b/hesabixCore/migrations/Version20250818042052.php new file mode 100644 index 0000000..67a5631 --- /dev/null +++ b/hesabixCore/migrations/Version20250818042052.php @@ -0,0 +1,35 @@ +addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial CHANGE activation_at activation_at DATETIME DEFAULT NULL + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial CHANGE activation_at activation_at VARCHAR(20) NOT NULL + SQL); + } +} diff --git a/hesabixCore/migrations/Version20250818042232.php b/hesabixCore/migrations/Version20250818042232.php new file mode 100644 index 0000000..ea3f647 --- /dev/null +++ b/hesabixCore/migrations/Version20250818042232.php @@ -0,0 +1,35 @@ +addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial CHANGE activation_at activation_at DATETIME DEFAULT NULL + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE plug_warranty_serial CHANGE activation_at activation_at VARCHAR(20) DEFAULT NULL + SQL); + } +} diff --git a/hesabixCore/src/Cog/PersonService.php b/hesabixCore/src/Cog/PersonService.php index 7895d6c..1137a6d 100644 --- a/hesabixCore/src/Cog/PersonService.php +++ b/hesabixCore/src/Cog/PersonService.php @@ -288,10 +288,7 @@ class PersonService if (isset($params['shenasemeli'])) $person->setShenasemeli($params['shenasemeli']); if (isset($params['company'])) $person->setCompany($params['company']); if (isset($params['tags'])) $person->setTags($params['tags']); - if (isset($params['requireTwoStep'])) { - error_log("Setting requireTwoStep: " . var_export($params['requireTwoStep'], true)); - $person->setRequireTwoStep((bool)$params['requireTwoStep']); - } + if (array_key_exists('prelabel', $params)) { if ($params['prelabel'] != '') { $prelabel = $em->getRepository(\App\Entity\PersonPrelabel::class)->findOneBy(['label' => $params['prelabel']]); diff --git a/hesabixCore/src/Controller/ApprovalController.php b/hesabixCore/src/Controller/ApprovalController.php new file mode 100644 index 0000000..e4f9b85 --- /dev/null +++ b/hesabixCore/src/Controller/ApprovalController.php @@ -0,0 +1,426 @@ +hasRole('settings'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $business = $acc['bid']; + $businessSettings = $entityManager->getRepository(Business::class)->find($business->getId()); + + // بررسی اینکه آیا تأیید دو مرحله‌ای فعال است + if (!$businessSettings->isRequireTwoStepApproval()) { + return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']); + } + + // پیدا کردن حواله انبار + $ticket = $entityManager->getRepository(\App\Entity\StoreroomTicket::class)->findOneBy([ + 'code' => $ticketCode, + 'bid' => $business + ]); + + if (!$ticket) { + return $this->json(['success' => false, 'message' => 'حواله انبار یافت نشد']); + } + + // بررسی مجوز تأیید + $canApprove = $this->canUserApproveStoreroomTicket($user, $businessSettings); + if (!$canApprove) { + return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این حواله را ندارید']); + } + + // تأیید حواله + $ticket->setIsPreview(false); + $ticket->setIsApproved(true); + $ticket->setApprovedBy($user); + + $entityManager->persist($ticket); + $entityManager->flush(); + + // ثبت لاگ + $logService->insert( + 'تأیید حواله انبار', + "حواله انبار {$ticket->getCode()} توسط {$user->getFullName()} تأیید شد", + $user, + $business + ); + + return $this->json([ + 'success' => true, + 'message' => 'حواله انبار با موفقیت تأیید شد' + ]); + + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در تأیید حواله انبار: ' . $e->getMessage() + ], 500); + } + } + + // تأیید فاکتور فروش + #[Route('/api/approval/approve/sales/{docId}', name: 'api_approval_approve_sales', methods: ['POST'])] + public function approveSalesInvoice( + $docId, + #[CurrentUser] ?User $user, + Access $access, + LogService $logService, + EntityManagerInterface $entityManager + ): Response { + try { + // بررسی دسترسی کاربر + $acc = $access->hasRole('settings'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $business = $acc['bid']; + $businessSettings = $entityManager->getRepository(Business::class)->find($business->getId()); + + // بررسی اینکه آیا تأیید دو مرحله‌ای فعال است + if (!$businessSettings->isRequireTwoStepApproval()) { + return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']); + } + + // پیدا کردن فاکتور فروش + $document = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + 'code' => $docId, + 'bid' => $business + ]); + + if (!$document) { + return $this->json(['success' => false, 'message' => 'فاکتور فروش یافت نشد']); + } + + // بررسی مجوز تأیید + $canApprove = $this->canUserApproveSalesInvoice($user, $businessSettings); + if (!$canApprove) { + return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این فاکتور را ندارید']); + } + + // تأیید فاکتور + $document->setIsPreview(false); + $document->setIsApproved(true); + $document->setApprovedBy($user); + + $entityManager->persist($document); + $entityManager->flush(); + + // ثبت لاگ + $logService->insert( + 'تأیید فاکتور فروش', + "فاکتور فروش {$document->getCode()} توسط {$user->getFullName()} تأیید شد", + $user, + $business + ); + + return $this->json([ + 'success' => true, + 'message' => 'فاکتور فروش با موفقیت تأیید شد' + ]); + + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در تأیید فاکتور فروش: ' . $e->getMessage() + ], 500); + } + } + + // تأیید سند مالی + #[Route('/api/approval/approve/financial/{docId}', name: 'api_approval_approve_financial', methods: ['POST'])] + public function approveFinancialDocument( + $docId, + #[CurrentUser] ?User $user, + Access $access, + LogService $logService, + EntityManagerInterface $entityManager + ): Response { + try { + // بررسی دسترسی کاربر + $acc = $access->hasRole('hasRole'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $business = $acc['bid']; + $businessSettings = $entityManager->getRepository(Business::class)->find($business->getId()); + + // بررسی اینکه آیا تأیید دو مرحله‌ای فعال است + if (!$businessSettings->isRequireTwoStepApproval()) { + return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']); + } + + // پیدا کردن سند مالی + $document = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + 'id' => $docId, + 'bid' => $business + ]); + + if (!$document) { + return $this->json(['success' => false, 'message' => 'سند مالی یافت نشد']); + } + + // بررسی مجوز تأیید + $canApprove = $this->canUserApproveFinancialDocument($user, $businessSettings); + if (!$canApprove) { + return $this->json(['success' => false, 'message' => 'شما مجوز تأیید این سند را ندارید']); + } + + // تأیید سند + $document->setIsPreview(false); + $document->setIsApproved(true); + $document->setApprovedBy($user); + + $entityManager->persist($document); + $entityManager->flush(); + + // ثبت لاگ + $logService->insert( + 'تأیید سند مالی', + "سند مالی {$document->getCode()} توسط {$user->getFullName()} تأیید شد", + $user, + $business + ); + + return $this->json([ + 'success' => true, + 'message' => 'سند مالی با موفقیت تأیید شد' + ]); + + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در تأیید سند مالی: ' . $e->getMessage() + ], 500); + } + } + + #[Route('/api/approval/reject/{docId}', name: 'api_approval_reject', methods: ['POST'])] + public function rejectDocument( + $docId, + Request $request, + #[CurrentUser] ?User $user, + Access $access, + LogService $logService, + EntityManagerInterface $entityManager + ): Response { + try { + // بررسی دسترسی کاربر + $acc = $access->hasRole('owner'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $business = $acc['bid']; + $businessSettings = $entityManager->getRepository(Business::class)->find($business->getId()); + + // بررسی اینکه آیا تأیید دو مرحله‌ای فعال است + if (!$businessSettings->isRequireTwoStepApproval()) { + return $this->json(['success' => false, 'message' => 'تأیید دو مرحله‌ای فعال نیست']); + } + + // پیدا کردن سند + $document = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + 'id' => $docId, + 'bid' => $business + ]); + + if (!$document) { + return $this->json(['success' => false, 'message' => 'سند یافت نشد']); + } + + // دریافت دلیل رد + $data = json_decode($request->getContent(), true); + $rejectionReason = $data['reason'] ?? 'دلیل مشخص نشده'; + + // رد سند + $document->setIsPreview(false); + $document->setIsApproved(false); + $document->setApprovedBy(null); + + // ردیف‌ها نیازی به تنظیم جداگانه ندارند - از سند پیروی می‌کنند + + $entityManager->persist($document); + $entityManager->flush(); + + // ثبت لاگ + $logService->insert( + 'رد سند', + "سند {$document->getCode()} توسط {$user->getFullName()} رد شد. دلیل: {$rejectionReason}", + $user, + $business + ); + + return $this->json([ + 'success' => true, + 'message' => 'سند با موفقیت رد شد' + ]); + + } catch (\Exception $e) { + return $this->json([ + 'success' => false, + 'message' => 'خطا در رد سند: ' . $e->getMessage() + ], 500); + } + } + + #[Route('/api/approval/check-permission/{docId}', name: 'api_approval_check_permission', methods: ['GET'])] + public function checkApprovalPermission( + $docId, + #[CurrentUser] ?User $user, + Access $access, + EntityManagerInterface $entityManager + ): Response { + try { + $acc = $access->hasRole('settings'); + if (!$acc) { + return $this->json(['canApprove' => false, 'message' => 'دسترسی محدود']); + } + + $business = $acc['bid']; + $businessSettings = $entityManager->getRepository(Business::class)->find($business->getId()); + + // پیدا کردن سند + $document = $entityManager->getRepository(HesabdariDoc::class)->findOneBy([ + 'id' => $docId, + 'bid' => $business + ]); + + if (!$document) { + return $this->json(['canApprove' => false, 'message' => 'سند یافت نشد']); + } + + $canApprove = $this->canUserApproveDocument($user, $businessSettings, $document); + + return $this->json([ + 'canApprove' => $canApprove, + 'documentStatus' => [ + 'isPreview' => $document->isPreview(), + 'isApproved' => $document->isApproved(), + 'approvedBy' => $document->getApprovedBy() ? $document->getApprovedBy()->getFullName() : null + ] + ]); + + } catch (\Exception $e) { + return $this->json([ + 'canApprove' => false, + 'message' => 'خطا در بررسی مجوز: ' . $e->getMessage() + ], 500); + } + } + + /** + * بررسی اینکه آیا کاربر می‌تواند سند را تأیید کند + */ + private function canUserApproveDocument(User $user, Business $business, HesabdariDoc $document): bool + { + // مدیر کسب و کار همیشه می‌تواند تأیید کند + if ($user->getEmail() === $business->getOwner()->getEmail()) { + return true; + } + + // بررسی تأییدکنندگان اختصاصی بر اساس نوع سند + $documentType = $this->getDocumentType($document); + + switch ($documentType) { + case 'invoice': + return $business->getInvoiceApprover() === $user->getEmail(); + case 'warehouse': + return $business->getWarehouseApprover() === $user->getEmail(); + case 'financial': + return $business->getFinancialApprover() === $user->getEmail(); + default: + return false; + } + } + + /** + * تشخیص نوع سند + */ + private function getDocumentType(HesabdariDoc $document): string + { + $type = $document->getType(); + + if (strpos($type, 'sell') !== false || strpos($type, 'invoice') !== false) { + return 'invoice'; + } + + if (strpos($type, 'warehouse') !== false || strpos($type, 'storeroom') !== false) { + return 'warehouse'; + } + + if (strpos($type, 'payment') !== false || strpos($type, 'receipt') !== false || strpos($type, 'hesabdari') !== false) { + return 'financial'; + } + + return 'unknown'; + } + + // بررسی مجوز تأیید حواله انبار + private function canUserApproveStoreroomTicket(User $user, Business $business): bool + { + // مدیر کسب و کار همیشه می‌تواند تأیید کند + if ($user->getEmail() === $business->getOwner()->getEmail()) { + return true; + } + + // کاربر تأییدکننده انبار + return $business->getWarehouseApprover() === $user->getEmail(); + } + + // بررسی مجوز تأیید فاکتور فروش + private function canUserApproveSalesInvoice(User $user, Business $business): bool + { + // مدیر کسب و کار همیشه می‌تواند تأیید کند + if ($user->getEmail() === $business->getOwner()->getEmail()) { + return true; + } + + // کاربر تأییدکننده فاکتور فروش + return $business->getInvoiceApprover() === $user->getEmail(); + } + + // بررسی مجوز تأیید سند مالی + private function canUserApproveFinancialDocument(User $user, Business $business): bool + { + // مدیر کسب و کار همیشه می‌تواند تأیید کند + if ($user->getEmail() === $business->getOwner()->getEmail()) { + return true; + } + + // کاربر تأییدکننده اسناد مالی + return $business->getFinancialApprover() === $user->getEmail(); + } +} diff --git a/hesabixCore/src/Controller/BusinessController.php b/hesabixCore/src/Controller/BusinessController.php index af3ed20..15b8754 100644 --- a/hesabixCore/src/Controller/BusinessController.php +++ b/hesabixCore/src/Controller/BusinessController.php @@ -246,6 +246,22 @@ class BusinessController extends AbstractController $business->setWalletEnable(false); } } + if (array_key_exists('requireTwoStepApproval', $params)) { + $business->setRequireTwoStepApproval((bool)$params['requireTwoStepApproval']); + } + + // Set approvers + if (array_key_exists('invoiceApprover', $params)) { + $business->setInvoiceApprover($params['invoiceApprover']); + } + + if (array_key_exists('warehouseApprover', $params)) { + $business->setWarehouseApprover($params['warehouseApprover']); + } + + if (array_key_exists('financialApprover', $params)) { + $business->setFinancialApprover($params['financialApprover']); + } //get Money type if (!array_key_exists('arzmain', $params) && $isNew) { diff --git a/hesabixCore/src/Controller/ImportWorkflowController.php b/hesabixCore/src/Controller/ImportWorkflowController.php index e0e058f..82bb075 100644 --- a/hesabixCore/src/Controller/ImportWorkflowController.php +++ b/hesabixCore/src/Controller/ImportWorkflowController.php @@ -13,6 +13,7 @@ use App\Service\Access; use App\Service\Log; use App\Service\Provider; use Doctrine\ORM\EntityManagerInterface; +use Morilog\Jalali\CalendarUtils; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; @@ -22,6 +23,8 @@ use App\Entity\StoreroomTicket; use App\Entity\Storeroom; use App\Entity\Person; use App\Entity\HesabdariDoc; +use App\Entity\Commodity; +use App\Service\FileStorage; class ImportWorkflowController extends AbstractController { @@ -80,6 +83,33 @@ class ImportWorkflowController extends AbstractController ]); } + #[Route('/api/import-workflow/documents/{docId}/download', name: 'api_import_workflow_document_download', methods: ['GET'])] + public function downloadDocument(string $docId, Request $request, Access $access, EntityManagerInterface $entityManager, FileStorage $storage): \Symfony\Component\HttpFoundation\Response + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $doc = $entityManager->getRepository(ImportWorkflowDocument::class)->find((int)$docId); + if (!$doc) { + throw $this->createNotFoundException('سند یافت نشد'); + } + // Enforce business ownership + $workflow = $doc->getImportWorkflow(); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + throw $this->createAccessDeniedException('دسترسی ندارید'); + } + $abs = $storage->absolutePath((string)$doc->getFilePath()); + if (!is_file($abs) || !is_readable($abs)) { + throw $this->createNotFoundException('فایل یافت نشد'); + } + $response = new \Symfony\Component\HttpFoundation\BinaryFileResponse($abs); + $response->setContentDisposition( + \Symfony\Component\HttpFoundation\ResponseHeaderBag::DISPOSITION_ATTACHMENT, + $doc->getFileName() ?: basename($abs) + ); + $response->headers->set('Content-Type', $doc->getFileType() ?: 'application/octet-stream'); + return $response; + } + #[Route('/api/import-workflow/create', name: 'api_import_workflow_create', methods: ['POST'])] public function create( Request $request, @@ -228,6 +258,11 @@ class ImportWorkflowController extends AbstractController foreach ($workflow->getItems() as $item) { $data['items'][] = [ 'id' => $item->getId(), + 'commodity' => $item->getCommodity() ? [ + 'id' => $item->getCommodity()->getId(), + 'code' => method_exists($item->getCommodity(), 'getCode') ? $item->getCommodity()->getCode() : null, + 'name' => method_exists($item->getCommodity(), 'getName') ? $item->getCommodity()->getName() : null, + ] : null, 'name' => $item->getName(), 'productCode' => $item->getProductCode(), 'brand' => $item->getBrand(), @@ -270,14 +305,13 @@ class ImportWorkflowController extends AbstractController 'id' => $document->getId(), 'type' => $document->getType(), 'title' => $document->getTitle(), - 'filePath' => $document->getFilePath(), + 'filePath' => $document->getFilePath(), // relative storage path 'fileName' => $document->getFileName(), 'fileSize' => $document->getFileSize(), 'fileType' => $document->getFileType(), 'description' => $document->getDescription(), 'documentNumber' => $document->getDocumentNumber(), 'issueDate' => $document->getIssueDate(), - 'expiryDate' => $document->getExpiryDate(), 'status' => $document->getStatus(), 'dateSubmit' => $document->getDateSubmit() ]; @@ -348,6 +382,530 @@ class ImportWorkflowController extends AbstractController ]); } + // Documents CRUD + #[Route('/api/import-workflow/{id}/documents/create', name: 'api_import_workflow_document_create', methods: ['POST'])] + public function createDocument(string $id, Request $request, Access $access, EntityManagerInterface $entityManager, FileStorage $storage): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $data = $request->request->all(); + $doc = new ImportWorkflowDocument(); + $doc->setImportWorkflow($workflow); + $doc->setType($data['type'] ?? 'other'); + $doc->setTitle($data['title'] ?? ''); + $doc->setDocumentNumber($data['documentNumber'] ?? null); + $doc->setIssueDate($this->jalaliToGregorian($data['issueDate']) ?? null); + $doc->setDescription($data['description'] ?? null); + $doc->setStatus('active'); + // handle file + $file = $request->files->get('file'); + if ($file) { + $stored = $storage->store($file, (string)$acc['bid']->getId(), 'import_docs'); + $doc->setFilePath($stored['relativePath']); + $doc->setFileName($stored['originalName']); + $doc->setFileSize($stored['size'] !== null ? (string)$stored['size'] : null); + $doc->setFileType($stored['mime']); + } + $entityManager->persist($doc); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$doc->getId()]]); + } + + #[Route('/api/import-workflow/{id}/documents/{docId}/update', name: 'api_import_workflow_document_update', methods: ['PUT','POST'])] + public function updateDocument(string $id, string $docId, Request $request, Access $access, EntityManagerInterface $entityManager, FileStorage $storage): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $doc = $entityManager->getRepository(ImportWorkflowDocument::class)->find((int)$docId); + if (!$doc || $doc->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'سند یافت نشد','Result'=>null], 404); + } + $data = $request->request->all(); + if (isset($data['type'])) $doc->setType($data['type']); + if (isset($data['title'])) $doc->setTitle($data['title']); + if (isset($data['documentNumber'])) $doc->setDocumentNumber($data['documentNumber']); + if (isset($data['issueDate'])) $doc->setIssueDate($this->jalaliToGregorian($data['issueDate']) ?? null); + if (isset($data['description'])) $doc->setDescription($data['description']); + $file = $request->files->get('file'); + if ($file) { + $stored = $storage->store($file, (string)$acc['bid']->getId(), 'import_docs'); + $doc->setFilePath($stored['relativePath']); + $doc->setFileName($stored['originalName']); + $doc->setFileSize($stored['size'] !== null ? (string)$stored['size'] : null); + $doc->setFileType($stored['mime']); + } + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$doc->getId()]]); + } + + #[Route('/api/import-workflow/{id}/documents/{docId}/delete', name: 'api_import_workflow_document_delete', methods: ['DELETE'])] + public function deleteDocument(string $id, string $docId, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $doc = $entityManager->getRepository(ImportWorkflowDocument::class)->find((int)$docId); + if (!$doc || $doc->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'سند یافت نشد','Result'=>null], 404); + } + $entityManager->remove($doc); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>true]); + } + + // Stages CRUD + #[Route('/api/import-workflow/{id}/stages/create', name: 'api_import_workflow_stage_create', methods: ['POST'])] + public function createStage(string $id, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $data = json_decode($request->getContent() ?: '{}', true); + $st = new ImportWorkflowStage(); + $st->setImportWorkflow($workflow); + $st->setStage($data['stage'] ?? ''); + $st->setStatus($data['status'] ?? 'pending'); + $st->setStartDate(isset($data['startDate']) ? ($this->jalaliToGregorian($data['startDate']) ?? null) : null); + $st->setEndDate(isset($data['endDate']) ? ($this->jalaliToGregorian($data['endDate']) ?? null) : null); + $st->setDescription($data['description'] ?? null); + $st->setAssignedTo($data['assignedTo'] ?? null); + $st->setNotes($data['notes'] ?? null); + $entityManager->persist($st); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$st->getId()]]); + } + + #[Route('/api/import-workflow/{id}/stages/{sid}/update', name: 'api_import_workflow_stage_update', methods: ['PUT'])] + public function updateStage(string $id, string $sid, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $st = $entityManager->getRepository(ImportWorkflowStage::class)->find((int)$sid); + if (!$st || $st->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'مرحله یافت نشد','Result'=>null], 404); + } + $data = json_decode($request->getContent() ?: '{}', true); + if (isset($data['stage'])) $st->setStage($data['stage']); + if (isset($data['status'])) $st->setStatus($data['status']); + if (isset($data['startDate'])) $st->setStartDate($this->jalaliToGregorian($data['startDate']) ?? null); + if (isset($data['endDate'])) $st->setEndDate($this->jalaliToGregorian($data['endDate']) ?? null); + if (isset($data['description'])) $st->setDescription($data['description']); + if (isset($data['assignedTo'])) $st->setAssignedTo($data['assignedTo']); + if (isset($data['notes'])) $st->setNotes($data['notes']); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$st->getId()]]); + } + + #[Route('/api/import-workflow/{id}/stages/{sid}/delete', name: 'api_import_workflow_stage_delete', methods: ['DELETE'])] + public function deleteStage(string $id, string $sid, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $st = $entityManager->getRepository(ImportWorkflowStage::class)->find((int)$sid); + if (!$st || $st->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'مرحله یافت نشد','Result'=>null], 404); + } + $entityManager->remove($st); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>true]); + } + + // Shipping CRUD + #[Route('/api/import-workflow/{id}/shipping/create', name: 'api_import_workflow_shipping_create', methods: ['POST'])] + public function createShipping(string $id, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $data = json_decode($request->getContent() ?: '{}', true); + $sh = new ImportWorkflowShipping(); + $sh->setImportWorkflow($workflow); + $sh->setType($data['type'] ?? 'sea'); + $sh->setContainerNumber($data['containerNumber'] ?? null); + $sh->setBillOfLading($data['billOfLading'] ?? null); + $sh->setShippingDate(isset($data['shippingDate']) ? ($this->jalaliToGregorian($data['shippingDate']) ?? null) : null); + $sh->setArrivalDate(isset($data['arrivalDate']) ? ($this->jalaliToGregorian($data['arrivalDate']) ?? null) : null); + $sh->setUnloadingDate(isset($data['unloadingDate']) ? ($this->jalaliToGregorian($data['unloadingDate']) ?? null) : null); + $sh->setShippingCompany($data['shippingCompany'] ?? null); + $sh->setShippingCompanyPhone($data['shippingCompanyPhone'] ?? null); + $sh->setShippingCompanyEmail($data['shippingCompanyEmail'] ?? null); + $sh->setOriginPort($data['originPort'] ?? null); + $sh->setDestinationPort($data['destinationPort'] ?? null); + $sh->setVesselName($data['vesselName'] ?? null); + $sh->setVoyageNumber($data['voyageNumber'] ?? null); + $sh->setDescription($data['description'] ?? null); + $sh->setStatus($data['status'] ?? 'active'); + $entityManager->persist($sh); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$sh->getId()]]); + } + + #[Route('/api/import-workflow/{id}/shipping/{sid}/update', name: 'api_import_workflow_shipping_update', methods: ['PUT','POST'])] + public function updateShipping(string $id, string $sid, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $sh = $entityManager->getRepository(ImportWorkflowShipping::class)->find((int)$sid); + if (!$sh || $sh->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'اطلاعات حمل یافت نشد','Result'=>null], 404); + } + $data = json_decode($request->getContent() ?: '{}', true); + if (isset($data['type'])) $sh->setType($data['type']); + if (isset($data['containerNumber'])) $sh->setContainerNumber($data['containerNumber']); + if (isset($data['billOfLading'])) $sh->setBillOfLading($data['billOfLading']); + if (isset($data['shippingDate'])) $sh->setShippingDate($this->jalaliToGregorian($data['shippingDate']) ?? null); + if (isset($data['arrivalDate'])) $sh->setArrivalDate($this->jalaliToGregorian($data['arrivalDate']) ?? null); + if (isset($data['unloadingDate'])) $sh->setUnloadingDate($this->jalaliToGregorian($data['unloadingDate']) ?? null); + if (isset($data['shippingCompany'])) $sh->setShippingCompany($data['shippingCompany']); + if (isset($data['shippingCompanyPhone'])) $sh->setShippingCompanyPhone($data['shippingCompanyPhone']); + if (isset($data['shippingCompanyEmail'])) $sh->setShippingCompanyEmail($data['shippingCompanyEmail']); + if (isset($data['originPort'])) $sh->setOriginPort($data['originPort']); + if (isset($data['destinationPort'])) $sh->setDestinationPort($data['destinationPort']); + if (isset($data['vesselName'])) $sh->setVesselName($data['vesselName']); + if (isset($data['voyageNumber'])) $sh->setVoyageNumber($data['voyageNumber']); + if (isset($data['description'])) $sh->setDescription($data['description']); + if (isset($data['status'])) $sh->setStatus($data['status']); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$sh->getId()]]); + } + + #[Route('/api/import-workflow/{id}/shipping/{sid}/delete', name: 'api_import_workflow_shipping_delete', methods: ['DELETE'])] + public function deleteShipping(string $id, string $sid, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $sh = $entityManager->getRepository(ImportWorkflowShipping::class)->find((int)$sid); + if (!$sh || $sh->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'اطلاعات حمل یافت نشد','Result'=>null], 404); + } + $entityManager->remove($sh); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>true]); + } + + // Customs CRUD + #[Route('/api/import-workflow/{id}/customs/create', name: 'api_import_workflow_customs_create', methods: ['POST'])] + public function createCustoms(string $id, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $data = json_decode($request->getContent() ?: '{}', true); + $c = new ImportWorkflowCustoms(); + $c->setImportWorkflow($workflow); + $c->setDeclarationNumber($data['declarationNumber'] ?? ''); + $c->setCustomsCode($data['customsCode'] ?? null); + $c->setClearanceDate(isset($data['clearanceDate']) ? ($this->jalaliToGregorian($data['clearanceDate']) ?? null) : null); + $c->setCustomsDuty(isset($data['customsDuty']) ? (string)$data['customsDuty'] : null); + $c->setValueAddedTax(isset($data['valueAddedTax']) ? (string)$data['valueAddedTax'] : null); + $c->setOtherCharges(isset($data['otherCharges']) ? (string)$data['otherCharges'] : null); + $c->setTotalCustomsCharges(isset($data['totalCustomsCharges']) ? (string)$data['totalCustomsCharges'] : null); + $c->setCustomsBroker($data['customsBroker'] ?? null); + $c->setCustomsBrokerPhone($data['customsBrokerPhone'] ?? null); + $c->setCustomsBrokerEmail($data['customsBrokerEmail'] ?? null); + $c->setWarehouseNumber($data['warehouseNumber'] ?? null); + $c->setWarehouseLocation($data['warehouseLocation'] ?? null); + $c->setDescription($data['description'] ?? null); + $c->setStatus($data['status'] ?? 'active'); + $entityManager->persist($c); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$c->getId()]]); + } + + #[Route('/api/import-workflow/{id}/customs/{cid}/update', name: 'api_import_workflow_customs_update', methods: ['PUT','POST'])] + public function updateCustoms(string $id, string $cid, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $c = $entityManager->getRepository(ImportWorkflowCustoms::class)->find((int)$cid); + if (!$c || $c->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'اطلاعات ترخیص یافت نشد','Result'=>null], 404); + } + $data = json_decode($request->getContent() ?: '{}', true); + if (isset($data['declarationNumber'])) $c->setDeclarationNumber($data['declarationNumber']); + if (isset($data['customsCode'])) $c->setCustomsCode($data['customsCode']); + if (isset($data['clearanceDate'])) $c->setClearanceDate($this->jalaliToGregorian($data['clearanceDate']) ?? null); + if (isset($data['customsDuty'])) $c->setCustomsDuty((string)$data['customsDuty']); + if (isset($data['valueAddedTax'])) $c->setValueAddedTax((string)$data['valueAddedTax']); + if (isset($data['otherCharges'])) $c->setOtherCharges((string)$data['otherCharges']); + if (isset($data['totalCustomsCharges'])) $c->setTotalCustomsCharges((string)$data['totalCustomsCharges']); + if (isset($data['customsBroker'])) $c->setCustomsBroker($data['customsBroker']); + if (isset($data['customsBrokerPhone'])) $c->setCustomsBrokerPhone($data['customsBrokerPhone']); + if (isset($data['customsBrokerEmail'])) $c->setCustomsBrokerEmail($data['customsBrokerEmail']); + if (isset($data['warehouseNumber'])) $c->setWarehouseNumber($data['warehouseNumber']); + if (isset($data['warehouseLocation'])) $c->setWarehouseLocation($data['warehouseLocation']); + if (isset($data['description'])) $c->setDescription($data['description']); + if (isset($data['status'])) $c->setStatus($data['status']); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$c->getId()]]); + } + + #[Route('/api/import-workflow/{id}/customs/{cid}/delete', name: 'api_import_workflow_customs_delete', methods: ['DELETE'])] + public function deleteCustoms(string $id, string $cid, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { throw $this->createAccessDeniedException(); } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $c = $entityManager->getRepository(ImportWorkflowCustoms::class)->find((int)$cid); + if (!$c || $c->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'اطلاعات ترخیص یافت نشد','Result'=>null], 404); + } + $entityManager->remove($c); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>true]); + } + + // Payments CRUD + #[Route('/api/import-workflow/{id}/payments/create', name: 'api_import_workflow_payment_create', methods: ['POST'])] + public function createPayment(string $id, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $data = json_decode($request->getContent() ?: '{}', true); + $p = new ImportWorkflowPayment(); + $p->setImportWorkflow($workflow); + $p->setType($data['type'] ?? 'other'); + $p->setAmount((string)($data['amount'] ?? '0')); + $p->setCurrency($data['currency'] ?? 'IRR'); + $p->setAmountIRR(isset($data['amountIRR']) ? (string)$data['amountIRR'] : null); + $p->setPaymentDate($this->jalaliToGregorian($data['paymentDate']) ?? date('Y-m-d')); + $p->setReferenceNumber($data['referenceNumber'] ?? null); + $p->setBankName($data['bankName'] ?? null); + $p->setAccountNumber($data['accountNumber'] ?? null); + $p->setRecipientName($data['recipientName'] ?? null); + $p->setStatus($data['status'] ?? 'pending'); + $p->setDescription($data['description'] ?? null); + $p->setReceiptNumber($data['receiptNumber'] ?? null); + $entityManager->persist($p); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$p->getId()]]); + } + + #[Route('/api/import-workflow/{id}/payments/{pid}/update', name: 'api_import_workflow_payment_update', methods: ['PUT'])] + public function updatePayment(string $id, string $pid, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $p = $entityManager->getRepository(ImportWorkflowPayment::class)->find((int)$pid); + if (!$p || $p->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرداخت یافت نشد','Result'=>null], 404); + } + $data = json_decode($request->getContent() ?: '{}', true); + if (isset($data['type'])) $p->setType($data['type']); + if (isset($data['amount'])) $p->setAmount((string)$data['amount']); + if (isset($data['currency'])) $p->setCurrency($data['currency']); + if (isset($data['amountIRR'])) $p->setAmountIRR((string)$data['amountIRR']); + if (isset($data['paymentDate'])) $p->setPaymentDate($this->jalaliToGregorian($data['paymentDate']) ?? date('Y-m-d')); + if (isset($data['referenceNumber'])) $p->setReferenceNumber($data['referenceNumber']); + if (isset($data['bankName'])) $p->setBankName($data['bankName']); + if (isset($data['accountNumber'])) $p->setAccountNumber($data['accountNumber']); + if (isset($data['recipientName'])) $p->setRecipientName($data['recipientName']); + if (isset($data['status'])) $p->setStatus($data['status']); + if (isset($data['description'])) $p->setDescription($data['description']); + if (isset($data['receiptNumber'])) $p->setReceiptNumber($data['receiptNumber']); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$p->getId()]]); + } + + #[Route('/api/import-workflow/{id}/payments/{pid}/delete', name: 'api_import_workflow_payment_delete', methods: ['DELETE'])] + public function deletePayment(string $id, string $pid, Access $access, EntityManagerInterface $entityManager): JsonResponse + { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $p = $entityManager->getRepository(ImportWorkflowPayment::class)->find((int)$pid); + if (!$p || $p->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرداخت یافت نشد','Result'=>null], 404); + } + $entityManager->remove($p); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>true]); + } + + #[Route('/api/import-workflow/{id}/items/create', name: 'api_import_workflow_item_create', methods: ['POST'])] + public function createItem( + string $id, + Request $request, + Access $access, + EntityManagerInterface $entityManager + ): JsonResponse { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + + $data = json_decode($request->getContent() ?: '{}', true); + $commodityId = isset($data['commodity_id']) ? (int)$data['commodity_id'] : 0; + if ($commodityId <= 0) { + return $this->json(['Success'=>false,'ErrorCode'=>400,'ErrorMessage'=>'کالا انتخاب نشده است','Result'=>null], 400); + } + $commodity = $entityManager->getRepository(Commodity::class)->find($commodityId); + if (!$commodity || $commodity->getBid()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'کالا یافت نشد','Result'=>null], 404); + } + + $item = new ImportWorkflowItem(); + $item->setImportWorkflow($workflow); + $item->setCommodity($commodity); + $item->setName($data['name'] ?? ($commodity->getName() ?? '')); + $item->setProductCode($data['productCode'] ?? ($commodity->getCode() ?? '')); + $item->setBrand($data['brand'] ?? null); + $item->setModel($data['model'] ?? null); + $item->setOriginCountry($data['originCountry'] ?? null); + $item->setQuantity($data['quantity'] ?? '0'); + $item->setUnitPrice($data['unitPrice'] ?? null); + $item->setUnitPriceIRR($data['unitPriceIRR'] ?? null); + $item->setTotalPrice($data['totalPrice'] ?? null); + $item->setTotalPriceIRR($data['totalPriceIRR'] ?? null); + $item->setWeight($data['weight'] ?? null); + $item->setVolume($data['volume'] ?? null); + $item->setDescription($data['description'] ?? null); + $item->setSpecifications($data['specifications'] ?? null); + + $entityManager->persist($item); + $entityManager->flush(); + + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$item->getId()]]); + } + + #[Route('/api/import-workflow/{id}/items/{itemId}/update', name: 'api_import_workflow_item_update', methods: ['PUT'])] + public function updateItem( + string $id, + string $itemId, + Request $request, + Access $access, + EntityManagerInterface $entityManager + ): JsonResponse { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $item = $entityManager->getRepository(ImportWorkflowItem::class)->find((int)$itemId); + if (!$item || $item->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'آیتم یافت نشد','Result'=>null], 404); + } + + $data = json_decode($request->getContent() ?: '{}', true); + if (isset($data['commodity_id'])) { + $commodity = $entityManager->getRepository(Commodity::class)->find((int)$data['commodity_id']); + if ($commodity && $commodity->getBid()->getId() === $acc['bid']->getId()) { + $item->setCommodity($commodity); + if (!isset($data['name'])) $item->setName($commodity->getName()); + if (!isset($data['productCode'])) $item->setProductCode($commodity->getCode()); + } + } + if (isset($data['name'])) $item->setName($data['name']); + if (isset($data['productCode'])) $item->setProductCode($data['productCode']); + if (isset($data['brand'])) $item->setBrand($data['brand']); + if (isset($data['model'])) $item->setModel($data['model']); + if (isset($data['originCountry'])) $item->setOriginCountry($data['originCountry']); + if (isset($data['quantity'])) $item->setQuantity($data['quantity']); + if (isset($data['unitPrice'])) $item->setUnitPrice($data['unitPrice']); + if (isset($data['unitPriceIRR'])) $item->setUnitPriceIRR($data['unitPriceIRR']); + if (isset($data['totalPrice'])) $item->setTotalPrice($data['totalPrice']); + if (isset($data['totalPriceIRR'])) $item->setTotalPriceIRR($data['totalPriceIRR']); + if (isset($data['weight'])) $item->setWeight($data['weight']); + if (isset($data['volume'])) $item->setVolume($data['volume']); + if (isset($data['description'])) $item->setDescription($data['description']); + if (isset($data['specifications'])) $item->setSpecifications($data['specifications']); + + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>['id'=>$item->getId()]]); + } + + #[Route('/api/import-workflow/{id}/items/{itemId}/delete', name: 'api_import_workflow_item_delete', methods: ['DELETE'])] + public function deleteItem( + string $id, + string $itemId, + Access $access, + EntityManagerInterface $entityManager + ): JsonResponse { + $acc = $access->hasRole('import_workflow'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + $workflow = $entityManager->getRepository(ImportWorkflow::class)->find((int)$id); + if (!$workflow || $workflow->getBusiness()->getId() !== $acc['bid']->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'پرونده واردات یافت نشد','Result'=>null], 404); + } + $item = $entityManager->getRepository(ImportWorkflowItem::class)->find((int)$itemId); + if (!$item || $item->getImportWorkflow()->getId() !== $workflow->getId()) { + return $this->json(['Success'=>false,'ErrorCode'=>404,'ErrorMessage'=>'آیتم یافت نشد','Result'=>null], 404); + } + $entityManager->remove($item); + $entityManager->flush(); + return $this->json(['Success'=>true,'ErrorCode'=>0,'ErrorMessage'=>'','Result'=>true]); + } + #[Route('/api/import-workflow/{id}/update', name: 'api_import_workflow_update', methods: ['PUT'])] public function update( string $id, @@ -497,7 +1055,7 @@ class ImportWorkflowController extends AbstractController $ticket->setType('input'); $ticket->setTypeString('ورود از واردات'); $ticket->setDes('ورود از پرونده واردات #' . $workflow->getCode()); - $ticket->setStatus('in_progress'); + // $ticket->setStatus('in_progress'); $ticket->setImportWorkflowCode($workflow->getCode()); $entityManager->persist($ticket); $entityManager->flush(); @@ -513,4 +1071,41 @@ class ImportWorkflowController extends AbstractController ] ]); } + + private function jalaliToGregorian(?string $input): ?string + { + if (!$input) { + return null; + } + + $s = trim($input); + + // پشتیبانی از جلالی با جداکننده '/' + if (preg_match('/^(\d{4})\/(\d{2})\/(\d{2})$/', $s, $m)) { + $y = (int)$m[1]; $mo = (int)$m[2]; $d = (int)$m[3]; + try { + $g = CalendarUtils::toGregorian($y, $mo, $d); + return sprintf('%04d-%02d-%02d', $g[0], $g[1], $g[2]); + } catch (\Throwable $e) { + return $s; + } + } + + // پشتیبانی از جلالی با جداکننده '-' + if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $s, $m)) { + $y = (int)$m[1]; $mo = (int)$m[2]; $d = (int)$m[3]; + // اگر سال جلالی باشد (مثلاً 1400) آن را تبدیل کن، در غیر اینصورت همان مقدار را برگردان + if ($y < 1700) { + try { + $g = CalendarUtils::toGregorian($y, $mo, $d); + return sprintf('%04d-%02d-%02d', $g[0], $g[1], $g[2]); + } catch (\Throwable $e) { + return $s; + } + } + return $s; // احتمالاً میلادی است + } + + return $s; + } } diff --git a/hesabixCore/src/Controller/Plugins/PlugWarrantyController.php b/hesabixCore/src/Controller/Plugins/PlugWarrantyController.php index 96aca7e..debc191 100644 --- a/hesabixCore/src/Controller/Plugins/PlugWarrantyController.php +++ b/hesabixCore/src/Controller/Plugins/PlugWarrantyController.php @@ -2,6 +2,8 @@ namespace App\Controller\Plugins; +use App\Repository\PlugWarrantySerialRepository; +use Morilog\Jalali\CalendarUtils; use Symfony\Component\Routing\Annotation\Route; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; @@ -10,6 +12,7 @@ use Doctrine\ORM\EntityManagerInterface; use App\Entity\PlugWarrantySerial; use App\Entity\Commodity; use App\Entity\HesabdariDoc; +use App\Entity\Business; use App\Service\Access; use App\Service\Log; use App\Service\Provider; @@ -19,57 +22,98 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; use App\Service\PluginService; use App\Service\SMS; use App\Service\registryMGR; +use Symfony\Component\Validator\Constraints as Assert; class PlugWarrantyController extends AbstractController { private $entityManager; - public function __construct(EntityManagerInterface $entityManager) + private function toDate(?string $s): ?\DateTimeImmutable + { + if (!$s) + return null; + try { + return new \DateTimeImmutable($s); + } catch (\Throwable $e) { + return null; + } + } + + private function expiredFlag(?\DateTimeImmutable $end): bool + { + return $end !== null && $end < new \DateTimeImmutable('today'); + } + + public function __construct(EntityManagerInterface $entityManager, private PlugWarrantySerialRepository $repository) { $this->entityManager = $entityManager; } #[Route('/api/plugins/warranty/assign/request', name: 'plugin_warranty_assign_request', methods: ['POST'])] - public function plugin_warranty_assign_request(Request $request, EntityManagerInterface $entityManager, Access $access, PluginService $pluginService): JsonResponse + public function plugin_warranty_assign_request(Request $request, EntityManagerInterface $em, Access $access, PluginService $pluginService): JsonResponse { + $acc = $access->hasRole('plugWarrantyManager'); + if (!$acc) + throw $this->createAccessDeniedException(); + if (!$pluginService->isActive('warranty', $acc['bid'])) + return $this->json(['success' => false, 'message' => 'افزونه گارانتی فعال نیست'], 403); + + $p = json_decode($request->getContent() ?: '{}', true); + $items = $p['items'] ?? []; // [{commodity_id, count, document_id, document_item_id}] + if (!is_array($items) || empty($items)) + return $this->json(['success' => false, 'message' => 'پارامترهای نامعتبر'], 400); + + $em->beginTransaction(); try { - $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) - throw $this->createAccessDeniedException(); + $business = $em->getRepository(Business::class)->find($acc['bid']); + if (!$business) + return $this->json(['success' => false, 'message' => 'کسب‌وکار یافت نشد'], 404); - if (!$pluginService->isActive('warranty', $acc['bid'])) { - return $this->json(['success' => false, 'message' => 'افزونه گارانتی فعال نیست'], 403); - } + $result = []; - $params = json_decode($request->getContent() ?: '{}', true); - // ورودی: commodity_id و count برای هر آیتم حواله - if (!isset($params['items']) || !is_array($params['items'])) { - return $this->json(['error' => 'پارامترهای نامعتبر'], 400); - } + foreach ($items as $i) { + $commodityId = (int) ($i['commodity_id'] ?? 0); + $qty = (int) ($i['count'] ?? 0); + $documentId = isset($i['document_id']) ? (int) $i['document_id'] : null; - $assignmentCodes = []; - foreach ($params['items'] as $index => $item) { - $commodityId = $item['commodity_id'] ?? null; - $count = (int)($item['count'] ?? 0); - if (!$commodityId || $count <= 0) { continue; } + if ($commodityId <= 0 || $qty <= 0) + continue; - // تولید کد یکتای تخصیص (تصادفی) - $assignmentCode = bin2hex(random_bytes(10)); - $assignmentCodes[] = [ + // انتخاب N کد available با قفل + $ids = $em->getConnection()->executeQuery( + "SELECT id FROM plug_warranty_serial + WHERE commodity_id = :cid AND status = 'available' + ORDER BY id ASC + FOR UPDATE SKIP LOCKED + LIMIT :lim", + ['cid' => $commodityId, 'lim' => $qty], + ['cid' => \PDO::PARAM_INT, 'lim' => \PDO::PARAM_INT] + )->fetchFirstColumn(); + + if (count($ids) < $qty) { + $em->rollBack(); + return $this->json(['success' => false, 'message' => 'کد گارانتی کافی برای یکی از اقلام موجود نیست'], 409); + } + + $now = new \DateTimeImmutable(); + $em->createQuery('UPDATE App\Entity\PlugWarrantySerial s SET s.status = :st, s.allocatedToDocumentId = :doc, s.allocatedAt = :at WHERE s.id IN (:ids)') + ->setParameter('st', PlugWarrantySerial::STATUS_ALLOCATED) + ->setParameter('doc', $documentId) + ->setParameter('at', $now) + ->setParameter('ids', $ids) + ->execute(); + + $result[] = [ 'commodity_id' => $commodityId, - 'count' => $count, - 'assignmentCode' => $assignmentCode + 'allocated' => $ids ]; } - return $this->json([ - 'success' => true, - 'codes' => $assignmentCodes - ]); - } catch (\Exception $e) { - return $this->json([ - 'error' => $e->getMessage() - ], 500); + $em->commit(); + return $this->json(['success' => true, 'allocated' => $result]); + } catch (\Throwable $e) { + $em->rollBack(); + return $this->json(['success' => false, 'message' => $e->getMessage()], 500); } } @@ -78,7 +122,7 @@ class PlugWarrantyController extends AbstractController { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); if (!$pluginService->isActive('warranty', $acc['bid'])) { @@ -87,11 +131,13 @@ class PlugWarrantyController extends AbstractController $params = json_decode($request->getContent() ?: '{}', true); $serialNumber = $params['serialNumber'] ?? null; - $assignmentCode = $params['assignmentCode'] ?? null; - $commodityId = $params['commodity_id'] ?? null; // نوع کالای مورد انتظار - $ticketCode = $params['ticketCode'] ?? null; // اختیاری برای ثبت استفاده + $commodityId = isset($params['commodity_id']) ? (int) $params['commodity_id'] : null; + $documentId = isset($params['document_id']) ? (int) $params['document_id'] : null; + $documentItemId = isset($params['document_item_id']) ? (int) $params['document_item_id'] : 0; + $physicalBoxBarcode = $params['physicalBoxBarcode'] ?? ''; + $productTypeCode = $params['productTypeCode'] ?? ''; - if (!$serialNumber || !$assignmentCode || !$commodityId) { + if (!$serialNumber || !$commodityId || !$documentItemId) { return $this->json(['success' => false, 'message' => 'پارامترهای ناقص'], 400); } @@ -99,30 +145,30 @@ class PlugWarrantyController extends AbstractController /** @var PlugWarrantySerial|null $serial */ $serial = $repo->findOneBy([ 'serialNumber' => $serialNumber, - 'bid' => $acc['bid'] + 'business' => $acc['bid'] ]); if (!$serial) { return $this->json(['success' => false, 'message' => 'کد گارانتی یافت نشد']); } - if ($serial->isUsed()) { - return $this->json(['success' => false, 'message' => 'این سریال قبلاً استفاده شده است']); - } - - if (!$serial->getCommodity() || $serial->getCommodity()->getId() !== (int)$commodityId) { + if ($serial->getCommodity()?->getId() !== $commodityId) { return $this->json(['success' => false, 'message' => 'مغایرت نوع محصول']); } - // در این نسخه، صرفاً صحت را تایید می‌کنیم و در صورت ارائه ticketCode، وضعیت را مصرف‌شده می‌کنیم - if ($ticketCode) { - $serial->setUsed(true); - $serial->setUsedAt(date('Y-m-d H:i:s')); - $serial->setUsedTicketCode((string)$ticketCode); - $entityManager->persist($serial); - $entityManager->flush(); + if (!in_array($serial->getStatus(), [PlugWarrantySerial::STATUS_ALLOCATED, PlugWarrantySerial::STATUS_VERIFIED], true)) { + return $this->json(['success' => false, 'message' => 'وضعیت کد برای اسکن معتبر نیست'], 409); } + if ($documentId !== null && $serial->getAllocatedToDocumentId() !== $documentId) { + return $this->json(['success' => false, 'message' => 'کد به سند دیگری تخصیص داده شده'], 409); + } + + // در این نسخه، صرفاً صحت را تایید می‌کنیم و وضعیت را verified می‌کنیم + $serial->setStatus(PlugWarrantySerial::STATUS_VERIFIED); + $entityManager->persist($serial); + $entityManager->flush(); + return $this->json(['success' => true, 'message' => 'تأیید شد']); } catch (\Exception $e) { return $this->json([ @@ -152,26 +198,23 @@ class PlugWarrantyController extends AbstractController return $this->json(['success' => false, 'message' => 'فاکتور یافت نشد'], 404); } - // سریال‌هایی که در حواله‌های مرتبط با این فاکتور استفاده شده‌اند + // سریال‌هایی که به این فاکتور تخصیص داده شده‌اند $serialRepo = $entityManager->getRepository(PlugWarrantySerial::class); $serials = $serialRepo->createQueryBuilder('s') - ->where('s.bid = :bid') - ->andWhere('s.used = true') - ->andWhere('s.usedTicketCode IN ( - SELECT t.code FROM App\\Entity\\StoreroomTicket t WHERE t.doc = :doc - )') + ->where('s.business = :bid') + ->andWhere('s.allocatedToDocumentId = :docId') ->setParameter('bid', $acc['bid']) - ->setParameter('doc', $doc) + ->setParameter('docId', $doc->getId()) ->getQuery() ->getResult(); - $result = array_map(function(PlugWarrantySerial $s) { + $result = array_map(function (PlugWarrantySerial $s) { return [ 'serialNumber' => $s->getSerialNumber(), 'commodity' => $s->getCommodity() ? $s->getCommodity()->getName() : null, - 'usedAt' => $s->getUsedAt(), - 'usedTicketCode' => $s->getUsedTicketCode(), - 'status' => $s->isUsed() ? 'used' : 'free' + 'status' => $s->getStatus(), + 'warrantyEndDate' => $s->getWarrantyEndDate()?->format('Y-m-d'), + 'expired' => $this->expiredFlag($s->getWarrantyEndDate()) ]; }, $serials); @@ -232,7 +275,7 @@ class PlugWarrantyController extends AbstractController { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); /** @var UploadedFile|null $file */ @@ -252,26 +295,33 @@ class PlugWarrantyController extends AbstractController // ردیف اول را عنوان فرض می‌کنیم $isHeader = true; foreach ($rows as $rowIndex => $row) { - if ($isHeader) { $isHeader = false; continue; } - $serialNumber = trim((string)($row['A'] ?? '')); - $commodity = trim((string)($row['B'] ?? '')); - $description = trim((string)($row['C'] ?? '')); - $warrantyStartDate = trim((string)($row['D'] ?? '')); - $warrantyEndDate = trim((string)($row['E'] ?? '')); - $status = trim((string)($row['F'] ?? 'active')); + if ($isHeader) { + $isHeader = false; + continue; + } + $serialNumber = trim((string) ($row['A'] ?? '')); + $commodity = trim((string) ($row['B'] ?? '')); + $description = trim((string) ($row['C'] ?? '')); + $warrantyStartDate = trim((string) ($row['D'] ?? '')); + $warrantyEndDate = trim((string) ($row['E'] ?? '')); + $status = trim((string) ($row['F'] ?? 'available')); if ($serialNumber === '' || $commodity === '') { $errors[] = 'ردیف ' . ($rowIndex) . ': شماره سریال یا محصول خالی است'; continue; } + // نگاشت وضعیت‌های قدیمی به جدید + $map = ['active' => 'available', 'inactive' => 'void', 'expired' => 'void']; + $status = $map[$status] ?? 'available'; + $preview[] = [ 'serialNumber' => $serialNumber, 'commodity' => $commodity, 'description' => $description, 'warrantyStartDate' => $warrantyStartDate, 'warrantyEndDate' => $warrantyEndDate, - 'status' => $status ?: 'active' + 'status' => $status ]; } @@ -291,7 +341,7 @@ class PlugWarrantyController extends AbstractController { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); /** @var UploadedFile|null $file */ @@ -307,25 +357,32 @@ class PlugWarrantyController extends AbstractController $serials = []; $isHeader = true; foreach ($rows as $row) { - if ($isHeader) { $isHeader = false; continue; } - $serialNumber = trim((string)($row['A'] ?? '')); - $commodity = trim((string)($row['B'] ?? '')); - $description = trim((string)($row['C'] ?? '')); - $warrantyStartDate = trim((string)($row['D'] ?? '')); - $warrantyEndDate = trim((string)($row['E'] ?? '')); - $status = trim((string)($row['F'] ?? 'active')); + if ($isHeader) { + $isHeader = false; + continue; + } + $serialNumber = trim((string) ($row['A'] ?? '')); + $commodity = trim((string) ($row['B'] ?? '')); + $description = trim((string) ($row['C'] ?? '')); + $warrantyStartDate = trim((string) ($row['D'] ?? '')); + $warrantyEndDate = trim((string) ($row['E'] ?? '')); + $status = trim((string) ($row['F'] ?? 'available')); if ($serialNumber === '' || $commodity === '') { continue; } - // توجه: ستون محصول می‌تواند ID یا CODE یا NAME باشد. اینجا صرفاً پاس‌ترو می‌کنیم و در مرحله نهایی، فرانت با ID نگاشت می‌کند. + + // نگاشت وضعیت‌های قدیمی به جدید + $map = ['active' => 'available', 'inactive' => 'void', 'expired' => 'void']; + $status = $map[$status] ?? 'available'; + $serials[] = [ 'serialNumber' => $serialNumber, 'commodity' => $commodity, 'description' => $description, 'warrantyStartDate' => $warrantyStartDate, 'warrantyEndDate' => $warrantyEndDate, - 'status' => $status ?: 'active' + 'status' => $status ]; } @@ -339,64 +396,20 @@ class PlugWarrantyController extends AbstractController } } - private function updateExpiredSerials(EntityManagerInterface $entityManager, $businessId): void - { - $repository = $entityManager->getRepository(PlugWarrantySerial::class); - $jdate = new Jdate(); - $today = $jdate->GetTodayDate(); - - $expiredSerials = $repository->createQueryBuilder('s') - ->where('s.bid = :businessId') - ->andWhere('s.status = :status') - ->andWhere('s.warrantyEndDate IS NOT NULL') - ->andWhere('s.warrantyEndDate < :today') - ->setParameter('businessId', $businessId) - ->setParameter('status', 'active') - ->setParameter('today', $today) - ->getQuery() - ->getResult(); - - foreach ($expiredSerials as $serial) { - $serial->setStatus('expired'); - } - - if (!empty($expiredSerials)) { - $entityManager->flush(); - } - } - - private function checkAndUpdateSerialStatus($serial, EntityManagerInterface $entityManager): void - { - $jdate = new Jdate(); - $today = $jdate->GetTodayDate(); - $warrantyEndDate = $serial->getWarrantyEndDate(); - - if ($serial->getStatus() === 'active' && - $warrantyEndDate && - $warrantyEndDate < $today) { - $serial->setStatus('expired'); - $entityManager->flush(); - } - } - #[Route('/api/plugins/warranty/serials', name: 'plugin_warranty_serials', methods: ['GET'])] - public function plugin_warranty_serials(EntityManagerInterface $entityManager, Access $access, Request $request): JsonResponse + public function plugin_warranty_serials(EntityManagerInterface $entityManager, PlugWarrantySerialRepository $repository, Access $access, Request $request): JsonResponse { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); - $this->updateExpiredSerials($entityManager, $acc['bid']); - $page = $request->query->get('page', 1); $limit = $request->query->get('limit', 20); $status = $request->query->get('status'); $commodityId = $request->query->get('commodity_id'); $search = $request->query->get('search'); - $repository = $entityManager->getRepository(PlugWarrantySerial::class); - if ($search) { $serials = $repository->searchSerials($acc['bid'], $search); } elseif ($status) { @@ -408,10 +421,13 @@ class PlugWarrantyController extends AbstractController } $data = []; - foreach($serials as $serial){ + foreach ($serials as $serial) { $commodity = $serial->getCommodity(); $submitter = $serial->getSubmitter(); - + $buyer = $serial->getBuyer(); + + $allocatedToDocument = $serial->getAllocatedToDocumentId() ? $entityManager->getRepository(HesabdariDoc::class)->find($serial->getAllocatedToDocumentId()) : null; + $data[] = [ 'id' => $serial->getId(), 'serialNumber' => $serial->getSerialNumber(), @@ -420,16 +436,28 @@ class PlugWarrantyController extends AbstractController 'name' => $commodity->getName(), 'code' => $commodity->getCode() ] : null, - 'dateSubmit' => $serial->getDateSubmit(), + 'dateSubmit' => $serial->getDateSubmit()->format('Y-m-d H:i:s'), 'description' => $serial->getDescription(), - 'warrantyStartDate' => $serial->getWarrantyStartDate(), - 'warrantyEndDate' => $serial->getWarrantyEndDate(), + 'warrantyStartDate' => $serial->getWarrantyStartDate()?->format('Y-m-d'), + 'warrantyEndDate' => $serial->getWarrantyEndDate()?->format('Y-m-d'), 'status' => $serial->getStatus(), + 'activation' => $serial->getActivation(), 'notes' => $serial->getNotes(), + 'expired' => $this->expiredFlag($serial->getWarrantyEndDate()), 'submitter' => $submitter ? [ 'id' => $submitter->getId(), 'name' => $submitter->getFullName() - ] : null + ] : null, + 'commoditySerial' => $serial->getCommoditySerial(), + 'buyer' => $buyer ? [ + 'id' => $buyer->getId(), + 'code' => $buyer->getCode(), + 'name' => $buyer->getName(), + 'nikename' => $buyer->getNikename(), + 'mobile' => $buyer->getMobile() + ] : null, + 'activationAt' => $serial->getActivationAt()?->format('Y-m-d H:i:s'), + 'allocatedToDocumentCode' => $allocatedToDocument ? $allocatedToDocument->getCode() : null ]; } @@ -446,19 +474,19 @@ class PlugWarrantyController extends AbstractController { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); - + $serial = $entityManager->getRepository(PlugWarrantySerial::class)->findOneBy([ 'id' => $id, - 'bid' => $acc['bid'] + 'business' => $acc['bid'] ]); - - if(!$serial) + + if (!$serial) throw $this->createNotFoundException(); - - $this->checkAndUpdateSerialStatus($serial, $entityManager); - + + $allocatedToDocument = $serial->getAllocatedToDocumentId() ? $entityManager->getRepository(HesabdariDoc::class)->find($serial->getAllocatedToDocumentId()) : null; + $data = [ 'id' => $serial->getId(), 'serialNumber' => $serial->getSerialNumber(), @@ -467,18 +495,30 @@ class PlugWarrantyController extends AbstractController 'name' => $serial->getCommodity()->getName(), 'code' => $serial->getCommodity()->getCode() ], - 'dateSubmit' => $serial->getDateSubmit(), + 'dateSubmit' => $serial->getDateSubmit()->format('Y-m-d H:i:s'), 'description' => $serial->getDescription(), - 'warrantyStartDate' => $serial->getWarrantyStartDate(), - 'warrantyEndDate' => $serial->getWarrantyEndDate(), + 'warrantyStartDate' => $this->jalaliToGregorian($serial->getWarrantyStartDate()?->format('Y-m-d')), + 'warrantyEndDate' => $this->jalaliToGregorian($serial->getWarrantyEndDate()?->format('Y-m-d')), 'status' => $serial->getStatus(), + 'activation' => $serial->getActivation(), 'notes' => $serial->getNotes(), + 'expired' => $this->expiredFlag($serial->getWarrantyEndDate()), 'submitter' => [ 'id' => $serial->getSubmitter()->getId(), 'name' => $serial->getSubmitter()->getFullName() - ] + ], + 'commoditySerial' => $serial->getCommoditySerial(), + 'buyer' => $serial->getBuyer() ? [ + 'id' => $serial->getBuyer()->getId(), + 'code' => $serial->getBuyer()->getCode(), + 'name' => $serial->getBuyer()->getName(), + 'nikename' => $serial->getBuyer()->getNikename(), + 'mobile' => $serial->getBuyer()->getMobile() + ] : null, + 'activationAt' => $serial->getActivationAt()?->format('Y-m-d H:i:s'), + 'allocatedToDocumentCode' => $allocatedToDocument ? $allocatedToDocument->getCode() : null ]; - + return $this->json($data); } catch (\Exception $e) { return $this->json([ @@ -488,11 +528,11 @@ class PlugWarrantyController extends AbstractController } #[Route('/api/plugins/warranty/serials/add', name: 'plugin_warranty_serial_add', methods: ['POST'])] - public function plugin_warranty_serial_add(Request $request, EntityManagerInterface $entityManager, Access $access, Log $log): JsonResponse + public function plugin_warranty_serial_add(Request $request, EntityManagerInterface $entityManager, PlugWarrantySerialRepository $repository, Access $access, Log $log): JsonResponse { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); $params = []; @@ -500,12 +540,10 @@ class PlugWarrantyController extends AbstractController $params = json_decode($content, true); } - if(!array_key_exists('serialNumber', $params) || !array_key_exists('commodity_id', $params)) + if (!array_key_exists('serialNumber', $params) || !array_key_exists('commodity_id', $params)) throw $this->createAccessDeniedException('پارامترهای ناقص'); - $repository = $entityManager->getRepository(PlugWarrantySerial::class); - - if($repository->isSerialNumberExists($params['serialNumber'], $acc['bid'])) { + if ($repository->isSerialNumberExists($params['serialNumber'], $acc['bid'])) { return $this->json(['error' => 'شماره سریال تکراری است'], 400); } @@ -513,21 +551,27 @@ class PlugWarrantyController extends AbstractController 'id' => $params['commodity_id'], 'bid' => $acc['bid'] ]); - - if(!$commodity) { + + if (!$commodity) { return $this->json(['error' => 'محصول یافت نشد'], 400); } + $business = $entityManager->getRepository(Business::class)->find($acc['bid']); + if (!$business) { + return $this->json(['error' => 'کسب‌وکار یافت نشد'], 404); + } + $serial = new PlugWarrantySerial(); $serial->setSerialNumber($params['serialNumber']); $serial->setCommodity($commodity); - $serial->setBid($acc['bid']); + $serial->setBusiness($business); $serial->setSubmitter($this->getUser()); $serial->setDescription($params['description'] ?? null); - $serial->setWarrantyStartDate($params['warrantyStartDate'] ?? null); - $serial->setWarrantyEndDate($params['warrantyEndDate'] ?? null); - $serial->setStatus($params['status'] ?? 'active'); + $serial->setWarrantyStartDate($this->toDate($this->jalaliToGregorian($params['warrantyStartDate']) ?? null)); + $serial->setWarrantyEndDate($this->toDate($this->jalaliToGregorian($params['warrantyEndDate']) ?? null)); + $serial->setStatus($params['status'] ?? PlugWarrantySerial::STATUS_AVAILABLE); $serial->setNotes($params['notes'] ?? null); + $serial->setActivation('deactive'); $entityManager->persist($serial); $entityManager->flush(); @@ -552,19 +596,19 @@ class PlugWarrantyController extends AbstractController } #[Route('/api/plugins/warranty/serials/edit/{id}', name: 'plugin_warranty_serial_edit', methods: ['POST'])] - public function plugin_warranty_serial_edit(Request $request, EntityManagerInterface $entityManager, Access $access, $id, Log $log): JsonResponse + public function plugin_warranty_serial_edit(Request $request, EntityManagerInterface $entityManager, PlugWarrantySerialRepository $repository, Access $access, $id, Log $log): JsonResponse { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); $serial = $entityManager->getRepository(PlugWarrantySerial::class)->findOneBy([ 'id' => $id, - 'bid' => $acc['bid'] + 'business' => $acc['bid'] ]); - - if(!$serial) + + if (!$serial) throw $this->createNotFoundException(); $params = []; @@ -572,11 +616,10 @@ class PlugWarrantyController extends AbstractController $params = json_decode($content, true); } - if(array_key_exists('serialNumber', $params)) { - $repository = $entityManager->getRepository(PlugWarrantySerial::class); + if (array_key_exists('serialNumber', $params)) { $existingSerial = $repository->createQueryBuilder('p') ->andWhere('p.serialNumber = :serialNumber') - ->andWhere('p.bid = :bid') + ->andWhere('p.business = :bid') ->andWhere('p.id != :id') ->setParameter('serialNumber', $params['serialNumber']) ->setParameter('bid', $acc['bid']) @@ -584,46 +627,50 @@ class PlugWarrantyController extends AbstractController ->getQuery() ->getOneOrNullResult(); - if($existingSerial) { + if ($existingSerial) { return $this->json(['error' => 'شماره سریال تکراری است'], 400); } - + $serial->setSerialNumber($params['serialNumber']); } - if(array_key_exists('commodity_id', $params)) { + if (array_key_exists('commodity_id', $params)) { $commodity = $entityManager->getRepository(Commodity::class)->findOneBy([ 'id' => $params['commodity_id'], 'bid' => $acc['bid'] ]); - - if(!$commodity) { + + if (!$commodity) { return $this->json(['error' => 'محصول یافت نشد'], 400); } - + $serial->setCommodity($commodity); } - if(array_key_exists('description', $params)) { + if (array_key_exists('description', $params)) { $serial->setDescription($params['description']); } - if(array_key_exists('warrantyStartDate', $params)) { - $serial->setWarrantyStartDate($params['warrantyStartDate']); + if (array_key_exists('warrantyStartDate', $params)) { + $serial->setWarrantyStartDate($this->toDate($this->jalaliToGregorian($params['warrantyStartDate']))); } - if(array_key_exists('warrantyEndDate', $params)) { - $serial->setWarrantyEndDate($params['warrantyEndDate']); + if (array_key_exists('warrantyEndDate', $params)) { + $serial->setWarrantyEndDate($this->toDate($this->jalaliToGregorian($params['warrantyEndDate']))); } - if(array_key_exists('status', $params)) { + if (array_key_exists('status', $params)) { $serial->setStatus($params['status']); } - if(array_key_exists('notes', $params)) { + if (array_key_exists('notes', $params)) { $serial->setNotes($params['notes']); } + if (array_key_exists('activation', $params)) { + $serial->setActivation($params['activation']); + } + $entityManager->flush(); $log->insert( @@ -645,19 +692,19 @@ class PlugWarrantyController extends AbstractController } #[Route('/api/plugins/warranty/serials/{id}', name: 'plugin_warranty_serial_delete', methods: ['DELETE'])] - public function plugin_warranty_serial_delete(EntityManagerInterface $entityManager, Access $access, $id, Log $log): JsonResponse + public function plugin_warranty_serial_delete(EntityManagerInterface $entityManager, PlugWarrantySerialRepository $repository, Access $access, $id, Log $log): JsonResponse { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); $serial = $entityManager->getRepository(PlugWarrantySerial::class)->findOneBy([ 'id' => $id, - 'bid' => $acc['bid'] + 'business' => $acc['bid'] ]); - - if(!$serial) + + if (!$serial) throw $this->createNotFoundException(); $serialNumber = $serial->getSerialNumber(); @@ -683,11 +730,11 @@ class PlugWarrantyController extends AbstractController } #[Route('/api/plugins/warranty/serials/bulk-import', name: 'plugin_warranty_serial_bulk_import', methods: ['POST'])] - public function plugin_warranty_serial_bulk_import(Request $request, EntityManagerInterface $entityManager, Access $access, Log $log): JsonResponse + public function plugin_warranty_serial_bulk_import(Request $request, EntityManagerInterface $entityManager, PlugWarrantySerialRepository $repository, Access $access, Log $log): JsonResponse { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); $params = []; @@ -695,36 +742,40 @@ class PlugWarrantyController extends AbstractController $params = json_decode($content, true); } - if(!array_key_exists('serials', $params) || !is_array($params['serials'])) + if (!array_key_exists('serials', $params) || !is_array($params['serials'])) throw $this->createAccessDeniedException('داده‌های نامعتبر'); - $repository = $entityManager->getRepository(PlugWarrantySerial::class); $commodityRepo = $entityManager->getRepository(Commodity::class); - + $business = $entityManager->getRepository(Business::class)->find($acc['bid']); + + if (!$business) { + return $this->json(['error' => 'کسب‌وکار یافت نشد'], 404); + } + $successCount = 0; $errorCount = 0; $errors = []; - foreach($params['serials'] as $index => $serialData) { + foreach ($params['serials'] as $index => $serialData) { try { - if(!array_key_exists('serialNumber', $serialData) || !array_key_exists('commodity_id', $serialData)) { + if (!array_key_exists('serialNumber', $serialData) || !array_key_exists('commodity_code', $serialData)) { $errors[] = "ردیف " . ($index + 1) . ": پارامترهای ناقص"; $errorCount++; continue; } - if($repository->isSerialNumberExists($serialData['serialNumber'], $acc['bid'])) { + if ($repository->isSerialNumberExists($serialData['serialNumber'], $acc['bid'])) { $errors[] = "ردیف " . ($index + 1) . ": شماره سریال تکراری است"; $errorCount++; continue; } $commodity = $commodityRepo->findOneBy([ - 'id' => $serialData['commodity_id'], + 'code' => $serialData['commodity_code'], 'bid' => $acc['bid'] ]); - - if(!$commodity) { + + if (!$commodity) { $errors[] = "ردیف " . ($index + 1) . ": محصول یافت نشد"; $errorCount++; continue; @@ -733,12 +784,17 @@ class PlugWarrantyController extends AbstractController $serial = new PlugWarrantySerial(); $serial->setSerialNumber($serialData['serialNumber']); $serial->setCommodity($commodity); - $serial->setBid($acc['bid']); + $serial->setBusiness($business); $serial->setSubmitter($this->getUser()); $serial->setDescription($serialData['description'] ?? null); - $serial->setWarrantyStartDate($serialData['warrantyStartDate'] ?? null); - $serial->setWarrantyEndDate($serialData['warrantyEndDate'] ?? null); - $serial->setStatus($serialData['status'] ?? 'active'); + $serial->setWarrantyStartDate($this->toDate($this->jalaliToGregorian($serialData['warrantyStartDate']) ?? null)); + $serial->setWarrantyEndDate($this->toDate($this->jalaliToGregorian($serialData['warrantyEndDate']) ?? null)); + $serial->setActivation('deactive'); + + $incomingStatus = $serialData['status'] ?? null; + $map = ['active' => 'available', 'inactive' => 'void', 'expired' => 'void']; + $serial->setStatus($map[$incomingStatus] ?? PlugWarrantySerial::STATUS_AVAILABLE); + $serial->setNotes($serialData['notes'] ?? null); $entityManager->persist($serial); @@ -774,48 +830,42 @@ class PlugWarrantyController extends AbstractController } #[Route('/api/plugins/warranty/stats', name: 'plugin_warranty_stats', methods: ['GET'])] - public function plugin_warranty_stats(EntityManagerInterface $entityManager, Access $access): JsonResponse + public function plugin_warranty_stats(EntityManagerInterface $entityManager, PlugWarrantySerialRepository $repository, Access $access): JsonResponse { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); - $this->updateExpiredSerials($entityManager, $acc['bid']); - - $repository = $entityManager->getRepository(PlugWarrantySerial::class); - $allSerials = $repository->createQueryBuilder('p') - ->andWhere('p.bid = :bid') + ->andWhere('p.business = :bid') ->setParameter('bid', $acc['bid']) ->getQuery() ->getResult(); - + $totalSerials = count($allSerials); - $activeSerials = 0; - $inactiveSerials = 0; - $expiredSerials = 0; - + $byStatus = [ + PlugWarrantySerial::STATUS_AVAILABLE => 0, + PlugWarrantySerial::STATUS_ALLOCATED => 0, + PlugWarrantySerial::STATUS_VERIFIED => 0, + PlugWarrantySerial::STATUS_BOUND => 0, + PlugWarrantySerial::STATUS_CONSUMED => 0, + PlugWarrantySerial::STATUS_VOID => 0, + ]; + $expired = 0; + foreach ($allSerials as $serial) { - $status = $serial->getStatus(); - switch ($status) { - case 'active': - $activeSerials++; - break; - case 'inactive': - $inactiveSerials++; - break; - case 'expired': - $expiredSerials++; - break; - } + $st = $serial->getStatus(); + if (isset($byStatus[$st])) + $byStatus[$st]++; + if ($this->expiredFlag($serial->getWarrantyEndDate())) + $expired++; } return $this->json([ 'totalSerials' => $totalSerials, - 'activeSerials' => $activeSerials, - 'inactiveSerials' => $inactiveSerials, - 'expiredSerials' => $expiredSerials, + 'byStatus' => $byStatus, + 'expiredFlagCount' => $expired ]); } catch (\Exception $e) { return $this->json([ @@ -824,49 +874,141 @@ class PlugWarrantyController extends AbstractController } } - #[Route('/api/plugins/warranty/serials/update-expired', name: 'plugin_warranty_update_expired', methods: ['POST'])] - public function plugin_warranty_update_expired(EntityManagerInterface $entityManager, Access $access): JsonResponse + #[Route('/api/plugins/warranty/serials/bulk-delete', name: 'plugin_warranty_serial_bulk_delete', methods: ['POST'])] + public function plugin_warranty_serial_bulk_delete(Request $request, EntityManagerInterface $entityManager, PlugWarrantySerialRepository $repository, Access $access, Log $log): JsonResponse { try { $acc = $access->hasRole('plugWarrantyManager'); - if(!$acc) + if (!$acc) throw $this->createAccessDeniedException(); - $repository = $entityManager->getRepository(PlugWarrantySerial::class); - $jdate = new Jdate(); - $today = $jdate->GetTodayDate(); - - $expiredSerials = $repository->createQueryBuilder('s') - ->where('s.bid = :businessId') - ->andWhere('s.status = :status') - ->andWhere('s.warrantyEndDate IS NOT NULL') - ->andWhere('s.warrantyEndDate < :today') - ->setParameter('businessId', $acc['bid']) - ->setParameter('status', 'active') - ->setParameter('today', $today) - ->getQuery() - ->getResult(); - - $updatedCount = 0; - - foreach ($expiredSerials as $serial) { - $serial->setStatus('expired'); - $updatedCount++; + $params = json_decode($request->getContent() ?: '{}', true); + $ids = $params['ids'] ?? []; + + if (!is_array($ids) || empty($ids)) { + return $this->json([ + 'success' => false, + 'message' => 'هیچ آیتمی برای حذف انتخاب نشده است' + ], 400); } - - if ($updatedCount > 0) { + + $deletedCount = 0; + $errors = []; + + foreach ($ids as $id) { + try { + $serial = $repository->findOneBy([ + 'id' => $id, + 'business' => $acc['bid'] + ]); + + if ($serial) { + $serialNumber = $serial->getSerialNumber(); + $entityManager->remove($serial); + $deletedCount++; + + $log->insert( + 'گارانتی', + 'حذف گروهی سریال: ' . $serialNumber, + $this->getUser(), + $acc['bid'] + ); + } + } catch (\Exception $e) { + $errors[] = "خطا در حذف سریال با ID {$id}: " . $e->getMessage(); + } + } + + if ($deletedCount > 0) { $entityManager->flush(); } return $this->json([ 'success' => true, - 'message' => 'به‌روزرسانی وضعیت سریال‌های منقضی شده تکمیل شد', - 'updatedCount' => $updatedCount + 'message' => "{$deletedCount} سریال با موفقیت حذف شد", + 'deletedCount' => $deletedCount, + 'errors' => $errors ]); } catch (\Exception $e) { return $this->json([ + 'success' => false, 'error' => $e->getMessage() ], 500); } } -} \ No newline at end of file + + #[Route('/api/plugins/warranty/settings/get', name: 'plugin_warranty_settings_get', methods: ['GET'])] + public function plugin_warranty_settings_get(Access $access, registryMGR $registryMGR): JsonResponse + { + $acc = $access->hasRole('plugWarrantyManager'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $require = filter_var($registryMGR->get('warranty', 'requireWarrantyOnDelivery'), FILTER_VALIDATE_BOOLEAN); + $grace = (int) ($registryMGR->get('warranty', 'activationGraceDays') ?? 7); + $match = filter_var($registryMGR->get('warranty', 'matchWarrantyToSerial'), FILTER_VALIDATE_BOOLEAN); + return $this->json([ + 'requireWarrantyOnDelivery' => (bool) $require, + 'activationGraceDays' => max(0, $grace), + 'matchWarrantyToSerial' => (bool) $match + ]); + } + + #[Route('/api/plugins/warranty/settings/save', name: 'plugin_warranty_settings_save', methods: ['POST'])] + public function plugin_warranty_settings_save(Request $request, Access $access, registryMGR $registryMGR): JsonResponse + { + $acc = $access->hasRole('plugWarrantyManager'); + if (!$acc) { + throw $this->createAccessDeniedException(); + } + + $params = json_decode($request->getContent() ?: '{}', true); + $require = isset($params['requireWarrantyOnDelivery']) && ($params['requireWarrantyOnDelivery'] === true || $params['requireWarrantyOnDelivery'] === '1' || $params['requireWarrantyOnDelivery'] === 1 || $params['requireWarrantyOnDelivery'] === 'true'); + $graceDays = isset($params['activationGraceDays']) ? (int) $params['activationGraceDays'] : 7; + if ($graceDays < 0) { $graceDays = 0; } + $match = isset($params['matchWarrantyToSerial']) && ($params['matchWarrantyToSerial'] === true || $params['matchWarrantyToSerial'] === '1' || $params['matchWarrantyToSerial'] === 1 || $params['matchWarrantyToSerial'] === 'true'); + + $registryMGR->update('warranty', 'requireWarrantyOnDelivery', $require ? '1' : '0'); + $registryMGR->update('warranty', 'activationGraceDays', (string) $graceDays); + $registryMGR->update('warranty', 'matchWarrantyToSerial', $match ? '1' : '0'); + return $this->json(['success' => true]); + } + + private function jalaliToGregorian(?string $input): ?string + { + if (!$input) { + return null; + } + + $s = trim($input); + + // پشتیبانی از جلالی با جداکننده '/' + if (preg_match('/^(\d{4})\/(\d{2})\/(\d{2})$/', $s, $m)) { + $y = (int)$m[1]; $mo = (int)$m[2]; $d = (int)$m[3]; + try { + $g = CalendarUtils::toGregorian($y, $mo, $d); + return sprintf('%04d-%02d-%02d', $g[0], $g[1], $g[2]); + } catch (\Throwable $e) { + return $s; + } + } + + // پشتیبانی از جلالی با جداکننده '-' + if (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $s, $m)) { + $y = (int)$m[1]; $mo = (int)$m[2]; $d = (int)$m[3]; + // اگر سال جلالی باشد (مثلاً 1400) آن را تبدیل کن، در غیر اینصورت همان مقدار را برگردان + if ($y < 1700) { + try { + $g = CalendarUtils::toGregorian($y, $mo, $d); + return sprintf('%04d-%02d-%02d', $g[0], $g[1], $g[2]); + } catch (\Throwable $e) { + return $s; + } + } + return $s; // احتمالاً میلادی است + } + + return $s; + } +} \ No newline at end of file diff --git a/hesabixCore/src/Controller/PublicController.php b/hesabixCore/src/Controller/PublicController.php new file mode 100644 index 0000000..04b87a8 --- /dev/null +++ b/hesabixCore/src/Controller/PublicController.php @@ -0,0 +1,400 @@ +isActive('warranty', $business)) { + return $this->json([ + 'success' => false, + 'message' => 'پلاگین گارانتی برای این کسب و کار فعال نیست', + 'error' => 'PLUGIN_NOT_ACTIVE' + ], 404); + } + return null; + } + #[Route('/api/public/{businessId}/warranty/check/{code}', name: 'api_public_warranty_check', methods: ['GET'])] + public function checkWarrantyCode(string $businessId, string $code, EntityManagerInterface $entityManager, PluginService $pluginService, registryMGR $registryMGR): JsonResponse + { + // Validate input + if (empty($code)) { + return $this->json([ + 'success' => false, + 'message' => 'کد گارانتی وارد نشده است' + ], 400); + } + + // Find business + $business = $entityManager->getRepository(Business::class)->find($businessId); + if (!$business) { + return $this->json([ + 'success' => false, + 'message' => 'کسب و کار مورد نظر یافت نشد' + ], 404); + } + + // Check if warranty plugin is active + $pluginCheck = $this->checkWarrantyPluginActive($business, $pluginService); + if ($pluginCheck) { + return $pluginCheck; + } + + // Find warranty serial + $warrantySerial = $entityManager->getRepository(PlugWarrantySerial::class)->findOneBy([ + 'business' => $business, + 'serialNumber' => $code + ]); + + if (!$warrantySerial) { + return $this->json([ + 'success' => false, + 'message' => 'کد گارانتی نامعتبر است یا متعلق به این کسب و کار نیست' + ], 404); + } + + // Check if already activated (used) + if (!$warrantySerial->isUsed()) { + return $this->json([ + 'success' => false, + 'message' => 'این گارانتی ثبت نشده است', + ], 400); + } + + // if ($warrantySerial->getActivation() === 'active') { + // return $this->json([ + // 'success' => false, + // 'message' => 'این گارانتی قبلاً فعال شده است', + // ], 400); + // } + + $activationTimeLimit = (int) ($registryMGR->get('warranty', 'activationGraceDays') ?? 7); + $allocatedAt = $warrantySerial->getAllocatedAt(); + $currentDate = new \DateTime(); + $daysDiff = $currentDate->diff($allocatedAt)->days; + + if ($daysDiff > $activationTimeLimit) { + return $this->json([ + 'success' => false, + 'message' => "مهلت فعال‌سازی این گارانتی ({$activationTimeLimit} روز) به پایان رسیده است" + ], 400); + } + + // Get commodity information + $commodity = $warrantySerial->getCommodity(); + + // Calculate warranty period end date + $warrantyEndDate = null; + if ($warrantySerial->getWarrantyEndDate()) { + $warrantyEndDate = $warrantySerial->getWarrantyEndDate(); + } elseif ($warrantySerial->getWarrantyStartDate()) { + // If only start date is set, assume 12 months warranty period + $startDate = new \DateTime($warrantySerial->getWarrantyStartDate()); + $endDate = $startDate->add(new \DateInterval('P12M')); + $warrantyEndDate = $endDate->format('Y-m-d'); + } + + // Prepare product information + $productInfo = [ + 'serialNumber' => $warrantySerial->getSerialNumber(), + 'commoditySerial' => $warrantySerial->getCommoditySerial(), + 'productName' => $commodity ? $commodity->getName() : 'نامشخص', + 'productCode' => $commodity ? $commodity->getCode() : null, + 'description' => $warrantySerial->getDescription(), + 'submitDate' => $warrantySerial->getDateSubmit(), + 'warrantyStartDate' => $warrantySerial->getWarrantyStartDate(), + 'warrantyEndDate' => $warrantyEndDate, + 'status' => $warrantySerial->getStatus(), + 'activation' => $warrantySerial->getActivation(), + 'notes' => $warrantySerial->getNotes(), + 'activationTimeLimit' => $activationTimeLimit, + 'daysRemaining' => max(0, $activationTimeLimit - $daysDiff), + 'submitter' => $warrantySerial->getSubmitter() ? $warrantySerial->getSubmitter()->getFullName() : null, + 'businessName' => $business->getName(), + 'activationTicketCode' => $warrantySerial->getActivationTicketCode(), + 'requireActivationSecret' => true + ]; + + return $this->json([ + 'success' => true, + 'data' => $productInfo + ]); + } + + #[Route('/api/public/{businessId}/warranty/activate/{code}', name: 'api_public_warranty_activate', methods: ['POST'])] + public function activateWarranty(string $businessId, string $code, Request $request, EntityManagerInterface $entityManager, PluginService $pluginService, registryMGR $registryMGR): JsonResponse + { + // Validate input + if (empty($code)) { + return $this->json([ + 'success' => false, + 'message' => 'کد گارانتی وارد نشده است' + ], 400); + } + + // Find business + $business = $entityManager->getRepository(Business::class)->find($businessId); + if (!$business) { + return $this->json([ + 'success' => false, + 'message' => 'کسب و کار مورد نظر یافت نشد' + ], 404); + } + + // Check if warranty plugin is active + $pluginCheck = $this->checkWarrantyPluginActive($business, $pluginService); + if ($pluginCheck) { + return $pluginCheck; + } + + // Find warranty serial + $warrantySerial = $entityManager->getRepository(PlugWarrantySerial::class)->findOneBy([ + 'business' => $business, + 'serialNumber' => $code + ]); + + if (!$warrantySerial) { + return $this->json([ + 'success' => false, + 'message' => 'کد گارانتی نامعتبر است یا متعلق به این کسب و کار نیست' + ], 404); + } + + // Check if already activated + if (!$warrantySerial->isUsed()) { + return $this->json([ + 'success' => false, + 'message' => 'این گارانتی ثبت نشده است', + ], 400); + } + + // Check status + if ($warrantySerial->getActivation() !== 'deactive') { + return $this->json([ + 'success' => false, + 'message' => 'وضعیت این گارانتی فعال است' + ], 400); + } + + $activationTimeLimit = (int) ($registryMGR->get('warranty', 'activationGraceDays') ?? 7); + $allocatedAt = $warrantySerial->getAllocatedAt(); + $currentDate = new \DateTime(); + $daysDiff = $currentDate->diff($allocatedAt)->days; + + if ($daysDiff > $activationTimeLimit) { + return $this->json([ + 'success' => false, + 'message' => "مهلت فعال‌سازی این گارانتی ({$activationTimeLimit} روز) به پایان رسیده است" + ], 400); + } + + $secret = json_decode($request->getContent() ?: '{}', true)['activationSecret'] ?? ''; + if ($warrantySerial->getActivationTicketSecret()) { + if (!$secret || $secret !== $warrantySerial->getActivationTicketSecret()) { + return $this->json([ + 'success' => false, + 'message' => 'کد فعال‌سازی حواله نامعتبر است' + ], 400); + } + } + + $warrantySerial->setActivation('active'); + $warrantySerial->setActivationAt(new \DateTimeImmutable()); + + // Set warranty start date to current date if not already set + // if (!$warrantySerial->getWarrantyStartDate()) { + // $warrantySerial->setWarrantyStartDate(date('Y-m-d')); + // } + + // Set warranty end date if not already set (default 12 months) + // if (!$warrantySerial->getWarrantyEndDate()) { + // $endDate = new \DateTime(); + // $endDate->add(new \DateInterval('P12M')); + // $warrantySerial->setWarrantyEndDate($endDate->format('Y-m-d')); + // } + + // Save changes + $entityManager->persist($warrantySerial); + $entityManager->flush(); + + // Get commodity information for response + $commodity = $warrantySerial->getCommodity(); + + // Prepare activation information + $activationInfo = [ + 'serialNumber' => $warrantySerial->getSerialNumber(), + 'productName' => $commodity ? $commodity->getName() : 'نامشخص', + 'productCode' => $commodity ? $commodity->getCode() : null, + 'activationDate' => $warrantySerial->getActivationAt()?->format('Y-m-d'), + 'warrantyStartDate' => $warrantySerial->getWarrantyStartDate() ? jalaliToGregorian($warrantySerial->getWarrantyStartDate()->format('Y/m/d')) : null, + 'warrantyEndDate' => $warrantySerial->getWarrantyEndDate() ? jalaliToGregorian($warrantySerial->getWarrantyEndDate()->format('Y/m/d')) : null, + 'businessName' => $business->getName(), + 'notes' => $warrantySerial->getNotes() + ]; + + return $this->json([ + 'success' => true, + 'message' => 'گارانتی با موفقیت فعال شد', + 'data' => $activationInfo + ]); + } + + #[Route('/api/public/{businessId}/warranty/help', name: 'api_public_warranty_help', methods: ['GET'])] + public function getWarrantyHelp(string $businessId, PluginService $pluginService, EntityManagerInterface $entityManager): JsonResponse + { + // Find business + $business = $entityManager->getRepository(Business::class)->find($businessId); + if (!$business) { + return $this->json([ + 'success' => false, + 'message' => 'کسب و کار مورد نظر یافت نشد' + ], 404); + } + + // Check if warranty plugin is active + $pluginCheck = $this->checkWarrantyPluginActive($business, $pluginService); + if ($pluginCheck) { + return $pluginCheck; + } + $helpInfo = [ + 'codeFormat' => [ + 'prefix' => 'WR', + 'length' => 11, + 'example' => 'WR-123456789', + 'description' => 'کد گارانتی با حروف WR شروع می‌شود و شامل 9 رقم است' + ], + 'locations' => [ + 'محصول' => 'روی برچسب چسبیده شده به محصول', + 'فاکتور' => 'در فاکتور خرید یا رسید پرداخت', + 'بسته‌بندی' => 'در جعبه یا بسته‌بندی محصول', + 'ایمیل' => 'در ایمیل تأیید خرید' + ], + 'activationTimeLimit' => [ + 'default' => 7, + 'unit' => 'روز', + 'description' => 'گارانتی باید حداکثر 7 روز پس از خرید فعال شود' + ], + 'support' => [ + 'phone' => '021-88888888', + 'email' => 'support@example.com', + 'hours' => 'شنبه تا پنج‌شنبه، 8 صبح تا 8 شب' + ] + ]; + + return $this->json([ + 'success' => true, + 'data' => $helpInfo + ]); + } + + #[Route('/api/public/{businessId}/status', name: 'api_public_business_status', methods: ['GET'])] + public function getBusinessStatus(string $businessId, PluginService $pluginService, EntityManagerInterface $entityManager): JsonResponse + { + // Find business + $business = $entityManager->getRepository(Business::class)->find($businessId); + if (!$business) { + return $this->json([ + 'success' => false, + 'message' => 'کسب و کار مورد نظر یافت نشد', + 'error' => 'BUSINESS_NOT_FOUND' + ], 404); + } + + // Check if warranty plugin is active + if (!$pluginService->isActive('warranty', $business)) { + return $this->json([ + 'success' => false, + 'message' => 'پلاگین گارانتی برای این کسب و کار فعال نیست', + 'error' => 'PLUGIN_NOT_ACTIVE' + ], 404); + } + + return $this->json([ + 'success' => true, + 'message' => 'کسب و کار و پلاگین گارانتی فعال است', + 'data' => [ + 'businessName' => $business->getName(), + 'pluginActive' => true + ] + ]); + } + + #[Route('/api/public/{businessId}/warranty/qr-scan', name: 'api_public_warranty_qr_scan', methods: ['POST'])] + public function scanQrCode(string $businessId, Request $request, PluginService $pluginService, EntityManagerInterface $entityManager): JsonResponse + { + // Find business + $business = $entityManager->getRepository(Business::class)->find($businessId); + if (!$business) { + return $this->json([ + 'success' => false, + 'message' => 'کسب و کار مورد نظر یافت نشد' + ], 404); + } + + // Check if warranty plugin is active + $pluginCheck = $this->checkWarrantyPluginActive($business, $pluginService); + if ($pluginCheck) { + return $pluginCheck; + } + + // TODO: Implement QR code scanning logic + + $params = json_decode($request->getContent(), true); + $qrData = $params['qrData'] ?? ''; + + if (empty($qrData)) { + return $this->json([ + 'success' => false, + 'message' => 'داده QR خالی است' + ], 400); + } + + // Extract warranty code from QR data + // Assuming QR contains warranty code directly or in a URL + $warrantyCode = $this->extractWarrantyCodeFromQr($qrData); + + if (!$warrantyCode) { + return $this->json([ + 'success' => false, + 'message' => 'کد گارانتی در QR یافت نشد' + ], 400); + } + + return $this->json([ + 'success' => true, + 'warrantyCode' => $warrantyCode + ]); + } + + private function extractWarrantyCodeFromQr(string $qrData): ?string + { + if (preg_match('/WR-\d{9}/', $qrData, $matches)) { + return $matches[0]; + } + + if (preg_match('/warranty.*code[=\/]([A-Za-z0-9-]+)/', $qrData, $matches)) { + return $matches[1]; + } + + return null; + } +} +function jalaliToGregorian($date) { + $p = explode('/', $date); + return implode('-', CalendarUtils::toGregorian($p[0], $p[1], $p[2])); +} \ No newline at end of file diff --git a/hesabixCore/src/Controller/SellController.php b/hesabixCore/src/Controller/SellController.php index 240d207..23d56bf 100644 --- a/hesabixCore/src/Controller/SellController.php +++ b/hesabixCore/src/Controller/SellController.php @@ -230,6 +230,16 @@ class SellController extends AbstractController $doc->setSubmitter($this->getUser()); $doc->setMoney($acc['money']); $doc->setCode($provider->getAccountingCode($acc['bid'], 'accounting')); + + // Set approval fields based on business settings + $business = $acc['bid']; + if ($business->isRequireTwoStepApproval()) { + $doc->setIsPreview(true); + $doc->setIsApproved(false); + } else { + $doc->setIsPreview(false); + $doc->setIsApproved(true); + } } if ($params['transferCost'] != 0) { $hesabdariRow = new HesabdariRow(); @@ -322,13 +332,17 @@ class SellController extends AbstractController $hesabdariRow->setPerson($person); $entityManager->persist($hesabdariRow); - // Two-step approval: اگر پرمیشن کسب‌وکار تأیید دو مرحله‌ای فروش را الزامی کرده باشد - $permission = $entityManager->getRepository(\App\Entity\Permission::class)->findOneBy(['bid' => $acc['bid'], 'user' => $acc['user']]); - $personRequire = $person && method_exists($person, 'isRequireTwoStep') ? (bool)$person->isRequireTwoStep() : false; - if (($permission && $permission->isRequireTwoStepSell()) || $personRequire) { - $doc->setStatus('pending_approval'); + // Two-step approval: اگر کسب‌وکار تأیید دو مرحله‌ای را الزامی کرده باشد + $business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']); + $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false; + if ($businessRequire) { + $doc->setIsPreview(true); + $doc->setIsApproved(false); + $doc->setApprovedBy(null); } else { - $doc->setStatus('approved'); + $doc->setIsPreview(false); + $doc->setIsApproved(true); + $doc->setApprovedBy($this->getUser()); } $entityManager->persist($doc); $entityManager->flush(); @@ -467,10 +481,13 @@ class SellController extends AbstractController $queryBuilder = $entityManager->createQueryBuilder() ->select('DISTINCT d.id, d.dateSubmit, d.date, d.type, d.code, d.des, d.amount') + ->addSelect('d.isPreview, d.isApproved') ->addSelect('u.fullName as submitter') + ->addSelect('approver.fullName as approvedByName, approver.id as approvedById, approver.email as approvedByEmail') ->addSelect('l.code as labelCode, l.label as labelLabel') ->from(HesabdariDoc::class, 'd') ->leftJoin('d.submitter', 'u') + ->leftJoin('d.approvedBy', 'approver') ->leftJoin('d.InvoiceLabel', 'l') ->leftJoin('d.hesabdariRows', 'r') ->where('d.bid = :bid') @@ -532,7 +549,9 @@ class SellController extends AbstractController 'plugin' => 'd.plugin', 'refData' => 'd.refData', 'shortlink' => 'd.shortlink', - 'status' => 'd.status', + 'isPreview' => 'd.isPreview', + 'isApproved' => 'd.isApproved', + 'approvedBy' => 'd.approvedBy', 'submitter' => 'u.fullName', 'label' => 'l.label', // از InvoiceLabel ]; @@ -578,6 +597,13 @@ class SellController extends AbstractController 'code' => $doc['labelCode'], 'label' => $doc['labelLabel'] ] : null, + 'isPreview' => $doc['isPreview'], + 'isApproved' => $doc['isApproved'], + 'approvedBy' => $doc['approvedByName'] ? [ + 'fullName' => $doc['approvedByName'], + 'id' => $doc['approvedById'], + 'email' => $doc['approvedByEmail'] + ] : null, ]; $mainRow = $entityManager->getRepository(HesabdariRow::class) @@ -1234,6 +1260,19 @@ class SellController extends AbstractController $hesabdariRow->setPerson($person); $entityManager->persist($hesabdariRow); + // Two-step approval: اگر کسب‌وکار تأیید دو مرحله‌ای را الزامی کرده باشد + $business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']); + $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false; + if ($businessRequire) { + $doc->setIsPreview(true); + $doc->setIsApproved(false); + $doc->setApprovedBy(null); + } else { + $doc->setIsPreview(false); + $doc->setIsApproved(true); + $doc->setApprovedBy($this->getUser()); + } + // ذخیره فاکتور $entityManager->persist($doc); $entityManager->flush(); @@ -1320,13 +1359,13 @@ class SellController extends AbstractController $entityManager->persist($receiveRow); // Two-step approval برای دریافت/پرداخت - $permission = $entityManager->getRepository(\App\Entity\Permission::class)->findOneBy(['bid' => $acc['bid'], 'user' => $acc['user']]); - $personRequire = $person && method_exists($person, 'isRequireTwoStep') ? (bool)$person->isRequireTwoStep() : false; - if (($permission && $permission->isRequireTwoStepPayment()) || $personRequire) { - $paymentDoc->setStatus('pending_approval'); - } else { - $paymentDoc->setStatus('approved'); - } + // $business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']); + // $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false; + // if ($businessRequire) { + // $paymentDoc->setStatus('pending_approval'); + // } else { + // $paymentDoc->setStatus('approved'); + // } $entityManager->persist($paymentDoc); } $entityManager->flush(); diff --git a/hesabixCore/src/Controller/StoreroomController.php b/hesabixCore/src/Controller/StoreroomController.php index 673cad1..9d68632 100644 --- a/hesabixCore/src/Controller/StoreroomController.php +++ b/hesabixCore/src/Controller/StoreroomController.php @@ -57,7 +57,7 @@ class StoreroomController extends AbstractController } #[Route('/api/storeroom/ticket/attachments/upload/{code}', name: 'app_storeroom_ticket_upload_attachment', methods: ['POST'])] - public function uploadTicketAttachment(string $code, Request $request, Access $access, EntityManagerInterface $entityManager): JsonResponse + public function uploadTicketAttachment(string $code, Request $request, Access $access, EntityManagerInterface $entityManager, \App\Service\FileStorage $storage): JsonResponse { $acc = $access->hasRole('store'); if (!$acc) throw $this->createAccessDeniedException(); @@ -68,23 +68,21 @@ class StoreroomController extends AbstractController if (!$file) { return $this->json(['result'=>-1,'message'=>'فایل ارسال نشده است'], 400); } - $uploadDir = __DIR__ . '/../../../public_html/uploads/storeroom/'; - if (!is_dir($uploadDir)) @mkdir($uploadDir, 0775, true); - $safeName = uniqid('st_', true) . '_' . preg_replace('/[^A-Za-z0-9_\.-]/','_', $file->getClientOriginalName()); - $file->move($uploadDir, $safeName); + // Store securely in var/storage + $stored = $storage->store($file, (string)$acc['bid']->getId(), 'storeroom_attachments'); $archive = new ArchiveFile(); $archive->setBid($acc['bid']); $archive->setSubmitter($acc['user']); $archive->setDateSubmit(date('Y-m-d H:i:s')); - $archive->setFilename('/uploads/storeroom/' . $safeName); + $archive->setFilename($stored['relativePath']); $archive->setCat('storeroom_ticket'); - $archive->setFileType($file->getClientMimeType() ?: 'application/octet-stream'); + $archive->setFileType($stored['mime'] ?: 'application/octet-stream'); $archive->setPublic(false); $archive->setDes($request->request->get('des')); $archive->setRelatedDocType('storeroom_ticket'); $archive->setRelatedDocCode($ticket->getCode()); - $archive->setFileSize((string) $file->getSize()); + $archive->setFileSize($stored['size'] !== null ? (string)$stored['size'] : null); $entityManager->persist($archive); $entityManager->flush(); @@ -115,6 +113,28 @@ class StoreroomController extends AbstractController }, $items)); } + #[Route('/api/storeroom/ticket/attachments/download/{id}', name: 'app_storeroom_ticket_download_attachment', methods: ['GET'])] + public function downloadTicketAttachment(int $id, Access $access, EntityManagerInterface $entityManager, \App\Service\FileStorage $storage): Response + { + $acc = $access->hasRole('store'); + if (!$acc) throw $this->createAccessDeniedException(); + $a = $entityManager->getRepository(ArchiveFile::class)->find($id); + if (!$a || $a->getBid()->getId() !== $acc['bid']->getId()) { + throw $this->createNotFoundException('فایل یافت نشد'); + } + $abs = $storage->absolutePath((string)$a->getFilename()); + if (!is_file($abs) || !is_readable($abs)) { + throw $this->createNotFoundException('فایل موجود نیست'); + } + $response = new \Symfony\Component\HttpFoundation\BinaryFileResponse($abs); + $response->setContentDisposition( + \Symfony\Component\HttpFoundation\ResponseHeaderBag::DISPOSITION_ATTACHMENT, + basename($abs) + ); + $response->headers->set('Content-Type', $a->getFileType() ?: 'application/octet-stream'); + return $response; + } + #[Route('/api/storeroom/mod/{code}', name: 'app_storeroom_mod')] public function app_storeroom_mod(Provider $provider, Request $request, Access $access, Log $log, EntityManagerInterface $entityManager, $code = 0): JsonResponse { @@ -195,8 +215,12 @@ class StoreroomController extends AbstractController foreach ($buys as $buy) { $temp = $provider->Entity2Array($buy, 0); $person = $this->getPerson($buy); - $temp['person'] = Explore::ExplorePerson($person); - $temp['person']['des'] = ' # ' . $person->getCode() . ' ' . $person->getNikename(); + if ($person) { + $temp['person'] = Explore::ExplorePerson($person); + $temp['person']['des'] = ' # ' . $person->getCode() . ' ' . $person->getNikename(); + } else { + $temp['person'] = null; + } $temp['commodities'] = $this->getCommodities($buy, $provider); //check storeroom exist $this->calcStoreRemaining($temp, $buy, $entityManager); @@ -216,8 +240,12 @@ class StoreroomController extends AbstractController foreach ($sells as $sell) { $temp = $provider->Entity2Array($sell, 0); $person = $this->getPerson($sell); - $temp['person'] = Explore::ExplorePerson($person); - $temp['person']['des'] = ' # ' . $person->getCode() . ' ' . $person->getNikename(); + if ($person) { + $temp['person'] = Explore::ExplorePerson($person); + $temp['person']['des'] = ' # ' . $person->getCode() . ' ' . $person->getNikename(); + } else { + $temp['person'] = null; + } $temp['commodities'] = $this->getCommodities($sell, $provider); //check storeroom exist $this->calcStoreRemaining($temp, $sell, $entityManager); @@ -237,8 +265,12 @@ class StoreroomController extends AbstractController foreach ($rfsells as $sell) { $temp = $provider->Entity2Array($sell, 0); $person = $this->getPerson($sell); - $temp['person'] = Explore::ExplorePerson($person); - $temp['person']['des'] = ' # ' . $person->getCode() . ' ' . $person->getNikename(); + if ($person) { + $temp['person'] = Explore::ExplorePerson($person); + $temp['person']['des'] = ' # ' . $person->getCode() . ' ' . $person->getNikename(); + } else { + $temp['person'] = null; + } $temp['commodities'] = $this->getCommodities($sell, $provider); //check storeroom exist $this->calcStoreRemaining($temp, $sell, $entityManager); @@ -258,8 +290,12 @@ class StoreroomController extends AbstractController foreach ($rfbuys as $buy) { $temp = $provider->Entity2Array($buy, 0); $person = $this->getPerson($buy); - $temp['person'] = Explore::ExplorePerson($person); - $temp['person']['des'] = ' # ' . $person->getCode() . ' ' . $person->getNikename(); + if ($person) { + $temp['person'] = Explore::ExplorePerson($person); + $temp['person']['des'] = ' # ' . $person->getCode() . ' ' . $person->getNikename(); + } else { + $temp['person'] = null; + } $temp['commodities'] = $this->getCommodities($buy, $provider); //check storeroom exist $this->calcStoreRemaining($temp, $buy, $entityManager); @@ -342,8 +378,12 @@ class StoreroomController extends AbstractController } } $res = $provider->Entity2Array($doc, 0); - $res['person'] = $provider->Entity2Array($person, 0); - $res['person']['des'] = ' # ' . $person->getCode() . ' ' . $person->getNikename(); + if ($person) { + $res['person'] = $provider->Entity2Array($person, 0); + $res['person']['des'] = ' # ' . $person->getCode() . ' ' . $person->getNikename(); + } else { + $res['person'] = null; + } $res['commodities'] = $provider->ArrayEntity2Array($commodities, 1, ['doc', 'bid', 'year']); //calculate rows data $this->calcStoreRemaining($res, $doc, $entityManager); @@ -464,6 +504,10 @@ class StoreroomController extends AbstractController $ticket->setTransfer($params['ticket']['transfer']); $ticket->setYear($acc['year']); $ticket->setCode($provider->getAccountingCode($acc['bid'], 'storeroom')); + $alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + $rand = ''; + for ($i = 0; $i < 8; $i++) { $rand .= $alphabet[random_int(0, strlen($alphabet)-1)]; } + $ticket->setActivationCode($rand); $ticket->setReceiver($params['ticket']['receiver']); $ticket->setTransferType($transferType); $ticket->setReferral($params['ticket']['referral']); @@ -472,9 +516,6 @@ class StoreroomController extends AbstractController $ticket->setType($params['ticket']['type']); $ticket->setTypeString($params['ticket']['typeString']); $ticket->setDes($params['ticket']['des']); - // وضعیت اولیه - $ticket->setStatus('in_progress'); - // اگر از پرونده واردات آمده if (array_key_exists('importWorkflowCode', $params['ticket'])) { $ticket->setImportWorkflowCode($params['ticket']['importWorkflowCode']); } @@ -485,37 +526,22 @@ class StoreroomController extends AbstractController $docRows = $entityManager->getRepository(HesabdariRow::class)->findBy([ 'doc' => $doc ]); - // بررسی الزام سریال: اگر در بدنه درخواست گزینه requireWarrantySerial=true باشد، قبل از ثبت نهایی، کف سریال‌های آزاد کالا را چک می‌کنیم - $requireWarrantySerial = isset($params['ticket']['requireWarrantySerial']) && $params['ticket']['requireWarrantySerial'] === true; + + // Determine if warranty serials are required based on flag or provided lines + $hasSerialLines = false; + foreach (($params['items'] ?? []) as $it) { + if (!empty($it['serialLines']) && is_array($it['serialLines'])) { $hasSerialLines = true; break; } + } + $requireWarrantySerial = (isset($params['ticket']['requireWarrantySerial']) && $params['ticket']['requireWarrantySerial'] === true) || $hasSerialLines; if ($requireWarrantySerial) { if (!$pluginService->isActive('warranty', $acc['bid'])) { - return $this->json([ - 'result' => -5, - 'message' => 'افزونه گارانتی فعال نیست' - ], 403); + return $this->json(['result' => -5, 'message' => 'افزونه گارانتی فعال نیست'], 403); } - } - if ($requireWarrantySerial) { + // Validate counts up-front foreach ($params['items'] as $item) { - $row = $entityManager->getRepository(HesabdariRow::class)->findOneBy([ - 'bid' => $acc['bid'], - 'doc' => $doc, - 'id' => $item['id'], - ]); - if (!$row || !$row->getCommodity()) - throw $this->createNotFoundException('کالا یافت نشد!'); - $commodity = $row->getCommodity(); - $freeSerialCount = $entityManager->getRepository(PlugWarrantySerial::class)->count([ - 'bid' => $acc['bid'], - 'commodity' => $commodity, - 'used' => null, - 'status' => 'active' - ]); - if ($freeSerialCount < (int)$item['ticketCount']) { - return $this->json([ - 'result' => -2, - 'message' => 'تعداد سریال گارانتی آزاد برای کالا کافی نیست' - ], 400); + $lines = isset($item['serialLines']) && is_array($item['serialLines']) ? $item['serialLines'] : []; + if ((int)($item['ticketCount'] ?? 0) > 0 && count($lines) < (int)$item['ticketCount']) { + return $this->json(['result' => -3, 'message' => 'تعداد سریال/گارانتی با تعداد حواله همخوانی ندارد'], 400); } } } @@ -543,44 +569,57 @@ class StoreroomController extends AbstractController $ticketItem->setCommodity($row->getCommodity()); $ticketItem->setType($item['type']); $entityManager->persist($ticketItem); - // اگر الزام سریال فعال است و سریال‌ها هم در همین درخواست آمده‌اند، آن‌ها را مصرف کنیم - if ($requireWarrantySerial && isset($item['warrantySerials']) && is_array($item['warrantySerials'])) { - $serials = $item['warrantySerials']; - if (count($serials) != (int)$item['ticketCount']) { - return $this->json([ - 'result' => -3, - 'message' => 'تعداد سریال‌های ارسالی با تعداد حواله همخوانی ندارد' - ], 400); - } - foreach ($serials as $serialNumber) { - /** @var PlugWarrantySerial|null $serial */ - $serial = $entityManager->getRepository(PlugWarrantySerial::class)->findOneBy([ - 'bid' => $acc['bid'], - 'serialNumber' => $serialNumber, - 'commodity' => $row->getCommodity(), - 'status' => 'active' - ]); - if (!$serial || $serial->isUsed()) { - return $this->json([ - 'result' => -4, - 'message' => 'سریال نامعتبر یا قبلاً مصرف شده: ' . $serialNumber - ], 400); + + // Bind warranty serials per item if provided + if ($requireWarrantySerial) { + $lines = isset($item['serialLines']) && is_array($item['serialLines']) ? $item['serialLines'] : []; + if ((int)$item['ticketCount'] > 0) { + // Ensure we have an id to bind to + $entityManager->flush(); + $lines = array_slice($lines, 0, (int)$item['ticketCount']); + foreach ($lines as $ln) { + $warrantyCode = $ln['warranty'] ?? null; + $deviceSerial = $ln['serial'] ?? null; + if (!$warrantyCode) { + return $this->json(['result' => -4, 'message' => 'کد گارانتی ارسال نشده است'], 400); + } + /** @var PlugWarrantySerial|null $serial */ + $serial = $entityManager->getRepository(PlugWarrantySerial::class)->findOneBy([ + 'business' => $acc['bid'], + 'serialNumber' => $warrantyCode, + 'commodity' => $row->getCommodity(), + ]); + if (!$serial || $serial->getStatus() !== PlugWarrantySerial::STATUS_AVAILABLE) { + return $this->json(['result' => -4, 'message' => 'گارانتی نامعتبر یا آزاد نیست: ' . $warrantyCode], 400); + } + $serial->setStatus(PlugWarrantySerial::STATUS_CONSUMED); + $serial->setCommoditySerial($deviceSerial); + $serial->setBuyer($person); + $serial->setAllocatedToDocumentId($doc->getId()); + $serial->setAllocatedAt(new \DateTimeImmutable()); + $serial->setBoundToItemId($ticketItem->getId()); + $serial->setBoundAt(new \DateTimeImmutable()); + $serial->setActivationTicketCode($ticket->getCode()); + $serial->setActivationTicketSecret($ticket->getActivationCode()); + $entityManager->persist($serial); } - $serial->setUsed(true); - $serial->setUsedAt(date('Y-m-d H:i:s')); - $serial->setUsedTicketCode($ticket->getCode()); - $entityManager->persist($serial); } } + } + $entityManager->flush(); - // اگر تأیید دو مرحله‌ای حواله فعال باشد، وضعیت را pending_approval بگذاریم - $permission = $entityManager->getRepository(\App\Entity\Permission::class)->findOneBy(['bid' => $acc['bid'], 'user' => $acc['user']]); - $personRequire = $person && method_exists($person, 'isRequireTwoStep') ? (bool)$person->isRequireTwoStep() : false; - if (($permission && $permission->isRequireTwoStepStore()) || $personRequire) { - $ticket->setStatus('pending_approval'); - $entityManager->persist($ticket); - $entityManager->flush(); + + $business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']); + $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false; + if ($businessRequire) { + $ticket->setIsPreview(true); + $ticket->setIsApproved(false); + $ticket->setApprovedBy(null); // هنوز تأیید نشده + } else { + $ticket->setIsPreview(false); + $ticket->setIsApproved(true); + $ticket->setApprovedBy($this->getUser()); // تأیید شده توسط کاربر فعلی } //save logs $log->insert('انبارداری', 'حواله انبار با شماره ' . $ticket->getCode() . ' اضافه / ویرایش شد.', $this->getUser(), $acc['bid']); @@ -660,7 +699,7 @@ class StoreroomController extends AbstractController if (!$ticket) { throw $this->createNotFoundException('حواله یافت نشد.'); } - $ticket->setStatus($status); + // $ticket->setStatus($status); $entityManager->persist($ticket); $entityManager->flush(); return $this->json(['result' => 0]); @@ -687,7 +726,6 @@ class StoreroomController extends AbstractController 'date' => $t->getDate(), 'type' => $t->getType(), 'typeString' => $t->getTypeString(), - 'status' => $t->getStatus(), 'importWorkflowCode' => $t->getImportWorkflowCode(), 'person' => $t->getPerson() ? $t->getPerson()->getNikename() : null, 'storeroom' => $t->getStoreroom() ? $t->getStoreroom()->getName() : null, @@ -708,15 +746,33 @@ class StoreroomController extends AbstractController ], [ 'date' => 'DESC' ]); - return $this->json($provider->ArrayEntity2ArrayJustIncludes($tickets, [ + $result = $provider->ArrayEntity2ArrayJustIncludes($tickets, [ 'getDes', 'getCode', 'getDate', 'getPerson', 'getNikename', 'getDoc', - 'getTypeString' - ])); + 'getTypeString', + 'isPreview', + 'isApproved' + ], 2); + + foreach ($result as $key => &$ticket) { + $ticketEntity = $tickets[$key]; + if ($ticketEntity->getApprovedBy()) { + $approvedBy = $ticketEntity->getApprovedBy(); + $ticket['approvedBy'] = [ + 'id' => $approvedBy->getId(), + 'fullName' => $approvedBy->getFullName(), + 'email' => $approvedBy->getEmail() + ]; + } else { + $ticket['approvedBy'] = null; + } + } + + return $this->json($result); } #[Route('/api/storeroom/tickets/info/{code}', name: 'app_storeroom_ticket_view')] @@ -734,7 +790,7 @@ class StoreroomController extends AbstractController //get items $items = $entityManager->getRepository(StoreroomItem::class)->findBy(['ticket' => $ticket]); $res = []; - $res['ticket'] = $provider->Entity2ArrayJustIncludes($ticket, ['getStoreroom', 'getManager', 'getDate', 'getSubmitDate', 'getDes', 'getReceiver', 'getTransfer', 'getCode', 'getType', 'getReferral', 'getTypeString'], 2); + $res['ticket'] = $provider->Entity2ArrayJustIncludes($ticket, ['getStoreroom', 'getManager', 'getDate', 'getSubmitDate', 'getDes', 'getReceiver', 'getTransfer', 'getCode', 'getType', 'getReferral', 'getTypeString', 'isPreview', 'isApproved'], 2); $res['transferType'] = $provider->Entity2ArrayJustIncludes($ticket->getTransferType(), ['getName'], 0); $res['person'] = $provider->Entity2ArrayJustIncludes($ticket->getPerson(), ['getKeshvar', 'getOstan', 'getShahr', 'getAddress', 'getNikename', 'getCodeeghtesadi', 'getPostalcode', 'getName', 'getTel', 'getSabt'], 0); //get rows @@ -851,10 +907,12 @@ class StoreroomController extends AbstractController } else { $title = 'حواله خروج از انبار'; } - // جلوگیری از چاپ پیش از تایید در صورت نیاز - $permission = $entityManager->getRepository(\App\Entity\Permission::class)->findOneBy(['bid' => $acc['bid'], 'user' => $acc['user']]); - if ($permission && $permission->isRequireTwoStepStore()) { - if ($doc->getStatus() !== 'approved' && $doc->getStatus() !== 'done') { + + $business = $entityManager->getRepository(\App\Entity\Business::class)->find($acc['bid']); + $businessRequire = $business && method_exists($business, 'isRequireTwoStepApproval') ? (bool)$business->isRequireTwoStepApproval() : false; + if ($businessRequire) { + // بررسی وضعیت تأیید از طریق StoreroomTicket + if ($doc->isPreview()) { return $this->json(['result' => -10, 'message' => 'حواله هنوز تایید نشده است'], 403); } } diff --git a/hesabixCore/src/Entity/Business.php b/hesabixCore/src/Entity/Business.php index 39e6b28..68263fb 100644 --- a/hesabixCore/src/Entity/Business.php +++ b/hesabixCore/src/Entity/Business.php @@ -313,6 +313,18 @@ class Business #[ORM\OneToMany(mappedBy: 'business', targetEntity: ImportWorkflow::class, orphanRemoval: true)] private Collection $importWorkflows; + #[ORM\Column(nullable: true)] + private ?bool $requireTwoStepApproval = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $invoiceApprover = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $warehouseApprover = null; + + #[ORM\Column(length: 255, nullable: true)] + private ?string $financialApprover = null; + public function __construct() { $this->logs = new ArrayCollection(); @@ -2198,4 +2210,48 @@ class Business } return $this; } + + public function isRequireTwoStepApproval(): ?bool + { + return $this->requireTwoStepApproval; + } + + public function setRequireTwoStepApproval(?bool $requireTwoStepApproval): static + { + $this->requireTwoStepApproval = $requireTwoStepApproval; + return $this; + } + + public function getInvoiceApprover(): ?string + { + return $this->invoiceApprover; + } + + public function setInvoiceApprover(?string $invoiceApprover): static + { + $this->invoiceApprover = $invoiceApprover; + return $this; + } + + public function getWarehouseApprover(): ?string + { + return $this->warehouseApprover; + } + + public function setWarehouseApprover(?string $warehouseApprover): static + { + $this->warehouseApprover = $warehouseApprover; + return $this; + } + + public function getFinancialApprover(): ?string + { + return $this->financialApprover; + } + + public function setFinancialApprover(?string $financialApprover): static + { + $this->financialApprover = $financialApprover; + return $this; + } } diff --git a/hesabixCore/src/Entity/HesabdariDoc.php b/hesabixCore/src/Entity/HesabdariDoc.php index c7f33c9..b3b86ff 100644 --- a/hesabixCore/src/Entity/HesabdariDoc.php +++ b/hesabixCore/src/Entity/HesabdariDoc.php @@ -726,4 +726,48 @@ class HesabdariDoc return $this; } + + // Approval fields + #[ORM\Column(nullable: true)] + private ?bool $isPreview = null; + + #[ORM\Column(nullable: true)] + private ?bool $isApproved = null; + + #[ORM\ManyToOne] + #[ORM\JoinColumn(nullable: true)] + private ?User $approvedBy = null; + + public function isPreview(): ?bool + { + return $this->isPreview; + } + + public function setIsPreview(?bool $isPreview): static + { + $this->isPreview = $isPreview; + return $this; + } + + public function isApproved(): ?bool + { + return $this->isApproved; + } + + public function setIsApproved(?bool $isApproved): static + { + $this->isApproved = $isApproved; + return $this; + } + + public function getApprovedBy(): ?User + { + return $this->approvedBy; + } + + public function setApprovedBy(?User $approvedBy): static + { + $this->approvedBy = $approvedBy; + return $this; + } } \ No newline at end of file diff --git a/hesabixCore/src/Entity/HesabdariRow.php b/hesabixCore/src/Entity/HesabdariRow.php index 6e91683..b138312 100644 --- a/hesabixCore/src/Entity/HesabdariRow.php +++ b/hesabixCore/src/Entity/HesabdariRow.php @@ -368,4 +368,6 @@ class HesabdariRow return $this; } + + } \ No newline at end of file diff --git a/hesabixCore/src/Entity/ImportWorkflowItem.php b/hesabixCore/src/Entity/ImportWorkflowItem.php index 19a0b2b..aa3dd68 100644 --- a/hesabixCore/src/Entity/ImportWorkflowItem.php +++ b/hesabixCore/src/Entity/ImportWorkflowItem.php @@ -6,6 +6,7 @@ use App\Repository\ImportWorkflowItemRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Serializer\Annotation\Ignore; +use App\Entity\Commodity; #[ORM\Entity(repositoryClass: ImportWorkflowItemRepository::class)] class ImportWorkflowItem @@ -20,6 +21,10 @@ class ImportWorkflowItem #[Ignore] private ?ImportWorkflow $importWorkflow = null; + #[ORM\ManyToOne] + #[ORM\JoinColumn(nullable: true)] + private ?Commodity $commodity = null; + #[ORM\Column(length: 255)] private ?string $name = null; @@ -86,6 +91,17 @@ class ImportWorkflowItem return $this; } + public function getCommodity(): ?Commodity + { + return $this->commodity; + } + + public function setCommodity(?Commodity $commodity): static + { + $this->commodity = $commodity; + return $this; + } + public function getName(): ?string { return $this->name; diff --git a/hesabixCore/src/Entity/Permission.php b/hesabixCore/src/Entity/Permission.php index cb3a0b2..48ac1a0 100644 --- a/hesabixCore/src/Entity/Permission.php +++ b/hesabixCore/src/Entity/Permission.php @@ -147,15 +147,6 @@ class Permission #[ORM\Column(nullable: true)] private ?bool $importWorkflow = null; - #[ORM\Column(nullable: true)] - private ?bool $requireTwoStepSell = null; - - #[ORM\Column(nullable: true)] - private ?bool $requireTwoStepPayment = null; - - #[ORM\Column(nullable: true)] - private ?bool $requireTwoStepStore = null; - public function getId(): ?int { return $this->id; @@ -688,37 +679,4 @@ class Permission return $this; } - - public function isRequireTwoStepSell(): ?bool - { - return $this->requireTwoStepSell; - } - - public function setRequireTwoStepSell(?bool $requireTwoStepSell): static - { - $this->requireTwoStepSell = $requireTwoStepSell; - return $this; - } - - public function isRequireTwoStepPayment(): ?bool - { - return $this->requireTwoStepPayment; - } - - public function setRequireTwoStepPayment(?bool $requireTwoStepPayment): static - { - $this->requireTwoStepPayment = $requireTwoStepPayment; - return $this; - } - - public function isRequireTwoStepStore(): ?bool - { - return $this->requireTwoStepStore; - } - - public function setRequireTwoStepStore(?bool $requireTwoStepStore): static - { - $this->requireTwoStepStore = $requireTwoStepStore; - return $this; - } -} +} \ No newline at end of file diff --git a/hesabixCore/src/Entity/Person.php b/hesabixCore/src/Entity/Person.php index 19ac6cb..13e26f0 100644 --- a/hesabixCore/src/Entity/Person.php +++ b/hesabixCore/src/Entity/Person.php @@ -161,8 +161,7 @@ class Person #[ORM\Column(type: Types::TEXT, nullable: true)] private ?string $tags = null; - #[ORM\Column(nullable: true)] - private ?bool $requireTwoStep = null; + public function __construct() { @@ -917,14 +916,5 @@ class Person return $this; } - public function isRequireTwoStep(): ?bool - { - return $this->requireTwoStep; - } - public function setRequireTwoStep(?bool $requireTwoStep): self - { - $this->requireTwoStep = $requireTwoStep; - return $this; - } } diff --git a/hesabixCore/src/Entity/PlugWarrantySerial.php b/hesabixCore/src/Entity/PlugWarrantySerial.php index d055782..a557047 100644 --- a/hesabixCore/src/Entity/PlugWarrantySerial.php +++ b/hesabixCore/src/Entity/PlugWarrantySerial.php @@ -2,212 +2,177 @@ namespace App\Entity; -use App\Repository\PlugWarrantySerialRepository; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Serializer\Annotation\Ignore; -#[ORM\Entity(repositoryClass: PlugWarrantySerialRepository::class)] +#[ORM\Entity] +#[ORM\Table(name: 'plug_warranty_serial')] +#[ORM\UniqueConstraint(name: 'uniq_warranty_serial', columns: ['serial_number'])] +#[ORM\Index(name: 'idx_status_product', columns: ['status', 'commodity_id'])] +#[ORM\Index(name: 'idx_alloc_doc', columns: ['allocated_to_document_id'])] class PlugWarrantySerial { + public const STATUS_AVAILABLE = 'available'; + public const STATUS_ALLOCATED = 'allocated'; + public const STATUS_VERIFIED = 'verified'; + public const STATUS_BOUND = 'bound'; + public const STATUS_CONSUMED = 'consumed'; + public const STATUS_VOID = 'void'; + #[ORM\Id] #[ORM\GeneratedValue] - #[ORM\Column] + #[ORM\Column(type: 'integer')] private ?int $id = null; #[ORM\ManyToOne(inversedBy: 'plugWarrantySerials')] #[ORM\JoinColumn(nullable: false)] - #[Ignore] - private ?Business $bid = null; + private ?Business $business = null; #[ORM\ManyToOne(inversedBy: 'plugWarrantySerials')] #[ORM\JoinColumn(nullable: false)] - #[Ignore] private ?Commodity $commodity = null; - #[ORM\Column(length: 255, unique: true)] + #[ORM\Column(name: 'serial_number', length: 255, unique: true)] private ?string $serialNumber = null; - #[ORM\Column(length: 25)] - private ?string $dateSubmit = null; + #[ORM\Column(type: 'datetime_immutable')] + private \DateTimeImmutable $dateSubmit; - #[ORM\ManyToOne(inversedBy: 'plugWarrantySerials')] - #[Ignore] + #[ORM\ManyToOne] private ?User $submitter = null; - #[ORM\Column(length: 255, nullable: true)] + #[ORM\Column(type: 'text', nullable: true)] private ?string $description = null; - #[ORM\Column(length: 25, nullable: true)] - private ?string $warrantyStartDate = null; + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeImmutable $warrantyStartDate = null; - #[ORM\Column(length: 25, nullable: true)] - private ?string $warrantyEndDate = null; + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeImmutable $warrantyEndDate = null; - #[ORM\Column(length: 50, nullable: true)] - private ?string $status = 'active'; + #[ORM\Column(length: 20)] + private string $status = self::STATUS_AVAILABLE; - #[ORM\Column(length: 255, nullable: true)] + #[ORM\Column(type: 'string', length: 20)] + private string $activation = 'deactive'; + + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeImmutable $activationAt = null; + + #[ORM\Column(type: 'text', nullable: true)] private ?string $notes = null; - #[ORM\Column(nullable: true)] - private ?bool $used = null; + #[ORM\Column(name: 'allocated_to_document_id', type: 'integer', nullable: true)] + private ?int $allocatedToDocumentId = null; - #[ORM\Column(length: 50, nullable: true)] - private ?string $usedAt = null; + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeImmutable $allocatedAt = null; - #[ORM\Column(length: 255, nullable: true)] - private ?string $usedTicketCode = null; + #[ORM\Column(name: 'bound_to_item_id', type: 'integer', nullable: true)] + private ?int $boundToItemId = null; + + #[ORM\Column(type: 'datetime_immutable', nullable: true)] + private ?\DateTimeImmutable $boundAt = null; + + #[ORM\Column(type: 'string', length: 255, nullable: true)] + private ?string $voidReason = null; + + #[ORM\Column(type: 'string', length: 255, nullable: true)] + private ?string $commoditySerial = null; + + #[ORM\ManyToOne] + private ?Person $buyer = null; + + #[ORM\Column(type: 'string', length: 32, nullable: true)] + private ?string $activationTicketCode = null; + + #[ORM\Column(type: 'string', length: 32, nullable: true)] + private ?string $activationTicketSecret = null; public function __construct() { - $this->dateSubmit = date('Y-m-d H:i:s'); + $this->dateSubmit = new \DateTimeImmutable(); } - public function getId(): ?int - { - return $this->id; + public function getId(): ?int { return $this->id; } + + public function getBusiness(): ?Business { return $this->business; } + public function setBusiness(?Business $business): self { $this->business = $business; return $this; } + + public function getCommodity(): ?Commodity { return $this->commodity; } + public function setCommodity(?Commodity $commodity): self { $this->commodity = $commodity; return $this; } + + public function getSerialNumber(): ?string { return $this->serialNumber; } + public function setSerialNumber(string $serialNumber): self { $this->serialNumber = $serialNumber; return $this; } + + public function getDateSubmit(): \DateTimeImmutable { return $this->dateSubmit; } + public function setDateSubmit(\DateTimeImmutable $d): self { $this->dateSubmit = $d; return $this; } + + public function getSubmitter(): ?User { return $this->submitter; } + public function setSubmitter(?User $submitter): self { $this->submitter = $submitter; return $this; } + + public function getDescription(): ?string { return $this->description; } + public function setDescription(?string $description): self { $this->description = $description; return $this; } + + public function getWarrantyStartDate(): ?\DateTimeImmutable { return $this->warrantyStartDate; } + public function setWarrantyStartDate(?\DateTimeImmutable $d): self { $this->warrantyStartDate = $d; return $this; } + + public function getWarrantyEndDate(): ?\DateTimeImmutable { return $this->warrantyEndDate; } + public function setWarrantyEndDate(?\DateTimeImmutable $d): self { $this->warrantyEndDate = $d; return $this; } + + public function getStatus(): string { return $this->status; } + public function setStatus(string $status): self { $this->status = $status; return $this; } + + public function getActivation(): string { return $this->activation; } + public function setActivation(string $activation): self { $this->activation = $activation; return $this; } + + public function getActivationAt(): ?\DateTimeImmutable { return $this->activationAt; } + public function setActivationAt(?\DateTimeImmutable $d): self { $this->activationAt = $d; return $this; } + + public function getNotes(): ?string { return $this->notes; } + public function setNotes(?string $notes): self { $this->notes = $notes; return $this; } + + public function getAllocatedToDocumentId(): ?int { return $this->allocatedToDocumentId; } + public function setAllocatedToDocumentId(?int $id): self { $this->allocatedToDocumentId = $id; return $this; } + + public function getAllocatedAt(): ?\DateTimeImmutable { return $this->allocatedAt; } + public function setAllocatedAt(?\DateTimeImmutable $d): self { $this->allocatedAt = $d; return $this; } + + public function getBoundToItemId(): ?int { return $this->boundToItemId; } + public function setBoundToItemId(?int $id): self { $this->boundToItemId = $id; return $this; } + + public function getBoundAt(): ?\DateTimeImmutable { return $this->boundAt; } + public function setBoundAt(?\DateTimeImmutable $d): self { $this->boundAt = $d; return $this; } + + public function getVoidReason(): ?string { return $this->voidReason; } + public function setVoidReason(?string $r): self { $this->voidReason = $r; return $this; } + + public function getCommoditySerial(): ?string { return $this->commoditySerial; } + public function setCommoditySerial(?string $commoditySerial): self { $this->commoditySerial = $commoditySerial; return $this; } + + public function getBuyer(): ?Person { return $this->buyer; } + public function setBuyer(?Person $buyer): self { $this->buyer = $buyer; return $this; } + + public function getActivationTicketCode(): ?string { return $this->activationTicketCode; } + public function setActivationTicketCode(?string $code): self { $this->activationTicketCode = $code; return $this; } + public function getActivationTicketSecret(): ?string { return $this->activationTicketSecret; } + public function setActivationTicketSecret(?string $secret): self { $this->activationTicketSecret = $secret; return $this; } + + public function isUsed(): bool { + return $this->status === self::STATUS_CONSUMED; } - public function getBid(): ?Business - { - return $this->bid; - } - - public function setBid(?Business $bid): static - { - $this->bid = $bid; + public function setUsed(bool $used): self { + $this->status = $used ? self::STATUS_CONSUMED : self::STATUS_AVAILABLE; return $this; } - public function getCommodity(): ?Commodity - { - return $this->commodity; - } - - public function setCommodity(?Commodity $commodity): static - { - $this->commodity = $commodity; + public function setUsedAt(string $usedAt): self { + $this->usedAt = new \DateTimeImmutable($usedAt); return $this; } - public function getSerialNumber(): ?string - { - return $this->serialNumber; - } - - public function setSerialNumber(string $serialNumber): static - { - $this->serialNumber = $serialNumber; + public function setUsedTicketCode(string $ticketCode): self { + $this->usedTicketCode = $ticketCode; return $this; } - - public function getDateSubmit(): ?string - { - return $this->dateSubmit; - } - - public function setDateSubmit(string $dateSubmit): static - { - $this->dateSubmit = $dateSubmit; - return $this; - } - - public function getSubmitter(): ?User - { - return $this->submitter; - } - - public function setSubmitter(?User $submitter): static - { - $this->submitter = $submitter; - return $this; - } - - public function getDescription(): ?string - { - return $this->description; - } - - public function setDescription(?string $description): static - { - $this->description = $description; - return $this; - } - - public function getWarrantyStartDate(): ?string - { - return $this->warrantyStartDate; - } - - public function setWarrantyStartDate(?string $warrantyStartDate): static - { - $this->warrantyStartDate = $warrantyStartDate; - return $this; - } - - public function getWarrantyEndDate(): ?string - { - return $this->warrantyEndDate; - } - - public function setWarrantyEndDate(?string $warrantyEndDate): static - { - $this->warrantyEndDate = $warrantyEndDate; - return $this; - } - - public function getStatus(): ?string - { - return $this->status; - } - - public function setStatus(?string $status): static - { - $this->status = $status; - return $this; - } - - public function getNotes(): ?string - { - return $this->notes; - } - - public function setNotes(?string $notes): static - { - $this->notes = $notes; - return $this; - } - - public function isUsed(): ?bool - { - return $this->used; - } - - public function setUsed(?bool $used): static - { - $this->used = $used; - return $this; - } - - public function getUsedAt(): ?string - { - return $this->usedAt; - } - - public function setUsedAt(?string $usedAt): static - { - $this->usedAt = $usedAt; - return $this; - } - - public function getUsedTicketCode(): ?string - { - return $this->usedTicketCode; - } - - public function setUsedTicketCode(?string $usedTicketCode): static - { - $this->usedTicketCode = $usedTicketCode; - return $this; - } -} \ No newline at end of file +} \ No newline at end of file diff --git a/hesabixCore/src/Entity/StoreroomTicket.php b/hesabixCore/src/Entity/StoreroomTicket.php index b1e3b1e..728e72e 100644 --- a/hesabixCore/src/Entity/StoreroomTicket.php +++ b/hesabixCore/src/Entity/StoreroomTicket.php @@ -77,12 +77,25 @@ class StoreroomTicket #[ORM\Column(nullable: true)] private ?bool $canShare = null; - #[ORM\Column(length: 50, nullable: true)] - private ?string $status = null; // in_progress | done | rejected + #[ORM\Column(length: 255, nullable: true)] private ?string $importWorkflowCode = null; + #[ORM\Column(length: 32, nullable: true)] + private ?string $activationCode = null; + + // Approval fields + #[ORM\Column(nullable: true)] + private ?bool $isPreview = null; + + #[ORM\Column(nullable: true)] + private ?bool $isApproved = null; + + #[ORM\ManyToOne] + #[ORM\JoinColumn(nullable: true)] + private ?User $approvedBy = null; + public function __construct() { $this->storeroomItems = new ArrayCollection(); @@ -339,16 +352,7 @@ class StoreroomTicket return $this; } - public function getStatus(): ?string - { - return $this->status; - } - public function setStatus(?string $status): static - { - $this->status = $status; - return $this; - } public function getImportWorkflowCode(): ?string { @@ -360,4 +364,49 @@ class StoreroomTicket $this->importWorkflowCode = $importWorkflowCode; return $this; } + + public function getActivationCode(): ?string + { + return $this->activationCode; + } + + public function setActivationCode(?string $activationCode): static + { + $this->activationCode = $activationCode; + return $this; + } + + // Approval methods + public function isPreview(): ?bool + { + return $this->isPreview; + } + + public function setIsPreview(?bool $isPreview): static + { + $this->isPreview = $isPreview; + return $this; + } + + public function isApproved(): ?bool + { + return $this->isApproved; + } + + public function setIsApproved(?bool $isApproved): static + { + $this->isApproved = $isApproved; + return $this; + } + + public function getApprovedBy(): ?User + { + return $this->approvedBy; + } + + public function setApprovedBy(?User $approvedBy): static + { + $this->approvedBy = $approvedBy; + return $this; + } } diff --git a/hesabixCore/src/Repository/PlugWarrantySerialRepository.php b/hesabixCore/src/Repository/PlugWarrantySerialRepository.php index 63fa855..746a565 100644 --- a/hesabixCore/src/Repository/PlugWarrantySerialRepository.php +++ b/hesabixCore/src/Repository/PlugWarrantySerialRepository.php @@ -22,7 +22,8 @@ class PlugWarrantySerialRepository extends ServiceEntityRepository public function findByBusiness($bid): array { return $this->createQueryBuilder('p') - ->andWhere('p.bid = :val') + ->join('p.business', 'b') + ->andWhere('b.id = :val') ->setParameter('val', $bid) ->orderBy('p.dateSubmit', 'DESC') ->getQuery() @@ -36,7 +37,7 @@ class PlugWarrantySerialRepository extends ServiceEntityRepository public function findByCommodity($bid, $commodityId): array { return $this->createQueryBuilder('p') - ->andWhere('p.bid = :bid') + ->andWhere('p.business = :bid') ->andWhere('p.commodity = :commodityId') ->setParameter('bid', $bid) ->setParameter('commodityId', $commodityId) @@ -52,7 +53,7 @@ class PlugWarrantySerialRepository extends ServiceEntityRepository public function findByStatus($bid, $status): array { return $this->createQueryBuilder('p') - ->andWhere('p.bid = :bid') + ->andWhere('p.business = :bid') ->andWhere('p.status = :status') ->setParameter('bid', $bid) ->setParameter('status', $status) @@ -72,7 +73,7 @@ class PlugWarrantySerialRepository extends ServiceEntityRepository ->setParameter('serialNumber', $serialNumber); if ($bid) { - $qb->andWhere('p.bid = :bid') + $qb->andWhere('p.business = :bid') ->setParameter('bid', $bid); } @@ -86,7 +87,7 @@ class PlugWarrantySerialRepository extends ServiceEntityRepository { return $this->createQueryBuilder('p') ->leftJoin('p.commodity', 'c') - ->andWhere('p.bid = :bid') + ->andWhere('p.business = :bid') ->andWhere('p.serialNumber LIKE :keyword OR c.name LIKE :keyword OR p.description LIKE :keyword') ->setParameter('bid', $bid) ->setParameter('keyword', '%' . $keyword . '%') diff --git a/hesabixCore/src/Service/Explore.php b/hesabixCore/src/Service/Explore.php index a146dc1..6d6aa05 100644 --- a/hesabixCore/src/Service/Explore.php +++ b/hesabixCore/src/Service/Explore.php @@ -171,6 +171,10 @@ class Explore 'amount' => $doc->getAmount(), 'mdate' => '', 'plugin' => $doc->getPlugin(), + // Approval fields + 'isPreview' => $doc->isPreview(), + 'isApproved' => $doc->isApproved(), + 'approvedBy' => $doc->getApprovedBy() ? self::ExploreUser($doc->getApprovedBy()) : null, ]; } @@ -210,6 +214,9 @@ class Explore $row->setDiscount(0); $temp['tax'] = $row->getTax(); $temp['discount'] = $row->getDiscount(); + + // Approval fields - از سند اصلی گرفته می‌شود + return $temp; } @@ -347,7 +354,7 @@ class Explore 'address' => $person->getAddress(), 'prelabel' => null, 'tags' => $person->getTags(), - 'requireTwoStep' => $person->isRequireTwoStep(), + 'requireTwoStep' => $person->getBid() ? $person->getBid()->isRequireTwoStepApproval() : false, ]; if ($person->getPrelabel()) { $res['prelabel'] = $person->getPrelabel()->getLabel(); @@ -571,6 +578,10 @@ class Explore 'shortlinks' => $item->isShortlinks(), 'walletEnabled' => $item->isWalletEnable(), 'walletMatchBank' => $item->getWalletMatchBank() ? $item->getWalletMatchBank()->getId() : null, + 'requireTwoStepApproval' => $item->isRequireTwoStepApproval(), + 'invoiceApprover' => $item->getInvoiceApprover(), + 'warehouseApprover' => $item->getWarehouseApprover(), + 'financialApprover' => $item->getFinancialApprover(), 'updateSellPrice' => $item->isCommodityUpdateSellPriceAuto(), 'updateBuyPrice' => $item->isCommodityUpdateBuyPriceAuto(), ]; diff --git a/hesabixCore/src/Service/FileStorage.php b/hesabixCore/src/Service/FileStorage.php new file mode 100644 index 0000000..938a4cb --- /dev/null +++ b/hesabixCore/src/Service/FileStorage.php @@ -0,0 +1,42 @@ +getClientOriginalName()); + $relativeDir = 'storage/' . trim($businessId) . '/' . trim($context); + $absDir = rtrim($this->kernel->getProjectDir(), '/').'/var/' . $relativeDir; + if (!is_dir($absDir)) { + @mkdir($absDir, 0775, true); + } + $newName = uniqid('f_', true) . '_' . $safeOriginal; + $file->move($absDir, $newName); + $absPath = $absDir . '/' . $newName; + $size = @filesize($absPath) ?: null; + $mime = function_exists('mime_content_type') ? @mime_content_type($absPath) : null; + return [ + 'relativePath' => $relativeDir . '/' . $newName, + 'originalName' => $file->getClientOriginalName(), + 'size' => $size, + 'mime' => $mime ?: $file->getClientMimeType(), + ]; + } + + public function absolutePath(string $relativePath): string + { + $relativePath = ltrim($relativePath, '/'); + return rtrim($this->kernel->getProjectDir(), '/').'/var/' . $relativePath; + } +} + + diff --git a/public_html/uploads/storeroom/st_6899dc09b4eb05.86469834_4837DCA8-0915-4C7B-9BE1-70C87FF4385A.jpeg b/public_html/uploads/storeroom/st_6899dc09b4eb05.86469834_4837DCA8-0915-4C7B-9BE1-70C87FF4385A.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..ffe5a5e8d4513a06cea05940b27d9a3709592fbb GIT binary patch literal 112180 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

C>bg0nePmkjeCvII8$F;i$2eH%)XoUEmTXN1 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 literal 0 HcmV?d00001 diff --git a/public_html/uploads/storeroom/st_68a1ad4e1bb6c5.93976672_Screenshot_2025-03-30_233821.png b/public_html/uploads/storeroom/st_68a1ad4e1bb6c5.93976672_Screenshot_2025-03-30_233821.png new file mode 100644 index 0000000000000000000000000000000000000000..795a7469726a4f282874606b8b4aff3944c0e7c3 GIT binary patch literal 2082 zcmchY`#TegAIB%0TsOufX0CP8p&k~SONADb%h;&Ly*5U!XD(?6$^A0sl921k$aO*? zQ42{9hj!ey%tAYvOK!QQ>^OhG>3P1te4pp_{=A>}^Z5f_X%6;Q(h^D%002PR8jZpp zcIsg=#C|%w)xXT54ofHuYh?jw9EL6&4iR5-J97Y_IZN`}4bj6b{wvxg3;+Q3{u`nG zkQ#3Q;Ap!w%G~L;=Vy+GAI=xrzg{i}X&72C_W#<$dY*aX#wfbNty;dzLEde>hcerz z6^}JL+inSBY2q0VnQT8V5DHrpM(ue0?MOnxXPafzr*B?YF>spb235Qz6F zad{{u#4u>)(+BRv$8OjrcdKzKIWF%mymgcNZ8FdZ_R+t+6lW(cIR91*7#k|oDfzv2 z>_u8POPNT(heNH^46&KitezUZ_WP)>#&^(C*LfG8e8yd!x*IXOM;ruJshXE4ZX15` zI*9~ERgzklCgVv+7-$=3S!Hd`U=$UZ)Bx4F4GiOtL_*eI7(IyG z#Zs1bCIe>ry8PqrXqn6Qkw1uqncyeL>|a>y(Bo+vubw}AEZ5q<@{okn+)jGYZ`Rme zIb1PYVx%J@dF@Q!Kr4h8u&LzD&oezJD0tU=+>Z;-uqjMxn{Z}6wRmt zT6drkXMUdx>K2UnCidZmlv$ImnMsk!&Fv!`-ZC<+m%X>&^XVk%^N`Cqf@84slbf(J zrJZ*psx5~!(>pRh(O#MFkWW&bR41&Vlf3i#k0UErV0hh9{#u#$T%j(raA&GhKBz;X zT)PUyoAViK)7m&=0yj_WK|Tvr0QYy5!owF4ktid+@^NUvY0m*=T^Ua*kj$+LyVhdT zs%8}L|Gq>}yNcAMSCZO-D|xvj(RIqkZ}kbQddDLE2G7xt zu3Y^b?-I-+WI5cqnXy^ccg!n>?)7rvwX;Aw7hEi2rMI!bao08B!J0yITr_R7H}uDf z>Kn&2-S4TU5%x4)gKQ4=ii%q6TSg0`1V6_p>hC}hDi9lu+c8wO4jT4lqVx82u4o+r zETTbS(|TLrrP>5Q%DAlhDWpY)`njWz2SC0;Q0O>^604^>S{T`Bd$Gmde~s>#UYvD; zdCig77EipI++1<%IlCocD^99ZDI2qD8okRr?V@v#Ic;SX>nivC+E8l=_0Dlpyyok| zuG-3XOAud$QG&hi8wzF3J}3@F)P0rfW1yeR>RU16rB=8APbeeEDTOO|#|!j7yF0ul zgxN{-U#a?d(Md2NXCZK_D}IRrbP@Kk6ca@BJA4Q-vV{r!oMAwF1GvN9HnuC!O{Hg; zK_Lxmdk z0R>!5q-XYan_a1s8X0MQ2JALN8w^aUZ9XN5s6&MR8^%z%)9Ar&m?>lFG7eI$h}Dz% z-*Ayd>o-fT#x_CdX%-)j6!fHJQKqdh#m<`OjipP-ZeRl*qc(2GdZv?f7F1&S7Y9}4 zJ1z!hw@GjHlA;aE7PKOi==!=wxO-7IS4TLG-d7g;g~*(^94rr7hbC8|VB=T6ZI zrX#k_o|LbPJx184@>#e{IV(5BVx(%%`bg>EnLubTA}3KMEjfF6Wn^l000s5Xze0~> zNHf!DDy$sxf%r}+bx`4*T+31?#}3qCBPAOat95z=F>2dVtvui}yBtHUlkSu-jV%aq(nzU_OI>!-TuS#3E4H~X z-ZFi{#0vr(JjJs;&fUbG9ksFa`I~Pb0U?H{X~4&$$z0BLBrO4cnMXw@>bQe=cGeUh z4-NG`3{7$`Ks+Q9T+-t`TXY^=AyqFtU~52o%MW!AUVXpnb*WND8hR6{KGW4;H#B$7 zX5v)TErbwxsSsIL6iSmK?+#8?wnU3O#1ej_nI9PF|M3DG934zpug3`4kPp2Mu)b`M JYP7f>{|_}f(N_Qf literal 0 HcmV?d00001 diff --git a/public_html/uploads/storeroom/st_68a1af08b47979.56036064_Screenshot_2025-03-30_233821.png b/public_html/uploads/storeroom/st_68a1af08b47979.56036064_Screenshot_2025-03-30_233821.png new file mode 100644 index 0000000000000000000000000000000000000000..795a7469726a4f282874606b8b4aff3944c0e7c3 GIT binary patch literal 2082 zcmchY`#TegAIB%0TsOufX0CP8p&k~SONADb%h;&Ly*5U!XD(?6$^A0sl921k$aO*? zQ42{9hj!ey%tAYvOK!QQ>^OhG>3P1te4pp_{=A>}^Z5f_X%6;Q(h^D%002PR8jZpp zcIsg=#C|%w)xXT54ofHuYh?jw9EL6&4iR5-J97Y_IZN`}4bj6b{wvxg3;+Q3{u`nG zkQ#3Q;Ap!w%G~L;=Vy+GAI=xrzg{i}X&72C_W#<$dY*aX#wfbNty;dzLEde>hcerz z6^}JL+inSBY2q0VnQT8V5DHrpM(ue0?MOnxXPafzr*B?YF>spb235Qz6F zad{{u#4u>)(+BRv$8OjrcdKzKIWF%mymgcNZ8FdZ_R+t+6lW(cIR91*7#k|oDfzv2 z>_u8POPNT(heNH^46&KitezUZ_WP)>#&^(C*LfG8e8yd!x*IXOM;ruJshXE4ZX15` zI*9~ERgzklCgVv+7-$=3S!Hd`U=$UZ)Bx4F4GiOtL_*eI7(IyG z#Zs1bCIe>ry8PqrXqn6Qkw1uqncyeL>|a>y(Bo+vubw}AEZ5q<@{okn+)jGYZ`Rme zIb1PYVx%J@dF@Q!Kr4h8u&LzD&oezJD0tU=+>Z;-uqjMxn{Z}6wRmt zT6drkXMUdx>K2UnCidZmlv$ImnMsk!&Fv!`-ZC<+m%X>&^XVk%^N`Cqf@84slbf(J zrJZ*psx5~!(>pRh(O#MFkWW&bR41&Vlf3i#k0UErV0hh9{#u#$T%j(raA&GhKBz;X zT)PUyoAViK)7m&=0yj_WK|Tvr0QYy5!owF4ktid+@^NUvY0m*=T^Ua*kj$+LyVhdT zs%8}L|Gq>}yNcAMSCZO-D|xvj(RIqkZ}kbQddDLE2G7xt zu3Y^b?-I-+WI5cqnXy^ccg!n>?)7rvwX;Aw7hEi2rMI!bao08B!J0yITr_R7H}uDf z>Kn&2-S4TU5%x4)gKQ4=ii%q6TSg0`1V6_p>hC}hDi9lu+c8wO4jT4lqVx82u4o+r zETTbS(|TLrrP>5Q%DAlhDWpY)`njWz2SC0;Q0O>^604^>S{T`Bd$Gmde~s>#UYvD; zdCig77EipI++1<%IlCocD^99ZDI2qD8okRr?V@v#Ic;SX>nivC+E8l=_0Dlpyyok| zuG-3XOAud$QG&hi8wzF3J}3@F)P0rfW1yeR>RU16rB=8APbeeEDTOO|#|!j7yF0ul zgxN{-U#a?d(Md2NXCZK_D}IRrbP@Kk6ca@BJA4Q-vV{r!oMAwF1GvN9HnuC!O{Hg; zK_Lxmdk z0R>!5q-XYan_a1s8X0MQ2JALN8w^aUZ9XN5s6&MR8^%z%)9Ar&m?>lFG7eI$h}Dz% z-*Ayd>o-fT#x_CdX%-)j6!fHJQKqdh#m<`OjipP-ZeRl*qc(2GdZv?f7F1&S7Y9}4 zJ1z!hw@GjHlA;aE7PKOi==!=wxO-7IS4TLG-d7g;g~*(^94rr7hbC8|VB=T6ZI zrX#k_o|LbPJx184@>#e{IV(5BVx(%%`bg>EnLubTA}3KMEjfF6Wn^l000s5Xze0~> zNHf!DDy$sxf%r}+bx`4*T+31?#}3qCBPAOat95z=F>2dVtvui}yBtHUlkSu-jV%aq(nzU_OI>!-TuS#3E4H~X z-ZFi{#0vr(JjJs;&fUbG9ksFa`I~Pb0U?H{X~4&$$z0BLBrO4cnMXw@>bQe=cGeUh z4-NG`3{7$`Ks+Q9T+-t`TXY^=AyqFtU~52o%MW!AUVXpnb*WND8hR6{KGW4;H#B$7 zX5v)TErbwxsSsIL6iSmK?+#8?wnU3O#1ej_nI9PF|M3DG934zpug3`4kPp2Mu)b`M JYP7f>{|_}f(N_Qf literal 0 HcmV?d00001 diff --git a/public_html/uploads/storeroom/st_68a1b350a9b8c3.34236142_NDr4WkNHDI.pdf b/public_html/uploads/storeroom/st_68a1b350a9b8c3.34236142_NDr4WkNHDI.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6717a5f1406ee2cb3c154f2330730befc95dc97d GIT binary patch literal 19375 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 +

+ + + + + diff --git a/webUI/src/components/common/ApprovalStatus.vue b/webUI/src/components/common/ApprovalStatus.vue new file mode 100644 index 0000000..e608050 --- /dev/null +++ b/webUI/src/components/common/ApprovalStatus.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/webUI/src/components/plugins/import-workflow/ImportWorkflowCustoms.vue b/webUI/src/components/plugins/import-workflow/ImportWorkflowCustoms.vue index f3208ce..f93ac61 100644 --- a/webUI/src/components/plugins/import-workflow/ImportWorkflowCustoms.vue +++ b/webUI/src/components/plugins/import-workflow/ImportWorkflowCustoms.vue @@ -22,7 +22,7 @@ no-data-text="اطلاعات ترخیص گمرکی ثبت نشده است" >