diff --git a/.ai/REFERENCE.md b/.ai/REFERENCE.md index a897258..764882b 100644 --- a/.ai/REFERENCE.md +++ b/.ai/REFERENCE.md @@ -81,8 +81,8 @@ ### `raw_materials` → RawMaterial - Relations: rawMaterialPrices(HasMany→RawMaterialPrice) ### `raw_material_prices` → RawMaterialPrice -`id` `raw_material_id`(FK→raw_materials) `variant`(200) `price`(uint) `stock`(uint,default:0) `created_at` `updated_at` `deleted_at` -- Casts: price(int), stock(int) +`id` `raw_material_id`(FK→raw_materials) `variant`(200) `price`(uint) `stock`(decimal(10,2),default:0) `created_at` `updated_at` `deleted_at` +- Casts: price(int), stock(decimal:2) - GlobalScope: orderBy(variant) - Relations: rawMaterial(BelongsTo→RawMaterial,withTrashed), cuttingMaterials(HasMany→CuttingMaterial), purchaseItems(HasMany→PurchaseItem) - Accessor: photo_url → first media presigned S3 URL @@ -199,8 +199,8 @@ ### `cutting_material_combinations` → CuttingMaterialCombination - Relations: cutting(BelongsTo→Cutting), cuttingMaterials(HasMany→CuttingMaterial,combination_id), user(BelongsTo→User) ### `cutting_materials` → CuttingMaterial -`id` `user_id`(FK→users,null) `cutting_id`(FK→cuttings,null) `raw_material_price_id`(FK→raw_material_prices) `combination_id`(FK→cutting_material_combinations,null) `material_usage`(int) `material_result`(int,null) `created_at` `updated_at` `deleted_at` -- Casts: material_usage(int), material_result(int) +`id` `user_id`(FK→users,null) `cutting_id`(FK→cuttings,null) `raw_material_price_id`(FK→raw_material_prices) `combination_id`(FK→cutting_material_combinations,null) `material_usage`(decimal(10,2)) `material_result`(int,null) `created_at` `updated_at` `deleted_at` +- Casts: material_usage(decimal:2), material_result(int) - Accessor: formatted_material_result → 'X.XXX', formatted_material_usage → 'X.XXX' - Relations: cutting(BelongsTo→Cutting), rawMaterialPrice(BelongsTo→RawMaterialPrice), combination(BelongsTo→CuttingMaterialCombination), user(BelongsTo→User) diff --git a/app/Http/Requests/Admin/Manage/CuttingRequest.php b/app/Http/Requests/Admin/Manage/CuttingRequest.php index 5d2dd64..e61db0b 100644 --- a/app/Http/Requests/Admin/Manage/CuttingRequest.php +++ b/app/Http/Requests/Admin/Manage/CuttingRequest.php @@ -23,7 +23,7 @@ public function rules(): array 'cutting_result' => ['required', 'integer', 'min:1'], 'materials' => ['required', 'array', 'min:1'], 'materials.*.raw_material_price_id' => ['required', 'integer', Rule::exists('raw_material_prices', 'id')], - 'materials.*.material_usage' => ['required', 'integer', 'min:1'], + 'materials.*.material_usage' => ['required', 'numeric', 'min:0.01', 'decimal:0,2'], 'materials.*.material_result' => ['required', 'integer'], 'materials.*.combination_index' => ['nullable', 'integer'], 'combinations' => ['nullable', 'array'], diff --git a/app/Http/Requests/Admin/Master/RawMaterial/RawMaterialRequest.php b/app/Http/Requests/Admin/Master/RawMaterial/RawMaterialRequest.php index 9b60a84..1f76159 100644 --- a/app/Http/Requests/Admin/Master/RawMaterial/RawMaterialRequest.php +++ b/app/Http/Requests/Admin/Master/RawMaterial/RawMaterialRequest.php @@ -32,7 +32,7 @@ public function rules(): array 'variants.*.id' => ['nullable', 'integer'], 'variants.*.variant' => ['required', 'string', 'max:200'], 'variants.*.price' => ['required', 'integer', 'min:0'], - 'variants.*.stock' => ['required', 'integer', 'min:0'], + 'variants.*.stock' => ['required', 'numeric', 'min:0', 'decimal:0,2'], 'variants.*.photo_key' => ['required', 'string', 'max:500'], ]; } diff --git a/app/Http/Requests/Admin/Master/RawMaterial/RawMaterialVariantRequest.php b/app/Http/Requests/Admin/Master/RawMaterial/RawMaterialVariantRequest.php index c247381..2b000af 100644 --- a/app/Http/Requests/Admin/Master/RawMaterial/RawMaterialVariantRequest.php +++ b/app/Http/Requests/Admin/Master/RawMaterial/RawMaterialVariantRequest.php @@ -25,7 +25,7 @@ public function rules(): array return [ 'variant' => ['required', 'string', 'max:200'], 'price' => ['required', 'integer', 'min:0'], - 'stock' => ['required', 'integer', 'min:0'], + 'stock' => ['required', 'numeric', 'min:0', 'decimal:0,2'], 'photo_key' => ['required', 'string', 'max:500'], ]; } diff --git a/app/Models/CuttingMaterial.php b/app/Models/CuttingMaterial.php index 69ad875..afc647e 100644 --- a/app/Models/CuttingMaterial.php +++ b/app/Models/CuttingMaterial.php @@ -19,7 +19,7 @@ class CuttingMaterial extends Model protected function casts(): array { return [ - 'material_usage' => 'integer', + 'material_usage' => 'decimal:2', 'material_result' => 'integer', ]; } @@ -34,7 +34,7 @@ protected function formattedMaterialResult(): Attribute protected function formattedMaterialUsage(): Attribute { return Attribute::make( - get: fn () => number_format($this->material_usage, 0, ',', '.'), + get: fn () => number_format((float) $this->material_usage, 2, ',', '.'), ); } diff --git a/app/Models/RawMaterialPrice.php b/app/Models/RawMaterialPrice.php index e573ded..d1c17c1 100644 --- a/app/Models/RawMaterialPrice.php +++ b/app/Models/RawMaterialPrice.php @@ -29,7 +29,7 @@ protected function casts(): array { return [ 'price' => 'integer', - 'stock' => 'integer', + 'stock' => 'decimal:2', ]; } @@ -43,7 +43,7 @@ protected function formattedPrice(): Attribute protected function formattedStock(): Attribute { return Attribute::make( - get: fn () => number_format($this->stock, 0, ',', '.'), + get: fn () => number_format((float) $this->stock, 2, ',', '.'), ); } diff --git a/app/Services/Admin/Manage/CuttingService.php b/app/Services/Admin/Manage/CuttingService.php index 63d9d26..0df9cdf 100644 --- a/app/Services/Admin/Manage/CuttingService.php +++ b/app/Services/Admin/Manage/CuttingService.php @@ -259,7 +259,7 @@ public function store(array $data): Cutting $pricesMap = RawMaterialPrice::whereIn('id', $priceIds)->get()->keyBy('id'); foreach ($data['materials'] as $materialData) { - $usage = (int) ($materialData['material_usage'] ?? 0); + $usage = (float) ($materialData['material_usage'] ?? 0); if ($usage <= 0) { continue; } @@ -320,7 +320,7 @@ public function store(array $data): Cutting 'updated_at' => $now, ]); - $usage = (int) ($materialData['material_usage'] ?? 0); + $usage = (float) ($materialData['material_usage'] ?? 0); if ($price && $usage > 0) { $stockDecrementMap[$materialData['raw_material_price_id']] = ($stockDecrementMap[$materialData['raw_material_price_id']] ?? 0) + $usage; } @@ -393,7 +393,7 @@ public function update(Cutting $cutting, array $data): Cutting $pricesMap = RawMaterialPrice::whereIn('id', $priceIds)->get()->keyBy('id'); foreach ($data['materials'] as $materialData) { - $usage = (int) ($materialData['material_usage'] ?? 0); + $usage = (float) ($materialData['material_usage'] ?? 0); if ($usage <= 0) { continue; } @@ -452,7 +452,7 @@ public function update(Cutting $cutting, array $data): Cutting 'updated_at' => $now, ]); - $usage = (int) ($materialData['material_usage'] ?? 0); + $usage = (float) ($materialData['material_usage'] ?? 0); if ($price && $usage > 0) { $stockDecrementMap[$materialData['raw_material_price_id']] = ($stockDecrementMap[$materialData['raw_material_price_id']] ?? 0) + $usage; } diff --git a/database/factories/RawMaterialPriceFactory.php b/database/factories/RawMaterialPriceFactory.php index 1fc6cfd..50702d0 100644 --- a/database/factories/RawMaterialPriceFactory.php +++ b/database/factories/RawMaterialPriceFactory.php @@ -13,7 +13,7 @@ public function definition(): array 'raw_material_id' => RawMaterial::factory(), 'variant' => fake()->words(2, true), 'price' => fake()->numberBetween(1000, 500000), - 'stock' => fake()->numberBetween(0, 1000), + 'stock' => round(fake()->randomFloat(2, 0, 1000), 2), ]; } } diff --git a/database/migrations/2026_08_15_192608_change_stock_column_in_raw_material_prices_table.php b/database/migrations/2026_08_15_192608_change_stock_column_in_raw_material_prices_table.php new file mode 100644 index 0000000..13ca0da --- /dev/null +++ b/database/migrations/2026_08_15_192608_change_stock_column_in_raw_material_prices_table.php @@ -0,0 +1,28 @@ +decimal('stock', 10, 2)->default(0)->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('raw_material_prices', function (Blueprint $table) { + $table->unsignedInteger('stock')->default(0)->change(); + }); + } +}; diff --git a/database/migrations/2026_08_15_194830_change_material_usage_column_in_cutting_materials_table.php b/database/migrations/2026_08_15_194830_change_material_usage_column_in_cutting_materials_table.php new file mode 100644 index 0000000..58bb3bc --- /dev/null +++ b/database/migrations/2026_08_15_194830_change_material_usage_column_in_cutting_materials_table.php @@ -0,0 +1,28 @@ +decimal('material_usage', 10, 2)->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('cutting_materials', function (Blueprint $table) { + $table->integer('material_usage')->change(); + }); + } +}; diff --git a/resources/js/pages/admin/manage/cutting/create.tsx b/resources/js/pages/admin/manage/cutting/create.tsx index be86706..c9f9853 100644 --- a/resources/js/pages/admin/manage/cutting/create.tsx +++ b/resources/js/pages/admin/manage/cutting/create.tsx @@ -27,7 +27,7 @@ import type { CuttingCreateData } from './columns'; type MaterialState = { raw_material_price_id: number; - material_usage: number; + material_usage: string; material_result: number; combination_id: number | null; variant: string; @@ -54,7 +54,7 @@ export default function CuttingCreate({ rawMaterials }: Props) { if (draft?.materials && draft.materials.length > 0) { return draft.materials.map((m) => ({ raw_material_price_id: m.raw_material_price_id, - material_usage: m.material_usage, + material_usage: String(m.material_usage), material_result: m.material_result ?? 0, combination_id: m.combination_index ?? null, variant: m.variant, @@ -185,7 +185,7 @@ export default function CuttingCreate({ rawMaterials }: Props) { ...prev, { raw_material_price_id: price.id, - material_usage: 0, + material_usage: '0', material_result: 0, combination_id: null, variant: price.variant, @@ -235,7 +235,7 @@ export default function CuttingCreate({ rawMaterials }: Props) { return { raw_material_price_id: priceId, - material_usage: 0, + material_usage: '0', material_result: 0, combination_id: comboIndex, variant: foundPrice?.variant ?? '', @@ -284,7 +284,7 @@ export default function CuttingCreate({ rawMaterials }: Props) { return materials.reduce((sum, m) => { const price = priceMap.get(m.raw_material_price_id); - return sum + (price ? price.price * m.material_usage : 0); + return sum + (price ? price.price * (parseFloat(String(m.material_usage)) || 0) : 0); }, 0); }, [materials, priceMap]); @@ -304,7 +304,7 @@ export default function CuttingCreate({ rawMaterials }: Props) { cutting_result: cuttingResult, materials: materialsRef.current.map((m) => ({ raw_material_price_id: m.raw_material_price_id, - material_usage: m.material_usage, + material_usage: parseFloat(String(m.material_usage)) || 0, material_result: m.material_result, combination_index: m.combination_id, })), @@ -646,11 +646,13 @@ export default function CuttingCreate({ rawMaterials }: Props) {