diff --git a/hesabixCore/migrations/Version20250826214359.php b/hesabixCore/migrations/Version20250826214359.php new file mode 100644 index 0000000..3f92bcd --- /dev/null +++ b/hesabixCore/migrations/Version20250826214359.php @@ -0,0 +1,41 @@ +addSql(<<<'SQL' + ALTER TABLE import_workflow ADD CONSTRAINT FK_CC6A26EC40C1FEA7 FOREIGN KEY (year_id) REFERENCES year (id) + SQL); + $this->addSql(<<<'SQL' + CREATE INDEX IDX_CC6A26EC40C1FEA7 ON import_workflow (year_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 import_workflow DROP FOREIGN KEY FK_CC6A26EC40C1FEA7 + SQL); + $this->addSql(<<<'SQL' + DROP INDEX IDX_CC6A26EC40C1FEA7 ON import_workflow + SQL); + } +} diff --git a/hesabixCore/src/Controller/Plugins/PlugImportWorkflowController.php b/hesabixCore/src/Controller/Plugins/PlugImportWorkflowController.php index a85a58c..1eef33d 100644 --- a/hesabixCore/src/Controller/Plugins/PlugImportWorkflowController.php +++ b/hesabixCore/src/Controller/Plugins/PlugImportWorkflowController.php @@ -130,7 +130,10 @@ class PlugImportWorkflowController extends AbstractController 'supplierName' => $workflow->getSupplierName(), 'totalAmount' => $workflow->getComputedTotalAmount(), 'currency' => $workflow->getCurrency(), - 'submitter' => $workflow->getSubmitter()->getFullName() + 'submitter' => $workflow->getSubmitter()->getFullName(), + 'totalPayments' => $this->calculateTotalPayments($workflow), + 'totalPaymentsIRR' => $this->calculateTotalPaymentsIRR($workflow), + 'exchangeRate' => $workflow->getExchangeRate() ]; } @@ -256,6 +259,7 @@ class PlugImportWorkflowController extends AbstractController $workflow->setCode($provider->getAccountingCode($request->headers->get('activeBid'), 'ImportWorkflow')); $workflow->setTitle($data['title'] ?? ''); $workflow->setBusiness($acc['bid']); + $workflow->setYear($acc['year']); $workflow->setSubmitter($user); $workflow->setDescription($data['description'] ?? ''); $workflow->setSupplierName($data['supplierName'] ?? ''); @@ -444,6 +448,9 @@ class PlugImportWorkflowController extends AbstractController 'supplierEmail' => $workflow->getSupplierEmail(), 'currency' => $workflow->getCurrency(), 'exchangeRate' => $workflow->getExchangeRate(), + 'totalAmount' => $workflow->getComputedTotalAmount(), + 'totalPayments' => $this->calculateTotalPayments($workflow), + 'totalPaymentsIRR' => $this->calculateTotalPaymentsIRR($workflow), 'submitter' => $workflow->getSubmitter()->getFullName(), 'items' => [], 'payments' => [], @@ -494,7 +501,8 @@ class PlugImportWorkflowController extends AbstractController 'status' => $payment->getStatus(), 'description' => $payment->getDescription(), 'receiptNumber' => $payment->getReceiptNumber(), - 'dateSubmit' => $payment->getDateSubmit() + 'dateSubmit' => $payment->getDateSubmit(), + 'paymentMode' => $payment->getPaymentMode() ]; } @@ -1513,6 +1521,7 @@ class PlugImportWorkflowController extends AbstractController $p = new ImportWorkflowPayment(); $p->setImportWorkflow($workflow); $p->setType($data['type'] ?? 'other'); + $p->setPaymentMode($data['paymentMode'] ?? 'foreign'); $p->setAmount(($data['amount'] ?? '0')); $p->setCurrency($data['currency'] ?? 'IRR'); $p->setAmountIRR(isset($data['amountIRR']) && $data['amountIRR'] !== '' ? (string)$data['amountIRR'] : null); @@ -1601,6 +1610,7 @@ class PlugImportWorkflowController extends AbstractController } $data = json_decode($request->getContent() ?: '{}', true); if (isset($data['type'])) $p->setType($data['type']); + if (isset($data['paymentMode'])) $p->setPaymentMode($data['paymentMode']); if (isset($data['amount'])) $p->setAmount((string)$data['amount']); if (isset($data['currency'])) $p->setCurrency($data['currency']); if (isset($data['amountIRR'])) $p->setAmountIRR($data['amountIRR'] !== '' ? (string)$data['amountIRR'] : null); @@ -2292,4 +2302,42 @@ class PlugImportWorkflowController extends AbstractController return $s; } + + private function calculateTotalPayments(ImportWorkflow $workflow): float + { + $total = 0.0; + $exchangeRate = (float) $workflow->getExchangeRate(); + + foreach ($workflow->getPayments() as $payment) { + $amount = (float) $payment->getAmount(); + + if ($payment->getPaymentMode() === 'foreign') { + $total += $amount; + } else { + if ($exchangeRate > 0) { + $total += $amount / $exchangeRate; + } + } + } + + return round($total, 2); + } + + private function calculateTotalPaymentsIRR(ImportWorkflow $workflow): float + { + $total = 0.0; + $exchangeRate = (float) $workflow->getExchangeRate(); + + foreach ($workflow->getPayments() as $payment) { + $amount = (float) $payment->getAmount(); + + if ($payment->getPaymentMode() === 'foreign') { + $total += $amount * $exchangeRate; + } else { + $total += $amount; + } + } + + return round($total, 2); + } } diff --git a/hesabixCore/src/Entity/ImportWorkflow.php b/hesabixCore/src/Entity/ImportWorkflow.php index 8dd4992..b60d9f6 100644 --- a/hesabixCore/src/Entity/ImportWorkflow.php +++ b/hesabixCore/src/Entity/ImportWorkflow.php @@ -66,6 +66,11 @@ class ImportWorkflow #[ORM\Column(type: Types::DECIMAL, precision: 15, scale: 2, nullable: true)] private ?string $exchangeRate = null; + #[ORM\ManyToOne(inversedBy: 'importWorkflows')] + #[ORM\JoinColumn(nullable: true)] + #[Ignore] + private ?Year $year = null; + #[ORM\OneToMany(mappedBy: 'importWorkflow', targetEntity: ImportWorkflowItem::class, orphanRemoval: true)] private Collection $items; @@ -266,6 +271,17 @@ class ImportWorkflow return $this; } + public function getYear(): ?Year + { + return $this->year; + } + + public function setYear(?Year $year): static + { + $this->year = $year; + return $this; + } + public function getItems(): Collection { return $this->items; diff --git a/hesabixCore/src/Entity/ImportWorkflowPayment.php b/hesabixCore/src/Entity/ImportWorkflowPayment.php index fe72698..4ba44db 100644 --- a/hesabixCore/src/Entity/ImportWorkflowPayment.php +++ b/hesabixCore/src/Entity/ImportWorkflowPayment.php @@ -23,6 +23,9 @@ class ImportWorkflowPayment #[ORM\Column(length: 255)] private ?string $type = null; + #[ORM\Column(length: 255)] + private ?string $paymentMode = null; + #[ORM\Column(type: Types::DECIMAL, precision: 15, scale: 2)] private ?string $amount = null; @@ -63,6 +66,7 @@ class ImportWorkflowPayment { $this->dateSubmit = date('Y-m-d H:i:s'); $this->status = 'pending'; + $this->paymentMode = 'foreign'; } public function getId(): ?int @@ -92,6 +96,17 @@ class ImportWorkflowPayment return $this; } + public function getPaymentMode(): ?string + { + return $this->paymentMode; + } + + public function setPaymentMode(string $paymentMode): static + { + $this->paymentMode = $paymentMode; + return $this; + } + public function getAmount(): ?string { return $this->amount; diff --git a/hesabixCore/src/Entity/Year.php b/hesabixCore/src/Entity/Year.php index b7b9062..f9d0325 100644 --- a/hesabixCore/src/Entity/Year.php +++ b/hesabixCore/src/Entity/Year.php @@ -61,12 +61,17 @@ class Year #[ORM\OneToMany(mappedBy: 'year', targetEntity: PreInvoiceDoc::class, orphanRemoval: true)] private Collection $preInvoiceDocs; + #[ORM\OneToMany(mappedBy: 'year', targetEntity: ImportWorkflow::class, orphanRemoval: true)] + #[Ignore] + private Collection $importWorkflows; + public function __construct() { $this->hesabdariDocs = new ArrayCollection(); $this->hesabdariRows = new ArrayCollection(); $this->storeroomTickets = new ArrayCollection(); $this->preInvoiceDocs = new ArrayCollection(); + $this->importWorkflows = new ArrayCollection(); } public function getId(): ?int @@ -265,4 +270,34 @@ class Year return $this; } + + /** + * @return Collection + */ + public function getImportWorkflows(): Collection + { + return $this->importWorkflows; + } + + public function addImportWorkflow(ImportWorkflow $importWorkflow): static + { + if (!$this->importWorkflows->contains($importWorkflow)) { + $this->importWorkflows->add($importWorkflow); + $importWorkflow->setYear($this); + } + + return $this; + } + + public function removeImportWorkflow(ImportWorkflow $importWorkflow): static + { + if ($this->importWorkflows->removeElement($importWorkflow)) { + // set the owning side to null (unless already changed) + if ($importWorkflow->getYear() === $this) { + $importWorkflow->setYear(null); + } + } + + return $this; + } } diff --git a/webUI/package.json b/webUI/package.json index e9b282f..98c9656 100755 --- a/webUI/package.json +++ b/webUI/package.json @@ -27,13 +27,16 @@ "animate.css": "^4.1.1", "apexcharts": "^4.6.0", "axios": "^1.8.4", + "chart.js": "^4.5.0", "date-fns": "^4.1.0", "date-fns-jalali": "^3.2.0-0", + "dayjs": "^1.11.13", "dompurify": "^3.2.6", "downloadjs": "^1.4.7", "file-saver": "^2.0.5", "html5-qrcode": "^2.3.8", "jalali-moment": "^3.3.11", + "jalaliday": "^3.1.0", "libphonenumber-js": "^1.12.7", "lodash": "^4.17.21", "marked": "^16.1.0", diff --git a/webUI/src/components/plugins/import-workflow/DashboardTab.vue b/webUI/src/components/plugins/import-workflow/DashboardTab.vue new file mode 100644 index 0000000..638c518 --- /dev/null +++ b/webUI/src/components/plugins/import-workflow/DashboardTab.vue @@ -0,0 +1,502 @@ + + + + + diff --git a/webUI/src/components/plugins/import-workflow/FinancialTab.vue b/webUI/src/components/plugins/import-workflow/FinancialTab.vue new file mode 100644 index 0000000..c6600d3 --- /dev/null +++ b/webUI/src/components/plugins/import-workflow/FinancialTab.vue @@ -0,0 +1,550 @@ + + + + + diff --git a/webUI/src/components/plugins/import-workflow/ImportWorkflowPayments.vue b/webUI/src/components/plugins/import-workflow/ImportWorkflowPayments.vue index adc9136..a9b8b02 100644 --- a/webUI/src/components/plugins/import-workflow/ImportWorkflowPayments.vue +++ b/webUI/src/components/plugins/import-workflow/ImportWorkflowPayments.vue @@ -3,12 +3,23 @@

