diff --git a/app/Services/Manage/CuttingService.php b/app/Services/Manage/CuttingService.php
index ce759e3..b13418a 100644
--- a/app/Services/Manage/CuttingService.php
+++ b/app/Services/Manage/CuttingService.php
@@ -138,7 +138,8 @@ public function rawMaterialCatalog(?Cutting $cutting = null, ?User $user = null)
->with([
'prices' => fn ($query) => $query
->orderBy('created_at')
- ->with('media'),
+ ->with('media')
+ ->where(fn ($q) => $q->where('stock', '>', 0)->orWhereIn('id', $selectedPriceIds)),
])
->where(function (Builder $query) use ($selectedPriceIds): void {
$query->active();
diff --git a/app/Services/Manage/OrderService.php b/app/Services/Manage/OrderService.php
index 601280e..60aa552 100644
--- a/app/Services/Manage/OrderService.php
+++ b/app/Services/Manage/OrderService.php
@@ -158,7 +158,13 @@ public function catalogItems(?Order $order = null, ?User $user = null): Collecti
->with([
'variants' => fn ($query) => $query
->with('media')
- ->orderBy('created_at'),
+ ->orderBy('created_at')
+ ->where(fn ($q) => $q
+ ->where('stock', '>', 0)
+ ->orWhere('reject_stock', '>', 0)
+ ->orWhere('retail_stock', '>', 0)
+ ->orWhereIn('id', $orderVariantIds)
+ ),
])
->where(function (Builder $query) use ($orderVariantIds): void {
$query->active();
@@ -857,13 +863,13 @@ private function presentVariantPricesFromCollection(int $variantId, Collection $
return $allPricesByVariant
->get($variantId, collect())
->map(fn ($price) => [
- 'type' => $price->price_type->value,
- 'type_label' => $price->price_type->label(),
- 'price' => (int) $price->price,
- 'price_formatted' => $price->price_formatted,
- 'price_input' => (string) $price->price,
- 'cost_per_unit' => (int) $price->cost_per_unit,
- 'cost_per_unit_formatted' => $price->cost_per_unit_formatted,
+ 'type' => is_array($price) ? $price['price_type'] : $price->price_type->value,
+ 'type_label' => PriceType::from(is_array($price) ? $price['price_type'] : $price->price_type->value)->label(),
+ 'price' => (int) (is_array($price) ? $price['price'] : $price->price),
+ 'price_formatted' => is_array($price) ? $price['price_formatted'] : $price->price_formatted,
+ 'price_input' => (string) (is_array($price) ? $price['price'] : $price->price),
+ 'cost_per_unit' => (int) (is_array($price) ? ($price['cost_per_unit'] ?? $price['price']) : $price->cost_per_unit),
+ 'cost_per_unit_formatted' => is_array($price) ? ($price['cost_per_unit_formatted'] ?? $price['price_formatted']) : $price->cost_per_unit_formatted,
])
->values()
->all();
diff --git a/resources/js/pages/admin/manage/cuttings/form/CuttingPosMaterialCatalogPanel.vue b/resources/js/pages/admin/manage/cuttings/form/CuttingPosMaterialCatalogPanel.vue
index 19b98ad..b5098fc 100644
--- a/resources/js/pages/admin/manage/cuttings/form/CuttingPosMaterialCatalogPanel.vue
+++ b/resources/js/pages/admin/manage/cuttings/form/CuttingPosMaterialCatalogPanel.vue
@@ -77,9 +77,10 @@ function openCombination(rawMaterial: CuttingRawMaterialCatalogItem, price: Cutt
-
-
+
+
+
{{ rawMaterial.unit_label }}
@@ -148,6 +149,7 @@ function openCombination(rawMaterial: CuttingRawMaterialCatalogItem, price: Cutt
+
diff --git a/resources/js/pages/admin/manage/orders/form/OrderPosCatalogPanel.vue b/resources/js/pages/admin/manage/orders/form/OrderPosCatalogPanel.vue
index 6c33ca1..6d11e5d 100644
--- a/resources/js/pages/admin/manage/orders/form/OrderPosCatalogPanel.vue
+++ b/resources/js/pages/admin/manage/orders/form/OrderPosCatalogPanel.vue
@@ -85,68 +85,66 @@ const emit = defineEmits<{
-
-
- Belum ada varian
-
-
+
-
-
-
- {{ variant.name }}
-
-
- Ecer: {{ variant.retail_stock ?? 0 }}
-
- Bagus: {{ variant.stock }}
+
+
+
+
+ {{ variant.name }}
+
+
+ Ecer: {{ variant.retail_stock ?? 0 }}
+
+ Bagus: {{ variant.stock }}
+ ·
+ Reject: {{ variant.reject_stock ?? 0 }}
+
·
- Reject: {{ variant.reject_stock ?? 0 }}
-
- ·
-
- {{ getVariantPrice(variant)!.price_formatted }}
+
+ {{ getVariantPrice(variant)!.price_formatted }}
+
+ -
+
+
+
+
+
+ {{ getCartItem(variant.id)!.quantity }}
- -
-
-
-
-
-
- {{ getCartItem(variant.id)!.quantity }}
-
-
+
-
-
-
-
-
+
+
diff --git a/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue b/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue
index 168ad59..8de6eb6 100644
--- a/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue
+++ b/resources/js/pages/admin/master/products/table/ProductGroupedTable.vue
@@ -125,6 +125,7 @@ function openVerificationDetail(requestId: number | undefined) {
+ #
Varian
Foto
Stok Bagus
@@ -139,7 +140,10 @@ function openVerificationDetail(requestId: number | undefined) {
Belum ada varian
-
+
+
+ {{ variantIndex + 1 }}
+
{{ variant.name }}
diff --git a/resources/js/pages/admin/master/raw-materials/table/RawMaterialGroupedTable.vue b/resources/js/pages/admin/master/raw-materials/table/RawMaterialGroupedTable.vue
index 1b812fd..f731a33 100644
--- a/resources/js/pages/admin/master/raw-materials/table/RawMaterialGroupedTable.vue
+++ b/resources/js/pages/admin/master/raw-materials/table/RawMaterialGroupedTable.vue
@@ -109,6 +109,7 @@ function openVerificationDetail(requestId: number | undefined) {
+ #
Varian
Foto
Stok
@@ -117,11 +118,14 @@ function openVerificationDetail(requestId: number | undefined) {
-
+
Belum ada varian
-
+
+
+ {{ priceIndex + 1 }}
+
{{ price.variant }}
diff --git a/tests/Feature/Admin/Manage/CuttingTest.php b/tests/Feature/Admin/Manage/CuttingTest.php
index 382ee21..24150df 100644
--- a/tests/Feature/Admin/Manage/CuttingTest.php
+++ b/tests/Feature/Admin/Manage/CuttingTest.php
@@ -1122,3 +1122,49 @@ function setupDraftItems(User $user): array
expect($combination->material_result)->toBe(15);
});
});
+
+// ─── Raw Material Catalog Stock Filter ───────────────────────
+
+describe('Raw Material Catalog Stock Filter', function () {
+ test('rawMaterialCatalog excludes variants with zero stock', function () {
+ $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_CREATE);
+
+ $rawMaterial = RawMaterial::factory()->create(['is_active' => true]);
+ $priceWithStock = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'stock' => 10]);
+ $priceWithoutStock = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'stock' => 0]);
+
+ $this->actingAs($user)
+ ->get(route('admin.manage.cuttings.create'))
+ ->assertOk();
+
+ $service = app(\App\Services\Manage\CuttingService::class);
+ $catalog = $service->rawMaterialCatalog(user: $user);
+ $material = $catalog->firstWhere('id', $rawMaterial->id);
+
+ $priceIds = $material->prices->pluck('id')->all();
+
+ expect($priceIds)->toContain($priceWithStock->id);
+ expect($priceIds)->not->toContain($priceWithoutStock->id);
+ });
+
+ test('rawMaterialCatalog includes zero-stock variants already used in cutting', function () {
+ $user = createCuttingUserWithPermission(PermissionEnum::CUTTINGS_VIEW, PermissionEnum::CUTTINGS_UPDATE);
+
+ $rawMaterial = RawMaterial::factory()->create(['is_active' => true]);
+ $priceZeroStock = RawMaterialPrice::factory()->create(['raw_material_id' => $rawMaterial->id, 'stock' => 0]);
+
+ $cutting = Cutting::factory()->create(['created_by_id' => $user->id]);
+ CuttingMaterial::factory()->create([
+ 'cutting_id' => $cutting->id,
+ 'raw_material_price_id' => $priceZeroStock->id,
+ ]);
+
+ $service = app(\App\Services\Manage\CuttingService::class);
+ $catalog = $service->rawMaterialCatalog($cutting);
+ $material = $catalog->firstWhere('id', $rawMaterial->id);
+
+ $priceIds = $material?->prices->pluck('id')->all() ?? [];
+
+ expect($priceIds)->toContain($priceZeroStock->id);
+ });
+});
diff --git a/tests/Feature/Admin/Manage/OrderTest.php b/tests/Feature/Admin/Manage/OrderTest.php
index 0f3ceea..9dbd684 100644
--- a/tests/Feature/Admin/Manage/OrderTest.php
+++ b/tests/Feature/Admin/Manage/OrderTest.php
@@ -613,3 +613,55 @@ function setupOrderDraftItems(User $user): ProductVariant
expect($item->trashed())->toBeTrue();
});
});
+
+// ─── Order Catalog Stock Filter ─────────────────────────────
+
+describe('Order Catalog Stock Filter', function () {
+ test('catalogItems excludes variants where all stock types are zero', function () {
+ $user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_CREATE);
+
+ $product = Product::factory()->create(['is_active' => true]);
+
+ $variantWithStock = ProductVariant::factory()->create(['product_id' => $product->id, 'stock' => 10, 'reject_stock' => 0, 'retail_stock' => 0]);
+ $variantAllZero = ProductVariant::factory()->create(['product_id' => $product->id, 'stock' => 0, 'reject_stock' => 0, 'retail_stock' => 0]);
+ $variantRejectOnly = ProductVariant::factory()->create(['product_id' => $product->id, 'stock' => 0, 'reject_stock' => 5, 'retail_stock' => 0]);
+ $variantRetailOnly = ProductVariant::factory()->create(['product_id' => $product->id, 'stock' => 0, 'reject_stock' => 0, 'retail_stock' => 3]);
+
+ $service = app(\App\Services\Manage\OrderService::class);
+ $catalog = $service->catalogItems(user: $user);
+ $found = $catalog->firstWhere('id', $product->id);
+
+ $variantIds = $found?->variants->pluck('id')->all() ?? [];
+
+ expect($variantIds)->toContain($variantWithStock->id);
+ expect($variantIds)->toContain($variantRejectOnly->id);
+ expect($variantIds)->toContain($variantRetailOnly->id);
+ expect($variantIds)->not->toContain($variantAllZero->id);
+ });
+
+ test('catalogItems includes all-zero-stock variants already in order', function () {
+ $user = createOrderUserWithPermission(PermissionEnum::ORDERS_VIEW, PermissionEnum::ORDERS_UPDATE);
+
+ $product = Product::factory()->create(['is_active' => true]);
+ $variantAllZero = ProductVariant::factory()->create([
+ 'product_id' => $product->id,
+ 'stock' => 0,
+ 'reject_stock' => 0,
+ 'retail_stock' => 0,
+ ]);
+
+ $order = Order::factory()->create(['created_by_id' => $user->id]);
+ OrderItem::factory()->create([
+ 'order_id' => $order->id,
+ 'product_variant_id' => $variantAllZero->id,
+ ]);
+
+ $service = app(\App\Services\Manage\OrderService::class);
+ $catalog = $service->catalogItems($order);
+ $found = $catalog->firstWhere('id', $product->id);
+
+ $variantIds = $found?->variants->pluck('id')->all() ?? [];
+
+ expect($variantIds)->toContain($variantAllZero->id);
+ });
+});