feat: update material usage and stock fields to use decimal type for improved precision
This commit is contained in:
parent
cff231097a
commit
dd3a2aa1eb
@ -81,8 +81,8 @@ ### `raw_materials` → RawMaterial
|
|||||||
- Relations: rawMaterialPrices(HasMany→RawMaterialPrice)
|
- Relations: rawMaterialPrices(HasMany→RawMaterialPrice)
|
||||||
|
|
||||||
### `raw_material_prices` → 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`
|
`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(int)
|
- Casts: price(int), stock(decimal:2)
|
||||||
- GlobalScope: orderBy(variant)
|
- GlobalScope: orderBy(variant)
|
||||||
- Relations: rawMaterial(BelongsTo→RawMaterial,withTrashed), cuttingMaterials(HasMany→CuttingMaterial), purchaseItems(HasMany→PurchaseItem)
|
- Relations: rawMaterial(BelongsTo→RawMaterial,withTrashed), cuttingMaterials(HasMany→CuttingMaterial), purchaseItems(HasMany→PurchaseItem)
|
||||||
- Accessor: photo_url → first media presigned S3 URL
|
- 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)
|
- Relations: cutting(BelongsTo→Cutting), cuttingMaterials(HasMany→CuttingMaterial,combination_id), user(BelongsTo→User)
|
||||||
|
|
||||||
### `cutting_materials` → CuttingMaterial
|
### `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`
|
`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(int), material_result(int)
|
- Casts: material_usage(decimal:2), material_result(int)
|
||||||
- Accessor: formatted_material_result → 'X.XXX', formatted_material_usage → 'X.XXX'
|
- Accessor: formatted_material_result → 'X.XXX', formatted_material_usage → 'X.XXX'
|
||||||
- Relations: cutting(BelongsTo→Cutting), rawMaterialPrice(BelongsTo→RawMaterialPrice), combination(BelongsTo→CuttingMaterialCombination), user(BelongsTo→User)
|
- Relations: cutting(BelongsTo→Cutting), rawMaterialPrice(BelongsTo→RawMaterialPrice), combination(BelongsTo→CuttingMaterialCombination), user(BelongsTo→User)
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ public function rules(): array
|
|||||||
'cutting_result' => ['required', 'integer', 'min:1'],
|
'cutting_result' => ['required', 'integer', 'min:1'],
|
||||||
'materials' => ['required', 'array', 'min:1'],
|
'materials' => ['required', 'array', 'min:1'],
|
||||||
'materials.*.raw_material_price_id' => ['required', 'integer', Rule::exists('raw_material_prices', 'id')],
|
'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.*.material_result' => ['required', 'integer'],
|
||||||
'materials.*.combination_index' => ['nullable', 'integer'],
|
'materials.*.combination_index' => ['nullable', 'integer'],
|
||||||
'combinations' => ['nullable', 'array'],
|
'combinations' => ['nullable', 'array'],
|
||||||
|
|||||||
@ -32,7 +32,7 @@ public function rules(): array
|
|||||||
'variants.*.id' => ['nullable', 'integer'],
|
'variants.*.id' => ['nullable', 'integer'],
|
||||||
'variants.*.variant' => ['required', 'string', 'max:200'],
|
'variants.*.variant' => ['required', 'string', 'max:200'],
|
||||||
'variants.*.price' => ['required', 'integer', 'min:0'],
|
'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'],
|
'variants.*.photo_key' => ['required', 'string', 'max:500'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ public function rules(): array
|
|||||||
return [
|
return [
|
||||||
'variant' => ['required', 'string', 'max:200'],
|
'variant' => ['required', 'string', 'max:200'],
|
||||||
'price' => ['required', 'integer', 'min:0'],
|
'price' => ['required', 'integer', 'min:0'],
|
||||||
'stock' => ['required', 'integer', 'min:0'],
|
'stock' => ['required', 'numeric', 'min:0', 'decimal:0,2'],
|
||||||
'photo_key' => ['required', 'string', 'max:500'],
|
'photo_key' => ['required', 'string', 'max:500'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ class CuttingMaterial extends Model
|
|||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'material_usage' => 'integer',
|
'material_usage' => 'decimal:2',
|
||||||
'material_result' => 'integer',
|
'material_result' => 'integer',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -34,7 +34,7 @@ protected function formattedMaterialResult(): Attribute
|
|||||||
protected function formattedMaterialUsage(): Attribute
|
protected function formattedMaterialUsage(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn () => number_format($this->material_usage, 0, ',', '.'),
|
get: fn () => number_format((float) $this->material_usage, 2, ',', '.'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,7 +29,7 @@ protected function casts(): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'price' => 'integer',
|
'price' => 'integer',
|
||||||
'stock' => 'integer',
|
'stock' => 'decimal:2',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ protected function formattedPrice(): Attribute
|
|||||||
protected function formattedStock(): Attribute
|
protected function formattedStock(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn () => number_format($this->stock, 0, ',', '.'),
|
get: fn () => number_format((float) $this->stock, 2, ',', '.'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -259,7 +259,7 @@ public function store(array $data): Cutting
|
|||||||
$pricesMap = RawMaterialPrice::whereIn('id', $priceIds)->get()->keyBy('id');
|
$pricesMap = RawMaterialPrice::whereIn('id', $priceIds)->get()->keyBy('id');
|
||||||
|
|
||||||
foreach ($data['materials'] as $materialData) {
|
foreach ($data['materials'] as $materialData) {
|
||||||
$usage = (int) ($materialData['material_usage'] ?? 0);
|
$usage = (float) ($materialData['material_usage'] ?? 0);
|
||||||
if ($usage <= 0) {
|
if ($usage <= 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -320,7 +320,7 @@ public function store(array $data): Cutting
|
|||||||
'updated_at' => $now,
|
'updated_at' => $now,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$usage = (int) ($materialData['material_usage'] ?? 0);
|
$usage = (float) ($materialData['material_usage'] ?? 0);
|
||||||
if ($price && $usage > 0) {
|
if ($price && $usage > 0) {
|
||||||
$stockDecrementMap[$materialData['raw_material_price_id']] = ($stockDecrementMap[$materialData['raw_material_price_id']] ?? 0) + $usage;
|
$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');
|
$pricesMap = RawMaterialPrice::whereIn('id', $priceIds)->get()->keyBy('id');
|
||||||
|
|
||||||
foreach ($data['materials'] as $materialData) {
|
foreach ($data['materials'] as $materialData) {
|
||||||
$usage = (int) ($materialData['material_usage'] ?? 0);
|
$usage = (float) ($materialData['material_usage'] ?? 0);
|
||||||
if ($usage <= 0) {
|
if ($usage <= 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -452,7 +452,7 @@ public function update(Cutting $cutting, array $data): Cutting
|
|||||||
'updated_at' => $now,
|
'updated_at' => $now,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$usage = (int) ($materialData['material_usage'] ?? 0);
|
$usage = (float) ($materialData['material_usage'] ?? 0);
|
||||||
if ($price && $usage > 0) {
|
if ($price && $usage > 0) {
|
||||||
$stockDecrementMap[$materialData['raw_material_price_id']] = ($stockDecrementMap[$materialData['raw_material_price_id']] ?? 0) + $usage;
|
$stockDecrementMap[$materialData['raw_material_price_id']] = ($stockDecrementMap[$materialData['raw_material_price_id']] ?? 0) + $usage;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ public function definition(): array
|
|||||||
'raw_material_id' => RawMaterial::factory(),
|
'raw_material_id' => RawMaterial::factory(),
|
||||||
'variant' => fake()->words(2, true),
|
'variant' => fake()->words(2, true),
|
||||||
'price' => fake()->numberBetween(1000, 500000),
|
'price' => fake()->numberBetween(1000, 500000),
|
||||||
'stock' => fake()->numberBetween(0, 1000),
|
'stock' => round(fake()->randomFloat(2, 0, 1000), 2),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('raw_material_prices', function (Blueprint $table) {
|
||||||
|
$table->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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('cutting_materials', function (Blueprint $table) {
|
||||||
|
$table->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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -27,7 +27,7 @@ import type { CuttingCreateData } from './columns';
|
|||||||
|
|
||||||
type MaterialState = {
|
type MaterialState = {
|
||||||
raw_material_price_id: number;
|
raw_material_price_id: number;
|
||||||
material_usage: number;
|
material_usage: string;
|
||||||
material_result: number;
|
material_result: number;
|
||||||
combination_id: number | null;
|
combination_id: number | null;
|
||||||
variant: string;
|
variant: string;
|
||||||
@ -54,7 +54,7 @@ export default function CuttingCreate({ rawMaterials }: Props) {
|
|||||||
if (draft?.materials && draft.materials.length > 0) {
|
if (draft?.materials && draft.materials.length > 0) {
|
||||||
return draft.materials.map((m) => ({
|
return draft.materials.map((m) => ({
|
||||||
raw_material_price_id: m.raw_material_price_id,
|
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,
|
material_result: m.material_result ?? 0,
|
||||||
combination_id: m.combination_index ?? null,
|
combination_id: m.combination_index ?? null,
|
||||||
variant: m.variant,
|
variant: m.variant,
|
||||||
@ -185,7 +185,7 @@ export default function CuttingCreate({ rawMaterials }: Props) {
|
|||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
raw_material_price_id: price.id,
|
raw_material_price_id: price.id,
|
||||||
material_usage: 0,
|
material_usage: '0',
|
||||||
material_result: 0,
|
material_result: 0,
|
||||||
combination_id: null,
|
combination_id: null,
|
||||||
variant: price.variant,
|
variant: price.variant,
|
||||||
@ -235,7 +235,7 @@ export default function CuttingCreate({ rawMaterials }: Props) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
raw_material_price_id: priceId,
|
raw_material_price_id: priceId,
|
||||||
material_usage: 0,
|
material_usage: '0',
|
||||||
material_result: 0,
|
material_result: 0,
|
||||||
combination_id: comboIndex,
|
combination_id: comboIndex,
|
||||||
variant: foundPrice?.variant ?? '',
|
variant: foundPrice?.variant ?? '',
|
||||||
@ -284,7 +284,7 @@ export default function CuttingCreate({ rawMaterials }: Props) {
|
|||||||
return materials.reduce((sum, m) => {
|
return materials.reduce((sum, m) => {
|
||||||
const price = priceMap.get(m.raw_material_price_id);
|
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);
|
}, 0);
|
||||||
}, [materials, priceMap]);
|
}, [materials, priceMap]);
|
||||||
|
|
||||||
@ -304,7 +304,7 @@ export default function CuttingCreate({ rawMaterials }: Props) {
|
|||||||
cutting_result: cuttingResult,
|
cutting_result: cuttingResult,
|
||||||
materials: materialsRef.current.map((m) => ({
|
materials: materialsRef.current.map((m) => ({
|
||||||
raw_material_price_id: m.raw_material_price_id,
|
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,
|
material_result: m.material_result,
|
||||||
combination_index: m.combination_id,
|
combination_index: m.combination_id,
|
||||||
})),
|
})),
|
||||||
@ -646,11 +646,13 @@ export default function CuttingCreate({ rawMaterials }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs text-muted-foreground whitespace-nowrap">Pemakaian <span className="text-destructive">*</span></span>
|
<span className="text-xs text-muted-foreground whitespace-nowrap">Pemakaian <span className="text-destructive">*</span></span>
|
||||||
<NumberInput
|
<Input
|
||||||
className="flex-1"
|
type="text"
|
||||||
value={m.material_usage}
|
inputMode="decimal"
|
||||||
onValueChange={(val) => updateMaterial(index, 'material_usage', val)}
|
className="flex-1"
|
||||||
/>
|
value={m.material_usage}
|
||||||
|
onChange={(e) => updateMaterial(index, 'material_usage', e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<InputError message={errors[`materials.${index}.material_usage` as keyof typeof errors]} />
|
<InputError message={errors[`materials.${index}.material_usage` as keyof typeof errors]} />
|
||||||
{group.comboIndex === null && (
|
{group.comboIndex === null && (
|
||||||
@ -675,7 +677,7 @@ export default function CuttingCreate({ rawMaterials }: Props) {
|
|||||||
<SheetFooter>
|
<SheetFooter>
|
||||||
<div className="flex items-center justify-between border-t pt-4">
|
<div className="flex items-center justify-between border-t pt-4">
|
||||||
<span className="text-sm">Total Pemakaian</span>
|
<span className="text-sm">Total Pemakaian</span>
|
||||||
<span className="text-sm font-semibold">{formatQuantity(materials.reduce((sum, m) => sum + m.material_usage, 0))}</span>
|
<span className="text-sm font-semibold">{formatQuantity(materials.reduce((sum, m) => sum + (parseFloat(String(m.material_usage)) || 0), 0))}</span>
|
||||||
</div>
|
</div>
|
||||||
</SheetFooter>
|
</SheetFooter>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import type { CuttingCreateData, CuttingForEdit } from './columns';
|
|||||||
type MaterialState = {
|
type MaterialState = {
|
||||||
id?: number;
|
id?: number;
|
||||||
raw_material_price_id: number;
|
raw_material_price_id: number;
|
||||||
material_usage: number;
|
material_usage: string;
|
||||||
material_result: number;
|
material_result: number;
|
||||||
combination_id: number | null;
|
combination_id: number | null;
|
||||||
variant: string;
|
variant: string;
|
||||||
@ -63,7 +63,7 @@ export default function CuttingEdit({ cutting, rawMaterials }: Props) {
|
|||||||
return {
|
return {
|
||||||
id: m.id,
|
id: m.id,
|
||||||
raw_material_price_id: m.raw_material_price_id,
|
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,
|
material_result: m.material_result ?? 0,
|
||||||
combination_id:
|
combination_id:
|
||||||
m.combination_id !== null
|
m.combination_id !== null
|
||||||
@ -168,7 +168,7 @@ export default function CuttingEdit({ cutting, rawMaterials }: Props) {
|
|||||||
...prev,
|
...prev,
|
||||||
{
|
{
|
||||||
raw_material_price_id: price.id,
|
raw_material_price_id: price.id,
|
||||||
material_usage: 0,
|
material_usage: '0',
|
||||||
material_result: 0,
|
material_result: 0,
|
||||||
combination_id: null,
|
combination_id: null,
|
||||||
variant: price.variant,
|
variant: price.variant,
|
||||||
@ -218,7 +218,7 @@ export default function CuttingEdit({ cutting, rawMaterials }: Props) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
raw_material_price_id: priceId,
|
raw_material_price_id: priceId,
|
||||||
material_usage: 0,
|
material_usage: '0',
|
||||||
material_result: 0,
|
material_result: 0,
|
||||||
combination_id: comboIndex,
|
combination_id: comboIndex,
|
||||||
variant: foundPrice?.variant ?? '',
|
variant: foundPrice?.variant ?? '',
|
||||||
@ -262,7 +262,7 @@ export default function CuttingEdit({ cutting, rawMaterials }: Props) {
|
|||||||
return materials.reduce((sum, m) => {
|
return materials.reduce((sum, m) => {
|
||||||
const price = priceMap.get(m.raw_material_price_id);
|
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);
|
}, 0);
|
||||||
}, [materials, priceMap]);
|
}, [materials, priceMap]);
|
||||||
|
|
||||||
@ -282,7 +282,7 @@ export default function CuttingEdit({ cutting, rawMaterials }: Props) {
|
|||||||
cutting_result: cuttingResult,
|
cutting_result: cuttingResult,
|
||||||
materials: materialsRef.current.map((m) => ({
|
materials: materialsRef.current.map((m) => ({
|
||||||
raw_material_price_id: m.raw_material_price_id,
|
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,
|
material_result: m.material_result,
|
||||||
combination_index: m.combination_id,
|
combination_index: m.combination_id,
|
||||||
})),
|
})),
|
||||||
@ -624,11 +624,13 @@ export default function CuttingEdit({ cutting, rawMaterials }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs text-muted-foreground whitespace-nowrap">Pemakaian <span className="text-destructive">*</span></span>
|
<span className="text-xs text-muted-foreground whitespace-nowrap">Pemakaian <span className="text-destructive">*</span></span>
|
||||||
<NumberInput
|
<Input
|
||||||
className="flex-1"
|
type="text"
|
||||||
value={m.material_usage}
|
inputMode="decimal"
|
||||||
onValueChange={(val) => updateMaterial(index, 'material_usage', val)}
|
className="flex-1"
|
||||||
/>
|
value={m.material_usage}
|
||||||
|
onChange={(e) => updateMaterial(index, 'material_usage', e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<InputError message={errors[`materials.${index}.material_usage` as keyof typeof errors]} />
|
<InputError message={errors[`materials.${index}.material_usage` as keyof typeof errors]} />
|
||||||
{group.comboIndex === null && (
|
{group.comboIndex === null && (
|
||||||
@ -653,7 +655,7 @@ export default function CuttingEdit({ cutting, rawMaterials }: Props) {
|
|||||||
<SheetFooter>
|
<SheetFooter>
|
||||||
<div className="flex items-center justify-between border-t pt-4">
|
<div className="flex items-center justify-between border-t pt-4">
|
||||||
<span className="text-sm">Total Pemakaian</span>
|
<span className="text-sm">Total Pemakaian</span>
|
||||||
<span className="text-sm font-semibold">{formatQuantity(materials.reduce((sum, m) => sum + m.material_usage, 0))}</span>
|
<span className="text-sm font-semibold">{formatQuantity(materials.reduce((sum, m) => sum + (parseFloat(String(m.material_usage)) || 0), 0))}</span>
|
||||||
</div>
|
</div>
|
||||||
</SheetFooter>
|
</SheetFooter>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { toast } from 'sonner';
|
|||||||
import { ConfirmDialog } from '@/components/dialogs';
|
import { ConfirmDialog } from '@/components/dialogs';
|
||||||
import { FileUpload } from '@/components/inputs';
|
import { FileUpload } from '@/components/inputs';
|
||||||
import { InputError } from '@/components/ui';
|
import { InputError } from '@/components/ui';
|
||||||
import { NumberInput } from '@/components/inputs';
|
|
||||||
import { RupiahInput } from '@/components/inputs';
|
import { RupiahInput } from '@/components/inputs';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -31,7 +30,7 @@ import {
|
|||||||
type VariantState = {
|
type VariantState = {
|
||||||
variant: string;
|
variant: string;
|
||||||
price: number;
|
price: number;
|
||||||
stock: number;
|
stock: string;
|
||||||
photo: string | null;
|
photo: string | null;
|
||||||
photoUrl: string | null;
|
photoUrl: string | null;
|
||||||
uploading: boolean;
|
uploading: boolean;
|
||||||
@ -50,7 +49,7 @@ export default function RawMaterialCreate() {
|
|||||||
return draft.variants.map((v) => ({
|
return draft.variants.map((v) => ({
|
||||||
variant: v.variant,
|
variant: v.variant,
|
||||||
price: v.price,
|
price: v.price,
|
||||||
stock: v.stock,
|
stock: String(v.stock),
|
||||||
photo: v.photo ?? null,
|
photo: v.photo ?? null,
|
||||||
photoUrl: v.photo ? getTemporaryUrl(v.photo) : null,
|
photoUrl: v.photo ? getTemporaryUrl(v.photo) : null,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
@ -61,7 +60,7 @@ export default function RawMaterialCreate() {
|
|||||||
{
|
{
|
||||||
variant: '',
|
variant: '',
|
||||||
price: 0,
|
price: 0,
|
||||||
stock: 0,
|
stock: '0',
|
||||||
photo: null,
|
photo: null,
|
||||||
photoUrl: null,
|
photoUrl: null,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
@ -92,7 +91,7 @@ export default function RawMaterialCreate() {
|
|||||||
{
|
{
|
||||||
variant: '',
|
variant: '',
|
||||||
price: 0,
|
price: 0,
|
||||||
stock: 0,
|
stock: '0',
|
||||||
photo: null,
|
photo: null,
|
||||||
photoUrl: null,
|
photoUrl: null,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
@ -178,7 +177,7 @@ export default function RawMaterialCreate() {
|
|||||||
variants: variantsRef.current.map((v) => ({
|
variants: variantsRef.current.map((v) => ({
|
||||||
variant: v.variant,
|
variant: v.variant,
|
||||||
price: Number(v.price),
|
price: Number(v.price),
|
||||||
stock: Number(v.stock),
|
stock: parseFloat(String(v.stock)) || 0,
|
||||||
photo_key: v.photo,
|
photo_key: v.photo,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
@ -422,17 +421,19 @@ export default function RawMaterialCreate() {
|
|||||||
*
|
*
|
||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
<NumberInput
|
<Input
|
||||||
|
type="text"
|
||||||
|
inputMode="decimal"
|
||||||
value={
|
value={
|
||||||
variant.stock
|
variant.stock
|
||||||
}
|
}
|
||||||
onValueChange={(
|
onChange={(
|
||||||
val,
|
e,
|
||||||
) =>
|
) =>
|
||||||
updateVariant(
|
updateVariant(
|
||||||
variantIndex,
|
variantIndex,
|
||||||
'stock',
|
'stock',
|
||||||
val,
|
e.target.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { toast } from 'sonner';
|
|||||||
import { ConfirmDialog } from '@/components/dialogs';
|
import { ConfirmDialog } from '@/components/dialogs';
|
||||||
import { FileUpload } from '@/components/inputs';
|
import { FileUpload } from '@/components/inputs';
|
||||||
import { InputError } from '@/components/ui';
|
import { InputError } from '@/components/ui';
|
||||||
import { NumberInput } from '@/components/inputs';
|
|
||||||
import { RupiahInput } from '@/components/inputs';
|
import { RupiahInput } from '@/components/inputs';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -37,7 +36,7 @@ type VariantState = {
|
|||||||
id: number | null;
|
id: number | null;
|
||||||
variant: string;
|
variant: string;
|
||||||
price: number;
|
price: number;
|
||||||
stock: number;
|
stock: string;
|
||||||
photo: string | null;
|
photo: string | null;
|
||||||
photoUrl: string | null;
|
photoUrl: string | null;
|
||||||
uploading: boolean;
|
uploading: boolean;
|
||||||
@ -58,7 +57,7 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
|||||||
id: v.id,
|
id: v.id,
|
||||||
variant: v.variant,
|
variant: v.variant,
|
||||||
price: v.price,
|
price: v.price,
|
||||||
stock: v.stock,
|
stock: String(v.stock),
|
||||||
photo: v.photo_key,
|
photo: v.photo_key,
|
||||||
photoUrl: v.photo_url,
|
photoUrl: v.photo_url,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
@ -73,7 +72,7 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
|||||||
id: null,
|
id: null,
|
||||||
variant: '',
|
variant: '',
|
||||||
price: 0,
|
price: 0,
|
||||||
stock: 0,
|
stock: '0',
|
||||||
photo: null,
|
photo: null,
|
||||||
photoUrl: null,
|
photoUrl: null,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
@ -106,7 +105,7 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
|||||||
id: null,
|
id: null,
|
||||||
variant: '',
|
variant: '',
|
||||||
price: 0,
|
price: 0,
|
||||||
stock: 0,
|
stock: '0',
|
||||||
photo: null,
|
photo: null,
|
||||||
photoUrl: null,
|
photoUrl: null,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
@ -193,7 +192,7 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
|||||||
id: v.id,
|
id: v.id,
|
||||||
variant: v.variant,
|
variant: v.variant,
|
||||||
price: Number(v.price),
|
price: Number(v.price),
|
||||||
stock: Number(v.stock),
|
stock: parseFloat(String(v.stock)) || 0,
|
||||||
photo_key: v.photo,
|
photo_key: v.photo,
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
@ -438,19 +437,19 @@ export default function RawMaterialEdit({ rawMaterial }: Props) {
|
|||||||
*
|
*
|
||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
<NumberInput
|
<Input
|
||||||
|
type="text"
|
||||||
|
inputMode="decimal"
|
||||||
value={
|
value={
|
||||||
Number(
|
variant.stock
|
||||||
variant.stock,
|
|
||||||
) || 0
|
|
||||||
}
|
}
|
||||||
onValueChange={(
|
onChange={(
|
||||||
val,
|
e,
|
||||||
) =>
|
) =>
|
||||||
updateVariant(
|
updateVariant(
|
||||||
variantIndex,
|
variantIndex,
|
||||||
'stock',
|
'stock',
|
||||||
val,
|
e.target.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { useState } from 'react';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { FileUpload } from '@/components/inputs';
|
import { FileUpload } from '@/components/inputs';
|
||||||
import { InputError } from '@/components/ui';
|
import { InputError } from '@/components/ui';
|
||||||
import { NumberInput } from '@/components/inputs';
|
|
||||||
import { RupiahInput } from '@/components/inputs';
|
import { RupiahInput } from '@/components/inputs';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
@ -28,7 +27,7 @@ type Props = {
|
|||||||
export default function RawMaterialVariantEdit({ variant }: Props) {
|
export default function RawMaterialVariantEdit({ variant }: Props) {
|
||||||
const [variantName, setVariantName] = useState(variant.variant);
|
const [variantName, setVariantName] = useState(variant.variant);
|
||||||
const [price, setPrice] = useState(variant.price);
|
const [price, setPrice] = useState(variant.price);
|
||||||
const [stock, setStock] = useState(Number(variant.stock) || 0);
|
const [stock, setStock] = useState(String(variant.stock) || '0');
|
||||||
const [photo, setPhoto] = useState<string | null>(variant.photo_key);
|
const [photo, setPhoto] = useState<string | null>(variant.photo_key);
|
||||||
const [photoUrl, setPhotoUrl] = useState<string | null>(variant.photo_url);
|
const [photoUrl, setPhotoUrl] = useState<string | null>(variant.photo_url);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
@ -37,7 +36,7 @@ export default function RawMaterialVariantEdit({ variant }: Props) {
|
|||||||
return {
|
return {
|
||||||
variant: variantName,
|
variant: variantName,
|
||||||
price: Number(price),
|
price: Number(price),
|
||||||
stock: Number(stock),
|
stock: parseFloat(String(stock)) || 0,
|
||||||
photo_key: photo,
|
photo_key: photo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -101,9 +100,11 @@ export default function RawMaterialVariantEdit({ variant }: Props) {
|
|||||||
<Label>
|
<Label>
|
||||||
Stok <span className="text-destructive">*</span>
|
Stok <span className="text-destructive">*</span>
|
||||||
</Label>
|
</Label>
|
||||||
<NumberInput
|
<Input
|
||||||
|
type="text"
|
||||||
|
inputMode="decimal"
|
||||||
value={stock}
|
value={stock}
|
||||||
onValueChange={setStock}
|
onChange={(e) => setStock(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<InputError message={errors.stock} />
|
<InputError message={errors.stock} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user