From 521e8c5538e313b7ed7a6690de0f9c5b23c752a5 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sat, 13 Jun 2026 00:24:05 +0700 Subject: [PATCH] Implement stock warning functionality for products and raw materials. Add minStock method to RawMaterialUnit enum and ProductVariant model. Enhance ProductService and RawMaterialService with stockWarningGroups method to identify items below minimum stock levels. Update admin index pages to display stock warning groups in MasterOutOfStockCatalogSection component. --- app/Enums/RawMaterialUnit.php | 15 ++++++ .../Admin/Master/ProductController.php | 1 + .../Admin/Master/RawMaterialController.php | 1 + app/Models/ProductVariant.php | 7 +++ app/Services/Master/ProductService.php | 46 +++++++++++++++- app/Services/Master/RawMaterialService.php | 52 ++++++++++++++++++- .../master/MasterOutOfStockCatalogSection.vue | 42 +++++++++++---- .../js/pages/admin/master/products/Index.vue | 12 ++++- .../admin/master/raw-materials/Index.vue | 10 ++++ resources/js/types/product.ts | 1 + 10 files changed, 175 insertions(+), 12 deletions(-) diff --git a/app/Enums/RawMaterialUnit.php b/app/Enums/RawMaterialUnit.php index 2d3596a..62b049e 100644 --- a/app/Enums/RawMaterialUnit.php +++ b/app/Enums/RawMaterialUnit.php @@ -12,6 +12,12 @@ enum RawMaterialUnit: string case METER = 'meter'; case KILOGRAM = 'kilogram'; + private const MIN_STOCK_YARD = 5.0; + + private const MIN_STOCK_METER = 5.0; + + private const MIN_STOCK_KILOGRAM = 2.0; + public function label(): string { return match ($this) { @@ -37,4 +43,13 @@ public static function values(): array { return array_column(self::cases(), 'value'); } + + public function minStock(): float + { + return match ($this) { + self::YARD => self::MIN_STOCK_YARD, + self::METER => self::MIN_STOCK_METER, + self::KILOGRAM => self::MIN_STOCK_KILOGRAM, + }; + } } diff --git a/app/Http/Controllers/Admin/Master/ProductController.php b/app/Http/Controllers/Admin/Master/ProductController.php index b573681..5870bd3 100644 --- a/app/Http/Controllers/Admin/Master/ProductController.php +++ b/app/Http/Controllers/Admin/Master/ProductController.php @@ -33,6 +33,7 @@ public function index(Request $request): Response return Inertia::render('admin/master/products/Index', [ 'products' => $this->productService->paginateForIndex($tableQuery, $isActive), 'outOfStockGroups' => $this->productService->outOfStockGroups(), + 'stockWarningGroups' => $this->productService->stockWarningGroups(), 'filters' => $this->dataTableFilters($tableQuery, [ 'is_active' => $isActive, ]), diff --git a/app/Http/Controllers/Admin/Master/RawMaterialController.php b/app/Http/Controllers/Admin/Master/RawMaterialController.php index ab7772f..06e2dc5 100644 --- a/app/Http/Controllers/Admin/Master/RawMaterialController.php +++ b/app/Http/Controllers/Admin/Master/RawMaterialController.php @@ -33,6 +33,7 @@ public function index(Request $request): Response return Inertia::render('admin/master/raw-materials/Index', [ 'rawMaterials' => $this->rawMaterialService->paginateForIndex($tableQuery, $isActive), 'outOfStockGroups' => $this->rawMaterialService->outOfStockGroups(), + 'stockWarningGroups' => $this->rawMaterialService->stockWarningGroups(), 'filters' => $this->dataTableFilters($tableQuery, [ 'is_active' => $isActive, ]), diff --git a/app/Models/ProductVariant.php b/app/Models/ProductVariant.php index 52173ad..1e7fe9b 100644 --- a/app/Models/ProductVariant.php +++ b/app/Models/ProductVariant.php @@ -20,6 +20,8 @@ class ProductVariant extends Model implements HasMedia use InteractsWithActivityLog; use SoftDeletes; + private const MIN_STOCK = 5; + protected function casts(): array { return [ @@ -47,6 +49,11 @@ public function product(): BelongsTo return $this->belongsTo(Product::class); } + public static function minStock(): int + { + return self::MIN_STOCK; + } + public static function mediaModuleName(): string { return 'product'; diff --git a/app/Services/Master/ProductService.php b/app/Services/Master/ProductService.php index eef5af6..13b42b7 100644 --- a/app/Services/Master/ProductService.php +++ b/app/Services/Master/ProductService.php @@ -34,12 +34,56 @@ public function __construct( * }> */ public function outOfStockGroups(): array + { + return $this->inventoryAlertGroups( + fn (ProductVariant $variant): bool => $variant->stock <= 0, + ); + } + + /** + * @return list>, + * }>, + * }> + */ + public function stockWarningGroups(): array + { + return $this->inventoryAlertGroups(function (ProductVariant $variant): bool { + if ($variant->stock <= 0) { + return false; + } + + return $variant->stock < ProductVariant::minStock(); + }); + } + + /** + * @return list>, + * }>, + * }> + */ + private function inventoryAlertGroups(callable $filter): array { return ProductVariant::query() ->with(['product', 'media']) - ->where('stock', '<=', 0) ->whereHas('product', fn (Builder $query) => $query->where('is_active', true)) ->get() + ->filter($filter) ->sortBy([ fn (ProductVariant $variant) => $variant->product->name, fn (ProductVariant $variant) => $variant->name, diff --git a/app/Services/Master/RawMaterialService.php b/app/Services/Master/RawMaterialService.php index f6b32b4..ca219dd 100644 --- a/app/Services/Master/RawMaterialService.php +++ b/app/Services/Master/RawMaterialService.php @@ -33,11 +33,61 @@ public function __construct( * }> */ public function outOfStockGroups(): array + { + return $this->inventoryAlertGroups( + fn (RawMaterialPrice $price): bool => (float) $price->stock <= 0, + ); + } + + /** + * @return list>, + * }>, + * }> + */ + public function stockWarningGroups(): array + { + return $this->inventoryAlertGroups(function (RawMaterialPrice $price): bool { + $stock = (float) $price->stock; + + if ($stock <= 0) { + return false; + } + + $minStock = $price->rawMaterial->unit->minStock(); + + return $stock < $minStock; + }); + } + + /** + * @return list>, + * }>, + * }> + */ + private function inventoryAlertGroups(callable $filter): array { return RawMaterialPrice::query() ->with(['rawMaterial', 'media']) - ->where('stock', '<=', 0) ->get() + ->filter($filter) ->sortBy([ fn (RawMaterialPrice $price) => $price->rawMaterial->name, fn (RawMaterialPrice $price) => $price->variant, diff --git a/resources/js/components/admin/master/MasterOutOfStockCatalogSection.vue b/resources/js/components/admin/master/MasterOutOfStockCatalogSection.vue index 931644f..e957338 100644 --- a/resources/js/components/admin/master/MasterOutOfStockCatalogSection.vue +++ b/resources/js/components/admin/master/MasterOutOfStockCatalogSection.vue @@ -1,6 +1,6 @@