diff --git a/app/Models/Product.php b/app/Models/Product.php index c1d69e8..52f1110 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -65,27 +65,39 @@ protected function draft(Builder $query): void public function totalRejectStockFormatted(): Attribute { return Attribute::make( - get: fn () => $this->relationLoaded('variants') - ? number_format($this->variants->sum('reject_stock'), 0, ',', '.') - : '-', + get: function () { + $allVariants = $this->getAttribute('all_variants'); + + return $allVariants !== null + ? number_format($allVariants->sum('reject_stock'), 0, ',', '.') + : '-'; + }, ); } public function totalRetailStockFormatted(): Attribute { return Attribute::make( - get: fn () => $this->relationLoaded('variants') - ? number_format($this->variants->sum('retail_stock'), 0, ',', '.') - : '-', + get: function () { + $allVariants = $this->getAttribute('all_variants'); + + return $allVariants !== null + ? number_format($allVariants->sum('retail_stock'), 0, ',', '.') + : '-'; + }, ); } public function totalStockFormatted(): Attribute { return Attribute::make( - get: fn () => $this->relationLoaded('variants') - ? number_format($this->variants->sum('stock'), 0, ',', '.') - : '-', + get: function () { + $allVariants = $this->getAttribute('all_variants'); + + return $allVariants !== null + ? number_format($allVariants->sum('stock'), 0, ',', '.') + : '-'; + }, ); } diff --git a/app/Models/RawMaterial.php b/app/Models/RawMaterial.php index 27b8f14..8ff9a8b 100644 --- a/app/Models/RawMaterial.php +++ b/app/Models/RawMaterial.php @@ -66,14 +66,19 @@ public function unitLabel(): Attribute public function totalInventoryValueFormatted(): Attribute { return Attribute::make( - get: fn () => $this->relationLoaded('prices') - ? 'Rp '.number_format( - $this->prices->sum(fn (RawMaterialPrice $price) => (float) $price->stock * (int) $price->price), - 0, - ',', - '.', - ) - : '-', + get: function () { + $source = $this->getAttribute('all_prices') + ?? ($this->relationLoaded('prices') ? $this->prices : null); + + return $source !== null + ? 'Rp '.number_format( + $source->sum(fn (RawMaterialPrice $price) => (float) $price->stock * (int) $price->price), + 0, + ',', + '.', + ) + : '-'; + }, ); } @@ -81,10 +86,14 @@ public function totalStockFormatted(): Attribute { return Attribute::make( get: function () { - if (! $this->relationLoaded('prices')) { + $source = $this->getAttribute('all_prices') + ?? ($this->relationLoaded('prices') ? $this->prices : null); + + if ($source === null) { return '-'; } - $total = $this->prices->sum(fn (RawMaterialPrice $price) => (float) $price->stock); + + $total = $source->sum(fn (RawMaterialPrice $price) => (float) $price->stock); $formatted = rtrim(rtrim(number_format($total, 4, ',', '.'), '0'), ','); return "{$formatted} {$this->unit->abbreviation()}"; diff --git a/app/Services/Master/ProductService.php b/app/Services/Master/ProductService.php index 0e7813f..6a8eebe 100644 --- a/app/Services/Master/ProductService.php +++ b/app/Services/Master/ProductService.php @@ -69,42 +69,53 @@ public function paginateForIndex(array $tableQuery, string $status, string $cate $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); - return $query + $paginator = $query ->paginate(25) - ->withQueryString() - ->through(function (Product $product) { - $product->variants->each(function (ProductVariant $variant): void { - $variant->setAttribute( - 'images', - MediaPresenter::collection($variant, 'images'), - ); - }); + ->withQueryString(); - $pendingRequest = $product->pendingOwnerVerificationRequest; + // Load ALL variants for total stock calculation (not affected by search) + $productIds = $paginator->pluck('id')->all(); + $allVariantsByProduct = ProductVariant::whereIn('product_id', $productIds) + ->get() + ->groupBy('product_id'); - // Also check for pending retail stock transfer on variants - if ($pendingRequest === null) { - $pendingRequest = OwnerVerificationRequest::query() - ->where('subject_type', ProductVariant::class) - ->whereIn('subject_id', $product->variants->pluck('id')) - ->where('action', OwnerVerificationAction::RETAIL_STOCK_TRANSFER) - ->pending() - ->latest() - ->first(); - } + return $paginator->through(function (Product $product) use ($allVariantsByProduct): Product { + // Set all variants for total stock calculation + $allVariants = $allVariantsByProduct->get($product->id, collect()); + $product->setAttribute('all_variants', $allVariants); - $product->setAttribute('has_pending_request', $pendingRequest !== null); - $product->setAttribute('pending_request_id', $pendingRequest?->id); - $product->setAttribute('pending_request_action', $pendingRequest?->action->value); - $product->setAttribute('pending_request_action_label', $pendingRequest?->action->label()); - $product->setAttribute('pending_request_submitted_by_name', $pendingRequest?->submittedBy?->profile?->full_name ?? $pendingRequest?->submittedBy?->username); - $product->setAttribute( - 'display_status', - $pendingRequest?->pendingToggleStatus() ?? $product->status->value, + $product->variants->each(function (ProductVariant $variant): void { + $variant->setAttribute( + 'images', + MediaPresenter::collection($variant, 'images'), ); - - return $product; }); + + $pendingRequest = $product->pendingOwnerVerificationRequest; + + // Also check for pending retail stock transfer on variants + if ($pendingRequest === null) { + $pendingRequest = OwnerVerificationRequest::query() + ->where('subject_type', ProductVariant::class) + ->whereIn('subject_id', $product->variants->pluck('id')) + ->where('action', OwnerVerificationAction::RETAIL_STOCK_TRANSFER) + ->pending() + ->latest() + ->first(); + } + + $product->setAttribute('has_pending_request', $pendingRequest !== null); + $product->setAttribute('pending_request_id', $pendingRequest?->id); + $product->setAttribute('pending_request_action', $pendingRequest?->action->value); + $product->setAttribute('pending_request_action_label', $pendingRequest?->action->label()); + $product->setAttribute('pending_request_submitted_by_name', $pendingRequest?->submittedBy?->profile?->full_name ?? $pendingRequest?->submittedBy?->username); + $product->setAttribute( + 'display_status', + $pendingRequest?->pendingToggleStatus() ?? $product->status->value, + ); + + return $product; + }); } public function findForEdit(Product $product): Product diff --git a/app/Services/Master/RawMaterialService.php b/app/Services/Master/RawMaterialService.php index 049c316..f8256f6 100644 --- a/app/Services/Master/RawMaterialService.php +++ b/app/Services/Master/RawMaterialService.php @@ -88,18 +88,30 @@ public function paginateForIndex(array $tableQuery, string $isActive, string $st $this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']); - return $query + $paginator = $query ->paginate(25) - ->withQueryString() - ->through(function (RawMaterial $rawMaterial) { - $rawMaterial->prices->each(function (RawMaterialPrice $price) use ($rawMaterial): void { - $price->setAttribute('images', MediaPresenter::collection($price, 'images')); - $price->setAttribute('unit_abbreviation', $rawMaterial->unit->abbreviation()); - $price->unsetRelation('rawMaterial'); - }); + ->withQueryString(); - return $rawMaterial; + // Load ALL prices for total stock calculation (not affected by search) + $rawMaterialIds = $paginator->pluck('id')->all(); + $allPricesByRawMaterial = RawMaterialPrice::whereIn('raw_material_id', $rawMaterialIds) + ->orderBy('created_at') + ->get() + ->groupBy('raw_material_id'); + + return $paginator->through(function (RawMaterial $rawMaterial) use ($allPricesByRawMaterial): RawMaterial { + // Set all prices for total stock calculation + $allPrices = $allPricesByRawMaterial->get($rawMaterial->id, collect()); + $rawMaterial->setAttribute('all_prices', $allPrices); + + $rawMaterial->prices->each(function (RawMaterialPrice $price) use ($rawMaterial): void { + $price->setAttribute('images', MediaPresenter::collection($price, 'images')); + $price->setAttribute('unit_abbreviation', $rawMaterial->unit->abbreviation()); + $price->unsetRelation('rawMaterial'); }); + + return $rawMaterial; + }); } public function findForEdit(RawMaterial $rawMaterial): RawMaterial