refactor: replace remaining material with sampel and hasil cutting diluar sampel in cutting requests and models, update related validation and frontend components

This commit is contained in:
Yoga Pangestu 2026-06-27 23:50:41 +07:00
parent 1f27acc63a
commit 9dba5e1491
22 changed files with 217 additions and 250 deletions

View File

@ -25,7 +25,6 @@ public function rules(): array
Rule::exists('raw_material_prices', 'id')->whereNull('deleted_at'),
],
'material_usage' => ['required', 'numeric', 'decimal:0,4', 'gt:0'],
'remaining_material' => ['required', 'numeric', 'decimal:0,4', 'gte:0'],
];
}
}

View File

@ -25,8 +25,8 @@ public function rules(): array
Rule::exists('product_variants', 'id')->whereNull('deleted_at'),
],
'cutting_result' => ['required', 'integer', 'min:1'],
'warehouse_stock' => ['required', 'integer', 'min:0'],
'cutting_reject' => ['required', 'integer', 'min:0'],
'sampel' => ['required', 'integer', 'min:0'],
'hasil_cutting_diluar_sampel' => ['required', 'integer', 'min:0'],
];
}
}

View File

@ -40,7 +40,6 @@ public function rules(): array
Rule::exists('raw_material_prices', 'id')->whereNull('deleted_at'),
];
$rules['materials.*.material_usage'] = ['required', 'numeric', 'decimal:0,4', 'gt:0'];
$rules['materials.*.remaining_material'] = ['required', 'numeric', 'decimal:0,4', 'gte:0'];
$rules['results'] = ['required', 'array', 'min:1'];
$rules['results.*.product_variant_id'] = [
@ -50,8 +49,8 @@ public function rules(): array
Rule::exists('product_variants', 'id')->whereNull('deleted_at'),
];
$rules['results.*.cutting_result'] = ['required', 'integer', 'min:1'];
$rules['results.*.warehouse_stock'] = ['required', 'integer', 'min:0'];
$rules['results.*.cutting_reject'] = ['required', 'integer', 'min:0'];
$rules['results.*.sampel'] = ['required', 'integer', 'min:0'];
$rules['results.*.hasil_cutting_diluar_sampel'] = ['required', 'integer', 'min:0'];
}
return $rules;
@ -67,12 +66,11 @@ public function attributes(): array
'materials' => 'bahan baku',
'materials.*.raw_material_price_id' => 'bahan baku',
'materials.*.material_usage' => 'pemakaian',
'materials.*.remaining_material' => 'sisa',
'results' => 'hasil produk',
'results.*.product_variant_id' => 'varian produk',
'results.*.cutting_result' => 'hasil',
'results.*.warehouse_stock' => 'bagus',
'results.*.cutting_reject' => 'reject',
'results.*.sampel' => 'sampel',
'results.*.hasil_cutting_diluar_sampel' => 'hasil cutting diluar sampel',
'sewing_cost' => 'jasa jahit',
'other_cost' => 'biaya lainnya',
];

View File

@ -46,8 +46,8 @@ public function rules(): array
'verification_note' => ['nullable', 'string', 'max:500'],
'results' => ['nullable', 'array'],
'results.*.product_variant_id' => ['required_with:results', 'integer', 'exists:product_variants,id'],
'results.*.warehouse_stock' => ['required_with:results', 'integer', 'min:0'],
'results.*.cutting_reject' => ['required_with:results', 'integer', 'min:0'],
'results.*.sampel' => ['required_with:results', 'integer', 'min:0'],
'results.*.hasil_cutting_diluar_sampel' => ['required_with:results', 'integer', 'min:0'],
'result_prices' => ['nullable', 'array'],
'result_prices.*.product_variant_id' => ['required_with:result_prices', 'integer', 'exists:product_variants,id'],
'result_prices.*.prices' => ['required_with:result_prices', 'array', 'min:1'],
@ -92,8 +92,8 @@ public function withValidator(Validator $validator): void
$cuttingResults = $cutting->results->keyBy('product_variant_id');
foreach ($this->input('results', []) as $index => $item) {
$variantId = $item['product_variant_id'] ?? 0;
$warehouseStock = (int) ($item['warehouse_stock'] ?? 0);
$cuttingReject = (int) ($item['cutting_reject'] ?? 0);
$sampel = (int) ($item['sampel'] ?? 0);
$hasilCuttingDiluarSampel = (int) ($item['hasil_cutting_diluar_sampel'] ?? 0);
$originalResult = $cuttingResults->get($variantId);
if ($originalResult === null) {
@ -102,8 +102,8 @@ public function withValidator(Validator $validator): void
continue;
}
if (($warehouseStock + $cuttingReject) !== (int) $originalResult->cutting_result) {
$validator->errors()->add("results.{$index}.warehouse_stock", "Total jumlah (diterima + reject) harus sama dengan hasil cutting asli ({$originalResult->cutting_result} pcs).");
if (($sampel + $hasilCuttingDiluarSampel) !== (int) $originalResult->cutting_result) {
$validator->errors()->add("results.{$index}.sampel", "Total jumlah (sampel + hasil cutting diluar sampel) harus sama dengan hasil cutting asli ({$originalResult->cutting_result} pcs).");
}
}
}

View File

@ -26,8 +26,8 @@ public function rules(): array
'verification_note' => ['nullable', 'string', 'max:500'],
'results' => ['nullable', 'array'],
'results.*.product_variant_id' => ['required_with:results', 'integer', 'exists:product_variants,id'],
'results.*.warehouse_stock' => ['required_with:results', 'integer', 'min:0'],
'results.*.cutting_reject' => ['required_with:results', 'integer', 'min:0'],
'results.*.sampel' => ['required_with:results', 'integer', 'min:0'],
'results.*.hasil_cutting_diluar_sampel' => ['required_with:results', 'integer', 'min:0'],
'result_prices' => ['nullable', 'array'],
'result_prices.*.product_variant_id' => ['required_with:result_prices', 'integer', 'exists:product_variants,id'],
'result_prices.*.prices' => ['required_with:result_prices', 'array', 'min:1'],
@ -50,8 +50,8 @@ public function attributes(): array
'result_prices.*.prices.*.price' => 'harga jual',
'results' => 'hasil cutting',
'results.*.product_variant_id' => 'varian produk',
'results.*.warehouse_stock' => 'stok bagus',
'results.*.cutting_reject' => 'stok reject',
'results.*.sampel' => 'sampel',
'results.*.hasil_cutting_diluar_sampel' => 'hasil cutting diluar sampel',
];
}
@ -71,8 +71,8 @@ public function withValidator(Validator $validator): void
$cuttingResults = $cutting->results->keyBy('product_variant_id');
foreach ($this->input('results', []) as $index => $item) {
$variantId = $item['product_variant_id'] ?? 0;
$warehouseStock = (int) ($item['warehouse_stock'] ?? 0);
$cuttingReject = (int) ($item['cutting_reject'] ?? 0);
$sampel = (int) ($item['sampel'] ?? 0);
$hasilCuttingDiluarSampel = (int) ($item['hasil_cutting_diluar_sampel'] ?? 0);
$originalResult = $cuttingResults->get($variantId);
if ($originalResult === null) {
@ -81,8 +81,8 @@ public function withValidator(Validator $validator): void
continue;
}
if (($warehouseStock + $cuttingReject) !== (int) $originalResult->cutting_result) {
$validator->errors()->add("results.{$index}.warehouse_stock", "Total jumlah (diterima + reject) harus sama dengan hasil cutting asli ({$originalResult->cutting_result} pcs).");
if (($sampel + $hasilCuttingDiluarSampel) !== (int) $originalResult->cutting_result) {
$validator->errors()->add("results.{$index}.sampel", "Total jumlah (sampel + hasil cutting diluar sampel) harus sama dengan hasil cutting asli ({$originalResult->cutting_result} pcs).");
}
}
}

