From ff8d5b43348f8b3343a7d33798b24ca914f2fac3 Mon Sep 17 00:00:00 2001 From: Ganes556 Date: Thu, 20 Feb 2025 09:32:09 +0800 Subject: [PATCH 1/4] subnavigation in show invoice categories --- .../Accounting/Invoice/UI/IndexInvoices.php | 47 +++++++++++++++++-- .../Accounting/Invoice/UI/IndexRefunds.php | 14 ++++-- .../UI/IndexInvoiceCategories.php | 2 +- .../UI/ShowInvoiceCategory.php | 22 ++++++++- .../WithInvoiceCategorySubNavigation.php | 14 ++++++ .../Org/Accounting/TableInvoiceCategories.vue | 14 ++++-- .../Grp/Org/Accounting/InvoiceCategory.vue | 4 +- routes/grp/web/org/accounting.php | 2 + 8 files changed, 103 insertions(+), 16 deletions(-) diff --git a/app/Actions/Accounting/Invoice/UI/IndexInvoices.php b/app/Actions/Accounting/Invoice/UI/IndexInvoices.php index 682a2c0568..681139c80b 100644 --- a/app/Actions/Accounting/Invoice/UI/IndexInvoices.php +++ b/app/Actions/Accounting/Invoice/UI/IndexInvoices.php @@ -9,6 +9,8 @@ namespace App\Actions\Accounting\Invoice\UI; use App\Actions\Accounting\Invoice\WithInvoicesSubNavigation; +use App\Actions\Accounting\InvoiceCategory\UI\ShowInvoiceCategory; +use App\Actions\Accounting\InvoiceCategory\WithInvoiceCategorySubNavigation; use App\Actions\CRM\Customer\UI\ShowCustomer; use App\Actions\CRM\Customer\UI\ShowCustomerClient; use App\Actions\CRM\Customer\UI\WithCustomerSubNavigation; @@ -25,6 +27,7 @@ use App\Http\Resources\Accounting\InvoicesResource; use App\InertiaTable\InertiaTable; use App\Models\Accounting\Invoice; +use App\Models\Accounting\InvoiceCategory; use App\Models\Catalogue\Shop; use App\Models\CRM\Customer; use App\Models\CRM\WebUser; @@ -48,11 +51,13 @@ class IndexInvoices extends OrgAction use WithFulfilmentCustomerSubNavigation; use WithCustomerSubNavigation; use WithInvoicesSubNavigation; + use WithInvoiceCategorySubNavigation; - private Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|Shop $parent; + + private Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|InvoiceCategory|Shop $parent; private string $bucket = ''; - public function handle(Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|Shop|Order $parent, $prefix = null): LengthAwarePaginator + public function handle(Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|InvoiceCategory|Shop|Order $parent, $prefix = null): LengthAwarePaginator { $globalSearch = AllowedFilter::callback('global', function ($query, $value) { @@ -93,6 +98,8 @@ public function handle(Group|Organisation|Fulfilment|Customer|CustomerClient|Ful $queryBuilder->where('invoices.order_id', $parent->id); } elseif ($parent instanceof Group) { $queryBuilder->where('invoices.group_id', $parent->id); + } elseif ($parent instanceof InvoiceCategory) { + $queryBuilder->where('invoices.invoice_category_id', $parent->id); } else { abort(422); } @@ -142,7 +149,7 @@ public function handle(Group|Organisation|Fulfilment|Customer|CustomerClient|Ful ->withQueryString(); } - public function tableStructure(Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|Shop|Order $parent, $prefix = null): Closure + public function tableStructure(Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|InvoiceCategory|Shop|Order $parent, $prefix = null): Closure { return function (InertiaTable $table) use ($prefix, $parent) { if ($prefix) { @@ -163,6 +170,9 @@ public function tableStructure(Group|Organisation|Fulfilment|Customer|CustomerCl } elseif ($parent instanceof Group) { $stats = $parent->orderingStats; $noResults = __("This group hasn't been invoiced"); + } elseif ($parent instanceof InvoiceCategory) { + $stats = $parent->stats; + $noResults = __("This invoice category hasn't been invoiced"); } else { $stats = $parent->salesStats; } @@ -226,6 +236,8 @@ public function authorize(ActionRequest $request): bool ); } elseif ($this->parent instanceof Group) { return $request->user()->authTo("group-overview"); + } elseif ($this->parent instanceof InvoiceCategory) { + return $request->user()->authTo("accounting.{$this->organisation->id}.view"); } return false; @@ -252,6 +264,8 @@ public function htmlResponse(LengthAwarePaginator $invoices, ActionRequest $requ $subNavigation = $this->getFulfilmentCustomerSubNavigation($this->parent, $request); } elseif ($this->parent instanceof Shop || $this->parent instanceof Fulfilment || $this->parent instanceof Organisation) { $subNavigation = $this->getInvoicesNavigation($this->parent); + } elseif ($this->parent instanceof InvoiceCategory) { + $subNavigation = $this->getInvoiceCategoryNavigation($this->parent); } @@ -274,7 +288,6 @@ public function htmlResponse(LengthAwarePaginator $invoices, ActionRequest $requ 'icon' => 'fal fa-file-invoice-dollar', ]; $afterTitle = [ - 'label' => __('invoices') ]; } elseif ($this->parent instanceof CustomerClient) { @@ -299,6 +312,14 @@ public function htmlResponse(LengthAwarePaginator $invoices, ActionRequest $requ 'icon' => ['fal', 'fa-user'], 'title' => __('customer') ]; + } elseif ($this->parent instanceof InvoiceCategory) { + $iconRight = null; + $model = __('Invoices'); + $title = $this->parent->name; + $icon = [ + 'icon' => ['fal', 'fa-file-invoice-dollar'], + 'title' => __('invoice category') + ]; } $routeName = $request->route()->getName(); @@ -443,6 +464,14 @@ public function inGroup(ActionRequest $request): LengthAwarePaginator return $this->handle(group()); } + public function inInvoiceCategory(Organisation $organisation, InvoiceCategory $invoiceCategory, ActionRequest $request): LengthAwarePaginator + { + $this->parent = $invoiceCategory; + $this->initialisation($invoiceCategory->organisation, $request)->withTab(InvoicesTabsEnum::values()); + + return $this->handle($invoiceCategory, InvoicesTabsEnum::INVOICES->value); + } + /** @noinspection PhpUnusedParameterInspection */ public function inFulfilmentCustomer(Organisation $organisation, Fulfilment $fulfilment, FulfilmentCustomer $fulfilmentCustomer, ActionRequest $request): LengthAwarePaginator { @@ -616,6 +645,16 @@ public function getBreadcrumbs(string $routeName, array $routeParameters): array ] ) ), + 'grp.org.accounting.invoice-categories.show.invoices.index' => + array_merge( + ShowInvoiceCategory::make()->getBreadcrumbs($this->parent, $routeName, $routeParameters), + $headCrumb( + [ + 'name' => $routeName, + 'parameters' => $routeParameters + ], + ) + ), 'grp.overview.ordering.invoices.index' => array_merge( diff --git a/app/Actions/Accounting/Invoice/UI/IndexRefunds.php b/app/Actions/Accounting/Invoice/UI/IndexRefunds.php index 1f97057595..40e41e30c2 100644 --- a/app/Actions/Accounting/Invoice/UI/IndexRefunds.php +++ b/app/Actions/Accounting/Invoice/UI/IndexRefunds.php @@ -23,6 +23,7 @@ use App\Http\Resources\Accounting\InvoicesResource; use App\InertiaTable\InertiaTable; use App\Models\Accounting\Invoice; +use App\Models\Accounting\InvoiceCategory; use App\Models\Catalogue\Shop; use App\Models\CRM\Customer; use App\Models\Dropshipping\CustomerClient; @@ -46,10 +47,10 @@ class IndexRefunds extends OrgAction use WithCustomerSubNavigation; use WithInvoicesSubNavigation; - private Invoice|Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|Shop $parent; + private Invoice|Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|InvoiceCategory|Shop $parent; private string $bucket; - public function handle(Invoice|Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|Shop|Order $parent, $prefix = null): LengthAwarePaginator + public function handle(Invoice|Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|InvoiceCategory|Shop|Order $parent, $prefix = null): LengthAwarePaginator { $globalSearch = AllowedFilter::callback('global', function ($query, $value) { $query->where(function ($query) use ($value) { @@ -84,6 +85,8 @@ public function handle(Invoice|Group|Organisation|Fulfilment|Customer|CustomerCl $queryBuilder->where('invoices.group_id', $parent->id); } elseif ($parent instanceof Invoice) { $queryBuilder->where('invoices.invoice_id', $parent->id); + } elseif ($parent instanceof InvoiceCategory) { + $queryBuilder->where('invoices.invoice_category_id', $parent->id); } else { abort(422); } @@ -133,7 +136,7 @@ public function handle(Invoice|Group|Organisation|Fulfilment|Customer|CustomerCl ->withQueryString(); } - public function tableStructure(Invoice|Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|Shop|Order $parent, $prefix = null): Closure + public function tableStructure(Invoice|Group|Organisation|Fulfilment|Customer|CustomerClient|FulfilmentCustomer|InvoiceCategory|Shop|Order $parent, $prefix = null): Closure { return function (InertiaTable $table) use ($prefix, $parent) { if ($prefix) { @@ -156,6 +159,9 @@ public function tableStructure(Invoice|Group|Organisation|Fulfilment|Customer|Cu } elseif ($parent instanceof Group) { $stats = $parent->orderingStats; $noResults = __("This group hasn't been invoiced"); + } elseif ($parent instanceof InvoiceCategory) { + $stats = $parent->stats; + $noResults = __("This invoice category hasn't been invoiced"); } else { $stats = $parent->salesStats; } @@ -220,6 +226,8 @@ public function authorize(ActionRequest $request): bool return $request->user()->authTo("fulfilment-shop.{$this->fulfilment->id}.view"); } elseif ($this->parent instanceof Group) { return $request->user()->authTo("group-overview"); + } elseif ($this->parent instanceof InvoiceCategory) { + return $request->user()->authTo("accounting.{$this->organisation->id}.view"); } return false; diff --git a/app/Actions/Accounting/InvoiceCategory/UI/IndexInvoiceCategories.php b/app/Actions/Accounting/InvoiceCategory/UI/IndexInvoiceCategories.php index 582b9ad403..eac861cce4 100644 --- a/app/Actions/Accounting/InvoiceCategory/UI/IndexInvoiceCategories.php +++ b/app/Actions/Accounting/InvoiceCategory/UI/IndexInvoiceCategories.php @@ -55,7 +55,7 @@ public function handle(Organisation $parent, $prefix = null): LengthAwarePaginat 'invoice_category_stats.number_invoices_type_invoice as number_type_invoices', 'invoice_category_stats.number_invoices_type_refund as number_type_refunds', ]) - ->allowedSorts(['name', 'state', 'number_type_invoices', 'amount', 'number_type_refunds']) + ->allowedSorts(['name', 'id' ,'state', 'number_type_invoices', 'amount', 'number_type_refunds']) ->allowedFilters([$globalSearch]) ->withPaginator($prefix, tableName: request()->route()->getName()) ->withQueryString(); diff --git a/app/Actions/Accounting/InvoiceCategory/UI/ShowInvoiceCategory.php b/app/Actions/Accounting/InvoiceCategory/UI/ShowInvoiceCategory.php index e0d9d5fa82..eadfe9b021 100644 --- a/app/Actions/Accounting/InvoiceCategory/UI/ShowInvoiceCategory.php +++ b/app/Actions/Accounting/InvoiceCategory/UI/ShowInvoiceCategory.php @@ -65,7 +65,7 @@ public function htmlResponse(InvoiceCategory $invoiceCategory, ActionRequest $re 'subNavigation' => $this->getInvoiceCategoryNavigation($invoiceCategory), 'model' => __('Invoice Category'), 'icon' => [ - 'icon' => ['fal', 'fa-money-check-alt'], + 'icon' => ['fal', 'fa-sitemap'], 'title' => __('invoice category') ], 'title' => $invoiceCategory->name, @@ -144,6 +144,26 @@ public function getBreadcrumbs(InvoiceCategory $invoiceCategory, string $routeNa $suffix ), ), + 'grp.org.accounting.invoice-categories.show.invoices.index' => array_merge( + ShowAccountingDashboard::make()->getBreadcrumbs( + 'grp.org.accounting.dashboard', + Arr::only($routeParameters, ['organisation']) + ), + $headCrumb( + $invoiceCategory, + [ + 'index' => [ + 'name' => 'grp.org.accounting.invoice-categories.index', + 'parameters' => Arr::only($routeParameters, ['organisation']) + ], + 'model' => [ + 'name' => 'grp.org.accounting.invoice-categories.show', + 'parameters' => Arr::only($routeParameters, ['organisation', 'invoiceCategory']) + ] + ], + $suffix + ), + ), default => [] }; } diff --git a/app/Actions/Accounting/InvoiceCategory/WithInvoiceCategorySubNavigation.php b/app/Actions/Accounting/InvoiceCategory/WithInvoiceCategorySubNavigation.php index 2093de2587..bfb1d9d7b3 100644 --- a/app/Actions/Accounting/InvoiceCategory/WithInvoiceCategorySubNavigation.php +++ b/app/Actions/Accounting/InvoiceCategory/WithInvoiceCategorySubNavigation.php @@ -27,6 +27,20 @@ protected function getInvoiceCategoryNavigation(InvoiceCategory $invoiceCategory ], ], ], + [ + "label" => __('Invoices'), + "route" => [ + "name" => 'grp.org.accounting.invoice-categories.show.invoices.index', + "parameters" => [ + 'organisation' => $invoiceCategory->organisation->slug, + 'invoiceCategory' => $invoiceCategory->slug + ], + ], + 'leftIcon' => [ + 'icon' => ['fal', 'fa-file-invoice-dollar'], + 'tooltip' => __('Invoices') + ] + ], // [ // "number" => $numberUnpaid, diff --git a/resources/js/Components/Tables/Grp/Org/Accounting/TableInvoiceCategories.vue b/resources/js/Components/Tables/Grp/Org/Accounting/TableInvoiceCategories.vue index 8880ecef1c..55f4b70bf5 100644 --- a/resources/js/Components/Tables/Grp/Org/Accounting/TableInvoiceCategories.vue +++ b/resources/js/Components/Tables/Grp/Org/Accounting/TableInvoiceCategories.vue @@ -37,8 +37,8 @@ function invoiceRoute(invoiceCategory: {}) { switch (route().current()) { case "grp.org.accounting.invoice-categories.index": return route( - "grp.org.accounting.invoices.index", - [route().params["organisation"]]) + "grp.org.accounting.invoice-categories.show.invoices.index", + [route().params["organisation"],invoiceCategory.slug]) default: return '' } @@ -47,9 +47,13 @@ function invoiceRoute(invoiceCategory: {}) { function refundRoute(invoiceCategory: {}) { switch (route().current()) { case "grp.org.accounting.invoice-categories.index": - return route( - "grp.org.accounting.refunds.index", - [route().params["organisation"]]) + return route( + "grp.org.accounting.invoice-categories.show.invoices.index", + { + "organisation": route().params["organisation"], + "invoiceCategory": invoiceCategory.slug, + 'tab': 'refunds' + }) default: return '' } diff --git a/resources/js/Pages/Grp/Org/Accounting/InvoiceCategory.vue b/resources/js/Pages/Grp/Org/Accounting/InvoiceCategory.vue index daed6f0627..fbbcdf4bf1 100644 --- a/resources/js/Pages/Grp/Org/Accounting/InvoiceCategory.vue +++ b/resources/js/Pages/Grp/Org/Accounting/InvoiceCategory.vue @@ -7,7 +7,7 @@ From 63e90b382565e3ee7df19a8f8150b659f3e3d7f5 Mon Sep 17 00:00:00 2001 From: KirinZero0 Date: Thu, 20 Feb 2025 10:26:42 +0800 Subject: [PATCH 4/4] Import Supplier Product (working) --- .../SupplyChain/SupplierProductImport.php | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/app/Imports/SupplyChain/SupplierProductImport.php b/app/Imports/SupplyChain/SupplierProductImport.php index 7be5998ba1..82efdacf7a 100644 --- a/app/Imports/SupplyChain/SupplierProductImport.php +++ b/app/Imports/SupplyChain/SupplierProductImport.php @@ -10,6 +10,7 @@ namespace App\Imports\SupplyChain; use App\Actions\SupplyChain\SupplierProduct\StoreSupplierProduct; +use App\Actions\SupplyChain\SupplierProduct\UpdateSupplierProduct; use App\Imports\WithImport; use App\Models\Helpers\Upload; use App\Models\SupplyChain\Supplier; @@ -35,33 +36,44 @@ public function __construct(Supplier $supplier, Upload $upload) public function storeModel($row, $uploadRecord): void { $sanitizedData = $this->processExcelData([$row]); - $fields = - array_merge( - array_keys( - $this->rules() - ) - ); - - if ($sanitizedData['availability'] == 'Available') { + $validatedData = array_intersect_key($sanitizedData, array_flip(array_keys($this->rules()))); + + if ($validatedData['availability'] == 'Available') { $availability = true; } else { $availability = false; } $modelData = [ - 'code' => $sanitizedData['suppliers_product_code'], - 'name' => $sanitizedData['suppliers_unit_description'], + 'code' => $validatedData['suppliers_product_code'], + 'name' => $validatedData['suppliers_unit_description'], 'is_available' => $availability, - 'cost' => $sanitizedData['unit_cost'], - 'units_per_pack' => $sanitizedData['units_per_sko'], - 'units_per_carton' => $sanitizedData['skos_per_carton'], - 'cbm' => $sanitizedData['carton_cbm'], + 'cost' => $validatedData['unit_cost'], + 'units_per_pack' => $validatedData['units_per_sko'], + 'units_per_carton' => $validatedData['skos_per_carton'], + 'cbm' => $validatedData['carton_cbm'], ]; + try { - StoreSupplierProduct::run( - $this->scope, - $modelData - ); + $partKey = $validatedData['id_supplier_part_key']; + $existingProduct = null; + if (is_numeric($partKey)) { + $partKey = (int) $partKey; + $existingProduct = $this->scope->supplierProducts() + ->where('id', $partKey) + ->first(); + } + + $isNew = is_string($validatedData['id_supplier_part_key']) + && strtolower($validatedData['id_supplier_part_key']) === 'new'; + + if ($existingProduct) { + UpdateSupplierProduct::run($existingProduct, $modelData); + } elseif ($isNew) { + StoreSupplierProduct::run($this->scope, $modelData); + } else { + throw new Exception("Part key not found"); + } $this->setRecordAsCompleted($uploadRecord); } catch (Exception $e) {