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:
parent
1f27acc63a
commit
9dba5e1491
@ -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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
];
|
||||
|
||||
@ -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).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -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 ?? [],
|
||||
})),
|
||||
}));
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user