refactor: enhance stock verification logic and error handling in StockVerifyDialog

This commit is contained in:
Yoga Pangestu 2026-06-29 09:53:22 +07:00
parent 2209f4901c
commit 39c8bd01a8

View File

@ -2,7 +2,7 @@
import { useForm } from '@inertiajs/vue3';
import { computed, watch } from 'vue';
import { toast } from 'vue-sonner';
import NumberInput from '@/components/form/number-input/NumberInput.vue';
import { NumberInput } from '@/components/form/number-input';
import { RupiahInput } from '@/components/form/rupiah-input';
import { Button } from '@/components/ui/button';
import {
@ -51,10 +51,14 @@ const verifyForm = useForm({
cutting_result: res.cutting_result,
original_sample: res.sample,
original_outside_sample: res.original_outside_sample,
good: res.original_outside_sample,
good: res.cutting_result,
reject: 0,
})),
variant_prices: [] as VariantPriceRow[],
variant_prices: props.cutting.results.map(res => ({
product_variant_id: res.product_variant?.id || 0,
name: `${res.product_variant?.product?.name || ''} (${res.product_variant?.name || ''})`,
prices: buildEmptyPrices(),
})),
shared_prices: buildEmptyPrices(),
});
@ -67,7 +71,7 @@ watch(open, (isOpen) => {
cutting_result: res.cutting_result,
original_sample: res.sample,
original_outside_sample: res.original_outside_sample,
good: res.original_outside_sample,
good: res.cutting_result,
reject: 0,
}));
verifyForm.variant_prices = props.cutting.results.map(res => ({
@ -80,6 +84,18 @@ watch(open, (isOpen) => {
}
});
function onGoodChange(result: any, value: string | number) {
const val = Number(value) || 0;
result.good = val;
result.reject = Math.max(0, result.cutting_result - val);
}
function onRejectChange(result: any, value: string | number) {
const val = Number(value) || 0;
result.reject = val;
result.good = Math.max(0, result.cutting_result - val);
}
function setSharedPrice(type: string, value: string) {
verifyForm.shared_prices[type] = value;
verifyForm.variant_prices = verifyForm.variant_prices.map((row) => ({
@ -89,13 +105,13 @@ function setSharedPrice(type: string, value: string) {
}
const resultPricesErrors = computed(() => {
const message = (verifyForm.errors as Record<string, string | undefined>).result_prices;
return message ? [message] : [];
return Object.entries(verifyForm.errors)
.filter(([key]) => key === 'result_prices' || key.startsWith('result_prices.'))
.map(([_, message]) => message) as string[];
});
function buildResultPricesPayload() {
return verifyForm.variant_prices.map((row) => ({
function buildResultPricesPayload(variantPrices: VariantPriceRow[]) {
return variantPrices.map((row) => ({
product_variant_id: row.product_variant_id,
prices: PRICE_TYPES.map((type) => ({
type,
@ -113,7 +129,7 @@ function submitVerify() {
good,
reject,
})),
result_prices: buildResultPricesPayload(),
result_prices: buildResultPricesPayload(data.variant_prices),
}))
.post(verify.url(props.cutting.id), {
preserveScroll: true,
@ -121,8 +137,22 @@ function submitVerify() {
open.value = false;
},
onError: (errors: Record<string, string>) => {
if (errors.system) {
toast.error(errors.system);
const shown = new Set<string>();
for (const [key, message] of Object.entries(errors)) {
if (
key === 'result_prices' ||
key.startsWith('result_prices.') ||
key.startsWith('results.') ||
key === 'verification_note'
) {
continue;
}
if (!shown.has(message)) {
shown.add(message);
toast.error(message);
}
}
},
});
@ -145,31 +175,38 @@ function submitVerify() {
<div class="space-y-2">
<div v-for="(result, index) in verifyForm.results" :key="result.product_variant_id"
class="flex items-center gap-3 rounded-lg border p-3">
<div class="flex-1 min-w-0">
<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_sample }} Sample, {{
result.original_outside_sample }} Diluar Sample)</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>
<NumberInput :id="`stock-good-${index}`" v-model="result.good"
class="h-7 w-20 px-1.5 text-xs text-center"
@input="result.reject = result.cutting_result - result.good" />
class="flex flex-col gap-1 rounded-lg border p-3">
<div class="flex items-center gap-3">
<div class="flex-1 min-w-0">
<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_sample }} Sample, {{
result.original_outside_sample }} Diluar Sample)</span>
</p>
</div>
<div class="text-center">
<label :for="`stock-reject-${index}`"
class="text-[10px] text-muted-foreground block">Reject</label>
<NumberInput :id="`stock-reject-${index}`" v-model="result.reject"
class="h-7 w-20 px-1.5 text-xs text-center"
@input="result.good = result.cutting_result - result.reject" />
<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>
<NumberInput :id="`stock-good-${index}`" :model-value="result.good"
class="h-7 w-20 px-1.5 text-xs text-center"
@update:model-value="val => onGoodChange(result, val)" />
</div>
<div class="text-center">
<label :for="`stock-reject-${index}`"
class="text-[10px] text-muted-foreground block">Reject</label>
<NumberInput :id="`stock-reject-${index}`" :model-value="result.reject"
class="h-7 w-20 px-1.5 text-xs text-center"
@update:model-value="val => onRejectChange(result, val)" />
</div>
</div>
</div>
<FieldError :errors="[
verifyForm.errors[`results.${index}.good`],
verifyForm.errors[`results.${index}.reject`],
verifyForm.errors[`results.${index}.product_variant_id`]
].filter(Boolean) as string[]" />
</div>
</div>