feat: enhance stock calculations by loading all variants and prices in product and raw material services
This commit is contained in:
parent
9089b32d7b
commit
636508c5c0
@ -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, ',', '.')
|
||||
: '-';
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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()}";
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user