refactor: update material usage and remaining material precision to four decimal places across cutting management components for improved accuracy
This commit is contained in:
parent
9c205cf3d0
commit
175e0dc5af
@ -24,8 +24,8 @@ public function rules(): array
|
|||||||
'integer',
|
'integer',
|
||||||
Rule::exists('raw_material_prices', 'id')->whereNull('deleted_at'),
|
Rule::exists('raw_material_prices', 'id')->whereNull('deleted_at'),
|
||||||
],
|
],
|
||||||
'material_usage' => ['required', 'numeric', 'decimal:0,2', 'gt:0'],
|
'material_usage' => ['required', 'numeric', 'decimal:0,4', 'gt:0'],
|
||||||
'remaining_material' => ['required', 'numeric', 'decimal:0,2', 'gte:0'],
|
'remaining_material' => ['required', 'numeric', 'decimal:0,4', 'gte:0'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,8 +39,8 @@ public function rules(): array
|
|||||||
'distinct',
|
'distinct',
|
||||||
Rule::exists('raw_material_prices', 'id')->whereNull('deleted_at'),
|
Rule::exists('raw_material_prices', 'id')->whereNull('deleted_at'),
|
||||||
];
|
];
|
||||||
$rules['materials.*.material_usage'] = ['required', 'numeric', 'decimal:0,2', 'gt:0'];
|
$rules['materials.*.material_usage'] = ['required', 'numeric', 'decimal:0,4', 'gt:0'];
|
||||||
$rules['materials.*.remaining_material'] = ['required', 'numeric', 'decimal:0,2', 'gte:0'];
|
$rules['materials.*.remaining_material'] = ['required', 'numeric', 'decimal:0,4', 'gte:0'];
|
||||||
|
|
||||||
$rules['results'] = ['required', 'array', 'min:1'];
|
$rules['results'] = ['required', 'array', 'min:1'];
|
||||||
$rules['results.*.product_variant_id'] = [
|
$rules['results.*.product_variant_id'] = [
|
||||||
|
|||||||
@ -31,8 +31,8 @@ class CuttingMaterial extends Model
|
|||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'material_usage' => 'decimal:2',
|
'material_usage' => 'decimal:4',
|
||||||
'remaining_material' => 'decimal:2',
|
'remaining_material' => 'decimal:4',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +172,7 @@ private function formatCmQuantity(float|string|null $value): ?string
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$formatted = rtrim(rtrim(number_format($cm, 2, ',', '.'), '0'), ',');
|
$formatted = rtrim(rtrim(number_format($cm, 4, ',', '.'), '0'), ',');
|
||||||
|
|
||||||
return "{$formatted} cm";
|
return "{$formatted} cm";
|
||||||
}
|
}
|
||||||
@ -195,18 +195,18 @@ private function formatCmQuantityInput(float|string|null $value): ?string
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return rtrim(rtrim(number_format($cm, 2, '.', ''), '0'), '.');
|
return rtrim(rtrim(number_format($cm, 4, '.', ''), '0'), '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatQuantity(float|string|null $value): string
|
private function formatQuantity(float|string|null $value): string
|
||||||
{
|
{
|
||||||
$formatted = rtrim(rtrim(number_format((float) $value, 2, ',', '.'), '0'), ',');
|
$formatted = rtrim(rtrim(number_format((float) $value, 4, ',', '.'), '0'), ',');
|
||||||
|
|
||||||
return "{$formatted} {$this->unitAbbreviation}";
|
return "{$formatted} {$this->unitAbbreviation}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatQuantityInput(float|string|null $value): string
|
private function formatQuantityInput(float|string|null $value): string
|
||||||
{
|
{
|
||||||
return rtrim(rtrim(number_format((float) $value, 2, '.', ''), '0'), '.');
|
return rtrim(rtrim(number_format((float) $value, 4, '.', ''), '0'), '.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -284,12 +284,12 @@ public function syncDraftMaterial(array $validated, User $user): array
|
|||||||
->findOrFail($validated['raw_material_price_id']);
|
->findOrFail($validated['raw_material_price_id']);
|
||||||
|
|
||||||
$unit = $price->rawMaterial?->unit;
|
$unit = $price->rawMaterial?->unit;
|
||||||
$usageInput = round((float) $validated['material_usage'], 2);
|
$usageInput = round((float) $validated['material_usage'], 4);
|
||||||
$remainingInput = round((float) $validated['remaining_material'], 2);
|
$remainingInput = round((float) $validated['remaining_material'], 4);
|
||||||
|
|
||||||
if ($unit !== null && $unit->usesLengthUnit()) {
|
if ($unit !== null && $unit->usesLengthUnit()) {
|
||||||
$materialUsage = round($unit->fromCm($usageInput), 2);
|
$materialUsage = round($unit->fromCm($usageInput), 4);
|
||||||
$remainingMaterial = round($unit->fromCm($remainingInput), 2);
|
$remainingMaterial = round($unit->fromCm($remainingInput), 4);
|
||||||
} else {
|
} else {
|
||||||
$materialUsage = $usageInput;
|
$materialUsage = $usageInput;
|
||||||
$remainingMaterial = $remainingInput;
|
$remainingMaterial = $remainingInput;
|
||||||
@ -655,12 +655,12 @@ private function buildMaterials(array $materials): array
|
|||||||
}
|
}
|
||||||
|
|
||||||
$unit = $price->rawMaterial?->unit;
|
$unit = $price->rawMaterial?->unit;
|
||||||
$usageInput = round((float) $itemData['material_usage'], 2);
|
$usageInput = round((float) $itemData['material_usage'], 4);
|
||||||
$remainingInput = round((float) $itemData['remaining_material'], 2);
|
$remainingInput = round((float) $itemData['remaining_material'], 4);
|
||||||
|
|
||||||
if ($unit !== null && $unit->usesLengthUnit()) {
|
if ($unit !== null && $unit->usesLengthUnit()) {
|
||||||
$materialUsage = round($unit->fromCm($usageInput), 2);
|
$materialUsage = round($unit->fromCm($usageInput), 4);
|
||||||
$remainingMaterial = round($unit->fromCm($remainingInput), 2);
|
$remainingMaterial = round($unit->fromCm($remainingInput), 4);
|
||||||
} else {
|
} else {
|
||||||
$materialUsage = $usageInput;
|
$materialUsage = $usageInput;
|
||||||
$remainingMaterial = $remainingInput;
|
$remainingMaterial = $remainingInput;
|
||||||
@ -898,10 +898,10 @@ private function presentDraftMaterial(CuttingMaterial $item): array
|
|||||||
|
|
||||||
// Convert stored native unit back to cm for display
|
// Convert stored native unit back to cm for display
|
||||||
if ($usesLengthUnit && $unit !== null) {
|
if ($usesLengthUnit && $unit !== null) {
|
||||||
$materialUsageCm = round($unit->toCm((float) $item->material_usage), 2);
|
$materialUsageCm = round($unit->toCm((float) $item->material_usage), 4);
|
||||||
$remainingMaterialCm = round($unit->toCm((float) $item->remaining_material), 2);
|
$remainingMaterialCm = round($unit->toCm((float) $item->remaining_material), 4);
|
||||||
$materialUsageDisplay = (string) $materialUsageCm;
|
$materialUsageDisplay = rtrim(rtrim(number_format($materialUsageCm, 4, '.', ''), '0'), '.');
|
||||||
$remainingMaterialDisplay = (string) $remainingMaterialCm;
|
$remainingMaterialDisplay = rtrim(rtrim(number_format($remainingMaterialCm, 4, '.', ''), '0'), '.');
|
||||||
} else {
|
} else {
|
||||||
$materialUsageDisplay = $this->formatQuantityInput((float) $item->material_usage);
|
$materialUsageDisplay = $this->formatQuantityInput((float) $item->material_usage);
|
||||||
$remainingMaterialDisplay = $this->formatQuantityInput((float) $item->remaining_material);
|
$remainingMaterialDisplay = $this->formatQuantityInput((float) $item->remaining_material);
|
||||||
@ -942,7 +942,7 @@ private function presentDraftResult(CuttingResult $item): array
|
|||||||
|
|
||||||
private function formatQuantityInput(float $value): string
|
private function formatQuantityInput(float $value): string
|
||||||
{
|
{
|
||||||
return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.');
|
return rtrim(rtrim(number_format($value, 4, '.', ''), '0'), '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function appendCostPreview(Cutting $cutting): void
|
private function appendCostPreview(Cutting $cutting): void
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('cutting_materials', function (Blueprint $table): void {
|
||||||
|
$table->decimal('material_usage', 18, 4)->change();
|
||||||
|
$table->decimal('remaining_material', 18, 4)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('cutting_materials', function (Blueprint $table): void {
|
||||||
|
$table->decimal('material_usage', 18, 2)->change();
|
||||||
|
$table->decimal('remaining_material', 18, 2)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -36,7 +36,7 @@ return '';
|
|||||||
let decimalPart = parts[1];
|
let decimalPart = parts[1];
|
||||||
|
|
||||||
if (decimalPart !== undefined) {
|
if (decimalPart !== undefined) {
|
||||||
decimalPart = decimalPart.slice(0, 2);
|
decimalPart = decimalPart.slice(0, 4);
|
||||||
|
|
||||||
return `${integerPart},${decimalPart}`;
|
return `${integerPart},${decimalPart}`;
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ function parseDecimal(value: string): string {
|
|||||||
const cleanParts = clean.split('.');
|
const cleanParts = clean.split('.');
|
||||||
|
|
||||||
if (cleanParts[1] !== undefined) {
|
if (cleanParts[1] !== undefined) {
|
||||||
clean = `${cleanParts[0]}.${cleanParts[1].slice(0, 2)}`;
|
clean = `${cleanParts[0]}.${cleanParts[1].slice(0, 4)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return clean;
|
return clean;
|
||||||
|
|||||||
@ -3,11 +3,8 @@ import { useForm } from '@inertiajs/vue3';
|
|||||||
import { Minus, Plus, Save, Scissors, Search, Trash2 } from '@lucide/vue';
|
import { Minus, Plus, Save, Scissors, Search, Trash2 } from '@lucide/vue';
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import { toast } from 'vue-sonner';
|
import { toast } from 'vue-sonner';
|
||||||
import PosCatalogCard from '../../shared/PosCatalogCard.vue';
|
|
||||||
import PosCatalogVariantThumb from '../../shared/PosCatalogVariantThumb.vue';
|
|
||||||
import { DecimalInput } from '@/components/form/decimal-input';
|
import { DecimalInput } from '@/components/form/decimal-input';
|
||||||
import { NumberInput } from '@/components/form/number-input';
|
import { NumberInput } from '@/components/form/number-input';
|
||||||
import { RupiahInput } from '@/components/form/rupiah-input';
|
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
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';
|
||||||
@ -45,6 +42,8 @@ import type {
|
|||||||
CuttingRawMaterialCatalogItem,
|
CuttingRawMaterialCatalogItem,
|
||||||
CuttingResultCartItem,
|
CuttingResultCartItem,
|
||||||
} from '@/types/cutting';
|
} from '@/types/cutting';
|
||||||
|
import PosCatalogCard from '../../shared/PosCatalogCard.vue';
|
||||||
|
import PosCatalogVariantThumb from '../../shared/PosCatalogVariantThumb.vue';
|
||||||
|
|
||||||
function usesLengthUnit(unit: string): boolean {
|
function usesLengthUnit(unit: string): boolean {
|
||||||
return unit === 'yard' || unit === 'meter';
|
return unit === 'yard' || unit === 'meter';
|
||||||
@ -167,7 +166,7 @@ const estimatedCostPerUnit = computed(() => {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Math.round(totalProductionCost.value / totalResultPieces.value);
|
return Math.round(totalMaterialCost.value / totalResultPieces.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
function populateForm() {
|
function populateForm() {
|
||||||
@ -314,7 +313,8 @@ async function decreaseMaterialQty(priceId: number) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextQty = (Number(item.material_usage) || 0) - 1;
|
const decrement = item.uses_length_unit ? 100 : 1;
|
||||||
|
const nextQty = (Number(item.material_usage) || 0) - decrement;
|
||||||
|
|
||||||
if (nextQty <= 0) {
|
if (nextQty <= 0) {
|
||||||
const index = materialCart.value.findIndex(
|
const index = materialCart.value.findIndex(
|
||||||
@ -355,8 +355,9 @@ async function addMaterial(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const defaultUsage = usesLengthUnit(rawMaterial.unit) ? '100' : '1';
|
const defaultUsage = usesLengthUnit(rawMaterial.unit) ? '100' : '1';
|
||||||
|
const increment = usesLengthUnit(rawMaterial.unit) ? 100 : 1;
|
||||||
const nextUsage = existing
|
const nextUsage = existing
|
||||||
? String((Number(existing.material_usage) || 0) + 1)
|
? String((Number(existing.material_usage) || 0) + increment)
|
||||||
: defaultUsage;
|
: defaultUsage;
|
||||||
const remaining = existing?.remaining_material ?? '0';
|
const remaining = existing?.remaining_material ?? '0';
|
||||||
|
|
||||||
@ -1002,7 +1003,7 @@ function submit() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 gap-2">
|
<div class="grid grid-cols-2 gap-2">
|
||||||
<Field>
|
<Field>
|
||||||
<FieldLabel class="text-xs">
|
<FieldLabel class="text-xs">
|
||||||
Pemakaian ({{ item.uses_length_unit ? 'cm' : item.unit_abbreviation }})
|
Pemakaian ({{ item.uses_length_unit ? 'cm' : item.unit_abbreviation }})
|
||||||
@ -1035,16 +1036,6 @@ function submit() {
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</Field>
|
</Field>
|
||||||
<Field>
|
|
||||||
<FieldLabel class="text-xs">
|
|
||||||
Harga Modal
|
|
||||||
</FieldLabel>
|
|
||||||
<div
|
|
||||||
class="flex h-8 items-center rounded-md border bg-muted/40 px-2 text-xs font-medium tabular-nums"
|
|
||||||
>
|
|
||||||
{{ formatRupiah(materialLineCost(item)) }}
|
|
||||||
</div>
|
|
||||||
</Field>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1151,41 +1142,6 @@ function submit() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div class="grid gap-3 sm:grid-cols-2">
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="sewing_cost">Jasa Jahit</FieldLabel>
|
|
||||||
<RupiahInput
|
|
||||||
id="sewing_cost"
|
|
||||||
v-model="form.sewing_cost"
|
|
||||||
/>
|
|
||||||
<FieldError
|
|
||||||
:errors="formErrors(form, 'sewing_cost')"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="other_cost">Biaya Lainnya</FieldLabel>
|
|
||||||
<RupiahInput
|
|
||||||
id="other_cost"
|
|
||||||
v-model="form.other_cost"
|
|
||||||
/>
|
|
||||||
<FieldError
|
|
||||||
:errors="formErrors(form, 'other_cost')"
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="outline"
|
|
||||||
class="w-full"
|
|
||||||
:disabled="materialCart.length === 0"
|
|
||||||
@click="costModalOpen = true"
|
|
||||||
>
|
|
||||||
Lihat Harga Modal
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@ -1238,17 +1194,9 @@ function submit() {
|
|||||||
{{ formatRupiah(materialLineCost(item)) }}
|
{{ formatRupiah(materialLineCost(item)) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between gap-3 border-t pt-2">
|
|
||||||
<span class="text-muted-foreground">Jasa Jahit</span>
|
|
||||||
<span class="font-medium tabular-nums">{{ formatRupiah(sewingCostAmount) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between gap-3">
|
|
||||||
<span class="text-muted-foreground">Biaya Lainnya</span>
|
|
||||||
<span class="font-medium tabular-nums">{{ formatRupiah(otherCostAmount) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between gap-3 border-t pt-2 font-semibold">
|
<div class="flex items-center justify-between gap-3 border-t pt-2 font-semibold">
|
||||||
<span>Total Harga Modal</span>
|
<span>Total Harga Modal</span>
|
||||||
<span class="tabular-nums">{{ formatRupiah(totalProductionCost) }}</span>
|
<span class="tabular-nums">{{ formatRupiah(totalMaterialCost) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -1345,7 +1293,7 @@ function submit() {
|
|||||||
<div class="border-t pt-3">
|
<div class="border-t pt-3">
|
||||||
<div class="flex items-center justify-between text-sm font-semibold">
|
<div class="flex items-center justify-between text-sm font-semibold">
|
||||||
<span>Total Harga Modal</span>
|
<span>Total Harga Modal</span>
|
||||||
<span class="text-primary">{{ formatRupiah(totalProductionCost) }}</span>
|
<span class="text-primary">{{ formatRupiah(totalMaterialCost) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -15,6 +15,37 @@ import {
|
|||||||
EmptyHeader,
|
EmptyHeader,
|
||||||
EmptyTitle,
|
EmptyTitle,
|
||||||
} from '@/components/ui/empty';
|
} from '@/components/ui/empty';
|
||||||
|
|
||||||
|
const CM_PER_YARD = 91.44;
|
||||||
|
const CM_PER_METER = 100;
|
||||||
|
|
||||||
|
function formatTotalMaterialUsage(totalUsage: number | null | undefined, materials: any[]): string {
|
||||||
|
if (!totalUsage || !materials.length) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
const unit = materials[0]?.raw_material_price?.raw_material?.unit;
|
||||||
|
|
||||||
|
if (unit === 'yard') {
|
||||||
|
return (totalUsage * CM_PER_YARD).toFixed(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unit === 'meter') {
|
||||||
|
return (totalUsage * CM_PER_METER).toFixed(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalUsage.toFixed(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotalMaterialUsageUnit(materials: any[]): string {
|
||||||
|
const unit = materials[0]?.raw_material_price?.raw_material?.unit;
|
||||||
|
|
||||||
|
if (unit === 'yard' || unit === 'meter') {
|
||||||
|
return 'cm';
|
||||||
|
}
|
||||||
|
|
||||||
|
return materials[0]?.raw_material_price?.raw_material?.unit_abbreviation ?? '';
|
||||||
|
}
|
||||||
import type { CuttingListItem } from '@/types/cutting';
|
import type { CuttingListItem } from '@/types/cutting';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -100,11 +131,7 @@ const totalCompleted = computed(() => props.cuttings.length);
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between text-[11px]">
|
<div class="flex items-center justify-between text-[11px]">
|
||||||
<span class="text-muted-foreground">Total Pemakaian Bahan:</span>
|
<span class="text-muted-foreground">Total Pemakaian Bahan:</span>
|
||||||
<span class="font-semibold tabular-nums">{{ cutting.total_material_usage ?? 0 }}</span>
|
<span class="font-semibold tabular-nums">{{ formatTotalMaterialUsage(cutting.total_material_usage, cutting.materials) }} {{ getTotalMaterialUsageUnit(cutting.materials) }}</span>
|
||||||
</div>
|
|
||||||
<div v-if="cutting.estimated_cost_per_unit_formatted" class="flex items-center justify-between text-[11px]">
|
|
||||||
<span class="text-muted-foreground">Harga Modal:</span>
|
|
||||||
<span class="font-semibold">{{ cutting.estimated_cost_per_unit_formatted }}/pcs</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -207,11 +207,6 @@ function getTotalMaterialUsageUnit(materials: any[]): string {
|
|||||||
<span>Total Pemakaian Bahan <strong class="text-primary">{{
|
<span>Total Pemakaian Bahan <strong class="text-primary">{{
|
||||||
formatTotalMaterialUsage(cutting.total_material_usage, cutting.materials) }}
|
formatTotalMaterialUsage(cutting.total_material_usage, cutting.materials) }}
|
||||||
{{ getTotalMaterialUsageUnit(cutting.materials) }}</strong></span>
|
{{ getTotalMaterialUsageUnit(cutting.materials) }}</strong></span>
|
||||||
<span>Total Biaya Produksi <strong class="text-primary">{{
|
|
||||||
cutting.total_production_cost_formatted ?? 0 }}</strong></span>
|
|
||||||
<span>Harga Modal <strong class="text-primary">{{
|
|
||||||
cutting.estimated_cost_per_unit_formatted ??
|
|
||||||
0 }}</strong></span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -15,6 +15,37 @@ import {
|
|||||||
EmptyHeader,
|
EmptyHeader,
|
||||||
EmptyTitle,
|
EmptyTitle,
|
||||||
} from '@/components/ui/empty';
|
} from '@/components/ui/empty';
|
||||||
|
|
||||||
|
const CM_PER_YARD = 91.44;
|
||||||
|
const CM_PER_METER = 100;
|
||||||
|
|
||||||
|
function formatTotalMaterialUsage(totalUsage: number | null | undefined, materials: any[]): string {
|
||||||
|
if (!totalUsage || !materials.length) {
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
const unit = materials[0]?.raw_material_price?.raw_material?.unit;
|
||||||
|
|
||||||
|
if (unit === 'yard') {
|
||||||
|
return (totalUsage * CM_PER_YARD).toFixed(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unit === 'meter') {
|
||||||
|
return (totalUsage * CM_PER_METER).toFixed(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalUsage.toFixed(4);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTotalMaterialUsageUnit(materials: any[]): string {
|
||||||
|
const unit = materials[0]?.raw_material_price?.raw_material?.unit;
|
||||||
|
|
||||||
|
if (unit === 'yard' || unit === 'meter') {
|
||||||
|
return 'cm';
|
||||||
|
}
|
||||||
|
|
||||||
|
return materials[0]?.raw_material_price?.raw_material?.unit_abbreviation ?? '';
|
||||||
|
}
|
||||||
import type { CuttingListItem } from '@/types/cutting';
|
import type { CuttingListItem } from '@/types/cutting';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -100,7 +131,7 @@ const totalInProgress = computed(() => props.cuttings.length);
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between text-[11px]">
|
<div class="flex items-center justify-between text-[11px]">
|
||||||
<span class="text-muted-foreground">Total Pemakaian Bahan:</span>
|
<span class="text-muted-foreground">Total Pemakaian Bahan:</span>
|
||||||
<span class="font-semibold tabular-nums">{{ cutting.total_material_usage ?? 0 }}</span>
|
<span class="font-semibold tabular-nums">{{ formatTotalMaterialUsage(cutting.total_material_usage, cutting.materials) }} {{ getTotalMaterialUsageUnit(cutting.materials) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -477,36 +477,6 @@ function actionIcon(status: string) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="cutting.estimated_cost_per_unit_formatted"
|
|
||||||
class="rounded-lg border bg-muted/40 p-3 text-sm space-y-1"
|
|
||||||
>
|
|
||||||
<p class="font-medium">Harga Modal Batch</p>
|
|
||||||
<p class="flex justify-between gap-2 text-muted-foreground">
|
|
||||||
<span>Bahan baku</span>
|
|
||||||
<span>{{ cutting.total_material_cost_formatted ?? '-' }}</span>
|
|
||||||
</p>
|
|
||||||
<p class="flex justify-between gap-2 text-muted-foreground">
|
|
||||||
<span>Jasa jahit</span>
|
|
||||||
<span>{{ cutting.sewing_cost_formatted ?? '-' }}</span>
|
|
||||||
</p>
|
|
||||||
<p class="flex justify-between gap-2 text-muted-foreground">
|
|
||||||
<span>Biaya lainnya</span>
|
|
||||||
<span>{{ cutting.other_cost_formatted ?? '-' }}</span>
|
|
||||||
</p>
|
|
||||||
<p class="flex justify-between gap-2 font-medium">
|
|
||||||
<span>Total</span>
|
|
||||||
<span>{{ cutting.total_production_cost_formatted ?? '-' }}</span>
|
|
||||||
</p>
|
|
||||||
<p class="flex justify-between gap-2 text-muted-foreground">
|
|
||||||
<span>Total hasil</span>
|
|
||||||
<span>{{ cutting.total_result_pieces ?? 0 }} pcs</span>
|
|
||||||
</p>
|
|
||||||
<p class="font-semibold tabular-nums pt-1 border-t">
|
|
||||||
{{ cutting.estimated_cost_per_unit_formatted }} / pcs
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div v-for="(result, index) in verifyForm.results" :key="result.product_variant_id" class="p-3 border rounded-lg space-y-3">
|
<div v-for="(result, index) in verifyForm.results" :key="result.product_variant_id" class="p-3 border rounded-lg space-y-3">
|
||||||
<div class="font-medium text-sm">
|
<div class="font-medium text-sm">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user