From bb74fb49c8a8e2cc57a20c546061dac6efd20b6b Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Mon, 13 Jul 2026 11:28:48 +0700 Subject: [PATCH] feat: require s3_keys for product and raw material variants, enhancing validation and user feedback in forms --- app/Http/Requests/Admin/Master/ProductRequest.php | 10 ++++++++++ .../Requests/Admin/Master/RawMaterialRequest.php | 10 ++++++++++ .../Requests/Concerns/HasProductVariantRules.php | 2 +- .../Requests/Concerns/HasRawMaterialPriceRules.php | 2 +- .../Requests/Concerns/ValidatesMediaUploads.php | 6 ++++-- .../cuttings/form/CuttingProductVariantSection.vue | 2 +- .../products/form/ProductVariantEditModal.vue | 2 +- .../master/products/form/ProductVariantSection.vue | 2 +- .../form/RawMaterialVariantEditModal.vue | 2 +- .../form/RawMaterialVariantSection.vue | 2 +- tests/Feature/Admin/Master/ProductTest.php | 14 ++++++++++++++ tests/Feature/Admin/Master/RawMaterialTest.php | 12 ++++++++++++ 12 files changed, 57 insertions(+), 9 deletions(-) diff --git a/app/Http/Requests/Admin/Master/ProductRequest.php b/app/Http/Requests/Admin/Master/ProductRequest.php index 65953fe..6f49b87 100644 --- a/app/Http/Requests/Admin/Master/ProductRequest.php +++ b/app/Http/Requests/Admin/Master/ProductRequest.php @@ -49,4 +49,14 @@ public function attributes(): array ...$this->productVariantAttributes(), ]; } + + /** + * @return array + */ + public function messages(): array + { + return [ + 'variants.*.s3_keys.required' => 'Foto varian wajib diisi.', + ]; + } } diff --git a/app/Http/Requests/Admin/Master/RawMaterialRequest.php b/app/Http/Requests/Admin/Master/RawMaterialRequest.php index 3f94093..b4bf052 100644 --- a/app/Http/Requests/Admin/Master/RawMaterialRequest.php +++ b/app/Http/Requests/Admin/Master/RawMaterialRequest.php @@ -45,4 +45,14 @@ public function attributes(): array ...$this->rawMaterialPriceAttributes(), ]; } + + /** + * @return array + */ + public function messages(): array + { + return [ + 'prices.*.s3_keys.required' => 'Foto varian wajib diisi.', + ]; + } } diff --git a/app/Http/Requests/Concerns/HasProductVariantRules.php b/app/Http/Requests/Concerns/HasProductVariantRules.php index e37d0ec..93bacd0 100644 --- a/app/Http/Requests/Concerns/HasProductVariantRules.php +++ b/app/Http/Requests/Concerns/HasProductVariantRules.php @@ -25,7 +25,7 @@ protected function productVariantRules(string $variantsKey = 'variants', ?int $p "{$variantsKey}.*.name" => ['required', 'string', 'max:200'], "{$variantsKey}.*.stock" => ['required', 'integer', 'min:0'], "{$variantsKey}.*.retail_stock" => ['required', 'integer', 'min:0'], - ...$this->variantImageRules($variantsKey), + ...$this->variantImageRules($variantsKey, required: true), ]; if ($productId) { diff --git a/app/Http/Requests/Concerns/HasRawMaterialPriceRules.php b/app/Http/Requests/Concerns/HasRawMaterialPriceRules.php index 38d733b..f116bf2 100644 --- a/app/Http/Requests/Concerns/HasRawMaterialPriceRules.php +++ b/app/Http/Requests/Concerns/HasRawMaterialPriceRules.php @@ -18,7 +18,7 @@ protected function rawMaterialPriceRules(string $pricesKey = 'prices', ?int $raw "{$pricesKey}.*.variant" => ['required', 'string', 'max:200'], "{$pricesKey}.*.price" => ['required', 'integer', 'gt:0'], "{$pricesKey}.*.stock" => ['required', 'numeric', 'decimal:0,4', 'min:0'], - ...$this->variantImageRules($pricesKey), + ...$this->variantImageRules($pricesKey, required: true), ]; if ($rawMaterialId) { diff --git a/app/Http/Requests/Concerns/ValidatesMediaUploads.php b/app/Http/Requests/Concerns/ValidatesMediaUploads.php index 02c5eb9..7606df6 100644 --- a/app/Http/Requests/Concerns/ValidatesMediaUploads.php +++ b/app/Http/Requests/Concerns/ValidatesMediaUploads.php @@ -22,10 +22,12 @@ protected function photoRules(string $prefix = 'photos', int $max = 1): array /** * @return array */ - protected function variantImageRules(string $variantsKey = 'variants', int $max = 5): array + protected function variantImageRules(string $variantsKey = 'variants', int $max = 5, bool $required = false): array { + $s3KeysRule = $required ? 'required' : 'nullable'; + return [ - "{$variantsKey}.*.s3_keys" => ['nullable', 'array', "max:{$max}"], + "{$variantsKey}.*.s3_keys" => [$s3KeysRule, 'array', "max:{$max}"], "{$variantsKey}.*.s3_keys.*" => ['required', 'string'], "{$variantsKey}.*.remove_media_ids" => ['nullable', 'array'], "{$variantsKey}.*.remove_media_ids.*" => ['integer'], diff --git a/resources/js/pages/admin/manage/cuttings/form/CuttingProductVariantSection.vue b/resources/js/pages/admin/manage/cuttings/form/CuttingProductVariantSection.vue index 3c38d8b..f92af35 100644 --- a/resources/js/pages/admin/manage/cuttings/form/CuttingProductVariantSection.vue +++ b/resources/js/pages/admin/manage/cuttings/form/CuttingProductVariantSection.vue @@ -77,7 +77,7 @@ const emit = defineEmits<{
diff --git a/resources/js/pages/admin/master/products/form/ProductVariantEditModal.vue b/resources/js/pages/admin/master/products/form/ProductVariantEditModal.vue index 2b76a35..f707716 100644 --- a/resources/js/pages/admin/master/products/form/ProductVariantEditModal.vue +++ b/resources/js/pages/admin/master/products/form/ProductVariantEditModal.vue @@ -210,7 +210,7 @@ function submit() { + :errors="formErrors(editForm, `variants.${editingVariantIndex}.s3_keys`)" /> diff --git a/resources/js/pages/admin/master/products/form/ProductVariantSection.vue b/resources/js/pages/admin/master/products/form/ProductVariantSection.vue index 648edaf..e473972 100644 --- a/resources/js/pages/admin/master/products/form/ProductVariantSection.vue +++ b/resources/js/pages/admin/master/products/form/ProductVariantSection.vue @@ -167,7 +167,7 @@ function updatePrice(type: string, value: string) {
diff --git a/resources/js/pages/admin/master/raw-materials/form/RawMaterialVariantEditModal.vue b/resources/js/pages/admin/master/raw-materials/form/RawMaterialVariantEditModal.vue index 4a6c18b..4ab0102 100644 --- a/resources/js/pages/admin/master/raw-materials/form/RawMaterialVariantEditModal.vue +++ b/resources/js/pages/admin/master/raw-materials/form/RawMaterialVariantEditModal.vue @@ -165,7 +165,7 @@ function submit() { + :errors="formErrors(editForm, `prices.${editingPriceIndex}.s3_keys`)" /> diff --git a/resources/js/pages/admin/master/raw-materials/form/RawMaterialVariantSection.vue b/resources/js/pages/admin/master/raw-materials/form/RawMaterialVariantSection.vue index da18bb6..130b048 100644 --- a/resources/js/pages/admin/master/raw-materials/form/RawMaterialVariantSection.vue +++ b/resources/js/pages/admin/master/raw-materials/form/RawMaterialVariantSection.vue @@ -84,7 +84,7 @@ const emit = defineEmits<{ + :max-files="5" required :errors="priceErrors(price.client_id, 's3_keys')" /> diff --git a/tests/Feature/Admin/Master/ProductTest.php b/tests/Feature/Admin/Master/ProductTest.php index e33b992..e0bef59 100644 --- a/tests/Feature/Admin/Master/ProductTest.php +++ b/tests/Feature/Admin/Master/ProductTest.php @@ -395,6 +395,20 @@ function variantUpdateData(int $id, string $name = 'Updated Variant', int $stock ->assertSessionHasErrors('variants.0.stock'); }); + test('variant s3_keys is required', function () { + $user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE); + + $category = Category::factory()->create(); + + $this->actingAs($user) + ->post(route('admin.master.products.store'), [ + 'name' => 'Produk Baru', + 'category_ids' => [$category->id], + 'variants' => [['name' => 'All Size', 'stock' => 10, 'retail_stock' => 0, 'prices' => defaultPrices()]], + ]) + ->assertSessionHasErrors('variants.0.s3_keys'); + }); + test('approving create request activates product with variants and categories', function () { $user = createProductUserWithPermission(PermissionEnum::PRODUCTS_VIEW, PermissionEnum::PRODUCTS_CREATE); $verifier = createProductVerifierUser(); diff --git a/tests/Feature/Admin/Master/RawMaterialTest.php b/tests/Feature/Admin/Master/RawMaterialTest.php index d2106e2..a33242d 100644 --- a/tests/Feature/Admin/Master/RawMaterialTest.php +++ b/tests/Feature/Admin/Master/RawMaterialTest.php @@ -382,6 +382,18 @@ function createRawMaterialVerifierUser(): User ->assertSessionHasErrors('prices.0.stock'); }); + test('price s3_keys is required', function () { + $user = createRawMaterialUserWithPermission(PermissionEnum::RAW_MATERIALS_VIEW, PermissionEnum::RAW_MATERIALS_CREATE); + + $this->actingAs($user) + ->post(route('admin.master.raw_materials.store'), [ + 'name' => 'Kain Sutra', + 'unit' => RawMaterialUnit::METER->value, + 'prices' => [['variant' => 'Merah', 'price' => 50000, 'stock' => 10]], + ]) + ->assertSessionHasErrors('prices.0.s3_keys'); + }); + test('creating raw material also creates prices', function () { $user = createRawMaterialUserWithPermission(PermissionEnum::RAW_MATERIALS_VIEW, PermissionEnum::RAW_MATERIALS_CREATE);