View File

@ -15,8 +15,6 @@
#[Appends([
'material_usage_formatted',
'material_usage_input',
'remaining_material_formatted',
'remaining_material_input',
'unit_abbreviation',
])]
class CuttingMaterial extends Model
@ -27,7 +25,6 @@ protected function casts(): array
{
return [
'material_usage' => 'decimal:4',
'remaining_material' => 'decimal:4',
];
}
@ -60,20 +57,6 @@ public function materialUsageInput(): Attribute
);
}
public function remainingMaterialFormatted(): Attribute
{
return Attribute::make(
get: fn () => $this->formatQuantity($this->remaining_material),
);
}
public function remainingMaterialInput(): Attribute
{
return Attribute::make(
get: fn () => $this->formatQuantityInput($this->remaining_material),
);
}
public function unitAbbreviation(): Attribute
{
return Attribute::make(
@ -89,7 +72,7 @@ public function materialCost(): int
return 0;
}
return (int) round(((float) $this->material_usage - (float) $this->remaining_material) * (int) $price->price);
return (int) round((float) $this->material_usage * (int) $price->price);
}
private function formatQuantity(float|string|null $value): string

View File

@ -18,8 +18,8 @@ protected function casts(): array
{
return [
'cutting_result' => 'integer',
'warehouse_stock' => 'integer',
'cutting_reject' => 'integer',
'sampel' => 'integer',
'hasil_cutting_diluar_sampel' => 'integer',
];
}

View File

