Add reject_stock input to product CRUD form
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (8.3) (push) Waiting to run
tests / ci (8.4) (push) Waiting to run
tests / ci (8.5) (push) Waiting to run

- Add reject_stock field alongside stock (renamed to 'Stok Bagus') and retail_stock ('Stok Ecer')
- Update TypeScript types (ProductVariantFormItem, ProductFormInitialData)
- Update ProductVariantSection, ProductVariantEditModal, and ProductForm with new field
- Add validation rule and attribute label for reject_stock
- Update ProductService to persist reject_stock in create, update, and apply flows
This commit is contained in:
Yoga Pangestu 2026-07-21 10:56:28 +07:00
parent ee9bff3043
commit ee08188190
7 changed files with 38 additions and 5 deletions

View File

@ -24,6 +24,7 @@ protected function productVariantRules(string $variantsKey = 'variants', ?int $p
"{$variantsKey}" => ['required', 'array', 'min:1'],
"{$variantsKey}.*.name" => ['required', 'string', 'max:200'],
"{$variantsKey}.*.stock" => ['required', 'integer', 'min:0'],
"{$variantsKey}.*.reject_stock" => ['required', 'integer', 'min:0'],
"{$variantsKey}.*.retail_stock" => ['required', 'integer', 'min:0'],
...$this->variantImageRules($variantsKey, required: $imagesRequired),
];
@ -61,7 +62,8 @@ protected function productVariantAttributes(string $variantsKey = 'variants'): a
return [
$variantsKey => 'varian',
"{$variantsKey}.*.name" => 'nama varian',
"{$variantsKey}.*.stock" => 'stok',
"{$variantsKey}.*.stock" => 'stok bagus',
"{$variantsKey}.*.reject_stock" => 'stok reject',
"{$variantsKey}.*.retail_stock" => 'stok ecer',
"{$variantsKey}.*.prices" => 'harga',
"{$variantsKey}.*.prices.distributor" => 'distributor',

View File

@ -141,6 +141,7 @@ function () use ($validated, $user, $isOwner, $isDraft): Product {
$variant = $product->variants()->create([
'name' => $variantData['name'],
'stock' => $variantData['stock'],
'reject_stock' => $variantData['reject_stock'],
'retail_stock' => $variantData['retail_stock'],
]);
@ -574,6 +575,7 @@ private function applyPayloadToProduct(
$variant->update([
'name' => $variantData['name'],
'stock' => $variantData['stock'],
'reject_stock' => $variantData['reject_stock'],
'retail_stock' => $variantData['retail_stock'],
]);
@ -596,6 +598,7 @@ private function applyPayloadToProduct(
$variant = $product->variants()->create([
'name' => $variantData['name'],
'stock' => $variantData['stock'],
'reject_stock' => $variantData['reject_stock'],
'retail_stock' => $variantData['retail_stock'],
]);
@ -763,6 +766,7 @@ private function snapshotProduct(Product $product): array
'id' => $variant->id,
'name' => $variant->name,
'stock' => $variant->stock,
'reject_stock' => $variant->reject_stock,
'retail_stock' => $variant->retail_stock,
'prices' => $variant->prices
->mapWithKeys(fn ($price) => [$price->type->value => $price->price])
@ -795,6 +799,7 @@ private function buildPayloadFromValidated(array $validated): array
'id' => $variantData['id'] ?? null,
'name' => $variantData['name'],
'stock' => $variantData['stock'],
'reject_stock' => $variantData['reject_stock'],
'retail_stock' => $variantData['retail_stock'],
'prices' => $variantData['prices'] ?? [],
'remove_media_ids' => $variantData['remove_media_ids'] ?? [],

View File

@ -22,6 +22,7 @@ const initialData = computed(() => ({
id: variant.id,
name: variant.name,
stock: variant.stock,
reject_stock: variant.reject_stock,
retail_stock: variant.retail_stock,
prices: variant.prices ?? [],
images: variant.images ?? [],

View File

@ -55,6 +55,7 @@ const {
client_id: createClientId(),
name: '',
stock: '0',
reject_stock: '0',
retail_stock: '0',
prices: {
distributor: '0',
@ -74,6 +75,7 @@ const {
client_id: createClientId(),
name: '',
stock: '0',
reject_stock: '0',
retail_stock: '0',
prices: {
distributor: '0',
@ -111,6 +113,7 @@ const {
id: variant.id,
name: variant.name ?? '',
stock: variant.stock != null ? String(variant.stock) : '0',
reject_stock: variant.reject_stock != null ? String(variant.reject_stock) : '0',
retail_stock: variant.retail_stock != null ? String(variant.retail_stock) : '0',
prices,
media: createMediaUploadState(variant.images ?? []),
@ -225,6 +228,7 @@ function buildFormData(): FormData {
appendToFormData(formData, (formData, index, variant) => {
formData.append(`variants[${index}][name]`, variant.name.trim());
formData.append(`variants[${index}][stock]`, String(Number.parseInt(String(variant.stock), 10) || 0));
formData.append(`variants[${index}][reject_stock]`, String(Number.parseInt(String(variant.reject_stock), 10) || 0));
formData.append(`variants[${index}][retail_stock]`, String(Number.parseInt(String(variant.retail_stock), 10) || 0));
if (variant.prices && showPrices.value) {
@ -291,6 +295,7 @@ function submit() {
@remove="confirmRemoveVariant(variant.client_id)"
@update:name="setVariantField(variant.client_id, 'name', $event)"
@update:stock="setVariantField(variant.client_id, 'stock', $event)"
@update:reject-stock="setVariantField(variant.client_id, 'reject_stock', $event)"
@update:retail-stock="setVariantField(variant.client_id, 'retail_stock', $event)"
@update:prices="setVariantField(variant.client_id, 'prices', $event)"
@update:media="setVariantField(variant.client_id, 'media', $event)"

View File

@ -76,6 +76,7 @@ function populateForm(variant: Variant | null) {
id: v.id,
name: v.name,
stock: String(v.stock),
reject_stock: String(v.reject_stock),
retail_stock: String(v.retail_stock),
prices,
media: createMediaUploadState(v.images ?? []),
@ -133,6 +134,7 @@ function submit() {
formData.append(`variants[${index}][name]`, v.name.trim());
formData.append(`variants[${index}][stock]`, String(Number.parseInt(v.stock, 10) || 0));
formData.append(`variants[${index}][reject_stock]`, String(Number.parseInt(v.reject_stock, 10) || 0));
formData.append(`variants[${index}][retail_stock]`, String(Number.parseInt(v.retail_stock, 10) || 0));
if (v.prices) {
@ -178,13 +180,19 @@ function submit() {
<FieldError :errors="formErrors(editForm, `variants.${editingVariantIndex}.name`)" />
</Field>
<div class="grid grid-cols-2 gap-4">
<div class="grid grid-cols-3 gap-4">
<Field>
<FieldLabel for="variant-stock" required>Stok</FieldLabel>
<FieldLabel for="variant-stock" required>Stok Bagus</FieldLabel>
<NumberInput id="variant-stock" v-model="editingVariantFormItem.stock" />
<FieldError :errors="formErrors(editForm, `variants.${editingVariantIndex}.stock`)" />
</Field>
<Field>
<FieldLabel for="variant-reject-stock" required>Stok Reject</FieldLabel>
<NumberInput id="variant-reject-stock" v-model="editingVariantFormItem.reject_stock" />
<FieldError :errors="formErrors(editForm, `variants.${editingVariantIndex}.reject_stock`)" />
</Field>
<Field>
<FieldLabel for="variant-retail-stock" required>Stok Ecer</FieldLabel>
<NumberInput id="variant-retail-stock" v-model="editingVariantFormItem.retail_stock" />

View File

@ -39,6 +39,7 @@ const emit = defineEmits<{
remove: [];
'update:name': [value: string];
'update:stock': [value: string];
'update:reject-stock': [value: string];
'update:retail-stock': [value: string];
'update:prices': [value: Record<string, string>];
'update:media': [value: MediaUploadState];
@ -95,7 +96,7 @@ function updatePrice(type: string, value: string) {
</CardHeader>
<CardContent>
<FieldGroup>
<FieldSet class="grid gap-4 md:grid-cols-3">
<FieldSet class="grid gap-4 md:grid-cols-4">
<Field>
<FieldLabel :for="`variant_name_${variant.client_id}`" required>
Nama Varian
@ -107,12 +108,21 @@ function updatePrice(type: string, value: string) {
</Field>
<Field>
<FieldLabel :for="`variant_stock_${variant.client_id}`" required>
Stok
Stok Bagus
</FieldLabel>
<NumberInput :id="`variant_stock_${variant.client_id}`" :model-value="variant.stock"
@update:model-value="emit('update:stock', String($event))" />
<FieldError :errors="variantErrors(variant.client_id, 'stock')" />
</Field>
<Field>
<FieldLabel :for="`variant_reject_stock_${variant.client_id}`" required>
Stok Reject
</FieldLabel>
<NumberInput :id="`variant_reject_stock_${variant.client_id}`"
:model-value="variant.reject_stock"
@update:model-value="emit('update:reject-stock', String($event))" />
<FieldError :errors="variantErrors(variant.client_id, 'reject_stock')" />
</Field>
<Field>
<FieldLabel :for="`variant_retail_stock_${variant.client_id}`" required>
Stok Ecer

View File

@ -92,6 +92,7 @@ export interface ProductVariantFormItem {
id?: number;
name: string;
stock: string | number;
reject_stock: string | number;
retail_stock: string | number;
prices?: Record<string, string>;
media: MediaUploadState;
@ -107,6 +108,7 @@ export type ProductFormInitialData = {
id?: number;
name?: string;
stock?: number | string;
reject_stock?: number | string;
retail_stock?: number | string;
prices?: Price[];
images?: MediaItem[];