diff --git a/app/Actions/Accounting/PaymentAccount/HydratePaymentAccount.php b/app/Actions/Accounting/PaymentAccount/HydratePaymentAccount.php index 417d64da6d..2a9d635df8 100644 --- a/app/Actions/Accounting/PaymentAccount/HydratePaymentAccount.php +++ b/app/Actions/Accounting/PaymentAccount/HydratePaymentAccount.php @@ -8,28 +8,27 @@ namespace App\Actions\Accounting\PaymentAccount; -use App\Actions\HydrateModel; +use App\Actions\Accounting\PaymentAccount\Hydrators\PaymentAccountHydratePAS; use App\Actions\Accounting\PaymentAccount\Hydrators\PaymentAccountHydratePayments; +use App\Actions\Traits\Hydrators\WithHydrateCommand; use App\Models\Accounting\PaymentAccount; -use Illuminate\Support\Collection; -class HydratePaymentAccount extends HydrateModel +class HydratePaymentAccount { - public string $commandSignature = 'hydrate:payment_account {slugs?*} {--o|org=*} {--g|group=*} '; - - public function handle(PaymentAccount $paymentAccount): void - { - PaymentAccountHydratePayments::run($paymentAccount); - } + use WithHydrateCommand; + public string $commandSignature = 'hydrate:payment_accounts {organisations?*} {--S|shop= shop slug} {--s|slug=} '; - protected function getModel(string $slug): PaymentAccount + public function __construct() { - return PaymentAccount::where('slug', $slug)->first(); + $this->model = PaymentAccount::class; } - protected function getAllModels(): Collection + public function handle(PaymentAccount $paymentAccount): void { - return PaymentAccount::withTrashed()->get(); + PaymentAccountHydratePayments::run($paymentAccount); + PaymentAccountHydratePAS::run($paymentAccount); } + + } diff --git a/app/Actions/Accounting/PaymentAccount/Hydrators/PaymentAccountHydratePAS.php b/app/Actions/Accounting/PaymentAccount/Hydrators/PaymentAccountHydratePAS.php index 396ef134ed..405d5e993b 100644 --- a/app/Actions/Accounting/PaymentAccount/Hydrators/PaymentAccountHydratePAS.php +++ b/app/Actions/Accounting/PaymentAccount/Hydrators/PaymentAccountHydratePAS.php @@ -47,12 +47,13 @@ public function handle(PaymentAccount $paymentAccount): void $stats, $this->getEnumStats( model: 'pas', - field: 'state', + field: 'payment_account_shop.state', enum: PaymentAccountShopStateEnum::class, models: PaymentAccountShop::class, where: function ($q) use ($paymentAccount) { $q->where('payment_account_id', $paymentAccount->id); - } + }, + fieldStatsLabel: 'state' ) ); diff --git a/app/Actions/Accounting/PaymentAccount/UI/IndexPaymentAccounts.php b/app/Actions/Accounting/PaymentAccount/UI/IndexPaymentAccounts.php index b0f3e767cd..261f4a3a58 100644 --- a/app/Actions/Accounting/PaymentAccount/UI/IndexPaymentAccounts.php +++ b/app/Actions/Accounting/PaymentAccount/UI/IndexPaymentAccounts.php @@ -24,6 +24,7 @@ use Closure; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Http\Resources\Json\AnonymousResourceCollection; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use Inertia\Inertia; use Inertia\Response; @@ -36,6 +37,13 @@ class IndexPaymentAccounts extends OrgAction public function handle(Group|Shop|Organisation|OrgPaymentServiceProvider $parent, $prefix = null): LengthAwarePaginator { + $organisation = null; + if ($parent instanceof Organisation) { + $organisation = $parent; + } elseif (!$parent instanceof Group) { + $organisation = $parent->organisation; + } + $globalSearch = AllowedFilter::callback('global', function ($query, $value) { $query->where(function ($query) use ($value) { $query->whereStartWith('payment_accounts.code', $value) @@ -59,18 +67,10 @@ public function handle(Group|Shop|Organisation|OrgPaymentServiceProvider $parent $queryBuilder->where('payment_accounts.group_id', $parent->id); } - /* - foreach ($this->elementGroups as $key => $elementGroup) { - $queryBuilder->whereElementGroup( - key: $key, - allowedElements: array_keys($elementGroup['elements']), - engine: $elementGroup['engine'], - prefix: $prefix - ); - } - */ + $queryBuilder->leftjoin('organisations', 'payment_accounts.organisation_id', 'organisations.id'); - return $queryBuilder + + $queryBuilder ->defaultSort('payment_accounts.code') ->select([ 'payment_accounts.id as id', @@ -82,14 +82,16 @@ public function handle(Group|Shop|Organisation|OrgPaymentServiceProvider $parent 'payment_service_providers.slug as payment_service_provider_slug', 'payment_service_providers.name as payment_service_provider_name', 'payment_service_providers.code as payment_service_provider_code', - 'organisations.name as organisation_name', - 'organisations.slug as organisation_slug', - 'payment_account_stats.number_pas_state_active' - ]) - ->leftJoin('payment_account_shop', 'payment_account_shop.payment_account_id', 'payment_accounts.id') - ->leftJoin('payment_account_stats', 'payment_accounts.id', 'payment_account_stats.payment_account_id') + 'payment_account_stats.number_pas_state_active', + 'org_amount_successfully_paid' + ]); + if ($organisation) { + $queryBuilder->addSelect(DB::raw("'{$organisation->currency->code}' AS org_currency_code")); + } + + return $queryBuilder->leftJoin('payment_account_stats', 'payment_accounts.id', 'payment_account_stats.payment_account_id') ->leftJoin('payment_service_providers', 'payment_service_provider_id', 'payment_service_providers.id') - ->allowedSorts(['code', 'name', 'number_payments','payment_service_provider_code','number_pas_state_active']) + ->allowedSorts(['code', 'name', 'number_payments', 'payment_service_provider_code', 'number_pas_state_active', 'org_amount_successfully_paid']) ->allowedFilters([$globalSearch]) ->withPaginator($prefix, tableName: request()->route()->getName()) ->withQueryString(); @@ -137,8 +139,10 @@ public function tableStructure(Group|Shop|Organisation|OrgPaymentServiceProvider $table->column(key: 'number_pas_state_active', label: __('shops'), canBeHidden: false, sortable: true, searchable: true); - $table->column(key: 'number_payments', label: __('payments'), canBeHidden: false, sortable: true, searchable: true) - ->defaultSort('code'); + $table->column(key: 'number_payments', label: __('payments'), canBeHidden: false, sortable: true, searchable: true); + $table->column(key: 'org_amount_successfully_paid', label: __('amount'), canBeHidden: false, sortable: true, searchable: true); + + $table->defaultSort('code'); }; } @@ -237,8 +241,8 @@ public function htmlResponse(LengthAwarePaginator $paymentAccounts, ActionReques ], - 'shops_list' => ShopResource::collection($shops), - 'data' => PaymentAccountsResource::collection($paymentAccounts) + 'shops_list' => ShopResource::collection($shops), + 'data' => PaymentAccountsResource::collection($paymentAccounts) ] @@ -302,7 +306,7 @@ public function getBreadcrumbs(string $routeName, array $routeParameters): array ShowGroupOverviewHub::make()->getBreadcrumbs(), $headCrumb( [ - 'name' => $routeName, + 'name' => $routeName, 'parameters' => $routeParameters ] ) diff --git a/app/Actions/Fulfilment/PalletReturn/UI/IndexPalletReturns.php b/app/Actions/Fulfilment/PalletReturn/UI/IndexPalletReturns.php index 6388f09068..20cd39f3d9 100644 --- a/app/Actions/Fulfilment/PalletReturn/UI/IndexPalletReturns.php +++ b/app/Actions/Fulfilment/PalletReturn/UI/IndexPalletReturns.php @@ -68,6 +68,7 @@ public function authorize(ActionRequest $request): bool public function asController(Organisation $organisation, Fulfilment $fulfilment, ActionRequest $request): LengthAwarePaginator { $this->parent = $fulfilment; + $this->restriction = 'all'; $this->initialisationFromFulfilment($fulfilment, $request)->withTab(PalletReturnsTabsEnum::values()); return $this->handle($fulfilment, PalletReturnsTabsEnum::RETURNS->value); @@ -82,6 +83,15 @@ public function inFulfilmentConfirmed(Organisation $organisation, Fulfilment $fu return $this->handle($fulfilment, PalletReturnsTabsEnum::RETURNS->value); } + public function inFulfilmentNew(Organisation $organisation, Fulfilment $fulfilment, ActionRequest $request): LengthAwarePaginator + { + $this->parent = $fulfilment; + $this->restriction = 'new'; + $this->initialisationFromFulfilment($fulfilment, $request)->withTab(PalletReturnsTabsEnum::values()); + + return $this->handle($fulfilment, PalletReturnsTabsEnum::RETURNS->value); + } + public function inFulfilmentPicking(Organisation $organisation, Fulfilment $fulfilment, ActionRequest $request): LengthAwarePaginator { $this->parent = $fulfilment; @@ -100,10 +110,29 @@ public function inFulfilmentPicked(Organisation $organisation, Fulfilment $fulfi return $this->handle($fulfilment, PalletReturnsTabsEnum::RETURNS->value); } + public function inFulfilmentDispatched(Organisation $organisation, Fulfilment $fulfilment, ActionRequest $request): LengthAwarePaginator + { + $this->parent = $fulfilment; + $this->restriction = 'dispatched'; + $this->initialisationFromFulfilment($fulfilment, $request)->withTab(PalletReturnsTabsEnum::values()); + + return $this->handle($fulfilment, PalletReturnsTabsEnum::RETURNS->value); + } + + public function inFulfilmentCancelled(Organisation $organisation, Fulfilment $fulfilment, ActionRequest $request): LengthAwarePaginator + { + $this->parent = $fulfilment; + $this->restriction = 'cancelled'; + $this->initialisationFromFulfilment($fulfilment, $request)->withTab(PalletReturnsTabsEnum::values()); + + return $this->handle($fulfilment, PalletReturnsTabsEnum::RETURNS->value); + } + /** @noinspection PhpUnusedParameterInspection */ public function inFulfilmentCustomer(Organisation $organisation, Fulfilment $fulfilment, FulfilmentCustomer $fulfilmentCustomer, ActionRequest $request): LengthAwarePaginator { $this->parent = $fulfilmentCustomer; + $this->restriction = 'all'; $this->initialisationFromFulfilment($fulfilment, $request)->withTab(PalletReturnsTabsEnum::values()); return $this->handle($fulfilmentCustomer, PalletReturnsTabsEnum::RETURNS->value); @@ -113,6 +142,7 @@ public function inFulfilmentCustomer(Organisation $organisation, Fulfilment $ful public function inWarehouse(Organisation $organisation, Warehouse $warehouse, ActionRequest $request): LengthAwarePaginator { $this->parent = $warehouse; + $this->restriction = 'all'; $this->initialisationFromWarehouse($warehouse, $request)->withTab(PalletReturnsTabsEnum::values()); return $this->handle($warehouse, PalletReturnsTabsEnum::RETURNS->value); @@ -168,23 +198,37 @@ public function inWarehouseDispatched(Organisation $organisation, Warehouse $war return $this->handle($warehouse, PalletReturnsTabsEnum::RETURNS->value); } + /** @noinspection PhpUnusedParameterInspection */ + public function inWarehouseCancelled(Organisation $organisation, Warehouse $warehouse, ActionRequest $request): LengthAwarePaginator + { + $this->parent = $warehouse; + $this->restriction = 'cancelled'; + $this->initialisationFromWarehouse($warehouse, $request)->withTab(PalletReturnsTabsEnum::values()); + + return $this->handle($warehouse, PalletReturnsTabsEnum::RETURNS->value); + } protected function getElementGroups(Organisation|FulfilmentCustomer|Fulfilment|Warehouse|PalletDelivery|PalletReturn|RecurringBill $parent): array { + $allowedStates = PalletReturnStateEnum::labels(forElements: true); + $countStates = PalletReturnStateEnum::count($parent, forElements: true); + + if ($this->restriction === 'new') { + $allowedStates = array_filter($allowedStates, fn($key) => in_array($key, ['in_process', 'submitted', 'confirmed']), ARRAY_FILTER_USE_KEY); + $countStates = array_filter($countStates, fn($key) => in_array($key, ['in_process', 'submitted', 'confirmed']), ARRAY_FILTER_USE_KEY); + } elseif ($this->parent instanceof Warehouse && $this->restriction === 'all') { + $allowedStates = array_filter($allowedStates, fn($key) => !in_array($key, ['in_process', 'submitted']), ARRAY_FILTER_USE_KEY); + $countStates = array_filter($countStates, fn($key) => !in_array($key, ['in_process', 'submitted']), ARRAY_FILTER_USE_KEY); + } + return [ 'state' => [ 'label' => __('State'), - 'elements' => array_merge_recursive( - PalletReturnStateEnum::labels(forElements: true), - PalletReturnStateEnum::count($parent, forElements: true) - ), - + 'elements' => array_merge_recursive($allowedStates, $countStates), 'engine' => function ($query, $elements) { $query->whereIn('pallet_returns.state', $elements); } ], - - ]; } @@ -219,14 +263,16 @@ public function handle(Fulfilment|Warehouse|FulfilmentCustomer|RecurringBill $pa if ($this->type) { $queryBuilder->where('type', $this->type); } - - foreach ($this->getElementGroups($parent) as $key => $elementGroup) { - $queryBuilder->whereElementGroup( - key: $key, - allowedElements: array_keys($elementGroup['elements']), - engine: $elementGroup['engine'], - prefix: $prefix - ); + + if($this->restriction == 'all' || $this->restriction == 'new') { + foreach ($this->getElementGroups($parent) as $key => $elementGroup) { + $queryBuilder->whereElementGroup( + key: $key, + allowedElements: array_keys($elementGroup['elements']), + engine: $elementGroup['engine'], + prefix: $prefix + ); + } } if ($this->restriction) { @@ -243,6 +289,15 @@ public function handle(Fulfilment|Warehouse|FulfilmentCustomer|RecurringBill $pa case 'picked': $queryBuilder->where('state', PalletReturnStateEnum::PICKED); break; + case 'dispatched': + $queryBuilder->where('state', PalletReturnStateEnum::DISPATCHED); + break; + case 'cancelled': + $queryBuilder->where('state', PalletReturnStateEnum::CANCEL); + break; + case 'new': + $queryBuilder->whereIn('state', [PalletReturnStateEnum::CONFIRMED, PalletReturnStateEnum::SUBMITTED, PalletReturnStateEnum::IN_PROCESS]); + break; case 'handling': $queryBuilder->whereIn( 'state', @@ -264,15 +319,16 @@ public function handle(Fulfilment|Warehouse|FulfilmentCustomer|RecurringBill $pa ->withQueryString(); } - public function tableStructure(Fulfilment|Warehouse|FulfilmentCustomer|RecurringBill $parent, ?array $modelOperations = null, $prefix = null): Closure + public function tableStructure(Fulfilment|Warehouse|FulfilmentCustomer|RecurringBill $parent, ?array $modelOperations = null, $prefix = null, string $restriction = 'all'): Closure { - return function (InertiaTable $table) use ($parent, $modelOperations, $prefix) { + return function (InertiaTable $table) use ($parent, $modelOperations, $prefix, $restriction) { if ($prefix) { $table ->name($prefix) ->pageName($prefix.'Page'); } + if($restriction == 'all' || $this->parent instanceof Fulfilment && $restriction == 'new') foreach ($this->getElementGroups($parent) as $key => $elementGroup) { $table->elementGroup( key: $key, @@ -426,7 +482,7 @@ public function htmlResponse(LengthAwarePaginator $returns, ActionRequest $reque : Inertia::lazy(fn () => PalletReturnItemUploadsResource::collection(IndexPalletReturnItemUploads::run($this->parent, PalletReturnsTabsEnum::UPLOADS->value))), ] - )->table($this->tableStructure(parent: $this->parent, prefix: PalletReturnsTabsEnum::RETURNS->value)) + )->table($this->tableStructure(parent: $this->parent, prefix: PalletReturnsTabsEnum::RETURNS->value, restriction: $this->restriction)) ->table(IndexPalletReturnItemUploads::make()->tableStructure(prefix: PalletReturnsTabsEnum::UPLOADS->value)); } @@ -449,6 +505,8 @@ public function getBreadcrumbs(string $routeName, array $routeParameters): array 'grp.org.warehouses.show.dispatching.pallet-returns.index', 'grp.org.warehouses.show.dispatching.pallet-returns.confirmed.index', 'grp.org.warehouses.show.dispatching.pallet-returns.picking.index', + 'grp.org.warehouses.show.dispatching.pallet-returns.dispatched.index', + 'grp.org.warehouses.show.dispatching.pallet-returns.cancelled.index', 'grp.org.warehouses.show.dispatching.pallet-returns.picked.index' => array_merge( ShowWarehouse::make()->getBreadcrumbs( $routeParameters @@ -475,6 +533,9 @@ public function getBreadcrumbs(string $routeName, array $routeParameters): array 'grp.org.fulfilments.show.operations.pallet-returns.index', 'grp.org.fulfilments.show.operations.pallet-returns.confirmed.index', 'grp.org.fulfilments.show.operations.pallet-returns.picking.index', + 'grp.org.fulfilments.show.operations.pallet-returns.dispatched.index', + 'grp.org.fulfilments.show.operations.pallet-returns.cancelled.index', + 'grp.org.fulfilments.show.operations.pallet-returns.new.index', 'grp.org.fulfilments.show.operations.pallet-returns.picked.index' => array_merge( ShowFulfilment::make()->getBreadcrumbs( $routeParameters diff --git a/app/Actions/Fulfilment/WithPalletReturnSubNavigation.php b/app/Actions/Fulfilment/WithPalletReturnSubNavigation.php index e89dd45fd3..0ca172a871 100644 --- a/app/Actions/Fulfilment/WithPalletReturnSubNavigation.php +++ b/app/Actions/Fulfilment/WithPalletReturnSubNavigation.php @@ -22,10 +22,13 @@ public function getPalletReturnSubNavigation(Fulfilment|Warehouse $parent, Actio 'isAnchor' => true, 'route' => [ 'name' => match (class_basename($parent)) { - 'Fulfilment' => 'grp.org.fulfilments.show.operations.pallet-returns.confirmed.index', + 'Fulfilment' => 'grp.org.fulfilments.show.operations.pallet-returns.new.index', 'Warehouse' => 'grp.org.warehouses.show.dispatching.pallet-returns.confirmed.index' }, - 'parameters' => $request->route()->originalParameters() + 'parameters' => match (class_basename($parent)) { + 'Fulfilment' => array_merge($request->route()->originalParameters(), ['returns_elements[state]' => 'confirmed']), + 'Warehouse' => $request->route()->originalParameters() + } ], @@ -71,6 +74,42 @@ public function getPalletReturnSubNavigation(Fulfilment|Warehouse $parent, Actio 'number' => $parent->stats->number_pallet_returns_state_picked ]; + $subNavigation[] = [ + 'route' => [ + 'name' => match (class_basename($parent)) { + 'Fulfilment' => 'grp.org.fulfilments.show.operations.pallet-returns.dispatched.index', + 'Warehouse' => 'grp.org.warehouses.show.dispatching.pallet-returns.dispatched.index' + }, + 'parameters' => $request->route()->originalParameters() + ], + + 'label' => __("Dispatched"), + "align" => "right", + 'leftIcon' => [ + 'icon' => 'fal fa-parking', + 'tooltip' => __("Dispatched"), + ], + 'number' => $parent->stats->number_pallet_returns_state_dispatched + ]; + + $subNavigation[] = [ + 'route' => [ + 'name' => match (class_basename($parent)) { + 'Fulfilment' => 'grp.org.fulfilments.show.operations.pallet-returns.cancelled.index', + 'Warehouse' => 'grp.org.warehouses.show.dispatching.pallet-returns.cancelled.index' + }, + 'parameters' => $request->route()->originalParameters() + ], + + 'label' => __("Cancelled"), + "align" => "right", + 'leftIcon' => [ + 'icon' => 'fal fa-parking', + 'tooltip' => __("Cancelled"), + ], + 'number' => $parent->stats->number_pallet_returns_state_cancel + ]; + $subNavigation[] = [ 'route' => [ 'name' => match (class_basename($parent)) { @@ -81,6 +120,7 @@ public function getPalletReturnSubNavigation(Fulfilment|Warehouse $parent, Actio ], 'label' => __("All"), + "align" => "right", 'leftIcon' => [ 'icon' => 'fal fa-parking', 'tooltip' => __("All"), diff --git a/app/Actions/SupplyChain/SupplierProduct/UI/IndexSupplierProducts.php b/app/Actions/SupplyChain/SupplierProduct/UI/IndexSupplierProducts.php index 2861487d76..b59c85f1eb 100644 --- a/app/Actions/SupplyChain/SupplierProduct/UI/IndexSupplierProducts.php +++ b/app/Actions/SupplyChain/SupplierProduct/UI/IndexSupplierProducts.php @@ -231,8 +231,8 @@ public function htmlResponse(LengthAwarePaginator $supplier_products, ActionRequ ] ] ]; - $attachRoutes = [ - 'attachRoute' => [ + $attachRoutes =[ + 'importRoute' => [ 'name' => 'grp.models.supplier.supplier-product.import', 'parameters' => [ 'supplier' => $this->scope->id, @@ -258,7 +258,7 @@ public function htmlResponse(LengthAwarePaginator $supplier_products, ActionRequ 'subNavigation' => $subNavigation, 'actions' => $actions ], - 'attachmentRoutes' => $attachRoutes, + 'importRoutes' => $attachRoutes, 'data' => SupplierProductsResource::collection($supplier_products), ] )->table($this->tableStructure()); diff --git a/app/Actions/SysAdmin/Organisation/UI/ShowOrganisationDashboard.php b/app/Actions/SysAdmin/Organisation/UI/ShowOrganisationDashboard.php index f5d46750ce..71f72b7133 100644 --- a/app/Actions/SysAdmin/Organisation/UI/ShowOrganisationDashboard.php +++ b/app/Actions/SysAdmin/Organisation/UI/ShowOrganisationDashboard.php @@ -87,7 +87,7 @@ public function getDashboardInterval(Organisation $organisation, array $userSett 'current' => $this->tabDashboardInterval, 'table' => [ [ - 'tab_label' => __('Invoices'), + 'tab_label' => __('Invoice per store'), 'tab_slug' => 'invoices', 'tab_icon' => 'fal fa-file-invoice-dollar', 'type' => 'table', @@ -108,14 +108,6 @@ public function getDashboardInterval(Organisation $organisation, array $userSett ]; $selectedCurrency = Arr::get($userSettings, 'selected_currency_in_org', 'org'); - // $total = [ - // 'total_sales' => $organisation->shops->sum(fn ($shop) => $shop->salesIntervals->{"sales_org_currency_$selectedInterval"} ?? 0), - // 'total_sales_percentages' => 0, - // 'total_invoices' => 0, - // 'total_invoices_percentages' => 0, - // 'total_refunds' => 0, - // 'total_refunds_percentages' => 0, - // ]; $total = [ 'total_sales' => 0, @@ -136,7 +128,6 @@ public function getDashboardInterval(Organisation $organisation, array $userSett $dashboard['table'][1]['data'] = $this->getInvoiceCategories($organisation, $invoiceCategories, $selectedInterval, $dashboard, $selectedCurrency, $total); } - // dd($dashboard); return $dashboard; } @@ -318,6 +309,40 @@ public function getInvoices(Organisation $organisation, $shops, $selectedInterva ] ); + $averageInvoices = []; + $totalAvg = 0; + for ($i = 0; $i < count($combined); $i++) { + $amount = 0; + if ($combinedInvoices[$i][2] != 0) { + $amount = $combined[$i][2] / $combinedInvoices[$i][2]; + } + $averageInvoices[$i] = [ + 'name' => $combined[$i][0], + 'currency_code' => $combined[$i][1], + 'amount' => $amount, + ]; + $totalAvg += $amount; + } + + + $dashboard['widgets']['components'][] = $this->getWidget( + type: 'chart_display', + data: [ + 'description' => __('Average invoices') + ], + visual: [ + 'type' => 'bar', + 'value' => [ + 'labels' => Arr::pluck($averageInvoices, 'name'), + 'currency_codes' => Arr::pluck($averageInvoices, 'currency_code'), + 'datasets' => [ + [ + 'data' => Arr::pluck($averageInvoices, 'amount') + ] + ] + ], + ] + ); return $data; } diff --git a/app/Actions/Traits/WithEnumStats.php b/app/Actions/Traits/WithEnumStats.php index 7455052672..b263082990 100644 --- a/app/Actions/Traits/WithEnumStats.php +++ b/app/Actions/Traits/WithEnumStats.php @@ -17,7 +17,8 @@ private function getEnumStats( string $field, $enum, $models, - $where = false + $where = false, + $fieldStatsLabel = null ): array { $stats = []; @@ -25,10 +26,13 @@ private function getEnumStats( if ($this->is_closure($where)) { $applyWhere = true; } else { - $where = function ($q) {}; + $where = function ($q) { + }; } - + if (is_null($fieldStatsLabel)) { + $fieldStatsLabel = $field; + } $count = $models::selectRaw("$field, count(*) as total") ->when( @@ -38,7 +42,7 @@ private function getEnumStats( ->groupBy($field) ->pluck('total', $field)->all(); foreach ($enum::cases() as $case) { - $stats["number_{$model}_{$field}_".$case->snake()] = Arr::get($count, $case->value, 0); + $stats["number_{$model}_{$fieldStatsLabel}_".$case->snake()] = Arr::get($count, $case->value, 0); } return $stats; diff --git a/app/Actions/UI/Accounting/ShowAccountingDashboard.php b/app/Actions/UI/Accounting/ShowAccountingDashboard.php index 26c2ed0908..f525c73ee2 100644 --- a/app/Actions/UI/Accounting/ShowAccountingDashboard.php +++ b/app/Actions/UI/Accounting/ShowAccountingDashboard.php @@ -130,7 +130,7 @@ public function htmlResponse(ActionRequest $request): Response 'parameters' => $parameters ], 'index' => [ - 'number' => $this->scope->accountingStats->number_invoices + 'number' => $this->scope->orderingStats->number_invoices ] ], @@ -183,7 +183,7 @@ public function htmlResponse(ActionRequest $request): Response 'parameters' => $parameters ], 'index' => [ - 'number' => $this->scope->accountingStats->number_invoices + 'number' => $this->scope->orderingStats->number_invoices ], 'rightSubLink' => [ 'tooltip' => __('invoice categories'), diff --git a/app/Actions/UI/Dashboards/ShowGroupDashboard.php b/app/Actions/UI/Dashboards/ShowGroupDashboard.php index 8c8fa951cc..2cbb3a38af 100644 --- a/app/Actions/UI/Dashboards/ShowGroupDashboard.php +++ b/app/Actions/UI/Dashboards/ShowGroupDashboard.php @@ -85,15 +85,15 @@ public function getDashboardInterval(Group $group, array $userSettings): array 'current' => $this->tabDashboardInterval, 'table' => [ [ - 'tab_label' => __('Sales'), - 'tab_slug' => 'sales', + 'tab_label' => __('Invoice per organisation'), + 'tab_slug' => 'invoice_organisations', 'tab_icon' => 'fas fa-chart-line', 'type' => 'table', 'data' => null ], [ - 'tab_label' => __('Shops'), - 'tab_slug' => 'shops', + 'tab_label' => __('Invoice per store'), + 'tab_slug' => 'invoice_shops', 'tab_icon' => 'fal fa-shopping-cart', 'type' => 'table', 'data' => null @@ -115,10 +115,10 @@ public function getDashboardInterval(Group $group, array $userSettings): array 'total_refunds_percentages' => 0, ]; - if ($this->tabDashboardInterval == GroupDashboardIntervalTabsEnum::SALES->value) { + if ($this->tabDashboardInterval == GroupDashboardIntervalTabsEnum::INVOICE_ORGANISATIONS->value) { $total['total_sales'] = $organisations->sum(fn ($organisation) => $organisation->salesIntervals->{"sales_grp_currency_$selectedInterval"} ?? 0); $dashboard['table'][0]['data'] = $this->getSales($group, $selectedInterval, $selectedCurrency, $organisations, $dashboard, $total); - } elseif ($this->tabDashboardInterval == GroupDashboardIntervalTabsEnum::SHOPS->value) { + } elseif ($this->tabDashboardInterval == GroupDashboardIntervalTabsEnum::INVOICE_SHOPS->value) { $shops = $group->shops->whereIn('organisation_id', $organisations->pluck('id')->toArray())->where('state', $selectedShopState); $total['total_sales'] = $shops->sum(fn ($shop) => $shop->salesIntervals->{"sales_grp_currency_$selectedInterval"} ?? 0); $dashboard['table'][1]['data'] = $this->getShops($group, $shops, $selectedInterval, $dashboard, $selectedCurrency, $total); diff --git a/app/Enums/UI/Group/GroupDashboardIntervalTabsEnum.php b/app/Enums/UI/Group/GroupDashboardIntervalTabsEnum.php index a0573bcbc7..9941f9aeb7 100644 --- a/app/Enums/UI/Group/GroupDashboardIntervalTabsEnum.php +++ b/app/Enums/UI/Group/GroupDashboardIntervalTabsEnum.php @@ -16,7 +16,7 @@ enum GroupDashboardIntervalTabsEnum: string { use EnumHelperTrait; - case SALES = 'sales'; - case SHOPS = 'shops'; + case INVOICE_ORGANISATIONS = 'invoice_organisations'; + case INVOICE_SHOPS = 'invoice_shops'; } diff --git a/app/Http/Resources/Accounting/PaymentAccountsResource.php b/app/Http/Resources/Accounting/PaymentAccountsResource.php index c9787b021c..926527eed0 100644 --- a/app/Http/Resources/Accounting/PaymentAccountsResource.php +++ b/app/Http/Resources/Accounting/PaymentAccountsResource.php @@ -12,36 +12,34 @@ /** * @property int $number_payments - * @property \App\Models\Catalogue\Shop $shops - * @property string $payment_service_provider_slug - * @property string $payment_service_provider_code - * @property string $payment_service_provider_name * @property string $id * @property string $slug * @property string $name * @property string $code - * @property string $shop_name - * @property string $shop_id + * @property mixed $number_pas_state_active + * @property mixed $org_amount_successfully_paid + * @property mixed $payment_service_provider_slug + * @property mixed $payment_service_provider_name + * @property mixed $payment_service_provider_code + * @property mixed $org_currency_code * */ class PaymentAccountsResource extends JsonResource { public function toArray($request): array { - return [ + return array( 'id' => $this->id, 'slug' => $this->slug, 'name' => $this->name, - 'payment_service_provider_slug' => $this->payment_service_provider_slug, - 'payment_service_provider_code' => $this->payment_service_provider_code, - 'payment_service_provider_name' => $this->payment_service_provider_name, 'number_payments' => $this->number_payments, - 'number_pas' => $this->number_pas, 'number_pas_state_active' => $this->number_pas_state_active, 'code' => $this->code, - 'number_shop' => $this->number_shop, - 'organisation_name' => $this->organisation_name, - 'organisation_slug' => $this->organisation_slug, - ]; + 'org_amount_successfully_paid' => $this->org_amount_successfully_paid, + 'org_currency_code' => $this->org_currency_code, + 'payment_service_provider_slug' => $this->payment_service_provider_slug, + 'payment_service_provider_name' => $this->payment_service_provider_name, + 'payment_service_provider_code' => $this->payment_service_provider_code, + ); } } diff --git a/maya/src/components/BaseList.jsx b/maya/src/components/BaseList.jsx index ab7809743b..187e123801 100644 --- a/maya/src/components/BaseList.jsx +++ b/maya/src/components/BaseList.jsx @@ -11,18 +11,18 @@ import request from '@/src/utils/Request'; import globalStyles from '@/globalStyles'; import {SearchIcon} from '@/src/components/ui/icon'; import {ALERT_TYPE, Toast} from 'react-native-alert-notification'; -import {Button, ButtonText} from '@/src/components/ui/button'; import {useSafeAreaInsets} from 'react-native-safe-area-context'; -import {Badge, BadgeText} from '@/src/components/ui/badge'; +import Empty from '@/src/components/Empty' + import { Input, InputField, InputSlot, InputIcon, } from '@/src/components/ui/input'; -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'; +/* import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'; import {faBarcodeScan} from '@/private/fa/pro-regular-svg-icons'; -import {faTimes} from '@/private/fa/pro-light-svg-icons'; +import {faTimes} from '@/private/fa/pro-light-svg-icons'; */ const BaseList = forwardRef((props, ref) => { const [data, setData] = useState([]); @@ -79,7 +79,7 @@ const BaseList = forwardRef((props, ref) => { const fetchMoreData = (isLoadMore = false) => { if (isLoadMore) { - if (meta.last_page != page) setPage(prevPage => prevPage + 1); + if (meta?.last_page != page) setPage(prevPage => prevPage + 1); } else { setPage(1); getDataFromServer(false); @@ -151,6 +151,7 @@ const BaseList = forwardRef((props, ref) => { showsVerticalScrollIndicator={false} onEndReached={() => fetchMoreData(true)} onEndReachedThreshold={1} + ListEmptyComponent={()} refreshControl={ { + return ( + + + + {title} + {description} + + + ); +}; + +export default Empty; \ No newline at end of file diff --git a/maya/src/screens/Pallet/ItemsInPallet.jsx b/maya/src/screens/Pallet/ItemsInPallet.jsx index e8f67a4bfa..8b5b20f6e3 100644 --- a/maya/src/screens/Pallet/ItemsInPallet.jsx +++ b/maya/src/screens/Pallet/ItemsInPallet.jsx @@ -1,15 +1,44 @@ -import React, {useContext} from 'react'; -import {Text, View, Button} from 'react-native'; -import {AuthContext} from '@/src/components/Context/context'; - -const ItemsInPallets = () => { - const {userData} = useContext(AuthContext); +import React from 'react'; +import { View, FlatList, TouchableOpacity, Text } from 'react-native'; +import globalStyles from '@/globalStyles'; +import Empty from '@/src/components/Empty' +const ItemsInPallets = ({navigation, route, data }) => { return ( - - Home + + item.id.toString()} + showsVerticalScrollIndicator={false} + ListEmptyComponent={( + + )} + renderItem={({ item }) => ( + + )} + /> ); }; + +const GroupItem = ({ item, navigation }) => { + return ( + + + + {item.name} + {item.code || 'No code available'} + + + + ); +}; + export default ItemsInPallets; diff --git a/maya/src/screens/Pallet/ShowPallet.jsx b/maya/src/screens/Pallet/ShowPallet.jsx index 64d36a9671..6c6f32f67a 100644 --- a/maya/src/screens/Pallet/ShowPallet.jsx +++ b/maya/src/screens/Pallet/ShowPallet.jsx @@ -1,10 +1,5 @@ -import React, {useContext, useEffect, useState, useRef} from 'react'; -import {View, ScrollView, ActivityIndicator, TouchableOpacity} from 'react-native'; -import {useForm, Controller} from 'react-hook-form'; -import {AuthContext} from '@/src/components/Context/context'; -import request from '@/src/utils/Request'; -import {ALERT_TYPE, Toast} from 'react-native-alert-notification'; -import {Button, ButtonText, ButtonSpinner} from '@/src/components/ui/button'; +import React from 'react'; +import {View, ScrollView } from 'react-native'; import {Card} from '@/src/components/ui/card'; import {Heading} from '@/src/components/ui/heading'; import {Center} from '@/src/components/ui/center'; @@ -12,179 +7,15 @@ import {Text} from '@/src/components/ui/text'; import globalStyles from '@/globalStyles'; import Barcode from 'react-native-barcode-svg'; import Description from '@/src/components/Description'; -import Modal from '@/src/components/Modal'; -import {Textarea, TextareaInput} from '@/src/components/ui/textarea'; -import { - Radio, - RadioGroup, - RadioIndicator, - RadioLabel, - RadioIcon, -} from '@/src/components/ui/radio'; -import {Input, InputField} from '@/src/components/ui/input'; -import {CircleIcon} from '@/src/components/ui/icon'; -import Menu from '@/src/components/Menu'; import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'; import {library} from '@fortawesome/fontawesome-svg-core'; import {faFragile, faSave} from '@/private/fa/pro-light-svg-icons'; -import {faBars} from '@/private/fa/pro-regular-svg-icons'; library.add(faFragile); -const ShowPallet = ({navigation, route}) => { - const {organisation, warehouse} = useContext(AuthContext); - const [data, setData] = useState(null); - const [loadingSave, setLoadingSave] = useState(false); - const [loading, setLoading] = useState(true); - const [showModalDamaged, setShowModalDamaged] = useState(false); - const [showModalMovePallet, setShowModalMovePallet] = useState(false); - const menuRef = useRef(null); - const {id} = route.params; - - const {control, handleSubmit, reset, setValue} = useForm({ - defaultValues: { - status: '', - notes: '', - location: '', - }, - }); - - const fetchData = async () => { - try { - const response = await request({ - urlKey: 'get-pallet', - args: [organisation.id, warehouse.id, id], - }); - setData(response.data); - } catch (error) { - Toast.show({ - type: ALERT_TYPE.DANGER, - title: 'Error', - textBody: error.detail?.message || 'Failed to fetch data', - }); - } finally { - setLoading(false); - } - }; - - useEffect(() => { - fetchData(); - }, [id, organisation.id, warehouse.id]); - - const onSubmitSetDamaged = async formData => { - request({ - urlKey: 'set-pallet-not-picked', - method: 'patch', - data: formData, - args: [data.id], - onSuccess: response => { - fetchData(); - setShowModalDamaged(false); - Toast.show({ - type: ALERT_TYPE.SUCCESS, - title: 'Success', - textBody: 'Updated pallet ' + data.reference, - }); - }, - onFailed: error => { - setShowModalDamaged(false); - Toast.show({ - type: ALERT_TYPE.DANGER, - title: 'Error', - textBody: - error.detail?.message || - 'Failed to update pallet ' + data.reference, - }); - }, - }); - }; - - - const Menus = [ - {id: 'move-pallet', title: 'Move Pallet'}, - {id: 'set-damaged', title: 'Set Damaged', attributes: { destructive: true }}, - ]; - - const onPressMenu = event => { - switch (event.event) { - case 'move-pallet': - setShowModalMovePallet(true) - break; - case 'set-damaged': - setShowModalDamaged(true) - break; - default: - console.log('Unknown option selected'); - } - }; - - - useEffect(() => { - navigation.setOptions({ - title: data ? `Pallet ${data.reference}` : 'Pallet Details', - headerRight: () => ( - - onPressMenu(nativeEvent)} - button={ - menuRef?.current?.menu.show()}> - - - } - actions={Menus} - /> - - ), - }); - }, [navigation, data]); - - - const onSubmitSetLocation = formData => { - request({ - method:'patch', - urlKey: 'set-pallet-location', - args : [data.id, formData.location ], - data:{}, - onSuccess: response => { - fetchData(); - setShowModalMovePallet(false) - Toast.show({ - type: ALERT_TYPE.SUCCESS, - title: 'Success', - textBody: 'success update pallet to ' + formData.location, - }); - }, - onFailed: error => { - console.log(error) - setShowModalMovePallet(false) - Toast.show({ - type: ALERT_TYPE.DANGER, - title: 'Error', - textBody: error.detail?.message || 'Failed to update data', - }); - }, - }) - }; - - if (loading) { - return ( - - - - ); - } - - if (!data) { - return ( - - No Data Available - - ); - } - +const ShowPallet = ({navigation, route, data }) => { return ( @@ -241,112 +72,6 @@ const ShowPallet = ({navigation, route}) => { */} - - setShowModalDamaged(false)}> - - Status - ( - - - - - - - Damaged - - - - - - - Lost - - - - - - - Other - - - - )} - /> - - Notes - ( - - - - )} - /> - - - {loadingSave ? ( - - ) : ( - - - Save - - )} - - - - - setShowModalMovePallet(false)}> - - Location - ( - - - - )} - /> - - - {loadingSave ? ( - - ) : ( - - - Move Pallet - - )} - - - ); }; diff --git a/maya/src/screens/StoredItem/PalletsInStoredItem.jsx b/maya/src/screens/StoredItem/PalletsInStoredItem.jsx new file mode 100644 index 0000000000..ffbb509f79 --- /dev/null +++ b/maya/src/screens/StoredItem/PalletsInStoredItem.jsx @@ -0,0 +1,98 @@ +import React from 'react'; +import {View, FlatList, TouchableOpacity, Text} from 'react-native'; +import globalStyles from '@/globalStyles'; +import Empty from '@/src/components/Empty'; + +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'; +import {library} from '@fortawesome/fontawesome-svg-core'; +import {CircleIcon} from '@/src/components/ui/icon'; +import { + faSeedling, + faShare, + faSpellCheck, + faCheck, + faCross, + faCheckDouble, + faWarehouseAlt, + faPallet, + faTimes, + faFragile, +} from '@/private/fa/pro-light-svg-icons'; +import { + faFileInvoiceDollar, + faInventory, + faTimes as faTimesRegular, + faHistory, + faSave, +} from '@/private/fa/pro-regular-svg-icons'; + +library.add( + faSeedling, + faShare, + faSpellCheck, + faCheck, + faCross, + faCheckDouble, + faWarehouseAlt, + faPallet, + faTimes, + faFileInvoiceDollar, +); + +const PalletsInItem = ({navigation, route, data}) => { + return ( + + item.id.toString()} + showsVerticalScrollIndicator={false} + ListEmptyComponent={} + renderItem={({item}) => ( + + )} + /> + + ); +}; + +const GroupItem = ({item, navigation}) => { + return ( + + + + + {item?.state_icon && ( + + )} + {item?.type_icon && ( + + )} + + + + + + {item?.customer_reference || 'N/A'} + + + + + {item?.reference} + + + + + + ); +}; + +export default PalletsInItem; diff --git a/maya/src/screens/StoredItem/ShowStoredItem.jsx b/maya/src/screens/StoredItem/ShowStoredItem.jsx index 94b10847e1..e7861900c5 100644 --- a/maya/src/screens/StoredItem/ShowStoredItem.jsx +++ b/maya/src/screens/StoredItem/ShowStoredItem.jsx @@ -1,109 +1,77 @@ -import React, {useContext, useEffect, useState} from 'react'; -import {View, ScrollView, ActivityIndicator} from 'react-native'; -import {AuthContext} from '@/src/components/Context/context'; -import request from '@/src/utils/Request'; -import {ALERT_TYPE, Toast} from 'react-native-alert-notification'; +import React from 'react'; +import {View, ScrollView} from 'react-native'; import {Card} from '@/src/components/ui/card'; import {Heading} from '@/src/components/ui/heading'; import {Text} from '@/src/components/ui/text'; -import dayjs from 'dayjs'; import Description from '@/src/components/Description'; -import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome'; -import { library } from '@fortawesome/fontawesome-svg-core' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'; +import {library} from '@fortawesome/fontawesome-svg-core'; -import { faSeedling, faShare, faSpellCheck, faCheck, faCheckDouble, faSortSizeUp, faCircle } from '@/private/fa/pro-light-svg-icons' -library.add(faSeedling, faShare, faSpellCheck, faCheck, faCheckDouble, faSortSizeUp) +import { + faSeedling, + faShare, + faSpellCheck, + faCheck, + faCheckDouble, + faSortSizeUp, + faCircle, +} from '@/private/fa/pro-light-svg-icons'; +library.add( + faSeedling, + faShare, + faSpellCheck, + faCheck, + faCheckDouble, + faSortSizeUp, +); -const ShowStoredItem = ({navigation, route}) => { - const {organisation, warehouse} = useContext(AuthContext); - const [data, setData] = useState(null); - const [loading, setLoading] = useState(false); - const id = route.params.id; - console.log(id) +const ShowStoredItem = ({navigation, route, data}) => { + const schema = [ + { + label: 'Customer', + value: data?.customer_name, + }, + { + label: 'State', + value: ( + + + {data?.state} + + ), + }, + { + label: 'Location', + value: data?.location, + }, + { + label: 'Quantity', + value: data?.quantity ? data?.quantity.toString() : '0', + }, + ]; - const getDataFromServer = async () => { - setLoading(true); - request({ - urlKey: 'get-stored-item', - args: [organisation.id, warehouse.id, id], - onSuccess: response => { - setData(response.data); - setLoading(false); - }, - onFailed: error => { - setLoading(false); - Toast.show({ - type: ALERT_TYPE.DANGER, - title: 'Error', - textBody: error.detail?.message || 'Failed to fetch data', - }); - }, - }); - }; - - const schema = [ - { - label : "Customer", - value : data?.customer_name - }, - { - label: 'State', - value: ( - - - {data?.state} - - ), - }, - { - label : "Location", - value : data?.location - }, - { - label : "Quantity", - value : data?.quantity ? data?.quantity.toString() : "0" - }, - ]; - - useEffect(() => { - getDataFromServer(); - }, []); - - if (loading) { return ( - - - - ); - } + + {/* Header */} + + + {data.reference || 'N/A'} + + + Status: {data.state.toUpperCase()} + + - if (!data) { - return ( - - No Data Available - + {/* Detail Section */} + + Details + + + ); - } - - return ( - - {/* Header */} - - - {data.reference || 'N/A'} - - - Status: {data.state.toUpperCase()} - - - - {/* Detail Section */} - - Details - - - - ); }; export default ShowStoredItem; diff --git a/maya/src/screens/routes/ItemStackScreens.jsx b/maya/src/screens/routes/ItemStackScreens.jsx new file mode 100644 index 0000000000..d8bc52a7f9 --- /dev/null +++ b/maya/src/screens/routes/ItemStackScreens.jsx @@ -0,0 +1,83 @@ +import React, {useContext, useEffect, useState} from 'react'; +import {View, ActivityIndicator} from 'react-native'; +import BottomTabs from '@/src/components/BottomTabs'; +import {AuthContext} from '@/src/components/Context/context'; +import Empty from '@/src/components/Empty'; +import request from '@/src/utils/Request'; +import {ALERT_TYPE, Toast} from 'react-native-alert-notification'; + +import ShowStoredItem from '@/src/screens/StoredItem/ShowStoredItem'; +import PalletsInStoredItem from '@/src/screens/StoredItem/PalletsInStoredItem'; +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'; +import { + faPallet, + faNarwhal, +} from '@/private/fa/pro-regular-svg-icons'; + +const ItemStackScreen = ({navigation, route}) => { + const {organisation, warehouse} = useContext(AuthContext); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const {id} = route.params; + + const getDataFromServer = async () => { + setLoading(true); + request({ + urlKey: 'get-stored-item', + args: [organisation.id, warehouse.id, id], + onSuccess: response => { + setData(response.data); + setLoading(false); + }, + onFailed: error => { + setLoading(false); + Toast.show({ + type: ALERT_TYPE.DANGER, + title: 'Error', + textBody: error.detail?.message || 'Failed to fetch data', + }); + }, + }); + }; + + useEffect(() => { + getDataFromServer(); + }, [id]); + + if (loading) { + return ( + + + + ); + } + + return ( + <> + {data ? ( + , + }, + { + route: 'pallets-in-item', + label: 'Pallet', + icon: faPallet, + component: () => ( + + ), + }, + ]} + /> + ) : ( + + )} + > + ); +}; + +export default ItemStackScreen; diff --git a/maya/src/screens/routes/MainStackScreen.jsx b/maya/src/screens/routes/MainStackScreen.jsx index 4351fe9147..a524df4b0c 100644 --- a/maya/src/screens/routes/MainStackScreen.jsx +++ b/maya/src/screens/routes/MainStackScreen.jsx @@ -11,6 +11,8 @@ import GoodsOutStackScreen from '@/src/screens/routes/GoodsOutStackScreen'; import LocationStackScreen from '@/src/screens/routes/LocationStackScreen'; import DeliveryStackScreen from '@/src/screens/routes/DeliveryStackScreens'; import RetrunStackScreen from '@/src/screens/routes/RetrunsStackScreens'; +import PalletStackScreen from '@/src/screens/routes/PalletStackScreen'; +import ItemStackScreens from '@/src/screens/routes/ItemStackScreens' import Settings from '@/src/screens/Settings'; import Organisation from '@/src/screens/Organisation'; import Warehouse from '@/src/screens/Warehouse' @@ -18,7 +20,6 @@ import Fulfilment from '@/src/screens/Fulfilment'; import ShowDeliveryNote from '@/src/screens/DeliveryNote/ShowDeliveryNote'; import ShowLocation from '@/src/screens/Location/ShowLocation'; import ShowArea from '@/src/screens/Area/ShowArea'; -import ShowPallet from '@/src/screens/Pallet/ShowPallet'; import ShowStockDelivery from '@/src/screens/Stock/ShowStockDelivery'; import ShowStoredItem from '@/src/screens/StoredItem/ShowStoredItem'; import SessionExpired from '@/src/screens/SessionExpired'; @@ -258,6 +259,30 @@ const HomeStack = () => { }}> + + + { title: 'Delivery Note', }} /> - - { title: 'Area', }} /> - { /> { + const {organisation, warehouse} = useContext(AuthContext); + const [data, setData] = useState(null); + const [loadingSave, setLoadingSave] = useState(false); + const [loading, setLoading] = useState(true); + const [showModalDamaged, setShowModalDamaged] = useState(false); + const [showModalMovePallet, setShowModalMovePallet] = useState(false); + const menuRef = useRef(null); + const {id} = route.params; + const {control, handleSubmit, reset, setValue} = useForm({ + defaultValues: { + status: '', + notes: '', + location: '', + }, + }); -const PalletStackScreen = ({navigation, route}) => { - const TabArr = [ + const Menus = [ + {id: 'move-pallet', title: 'Move Pallet'}, { - route: 'show-pallet', - label: 'Pallet', - icon: faMapSigns, - component: props => ( - - ), - }, - { - route: 'items-in-pallet', - label: 'Items In Pallet', - icon: faInventory, - component: props => ( - - ), + id: 'set-damaged', + title: 'Set Damaged', + attributes: {destructive: true}, }, ]; + const onPressMenu = event => { + switch (event.event) { + case 'move-pallet': + setShowModalMovePallet(true); + break; + case 'set-damaged': + setShowModalDamaged(true); + break; + default: + console.log('Unknown option selected'); + } + }; + + const fetchData = async () => { + try { + const response = await request({ + urlKey: 'get-pallet', + args: [organisation.id, warehouse.id, id], + }); + setData(response.data); + } catch (error) { + Toast.show({ + type: ALERT_TYPE.DANGER, + title: 'Error', + textBody: error.detail?.message || 'Failed to fetch data', + }); + } finally { + setLoading(false); + } + }; + + const onSubmitSetDamaged = async formData => { + request({ + urlKey: 'set-pallet-not-picked', + method: 'patch', + data: formData, + args: [data.id], + onSuccess: response => { + fetchData(); + setShowModalDamaged(false); + Toast.show({ + type: ALERT_TYPE.SUCCESS, + title: 'Success', + textBody: 'Updated pallet ' + data.reference, + }); + }, + onFailed: error => { + setShowModalDamaged(false); + Toast.show({ + type: ALERT_TYPE.DANGER, + title: 'Error', + textBody: + error.detail?.message || + 'Failed to update pallet ' + data.reference, + }); + }, + }); + }; + + const onSubmitSetLocation = formData => { + request({ + method: 'patch', + urlKey: 'set-pallet-location', + args: [data.id, formData.location], + data: {}, + onSuccess: response => { + fetchData(); + setShowModalMovePallet(false); + Toast.show({ + type: ALERT_TYPE.SUCCESS, + title: 'Success', + textBody: 'success update pallet to ' + formData.location, + }); + }, + onFailed: error => { + console.log(error); + setShowModalMovePallet(false); + Toast.show({ + type: ALERT_TYPE.DANGER, + title: 'Error', + textBody: error.detail?.message || 'Failed to update data', + }); + }, + }); + }; + + useEffect(() => { + fetchData(); + }, [id]); + + useEffect(() => { + navigation.setOptions({ + title: data ? `Pallet ${data.reference}` : 'Pallet Details', + headerRight: () => ( + + + onPressMenu(nativeEvent) + } + button={ + menuRef?.current?.menu.show()}> + + + } + actions={Menus} + /> + + ), + }); + }, [navigation, data]); + + if (loading) { + return ( + + + + ); + } + return ( - - - + <> + {data ? }, + { route: 'items-in-pallet', label: 'SKU In Pallet', icon: faNarwhal, component: () => } + ]} /> : } + + setShowModalDamaged(false)}> + + Status + ( + + + + + + + Damaged + + + + + + + Lost + + + + + + + Other + + + + )} + /> + + + Notes + + ( + + + + )} + /> + + + {loadingSave ? ( + + ) : ( + + + Save + + )} + + + + + setShowModalMovePallet(false)}> + + Location + ( + + + + )} + /> + + + {loadingSave ? ( + + ) : ( + + + Move Pallet + + )} + + + + > ); }; diff --git a/maya/src/utils/Request.js b/maya/src/utils/Request.js index e4cd2952c0..ee64934271 100644 --- a/maya/src/utils/Request.js +++ b/maya/src/utils/Request.js @@ -127,7 +127,7 @@ const request = async ({ if (!useCustomErrorMessage && error.response?.data.message) { errorMessage = error.response.data; } - const errorObj = { status, detail: errorMessage }; + const errorObj = { status, data: errorMessage }; onFailed(errorObj, extra); onBoth(false, errorObj, extra); return defaultValue; diff --git a/resources/js/Components/DataDisplay/Dashboard/DashboardTable.vue b/resources/js/Components/DataDisplay/Dashboard/DashboardTable.vue index 976e484a0f..1c3164d586 100644 --- a/resources/js/Components/DataDisplay/Dashboard/DashboardTable.vue +++ b/resources/js/Components/DataDisplay/Dashboard/DashboardTable.vue @@ -51,6 +51,7 @@ const selectedTab = computed(() => { return props.dashboardTable.find((tab) => tab.tab_slug === activeIndexTab.value) }) function useTabChangeDashboard(tab_slug: string) { + if (tab_slug === activeIndexTab.value) { return } diff --git a/resources/js/Components/DataDisplay/Dashboard/Widget/ChartDisplay.vue b/resources/js/Components/DataDisplay/Dashboard/Widget/ChartDisplay.vue index 0adbbcec63..2c938ce552 100644 --- a/resources/js/Components/DataDisplay/Dashboard/Widget/ChartDisplay.vue +++ b/resources/js/Components/DataDisplay/Dashboard/Widget/ChartDisplay.vue @@ -11,7 +11,7 @@ import ChartDashboardDynamic from "../../ChartDashboardDynamic.vue" import Chart from "primevue/chart" import ProgressDashboardCard from "../../ProgressDashboardCard.vue" import { Link } from "@inertiajs/vue3" -import { layoutStructure } from "@/Composables/useLayoutStructure"; +import { layoutStructure } from "@/Composables/useLayoutStructure" library.add(faCheck, faExclamation, faInfo, faPlay) // Props for dynamic behavior @@ -104,7 +104,7 @@ const widgets = { } const locale = inject("locale", aikuLocaleStructure) -const layoutStore = inject("layout", layoutStructure); +const layoutStore = inject("layout", layoutStructure) const getStatusColor = (status: string) => { switch (status) { @@ -121,7 +121,6 @@ const getStatusColor = (status: string) => { } } - const printLabelByType = (label?: string) => { switch (props.widget.type) { case "currency": @@ -139,24 +138,25 @@ function NumberDashboard(shop: any) { const setChartOptions = () => ({ responsive: true, maintainAspectRatio: false, - + plugins: { - legend: { - display: false, - }, - tooltip: { - callbacks: { - label: (context) => { - const value = parseFloat(context.parsed as string) || 0; - const currencyCode = - props.visual?.value?.currency_codes?.[context.dataIndex] || - props.widget.currency_code || - "usd"; - return locale.currencyFormat(currencyCode, value); - }, - }, - }, - }, + legend: { + display: false, + }, + tooltip: { + callbacks: { + label: (context) => { + const value = parseFloat(context.parsed.y ?? context.parsed) || 0 + const currencyCode = props.visual?.value?.currency_codes?.[context.dataIndex] + + if (currencyCode) { + return locale.currencyFormat(currencyCode, value) + } + return locale.number(value); + }, + }, + }, + }, }) // const chartLabels = ["1", "2", "3", "4", "5", "6", "7", "8"] @@ -175,8 +175,7 @@ const setChartOptions = () => ({ - + @@ -216,9 +215,11 @@ const setChartOptions = () => ({ {{ widget.description }} - + - + () +const locale = useLocaleStore(); + function paymentAccountRoute(paymentAccount: PaymentAccount) { switch (route().current()) { @@ -99,17 +102,19 @@ function shopsRoute(paymentAccount: PaymentAccount) { - {{ paymentAccount['number_payments'] }} + {{ useLocaleStore().number(paymentAccount['number_payments']) }} - {{ paymentAccount['number_pas_state_active'] }} + {{ useLocaleStore().number(paymentAccount['number_pas_state_active']) }} - + + {{ useLocaleStore().currencyFormat( paymentAccount.org_currency_code, paymentAccount.org_amount_successfully_paid) }} + diff --git a/resources/js/Components/Tables/Grp/Org/Fulfilment/TablePalletReturns.vue b/resources/js/Components/Tables/Grp/Org/Fulfilment/TablePalletReturns.vue index 9fc7820370..fcff2b8aaf 100644 --- a/resources/js/Components/Tables/Grp/Org/Fulfilment/TablePalletReturns.vue +++ b/resources/js/Components/Tables/Grp/Org/Fulfilment/TablePalletReturns.vue @@ -34,6 +34,8 @@ function palletReturnRoute(palletReturn: PalletDelivery) { case 'grp.org.warehouses.show.dispatching.pallet-returns.confirmed.index': case 'grp.org.warehouses.show.dispatching.pallet-returns.picking.index': case 'grp.org.warehouses.show.dispatching.pallet-returns.picked.index': + case 'grp.org.warehouses.show.dispatching.pallet-returns.dispatched.index': + case 'grp.org.warehouses.show.dispatching.pallet-returns.cancelled.index': return route( 'grp.org.warehouses.show.dispatching.pallet-returns.show', [ @@ -45,6 +47,9 @@ function palletReturnRoute(palletReturn: PalletDelivery) { case 'grp.org.fulfilments.show.operations.pallet-returns.confirmed.index': case 'grp.org.fulfilments.show.operations.pallet-returns.picking.index': case 'grp.org.fulfilments.show.operations.pallet-returns.picked.index': + case 'grp.org.fulfilments.show.operations.pallet-returns.dispatched.index': + case 'grp.org.fulfilments.show.operations.pallet-returns.new.index': + case 'grp.org.fulfilments.show.operations.pallet-returns.cancelled.index': return route( 'grp.org.fulfilments.show.operations.pallet-returns.show', [ @@ -76,6 +81,8 @@ function storedItemReturnRoute(palletReturn: PalletDelivery) { case 'grp.org.warehouses.show.dispatching.pallet-returns.confirmed.index': case 'grp.org.warehouses.show.dispatching.pallet-returns.picking.index': case 'grp.org.warehouses.show.dispatching.pallet-returns.picked.index': + case 'grp.org.warehouses.show.dispatching.pallet-returns.dispatched.index': + case 'grp.org.warehouses.show.dispatching.pallet-returns.cancelled.index': return route( 'grp.org.warehouses.show.dispatching.pallet-return-with-stored-items.show', [ @@ -87,6 +94,9 @@ function storedItemReturnRoute(palletReturn: PalletDelivery) { case 'grp.org.fulfilments.show.operations.pallet-returns.confirmed.index': case 'grp.org.fulfilments.show.operations.pallet-returns.picking.index': case 'grp.org.fulfilments.show.operations.pallet-returns.picked.index': + case 'grp.org.fulfilments.show.operations.pallet-returns.dispatched.index': + case 'grp.org.fulfilments.show.operations.pallet-returns.cancelled.index': + case 'grp.org.fulfilments.show.operations.pallet-returns.new.index': return route( 'grp.org.fulfilments.show.operations.pallet-return-with-stored-items.show', [ diff --git a/resources/js/Components/Upload/UploadSpreadsheet.vue b/resources/js/Components/Upload/UploadSpreadsheet.vue new file mode 100644 index 0000000000..16a16bea4e --- /dev/null +++ b/resources/js/Components/Upload/UploadSpreadsheet.vue @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/resources/js/Components/Utils/ModalUploadSpreadsheet.vue b/resources/js/Components/Utils/ModalUploadSpreadsheet.vue new file mode 100644 index 0000000000..61dc3cbcd4 --- /dev/null +++ b/resources/js/Components/Utils/ModalUploadSpreadsheet.vue @@ -0,0 +1,259 @@ + + + + closeModal()" :closeButton="true" width="w-[500px]"> + + + + + + + {{ title?.label }} + + + + + + + + (e.preventDefault(), onUploadFile(e.dataTransfer.files[0]))" + @dragover.prevent + @dragenter.prevent + @dragleave.prevent + class="overflow-hidden relative w-full max-w-full flex items-center justify-center rounded-lg border border-dashed border-gray-700/25 px-6 py-3 bg-gray-400/10" + :class="[ + { 'hover:bg-gray-400/20': !isLoadingUpload }, + errorMessage ? 'errorShake' : '', + ]"> + + + + + {{ selectedFile?.name }} + + clearAll()" + label="Remove file" + type="negative" + size="s" /> + + + + + + onUploadFile(e.target.files[0])" + ref="fileInput" /> + + {{ trans("Drop your file here") }} + + + + + + {{ trans("Upload file") }} + + + + {{ trans("Drag and drop, or browse your files") }} + + + + + + + + *{{ errorMessage }} + + + + + + + submitUpload()" + label="Submit" + size="l" + full + + :loading="isLoadingUpload" /> + + + + + + + + + + + \ No newline at end of file diff --git a/resources/js/Pages/Grp/SupplyChain/SupplierProducts.vue b/resources/js/Pages/Grp/SupplyChain/SupplierProducts.vue index 327887ac43..6469749369 100644 --- a/resources/js/Pages/Grp/SupplyChain/SupplierProducts.vue +++ b/resources/js/Pages/Grp/SupplyChain/SupplierProducts.vue @@ -10,17 +10,39 @@ import PageHeading from '@/Components/Headings/PageHeading.vue'; import TableSupplierProducts from "@/Components/Tables/Grp/SupplyChain/TableSupplierProducts.vue"; import { capitalize } from "@/Composables/capitalize" import { PageHeading as TSPageHeading } from "@/types/PageHeading"; +import { ref } from 'vue'; +import UploadSpreadsheet from '@/Components/Upload/UploadSpreadsheet.vue'; +import Button from '@/Components/Elements/Buttons/Button.vue'; +import { trans } from 'laravel-vue-i18n' const props = defineProps <{ pageHead: TSPageHeading title: string - data:object + data: object + importRoutes: object }>() + +const isModalUploadOpen = ref(false) + - + + + isModalUploadOpen = true" + :label="trans('Attach file')" + icon="fal fa-upload" + type="secondary" + /> + + + + diff --git a/routes/grp/web/org/fulfilments/operations.php b/routes/grp/web/org/fulfilments/operations.php index 48d50573e4..e0842071a5 100644 --- a/routes/grp/web/org/fulfilments/operations.php +++ b/routes/grp/web/org/fulfilments/operations.php @@ -70,6 +70,9 @@ Route::get('returns/confirmed', [IndexPalletReturns::class, 'inFulfilmentConfirmed'])->name('pallet-returns.confirmed.index'); Route::get('returns/picking', [IndexPalletReturns::class, 'inFulfilmentPicking'])->name('pallet-returns.picking.index'); Route::get('returns/picked', [IndexPalletReturns::class, 'inFulfilmentPicked'])->name('pallet-returns.picked.index'); +Route::get('returns/dispatched', [IndexPalletReturns::class, 'inFulfilmentDispatched'])->name('pallet-returns.dispatched.index'); +Route::get('returns/cancelled', [IndexPalletReturns::class, 'inFulfilmentCancelled'])->name('pallet-returns.cancelled.index'); +Route::get('returns/new', [IndexPalletReturns::class, 'inFulfilmentNew'])->name('pallet-returns.new.index'); Route::get('returns/{palletReturn}', ShowPalletReturn::class)->name('pallet-returns.show'); Route::get('return-with-stored-items/{palletReturn}', ShowStoredItemReturn::class)->name('pallet-return-with-stored-items.show'); diff --git a/routes/grp/web/org/warehouses/dispatching.php b/routes/grp/web/org/warehouses/dispatching.php index b718adb1ed..49fbf9e5ca 100644 --- a/routes/grp/web/org/warehouses/dispatching.php +++ b/routes/grp/web/org/warehouses/dispatching.php @@ -24,5 +24,7 @@ Route::get('returns/confirmed', [IndexPalletReturns::class, 'inWarehouseConfirmed'])->name('pallet-returns.confirmed.index'); Route::get('returns/picking', [IndexPalletReturns::class, 'inWarehousePicking'])->name('pallet-returns.picking.index'); Route::get('returns/picked', [IndexPalletReturns::class, 'inWarehousePicked'])->name('pallet-returns.picked.index'); +Route::get('returns/dispatched', [IndexPalletReturns::class, 'inWarehouseDispatched'])->name('pallet-returns.dispatched.index'); +Route::get('returns/cancelled', [IndexPalletReturns::class, 'inWarehouseCancelled'])->name('pallet-returns.cancelled.index'); Route::get('returns/{palletReturn}', [ShowPalletReturn::class, 'inWarehouse'])->name('pallet-returns.show'); Route::get('return-stored-items/{palletReturn}', [ShowStoredItemReturn::class, 'inWarehouse'])->name('pallet-return-with-stored-items.show');
+
@@ -216,9 +215,11 @@ const setChartOptions = () => ({
{{ widget.description }}
+ {{ trans("Drag and drop, or browse your files") }} +