@ -307,7 +307,6 @@ public function syncDraftMaterial(array $validated, User $user): array
->findOrFail($validated['raw_material_price_id']);
$materialUsage = round((float) $validated['material_usage'], 2);
$remainingMaterial = round((float) $validated['remaining_material'], 2);
if ($materialUsage <= 0) {
throw ValidationException::withMessages([
@ -315,12 +314,6 @@ public function syncDraftMaterial(array $validated, User $user): array
]);
}
if ($remainingMaterial < 0) {
throw ValidationException::withMessages([
'remaining_material' => 'Sisa bahan tidak boleh negatif.',
]);
}
$item = CuttingMaterial::query()->updateOrCreate(
[
'user_id' => $user->id,
@ -329,7 +322,6 @@ public function syncDraftMaterial(array $validated, User $user): array
],
[
'material_usage' => $materialUsage,
'remaining_material' => $remainingMaterial,
],
);
@ -352,8 +344,8 @@ public function syncDraftResult(array $validated, User $user): array
->findOrFail($validated['product_variant_id']);
$cuttingResult = (int) $validated['cutting_result'];
$warehouseStock = (int) $validated['warehouse_stock'];
$cuttingReject = (int) ($validated['cutting_reject'] ?? 0);
$sampel = (int) $validated['sampel'];
$hasilCuttingDiluarSampel = (int) ($validated['hasil_cutting_diluar_sampel'] ?? 0);
if ($cuttingResult < 1) {
throw ValidationException::withMessages([
@ -361,21 +353,21 @@ public function syncDraftResult(array $validated, User $user): array
]);
}
if ($warehouseStock < 0) {
if ($sampel < 0) {
throw ValidationException::withMessages([
'warehouse_stock' => 'Stok gudang tidak boleh negatif.',
'sampel' => 'Sampel tidak boleh negatif.',
]);
}
if ($cuttingReject < 0) {
if ($hasilCuttingDiluarSampel < 0) {
throw ValidationException::withMessages([
'cutting_reject' => 'Reject tidak boleh negatif.',
'hasil_cutting_diluar_sampel' => 'Hasil cutting diluar sampel tidak boleh negatif.',
]);
}
if (($warehouseStock + $cuttingReject) !== $cuttingResult) {
if (($sampel + $hasilCuttingDiluarSampel) !== $cuttingResult) {
throw ValidationException::withMessages([
'cutting_result' => 'Hasil cutting harus sama dengan stok gudang ditambah reject.',
'cutting_result' => 'Hasil cutting harus sama dengan sampel ditambah hasil cutting diluar sampel.',
]);
}
@ -387,8 +379,8 @@ public function syncDraftResult(array $validated, User $user): array
],
[
'cutting_result' => $cuttingResult,
'warehouse_stock' => $warehouseStock,
'cutting_reject' => $cuttingReject,
'sampel' => $sampel,
'hasil_cutting_diluar_sampel' => $hasilCuttingDiluarSampel,
],
);
@ -647,8 +639,8 @@ public function transitionStatus(
$cutting->results()
->where('product_variant_id', $item['product_variant_id'])
->update([
'warehouse_stock' => $item['warehouse_stock'],
'cutting_reject' => $item['cutting_reject'],
'sampel' => $item['sampel'],
'hasil_cutting_diluar_sampel' => $item['hasil_cutting_diluar_sampel'],
]);
}
$cutting->load('results');
@ -709,8 +701,8 @@ public function transitionStatus(
}
/**
* @param list<array{raw_material_price_id: int, material_usage: float|int|string, remaining_material: float|int|string}> $materials
* @return list<array{raw_material_price_id: int, material_usage: float, remaining_material: float}>
* @param list<array{raw_material_price_id: int, material_usage: float|int|string}> $materials
* @return list<array{raw_material_price_id: int, material_usage: float}>
*/
private function buildMaterials(array $materials): array
{
@ -727,7 +719,6 @@ private function buildMaterials(array $materials): array
}
$materialUsage = round((float) $itemData['material_usage'], 2);
$remainingMaterial = round((float) $itemData['remaining_material'], 2);
if ($materialUsage <= 0) {
throw ValidationException::withMessages([
@ -735,24 +726,17 @@ private function buildMaterials(array $materials): array
]);
}
if ($remainingMaterial < 0) {
throw ValidationException::withMessages([
"materials.{$index}.remaining_material" => 'Sisa bahan tidak boleh negatif.',
]);
}
return [
'raw_material_price_id' => $price->id,
'material_usage' => $materialUsage,
'remaining_material' => $remainingMaterial,
];
})
->all();
}
/**
* @param list<array{product_variant_id: int, cutting_result: int|string, warehouse_stock: int|string, cutting_reject?: int|string}> $results
* @return list<array{product_variant_id: int, cutting_result: int, warehouse_stock: int, cutting_reject: int}>
* @param list<array{product_variant_id: int, cutting_result: int|string, sampel: int|string, hasil_cutting_diluar_sampel?: int|string}> $results
* @return list<array{product_variant_id: int, cutting_result: int, sampel: int, hasil_cutting_diluar_sampel: int}>
*/
private function buildResults(array $results): array
{
@ -767,8 +751,8 @@ private function buildResults(array $results): array
}
$cuttingResult = (int) $itemData['cutting_result'];
$warehouseStock = (int) $itemData['warehouse_stock'];
$cuttingReject = (int) ($itemData['cutting_reject'] ?? 0);
$sampel = (int) $itemData['sampel'];
$hasilCuttingDiluarSampel = (int) ($itemData['hasil_cutting_diluar_sampel'] ?? 0);
if ($cuttingResult < 1) {
throw ValidationException::withMessages([
@ -776,29 +760,29 @@ private function buildResults(array $results): array
]);
}
if ($warehouseStock < 0) {
if ($sampel < 0) {
throw ValidationException::withMessages([
"results.{$index}.warehouse_stock" => 'Stok gudang tidak boleh negatif.',
"results.{$index}.sampel" => 'Sampel tidak boleh negatif.',
]);
}
if ($cuttingReject < 0) {
if ($hasilCuttingDiluarSampel < 0) {
throw ValidationException::withMessages([
"results.{$index}.cutting_reject" => 'Reject tidak boleh negatif.',
"results.{$index}.hasil_cutting_diluar_sampel" => 'Hasil cutting diluar sampel tidak boleh negatif.',
]);
}
if (($warehouseStock + $cuttingReject) !== $cuttingResult) {
if (($sampel + $hasilCuttingDiluarSampel) !== $cuttingResult) {
throw ValidationException::withMessages([
"results.{$index}.cutting_result" => 'Hasil cutting harus sama dengan stok gudang ditambah reject.',
"results.{$index}.cutting_result" => 'Hasil cutting harus sama dengan sampel ditambah hasil cutting diluar sampel.',
]);
}
return [
'product_variant_id' => $variant->id,
'cutting_result' => $cuttingResult,
'warehouse_stock' => $warehouseStock,
'cutting_reject' => $cuttingReject,
'sampel' => $sampel,
'hasil_cutting_diluar_sampel' => $hasilCuttingDiluarSampel,
];
})
->all();
@ -807,7 +791,7 @@ private function buildResults(array $results): array
private function deductMaterialStock(Cutting $cutting): void
{
foreach ($cutting->materials as $material) {
$totalTaken = (float) $material->material_usage - (float) $material->remaining_material;
$totalTaken = (float) $material->material_usage;
$price = RawMaterialPrice::query()->lockForUpdate()->find($material->raw_material_price_id);
if ($price === null) {
@ -829,36 +813,34 @@ private function deductMaterialStock(Cutting $cutting): void
private function reverseTotalMaterialStock(Cutting $cutting): void
{
foreach ($cutting->materials as $material) {
$totalTaken = (float) $material->material_usage - (float) $material->remaining_material;
RawMaterialPrice::query()
->whereKey($material->raw_material_price_id)
->increment('stock', $totalTaken);
->increment('stock', (float) $material->material_usage);
}
}
private function reverseMaterialStock(Cutting $cutting): void
{
foreach ($cutting->materials as $material) {
$totalTaken = (float) $material->material_usage - (float) $material->remaining_material;
RawMaterialPrice::query()
->whereKey($material->raw_material_price_id)
->increment('stock', $totalTaken);
->increment('stock', (float) $material->material_usage);
}
}
private function applyProductStockOnVerify(Cutting $cutting): void
{
foreach ($cutting->results as $result) {
if ($result->warehouse_stock > 0) {
if ($result->sampel > 0) {
ProductVariant::query()
->whereKey($result->product_variant_id)
->increment('stock', $result->warehouse_stock);
->increment('stock', $result->sampel);
}
if ($result->cutting_reject > 0) {
if ($result->hasil_cutting_diluar_sampel > 0) {
ProductVariant::query()
->whereKey($result->product_variant_id)
->increment('reject_stock', $result->cutting_reject);
->increment('reject_stock', $result->hasil_cutting_diluar_sampel);
}
}
}
@ -866,16 +848,16 @@ private function applyProductStockOnVerify(Cutting $cutting): void
private function reverseProductStock(Cutting $cutting): void
{
foreach ($cutting->results as $result) {
if ($result->warehouse_stock > 0) {
if ($result->sampel > 0) {
ProductVariant::query()
->whereKey($result->product_variant_id)
->decrement('stock', $result->warehouse_stock);
->decrement('stock', $result->sampel);
}
if ($result->cutting_reject > 0) {
if ($result->hasil_cutting_diluar_sampel > 0) {
ProductVariant::query()
->whereKey($result->product_variant_id)
->decrement('reject_stock', $result->cutting_reject);
->decrement('reject_stock', $result->hasil_cutting_diluar_sampel);
}
}
}
@ -943,7 +925,6 @@ private function presentDraftMaterial(CuttingMaterial $item): array
'unit_abbreviation' => $rawMaterial?->unit?->abbreviation() ?? '',
'stock_input' => $price?->stock_input ?? '',
'material_usage' => $this->formatQuantityInput((float) $item->material_usage),
'remaining_material' => $this->formatQuantityInput((float) $item->remaining_material),
'images' => $price ? MediaPresenter::collection($price, 'images') : [],
];
}
@ -961,8 +942,8 @@ private function presentDraftResult(CuttingResult $item): array
'variant_name' => $variant?->name ?? '',
'stock' => $variant?->stock ?? 0,
'cutting_result' => (string) $item->cutting_result,
'warehouse_stock' => (string) $item->warehouse_stock,
'cutting_reject' => (string) $item->cutting_reject,
'sampel' => (string) $item->sampel,
'hasil_cutting_diluar_sampel' => (string) $item->hasil_cutting_diluar_sampel,
'images' => $variant ? MediaPresenter::collection($variant, 'images') : [],
];
}
@ -975,7 +956,7 @@ private function formatQuantityInput(float $value): string
private function appendCostPreview(Cutting $cutting): void
{
$cutting->setAttribute('total_result_pieces', (int) $cutting->results->sum('cutting_result'));
$cutting->setAttribute('total_material_usage', (float) $cutting->materials->sum(fn (CuttingMaterial $material) => (float) $material->material_usage - (float) $material->remaining_material));
$cutting->setAttribute('total_material_usage', (float) $cutting->materials->sum(fn (CuttingMaterial $material) => (float) $material->material_usage));
$totalMaterialCost = $cutting->total_material_cost ?? $this->calculateTotalMaterialCost($cutting);
$sewingCost = (int) ($cutting->sewing_cost ?? 0);

View File

@ -71,7 +71,7 @@ public function getPendingApprovalCuttings(User $user): Collection
/**
* Submit verification for a completed cutting - saves data but does NOT add stock yet.
*
* @param list<array{product_variant_id: int, warehouse_stock: int, cutting_reject: int}>|null $results
* @param list<array{product_variant_id: int, sampel: int, hasil_cutting_diluar_sampel: int}>|null $results
* @param list<array{product_variant_id: int, prices: list<array{type: string, price: int}>}>|null $resultPrices
*/
public function submitVerification(
@ -96,8 +96,8 @@ public function submitVerification(
$cutting->results()
->where('product_variant_id', $item['product_variant_id'])
->update([
'warehouse_stock' => $item['warehouse_stock'],
'cutting_reject' => $item['cutting_reject'],
'sampel' => $item['sampel'],
'hasil_cutting_diluar_sampel' => $item['hasil_cutting_diluar_sampel'],
]);
}
$cutting->load('results');
@ -248,16 +248,16 @@ public function rejectVerification(
private function applyProductStockOnVerify(Cutting $cutting): void
{
foreach ($cutting->results as $result) {
if ($result->warehouse_stock > 0) {
if ($result->sampel > 0) {
ProductVariant::query()
->whereKey($result->product_variant_id)
->increment('stock', $result->warehouse_stock);
->increment('stock', $result->sampel);
}
if ($result->cutting_reject > 0) {
if ($result->hasil_cutting_diluar_sampel > 0) {
ProductVariant::query()
->whereKey($result->product_variant_id)
->increment('reject_stock', $result->cutting_reject);
->increment('reject_stock', $result->hasil_cutting_diluar_sampel);
}
}
}

View File

@ -14,14 +14,10 @@ class CuttingMaterialFactory extends Factory
{
public function definition(): array
{
$materialUsage = fake()->randomFloat(4, 1, 25);
$remainingMaterial = fake()->randomFloat(4, 0, 10);
return [
'cutting_id' => Cutting::factory(),
'raw_material_price_id' => RawMaterialPrice::factory(),
'material_usage' => $materialUsage,
'remaining_material' => $remainingMaterial,
'material_usage' => fake()->randomFloat(4, 1, 25),
];
}
}

View File

@ -15,14 +15,14 @@ class CuttingResultFactory extends Factory
public function definition(): array
{
$cuttingResult = fake()->numberBetween(10, 200);
$cuttingReject = fake()->numberBetween(0, (int) ($cuttingResult * 0.1));
$hasilCuttingDiluarSampel = fake()->numberBetween(0, (int) ($cuttingResult * 0.1));
return [
'cutting_id' => Cutting::factory(),
'product_variant_id' => ProductVariant::factory(),
'cutting_result' => $cuttingResult,
'warehouse_stock' => $cuttingResult - $cuttingReject,
'cutting_reject' => $cuttingReject,
'sampel' => $cuttingResult - $hasilCuttingDiluarSampel,
'hasil_cutting_diluar_sampel' => $hasilCuttingDiluarSampel,
];
}
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
// 1. Drop remaining_material from cutting_materials
Schema::table('cutting_materials', function (Blueprint $table): void {
$table->dropColumn('remaining_material');
});
// 2. Rename warehouse_stock -> sampel, cutting_reject -> hasil_cutting_diluar_sampel in cutting_results
Schema::table('cutting_results', function (Blueprint $table): void {
$table->renameColumn('warehouse_stock', 'sampel');
$table->renameColumn('cutting_reject', 'hasil_cutting_diluar_sampel');
});
}
public function down(): void
{
// 1. Add back remaining_material to cutting_materials
Schema::table('cutting_materials', function (Blueprint $table): void {
$table->decimal('remaining_material', 18, 2)->after('material_usage');
});
// 2. Rename back to original names
Schema::table('cutting_results', function (Blueprint $table): void {
$table->renameColumn('sampel', 'warehouse_stock');
$table->renameColumn('hasil_cutting_diluar_sampel', 'cutting_reject');
});
}
};

View File

@ -30,7 +30,6 @@ const initialData = computed(() => ({
item.raw_material_price?.raw_material?.unit_abbreviation ?? '',
stock_input: item.raw_material_price?.stock_input ?? '',
material_usage: item.material_usage_input,
remaining_material: item.remaining_material_input,
images: item.raw_material_price?.images ?? [],
})),
results: props.cutting.results.map((item) => ({
@ -39,8 +38,8 @@ const initialData = computed(() => ({
variant_name: item.product_variant?.name ?? '',
stock: item.product_variant?.stock ?? 0,
cutting_result: String(item.cutting_result),
warehouse_stock: String(item.warehouse_stock),
cutting_reject: String(item.cutting_reject),
sampel: String(item.sampel),
hasil_cutting_diluar_sampel: String(item.hasil_cutting_diluar_sampel),
images: item.product_variant?.images ?? [],
})),
}));

View File

@ -235,27 +235,26 @@ function upsertResultCartItem(item: CuttingResultCartItem) {
resultCart.value[index] = { ...item };
}
async function syncDraftMaterial(rawMaterial: CuttingRawMaterialCatalogItem, price: CatalogPrice, materialUsage: string, remainingMaterial: string) {
async function syncDraftMaterial(rawMaterial: CuttingRawMaterialCatalogItem, price: CatalogPrice, materialUsage: string) {
const { item } = await apiFetch<{ item: CuttingMaterialCartItem }>(draft_materials.store.url(), {
method: 'POST',
body: JSON.stringify({
raw_material_price_id: price.id,
material_usage: materialUsage,
remaining_material: remainingMaterial,
}),
});
upsertMaterialCartItem(item);
}
async function syncDraftResult(product: CuttingProductCatalogItem, variant: CuttingProductCatalogItem['variants'][number], cuttingResult: string, warehouseStock: string, cuttingReject: string) {
async function syncDraftResult(product: CuttingProductCatalogItem, variant: CuttingProductCatalogItem['variants'][number], cuttingResult: string, sampel: string, hasilCuttingDiluarSampel: string) {
const { item } = await apiFetch<{ item: CuttingResultCartItem }>(draft_results.store.url(), {
method: 'POST',
body: JSON.stringify({
product_variant_id: variant.id,
cutting_result: cuttingResult,
warehouse_stock: warehouseStock,
cutting_reject: cuttingReject,
sampel: sampel,
hasil_cutting_diluar_sampel: hasilCuttingDiluarSampel,
}),
});
@ -300,7 +299,6 @@ async function decreaseMaterialQty(priceId: number) {
{ id: 0, name: '', unit: '', unit_label: '', unit_abbreviation: '', prices: [] } as CuttingRawMaterialCatalogItem,
{ id: priceId, variant: '', price: 0, price_formatted: '', stock_input: '', stock_formatted: '', images: [] } as CatalogPrice,
String(nextQty),
item.remaining_material,
);
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Gagal memperbarui jumlah item.');
@ -325,11 +323,10 @@ async function addMaterial(
const nextUsage = existing
? String((Number(existing.material_usage) || 0) + increment)
: defaultUsage;
const remaining = existing?.remaining_material ?? '0';
if (isCreateMode.value) {
try {
await syncDraftMaterial(rawMaterial, price, nextUsage, remaining);
await syncDraftMaterial(rawMaterial, price, nextUsage);
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Gagal menyimpan item ke keranjang.');
}
@ -351,7 +348,6 @@ async function addMaterial(
unit_abbreviation: rawMaterial.unit_abbreviation,
stock_input: price.stock_input,
material_usage: defaultUsage,
remaining_material: '0',
images: price.images ?? [],
});
}
@ -406,8 +402,8 @@ async function decreaseResultQty(variantId: number) {
return;
}
const nextWarehouse = Math.max(0, (Number(item.warehouse_stock) || 0) - 1);
const nextReject = Math.max(nextQty - nextWarehouse, 0);
const nextSampel = Math.max(0, (Number(item.sampel) || 0) - 1);
const nextHasilCuttingDiluarSampel = Math.max(nextQty - nextSampel, 0);
if (isCreateMode.value) {
try {
@ -415,8 +411,8 @@ async function decreaseResultQty(variantId: number) {
{ id: 0, name: '', variants: [] } as CuttingProductCatalogItem,
{ id: variantId, name: '', stock: 0, images: [] } as CuttingProductCatalogItem['variants'][number],
String(nextQty),
String(nextWarehouse),
String(nextReject),
String(nextSampel),
String(nextHasilCuttingDiluarSampel),
);
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Gagal memperbarui jumlah item.');
@ -426,7 +422,7 @@ async function decreaseResultQty(variantId: number) {
}
item.cutting_result = String(nextQty);
item.warehouse_stock = String(nextWarehouse);
item.sampel = String(nextSampel);
syncResultTotals(item);
}
@ -439,12 +435,12 @@ async function addResult(
);
const nextQty = existing ? (Number(existing.cutting_result) || 0) + 1 : 1;
const nextWarehouse = existing ? (Number(existing.warehouse_stock) || 0) + 1 : 1;
const nextReject = Math.max(nextQty - nextWarehouse, 0);
const nextSampel = existing ? (Number(existing.sampel) || 0) + 1 : 1;
const nextHasilCuttingDiluarSampel = Math.max(nextQty - nextSampel, 0);
if (isCreateMode.value) {
try {
await syncDraftResult(product, variant, String(nextQty), String(nextWarehouse), String(nextReject));
await syncDraftResult(product, variant, String(nextQty), String(nextSampel), String(nextHasilCuttingDiluarSampel));
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Gagal menyimpan item ke keranjang.');
}
@ -454,7 +450,7 @@ async function addResult(
if (existing) {
existing.cutting_result = String(nextQty);
existing.warehouse_stock = String(nextWarehouse);
existing.sampel = String(nextSampel);
syncResultTotals(existing);
return;
@ -466,8 +462,8 @@ async function addResult(
variant_name: variant.name,
stock: variant.stock,
cutting_result: '1',
warehouse_stock: '1',
cutting_reject: '0',
sampel: '1',
hasil_cutting_diluar_sampel: '0',
images: variant.images ?? [],
});
}
@ -493,10 +489,10 @@ async function removeResult(index: number) {
function syncResultTotals(item: CuttingResultCartItem) {
const total = Number(item.cutting_result) || 0;
const warehouse = Number(item.warehouse_stock) || 0;
const reject = Math.max(total - warehouse, 0);
const sampel = Number(item.sampel) || 0;
const hasilCuttingDiluarSampel = Math.max(total - sampel, 0);
item.cutting_reject = String(reject);
item.hasil_cutting_diluar_sampel = String(hasilCuttingDiluarSampel);
}
async function syncMaterialField(index: number) {
@ -512,7 +508,6 @@ async function syncMaterialField(index: number) {
body: JSON.stringify({
raw_material_price_id: item.raw_material_price_id,
material_usage: item.material_usage,
remaining_material: item.remaining_material,
}),
});
} catch (error) {
@ -533,8 +528,8 @@ async function syncResultField(index: number) {
body: JSON.stringify({
product_variant_id: item.product_variant_id,
cutting_result: item.cutting_result,
warehouse_stock: item.warehouse_stock,
cutting_reject: item.cutting_reject,
sampel: item.sampel,
hasil_cutting_diluar_sampel: item.hasil_cutting_diluar_sampel,
}),
});
} catch (error) {
@ -553,13 +548,12 @@ function buildPayload() {
payload.materials = materialCart.value.map((item) => ({
raw_material_price_id: item.raw_material_price_id,
material_usage: item.material_usage,
remaining_material: item.remaining_material,
}));
payload.results = resultCart.value.map((item) => ({
product_variant_id: item.product_variant_id,
cutting_result: item.cutting_result,
warehouse_stock: item.warehouse_stock,
cutting_reject: item.cutting_reject,
sampel: item.sampel,
hasil_cutting_diluar_sampel: item.hasil_cutting_diluar_sampel,
}));
}
@ -838,7 +832,7 @@ function submit() {
</Button>
</div>
<div class="grid grid-cols-2 gap-2">
<div class="grid grid-cols-1 gap-2">
<Field>
<FieldLabel class="text-xs">
Pemakaian ({{ item.unit_abbreviation }})
@ -850,17 +844,6 @@ function submit() {
)
" />
</Field>
<Field>
<FieldLabel class="text-xs">
Sisa ({{ item.unit_abbreviation }})
</FieldLabel>
<DecimalInput v-model="item.remaining_material
" class="h-8" @change="
syncMaterialField(
index,
)
" />
</Field>
</div>
</div>
</div>
@ -912,16 +895,16 @@ function submit() {
" />
</Field>
<Field>
<FieldLabel class="text-xs">Bagus</FieldLabel>
<NumberInput v-model="item.warehouse_stock
<FieldLabel class="text-xs">Sampel</FieldLabel>
<NumberInput v-model="item.sampel
" class="h-8" @change="
syncResultTotals(item);
syncResultField(index)
" />
</Field>
<Field>
<FieldLabel class="text-xs">Reject</FieldLabel>
<NumberInput v-model="item.cutting_reject
<FieldLabel class="text-xs">Diluar Sampel</FieldLabel>
<NumberInput v-model="item.hasil_cutting_diluar_sampel
" class="h-8" />
</Field>
</div>
@ -1023,7 +1006,6 @@ function submit() {
</span>
</div>
<div class="mt-1.5 flex items-center justify-between gap-2 text-xs text-muted-foreground">
<span>Sisa: {{ item.remaining_material }} {{ item.unit_abbreviation }}</span>
<span class="font-medium text-foreground">{{ formatRupiah(materialLineCost(item))
}}</span>
</div>
@ -1046,9 +1028,9 @@ function submit() {
<div class="mt-1.5 flex items-center gap-3 text-xs tabular-nums">
<span>Hasil: <strong>{{ item.cutting_result }}</strong></span>
<span class="text-muted-foreground">·</span>
<span>Bagus: <strong>{{ item.warehouse_stock }}</strong></span>
<span>Sampel: <strong>{{ item.sampel }}</strong></span>
<span class="text-muted-foreground">·</span>
<span>Reject: <strong>{{ item.cutting_reject }}</strong></span>
<span>Diluar Sampel: <strong>{{ item.hasil_cutting_diluar_sampel }}</strong></span>
</div>
</div>
</div>

View File

@ -30,7 +30,7 @@ function getGroupedMaterialUsage(materials: any[]): MaterialUsageGroup[] {
materials.forEach((mat) => {
const unit = mat.raw_material_price?.raw_material?.unit_abbreviation ?? '';
const usage = Number(mat.material_usage ?? 0) - Number(mat.remaining_material ?? 0);
const usage = Number(mat.material_usage ?? 0);
groups[unit] = (groups[unit] ?? 0) + usage;
});

View File

@ -143,7 +143,7 @@ function getGroupedMaterialUsage(materials: any[]): MaterialUsageGroup[] {
materials.forEach((mat) => {
const unit = mat.raw_material_price?.raw_material?.unit_abbreviation ?? '';
const usage = Number(mat.material_usage ?? 0) - Number(mat.remaining_material ?? 0);
const usage = Number(mat.material_usage ?? 0);
groups[unit] = (groups[unit] ?? 0) + usage;
});
@ -209,19 +209,18 @@ function getGroupedMaterialUsage(materials: any[]): MaterialUsageGroup[] {
<TableRow>
<TableHead>Varian</TableHead>
<TableHead>Pemakaian</TableHead>
<TableHead>Sisa</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow v-if="!cutting.materials.length" :key="`${cutting.id}-material-empty`">
<TableCell colspan="3" class="text-muted-foreground">
<TableCell colspan="2" class="text-muted-foreground">
Belum ada bahan baku
</TableCell>
</TableRow>
<template v-else v-for="group in getGroupedMaterials(cutting.materials)"
:key="group.rawMaterialId">
<TableRow class="bg-muted/20 hover:bg-muted/20">
<TableCell colspan="3" class="font-semibold text-foreground">
<TableCell colspan="2" class="font-semibold text-foreground">
{{ group.rawMaterialName }}
<Badge v-if="group.unitLabel" variant="secondary"
class="ml-2 font-normal">
@ -236,9 +235,6 @@ function getGroupedMaterialUsage(materials: any[]): MaterialUsageGroup[] {
<TableCell class="tabular-nums">
{{ material.material_usage_formatted }}
</TableCell>
<TableCell class="tabular-nums">
{{ material.remaining_material_formatted }}
</TableCell>
</TableRow>
</template>
</TableBody>
@ -254,8 +250,8 @@ function getGroupedMaterialUsage(materials: any[]): MaterialUsageGroup[] {
<TableRow>
<TableHead>Varian</TableHead>
<TableHead>Hasil</TableHead>
<TableHead>Bagus</TableHead>
<TableHead>Reject</TableHead>
<TableHead>Sampel</TableHead>
<TableHead>Diluar Sampel</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@ -279,10 +275,10 @@ function getGroupedMaterialUsage(materials: any[]): MaterialUsageGroup[] {
{{ result.cutting_result }} pcs
</TableCell>
<TableCell class="tabular-nums">
{{ result.warehouse_stock }} pcs
{{ result.sampel }} pcs
</TableCell>
<TableCell class="tabular-nums">
{{ result.cutting_reject }} pcs
{{ result.hasil_cutting_diluar_sampel }} pcs
</TableCell>
</TableRow>
</template>

View File

@ -21,7 +21,7 @@ function getGroupedMaterialUsage(materials: any[]): MaterialUsageGroup[] {
materials.forEach((mat) => {
const unit = mat.raw_material_price?.raw_material?.unit_abbreviation ?? '';
const usage = Number(mat.material_usage ?? 0) - Number(mat.remaining_material ?? 0);
const usage = Number(mat.material_usage ?? 0);
groups[unit] = (groups[unit] ?? 0) + usage;
});

View File

@ -67,10 +67,10 @@ const verifyForm = useForm({
product_variant_id: res.product_variant?.id || 0,
name: `${res.product_variant?.product?.name || ''} (${res.product_variant?.name || ''})`,
cutting_result: res.cutting_result,
warehouse_stock: res.warehouse_stock,
cutting_reject: res.cutting_reject,
original_warehouse_stock: res.warehouse_stock,
original_cutting_reject: res.cutting_reject,
sampel: res.sampel,
hasil_cutting_diluar_sampel: res.hasil_cutting_diluar_sampel,
original_sampel: res.sampel,
original_hasil_cutting_diluar_sampel: res.hasil_cutting_diluar_sampel,
prices: buildEmptyPrices(),
})),
result_prices: [] as Array<{
@ -87,10 +87,10 @@ watch(verifyDialogOpen, (isOpen) => {
product_variant_id: res.product_variant?.id || 0,
name: `${res.product_variant?.product?.name || ''} (${res.product_variant?.name || ''})`,
cutting_result: res.cutting_result,
warehouse_stock: res.warehouse_stock,
cutting_reject: res.cutting_reject,
original_warehouse_stock: res.warehouse_stock,
original_cutting_reject: res.cutting_reject,
sampel: res.sampel,
hasil_cutting_diluar_sampel: res.hasil_cutting_diluar_sampel,
original_sampel: res.sampel,
original_hasil_cutting_diluar_sampel: res.hasil_cutting_diluar_sampel,
prices: buildEmptyPrices(),
}));
verifyForm.result_prices = [];
@ -105,8 +105,8 @@ watch(allMatches, (matches) => {
verifyForm.results = verifyForm.results.map((result) => ({
...result,
warehouse_stock: result.original_warehouse_stock,
cutting_reject: result.original_cutting_reject,
sampel: result.original_sampel,
hasil_cutting_diluar_sampel: result.original_hasil_cutting_diluar_sampel,
}));
});
@ -116,8 +116,8 @@ function setAllMatches(value: boolean) {
if (value) {
verifyForm.results = verifyForm.results.map((result) => ({
...result,
warehouse_stock: result.original_warehouse_stock,
cutting_reject: result.original_cutting_reject,
sampel: result.original_sampel,
hasil_cutting_diluar_sampel: result.original_hasil_cutting_diluar_sampel,
}));
}
}
@ -159,11 +159,11 @@ function canPerformAction(action: CuttingStatusAction): boolean {
function statusConfirmDescription(action: CuttingStatusAction): string {
if (action.status === CuttingStatus.COMPLETED) {
return 'Cutting akan ditandai selesai. Stok bahan baku akan dipotong dan sisa dikembalikan. Menunggu verifikasi admin toko.';
return 'Cutting akan ditandai selesai. Stok bahan baku akan dipotong sesuai pemakaian. Menunggu verifikasi admin toko.';
}
if (action.status === CuttingStatus.VERIFIED) {
return 'Hasil cutting akan diverifikasi. Stok bagus dan reject akan ditambahkan ke produk.';
return 'Hasil cutting akan diverifikasi. Stok sampel dan hasil cutting diluar sampel akan ditambahkan ke produk.';
}
if (action.status === CuttingStatus.IN_PROGRESS) {
@ -197,10 +197,10 @@ function submitVerify() {
.transform((data) => ({
status: data.status,
verification_note: data.verification_note,
results: data.results.map(({ product_variant_id, warehouse_stock, cutting_reject }) => ({
results: data.results.map(({ product_variant_id, sampel, hasil_cutting_diluar_sampel }) => ({
product_variant_id,
warehouse_stock,
cutting_reject,
sampel,
hasil_cutting_diluar_sampel,
})),
result_prices: buildResultPricesPayload(),
}))
@ -379,34 +379,34 @@ function actionIcon(status: string) {
<div>
<span class="text-muted-foreground block mb-0.5">Data cutting:</span>
<span class="font-medium tabular-nums">
{{ result.original_warehouse_stock }} bagus ·
{{ result.original_cutting_reject }} reject
{{ result.original_sampel }} sampel ·
{{ result.original_hasil_cutting_diluar_sampel }} diluar sampel
</span>
</div>
<div>
<span class="text-muted-foreground block">Stok Reject:</span>
<span class="text-muted-foreground block">Diluar Sampel:</span>
<Badge variant="secondary" class="font-semibold">
{{ result.cutting_reject }} pcs
{{ result.hasil_cutting_diluar_sampel }} pcs
</Badge>
</div>
</div>
<div class="grid grid-cols-2 gap-2 items-end text-xs">
<div>
<label :for="`good-${index}`" class="text-muted-foreground block mb-0.5">Stok Bagus
<label :for="`sampel-${index}`" class="text-muted-foreground block mb-0.5">Sampel
(diterima):</label>
<Input :id="`good-${index}`" type="number" v-model.number="result.warehouse_stock"
<Input :id="`sampel-${index}`" type="number" v-model.number="result.sampel"
min="0" :max="result.cutting_result" class="h-8 w-full px-2 text-xs"
:disabled="allMatches"
@input="result.cutting_reject = result.cutting_result - result.warehouse_stock" />
@input="result.hasil_cutting_diluar_sampel = result.cutting_result - result.sampel" />
</div>
<div>
<label :for="`reject-${index}`" class="text-muted-foreground block mb-0.5">Stok
Reject
<label :for="`diluar-sampel-${index}`" class="text-muted-foreground block mb-0.5">Diluar
Sampel
(diterima):</label>
<Input :id="`reject-${index}`" type="number" v-model.number="result.cutting_reject"
<Input :id="`diluar-sampel-${index}`" type="number" v-model.number="result.hasil_cutting_diluar_sampel"
min="0" :max="result.cutting_result" class="h-8 w-full px-2 text-xs"
:disabled="allMatches"
@input="result.warehouse_stock = result.cutting_result - result.cutting_reject" />
@input="result.sampel = result.cutting_result - result.hasil_cutting_diluar_sampel" />
</div>
</div>

View File

@ -35,7 +35,7 @@ defineProps<{
{{ res.product_variant?.product?.name }} ({{ res.product_variant?.name }}) -
{{ res.cutting_result }} pcs
<span class="text-[10px]">
({{ res.warehouse_stock }} bagus, {{ res.cutting_reject }} reject)
({{ res.sampel }} sampel, {{ res.hasil_cutting_diluar_sampel }} diluar sampel)
</span>
</li>
<li v-if="!cutting.results.length">Belum ada hasil produk</li>

View File

@ -100,8 +100,8 @@ const showingCount = computed(() => props.cuttings.length);
<TableRow>
<TableHead>Varian</TableHead>
<TableHead>Hasil</TableHead>
<TableHead>Bagus</TableHead>
<TableHead>Reject</TableHead>
<TableHead>Sampel</TableHead>
<TableHead>Diluar Sampel</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@ -125,10 +125,10 @@ const showingCount = computed(() => props.cuttings.length);
{{ result.cutting_result }} pcs
</TableCell>
<TableCell class="tabular-nums">
{{ result.warehouse_stock }} pcs
{{ result.sampel }} pcs
</TableCell>
<TableCell class="tabular-nums">
{{ result.cutting_reject }} pcs
{{ result.hasil_cutting_diluar_sampel }} pcs
</TableCell>
</TableRow>
</template>

View File

@ -56,10 +56,10 @@ const verifyForm = useForm({
product_variant_id: res.product_variant?.id || 0,
name: `${res.product_variant?.product?.name || ''} (${res.product_variant?.name || ''})`,
cutting_result: res.cutting_result,
warehouse_stock: res.warehouse_stock,
cutting_reject: res.cutting_reject,
original_warehouse_stock: res.warehouse_stock,
original_cutting_reject: res.cutting_reject,
sampel: res.sampel,
hasil_cutting_diluar_sampel: res.hasil_cutting_diluar_sampel,
original_sampel: res.sampel,
original_hasil_cutting_diluar_sampel: res.hasil_cutting_diluar_sampel,
})),
variant_prices: [] as VariantPriceRow[],
shared_prices: buildEmptyPrices(),
@ -73,10 +73,10 @@ watch(verifyDialogOpen, (isOpen) => {
product_variant_id: res.product_variant?.id || 0,
name: `${res.product_variant?.product?.name || ''} (${res.product_variant?.name || ''})`,
cutting_result: res.cutting_result,
warehouse_stock: res.warehouse_stock,
cutting_reject: res.cutting_reject,
original_warehouse_stock: res.warehouse_stock,
original_cutting_reject: res.cutting_reject,
sampel: res.sampel,
hasil_cutting_diluar_sampel: res.hasil_cutting_diluar_sampel,
original_sampel: res.sampel,
original_hasil_cutting_diluar_sampel: res.hasil_cutting_diluar_sampel,
}));
verifyForm.variant_prices = props.cutting.results.map(res => ({
product_variant_id: res.product_variant?.id || 0,
@ -95,8 +95,8 @@ watch(allMatches, (matches) => {
verifyForm.results = verifyForm.results.map((result) => ({
...result,
warehouse_stock: result.original_warehouse_stock,
cutting_reject: result.original_cutting_reject,
sampel: result.original_sampel,
hasil_cutting_diluar_sampel: result.original_hasil_cutting_diluar_sampel,
}));
});
@ -106,8 +106,8 @@ function setAllMatches(value: boolean) {
if (value) {
verifyForm.results = verifyForm.results.map((result) => ({
...result,
warehouse_stock: result.original_warehouse_stock,
cutting_reject: result.original_cutting_reject,
sampel: result.original_sampel,
hasil_cutting_diluar_sampel: result.original_hasil_cutting_diluar_sampel,
}));
}
}
@ -134,10 +134,10 @@ function submitVerify() {
verifyForm
.transform((data) => ({
verification_note: data.verification_note,
results: data.results.map(({ product_variant_id, warehouse_stock, cutting_reject }) => ({
results: data.results.map(({ product_variant_id, sampel, hasil_cutting_diluar_sampel }) => ({
product_variant_id,
warehouse_stock,
cutting_reject,
sampel,
hasil_cutting_diluar_sampel,
})),
result_prices: buildResultPricesPayload(),
}))
@ -186,26 +186,26 @@ function submitVerify() {
<p class="font-medium text-sm truncate">{{ result.name }}</p>
<p class="text-xs text-muted-foreground">
{{ result.cutting_result }} pcs
<span class="text-[10px]">({{ result.original_warehouse_stock }} bagus, {{
result.original_cutting_reject }} reject)</span>
<span class="text-[10px]">({{ result.original_sampel }} sampel, {{
result.original_hasil_cutting_diluar_sampel }} diluar sampel)</span>
</p>
</div>
<div class="flex items-center gap-2 shrink-0">
<div class="text-center">
<label :for="`stock-good-${index}`"
class="text-[10px] text-muted-foreground block">Bagus</label>
<Input :id="`stock-good-${index}`" type="number"
v-model.number="result.warehouse_stock" min="0" :max="result.cutting_result"
<label :for="`stock-sampel-${index}`"
class="text-[10px] text-muted-foreground block">Sampel</label>
<Input :id="`stock-sampel-${index}`" type="number"
v-model.number="result.sampel" min="0" :max="result.cutting_result"
class="h-7 w-16 px-1.5 text-xs text-center" :disabled="allMatches"
@input="result.cutting_reject = result.cutting_result - result.warehouse_stock" />
@input="result.hasil_cutting_diluar_sampel = result.cutting_result - result.sampel" />
</div>
<div class="text-center">
<label :for="`stock-reject-${index}`"
class="text-[10px] text-muted-foreground block">Reject</label>
<Input :id="`stock-reject-${index}`" type="number"
v-model.number="result.cutting_reject" min="0" :max="result.cutting_result"
<label :for="`stock-diluar-sampel-${index}`"
class="text-[10px] text-muted-foreground block">Diluar Sampel</label>
<Input :id="`stock-diluar-sampel-${index}`" type="number"
v-model.number="result.hasil_cutting_diluar_sampel" min="0" :max="result.cutting_result"
class="h-7 w-16 px-1.5 text-xs text-center" :disabled="allMatches"
@input="result.warehouse_stock = result.cutting_result - result.cutting_reject" />
@input="result.sampel = result.cutting_result - result.hasil_cutting_diluar_sampel" />
</div>
</div>
</div>

View File

@ -13,7 +13,6 @@ export type CuttingStatusAction = {
export type CuttingMaterialListItem = {
id: number;
material_usage_formatted: string;
remaining_material_formatted: string;
raw_material_price?: {
id: number;
variant: string;
@ -41,8 +40,8 @@ export type CuttingResultPriceItem = {
export type CuttingResultListItem = {
id: number;
cutting_result: number;
warehouse_stock: number;
cutting_reject: number;
sampel: number;
hasil_cutting_diluar_sampel: number;
product_variant?: {
id: number;
name: string;
@ -100,7 +99,6 @@ export type CuttingMaterialCartItem = {
unit_abbreviation: string;
stock_input: string;
material_usage: string;
remaining_material: string;
images?: MediaItem[];
};
@ -110,8 +108,8 @@ export type CuttingResultCartItem = {
variant_name: string;
stock: number;
cutting_result: string;
warehouse_stock: string;
cutting_reject: string;
sampel: string;
hasil_cutting_diluar_sampel: string;
images?: MediaItem[];
};
@ -124,7 +122,6 @@ export type CuttingEditItem = {
materials: Array<{
raw_material_price_id: number;
material_usage_input: string;
remaining_material_input: string;
raw_material_price?: {
variant: string;
stock_input?: string;
@ -139,8 +136,8 @@ export type CuttingEditItem = {
results: Array<{
product_variant_id: number;
cutting_result: number;
warehouse_stock: number;
cutting_reject: number;
sampel: number;
hasil_cutting_diluar_sampel: number;
product_variant?: {
name: string;
stock: number;