پرداخت‌ها

- - افزودن پرداخت - +
+ + + افزودن پرداخت + +
- + + diff --git a/webUI/src/components/plugins/import-workflow/ProductsTab.vue b/webUI/src/components/plugins/import-workflow/ProductsTab.vue new file mode 100644 index 0000000..528786b --- /dev/null +++ b/webUI/src/components/plugins/import-workflow/ProductsTab.vue @@ -0,0 +1,689 @@ + + + + + diff --git a/webUI/src/components/plugins/import-workflow/SuppliersTab.vue b/webUI/src/components/plugins/import-workflow/SuppliersTab.vue new file mode 100644 index 0000000..ae319a8 --- /dev/null +++ b/webUI/src/components/plugins/import-workflow/SuppliersTab.vue @@ -0,0 +1,592 @@ + + + + + diff --git a/webUI/src/router/index.ts b/webUI/src/router/index.ts index fb60f90..84e48be 100755 --- a/webUI/src/router/index.ts +++ b/webUI/src/router/index.ts @@ -1153,6 +1153,16 @@ const router = createRouter({ name: 'import_workflow_intro', component: () => import('../views/acc/plugins/import-workflow/intro.vue'), + }, + { + path: 'plugins/import-workflow/reports', + name: 'import_workflow_reports', + component: () => + import('../views/acc/plugins/import-workflow/reports.vue'), + meta: { + 'title': 'گزارشات پرونده‌های واردات', + 'login': true, + } } ], }, diff --git a/webUI/src/views/acc/App.vue b/webUI/src/views/acc/App.vue index 48471ab..a43f598 100755 --- a/webUI/src/views/acc/App.vue +++ b/webUI/src/views/acc/App.vue @@ -217,6 +217,7 @@ export default { { path: '/acc/plugins/tax/settings', key: 'T', label: this.$t('drawer.tax_settings'), ctrl: true, shift: true, permission: () => this.permissions.settings && this.isPluginActive('taxsettings') }, { path: '/acc/plugins/custominvoice/templates', key: 'I', label: 'قالب‌های فاکتور', ctrl: true, shift: true, permission: () => this.permissions.settings && this.isPluginActive('custominvoice') }, { path: '/acc/plugins/import-workflow', key: 'I', label: 'مدیریت واردات کالا', ctrl: true, shift: true, permission: () => this.permissions.plugImportWorkflow }, + { path: '/acc/plugins/import-workflow/reports', key: 'I', label: 'گزارشات پرونده‌های واردات', ctrl: true, shift: true, permission: () => this.permissions.plugImportWorkflow }, { path: '/acc/storeroom/tickets/list/helper', key: 'I', label: this.$t('drawer.storeroom_ticket_helper'), ctrl: true, shift: true, permission: () => (this.permissions.storehelper || this.permissions.store) && this.isPluginActive('accpro') }, ]; }, @@ -821,6 +822,12 @@ export default { + + + گزارشات پرونده‌های واردات + {{ getShortcutKey('/acc/plugins/import-workflow/reports') }} + + لیست پرونده‌های واردات diff --git a/webUI/src/views/acc/plugins/import-workflow/list.vue b/webUI/src/views/acc/plugins/import-workflow/list.vue index a9e0359..e706764 100644 --- a/webUI/src/views/acc/plugins/import-workflow/list.vue +++ b/webUI/src/views/acc/plugins/import-workflow/list.vue @@ -145,6 +145,15 @@ class="ml-2" /> + + گزارشات + { // loadStats() } +const goToReports = () => { + router.push('/acc/plugins/import-workflow/reports') +} + const formatNumber = (number) => { if (!number) return '0' return new Intl.NumberFormat('fa-IR').format(number) diff --git a/webUI/src/views/acc/plugins/import-workflow/reports.vue b/webUI/src/views/acc/plugins/import-workflow/reports.vue new file mode 100644 index 0000000..f432396 --- /dev/null +++ b/webUI/src/views/acc/plugins/import-workflow/reports.vue @@ -0,0 +1,386 @@ + + + + + \ No newline at end of file diff --git a/webUI/src/views/acc/plugins/import-workflow/view.vue b/webUI/src/views/acc/plugins/import-workflow/view.vue index 2f8ce6a..4002083 100644 --- a/webUI/src/views/acc/plugins/import-workflow/view.vue +++ b/webUI/src/views/acc/plugins/import-workflow/view.vue @@ -36,6 +36,65 @@