feat: make cutting results nullable and update UI to support create mode
This commit is contained in:
parent
9f0676c923
commit
4466c61352
@ -24,9 +24,9 @@ public function rules(): array
|
|||||||
'integer',
|
'integer',
|
||||||
Rule::exists('product_variants', 'id')->whereNull('deleted_at'),
|
Rule::exists('product_variants', 'id')->whereNull('deleted_at'),
|
||||||
],
|
],
|
||||||
'cutting_result' => ['required', 'integer', 'min:1'],
|
'cutting_result' => ['nullable', 'integer', 'min:1'],
|
||||||
'sample' => ['required', 'integer', 'min:0'],
|
'sample' => ['nullable', 'integer', 'min:0'],
|
||||||
'original_outside_sample' => ['required', 'integer', 'min:0'],
|
'original_outside_sample' => ['nullable', 'integer', 'min:0'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -311,32 +311,40 @@ public function syncDraftResult(array $validated, User $user): array
|
|||||||
->with('product:id,name')
|
->with('product:id,name')
|
||||||
->findOrFail($validated['product_variant_id']);
|
->findOrFail($validated['product_variant_id']);
|
||||||
|
|
||||||
$cuttingResult = (int) $validated['cutting_result'];
|
$cuttingResult = array_key_exists('cutting_result', $validated) && $validated['cutting_result'] !== null
|
||||||
$sample = (int) $validated['sample'];
|
? (int) $validated['cutting_result']
|
||||||
$originalOutsideSample = (int) ($validated['original_outside_sample'] ?? 0);
|
: null;
|
||||||
|
$sample = array_key_exists('sample', $validated) && $validated['sample'] !== null
|
||||||
|
? (int) $validated['sample']
|
||||||
|
: null;
|
||||||
|
$originalOutsideSample = array_key_exists('original_outside_sample', $validated) && $validated['original_outside_sample'] !== null
|
||||||
|
? (int) $validated['original_outside_sample']
|
||||||
|
: null;
|
||||||
|
|
||||||
if ($cuttingResult < 1) {
|
if ($cuttingResult !== null) {
|
||||||
throw ValidationException::withMessages([
|
if ($cuttingResult < 1) {
|
||||||
'cutting_result' => 'Hasil cutting minimal 1 pcs.',
|
throw ValidationException::withMessages([
|
||||||
]);
|
'cutting_result' => 'Hasil cutting minimal 1 pcs.',
|
||||||
}
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
if ($sample < 0) {
|
if ($sample !== null && $sample < 0) {
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
'sample' => 'sample tidak boleh negatif.',
|
'sample' => 'sample tidak boleh negatif.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($originalOutsideSample < 0) {
|
if ($originalOutsideSample !== null && $originalOutsideSample < 0) {
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
'original_outside_sample' => 'Hasil cutting diluar sample tidak boleh negatif.',
|
'original_outside_sample' => 'Hasil cutting diluar sample tidak boleh negatif.',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($sample + $originalOutsideSample) !== $cuttingResult) {
|
if ($sample !== null && $originalOutsideSample !== null && ($sample + $originalOutsideSample) !== $cuttingResult) {
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
'cutting_result' => 'Hasil cutting harus sama dengan sample ditambah hasil cutting diluar sample.',
|
'cutting_result' => 'Hasil cutting harus sama dengan sample ditambah hasil cutting diluar sample.',
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$item = CuttingResult::query()->updateOrCreate(
|
$item = CuttingResult::query()->updateOrCreate(
|
||||||
@ -909,9 +917,9 @@ private function presentDraftResult(CuttingResult $item): array
|
|||||||
'product_name' => $variant?->product?->name ?? '',
|
'product_name' => $variant?->product?->name ?? '',
|
||||||
'variant_name' => $variant?->name ?? '',
|
'variant_name' => $variant?->name ?? '',
|
||||||
'stock' => $variant?->stock ?? 0,
|
'stock' => $variant?->stock ?? 0,
|
||||||
'cutting_result' => (string) $item->cutting_result,
|
'cutting_result' => $item->cutting_result !== null ? (string) $item->cutting_result : null,
|
||||||
'sample' => (string) $item->sample,
|
'sample' => $item->sample !== null ? (string) $item->sample : null,
|
||||||
'original_outside_sample' => (string) $item->original_outside_sample,
|
'original_outside_sample' => $item->original_outside_sample !== null ? (string) $item->original_outside_sample : null,
|
||||||
'images' => $variant ? MediaPresenter::collection($variant, 'images') : [],
|
'images' => $variant ? MediaPresenter::collection($variant, 'images') : [],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
<?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_results', function (Blueprint $table) {
|
||||||
|
$table->unsignedInteger('cutting_result')->nullable()->change();
|
||||||
|
$table->unsignedInteger('sample')->nullable()->change();
|
||||||
|
$table->unsignedInteger('original_outside_sample')->nullable()->default(null)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('cutting_results', function (Blueprint $table) {
|
||||||
|
$table->unsignedInteger('cutting_result')->nullable(false)->change();
|
||||||
|
$table->unsignedInteger('sample')->nullable(false)->change();
|
||||||
|
$table->unsignedInteger('original_outside_sample')->nullable(false)->default(0)->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -36,9 +36,9 @@ const initialData = computed(() => ({
|
|||||||
product_name: item.product_variant?.product?.name ?? '',
|
product_name: item.product_variant?.product?.name ?? '',
|
||||||
variant_name: item.product_variant?.name ?? '',
|
variant_name: item.product_variant?.name ?? '',
|
||||||
stock: item.product_variant?.stock ?? 0,
|
stock: item.product_variant?.stock ?? 0,
|
||||||
cutting_result: String(item.cutting_result),
|
cutting_result: String(item.cutting_result ?? 0),
|
||||||
sample: String(item.sample),
|
sample: String(item.sample ?? 0),
|
||||||
original_outside_sample: String(item.original_outside_sample),
|
original_outside_sample: String(item.original_outside_sample ?? 0),
|
||||||
images: item.product_variant?.images ?? [],
|
images: item.product_variant?.images ?? [],
|
||||||
})),
|
})),
|
||||||
}));
|
}));
|
||||||
|
|||||||
@ -140,7 +140,7 @@ const groupedResults = computed<GroupedResults[]>(() => {
|
|||||||
{{ material.variant }}
|
{{ material.variant }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<MediaThumbnailCell :items="material.images ?? []" />
|
<MediaThumbnailCell :items="material.images ?? []" :max-visible="1" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right tabular-nums">
|
<TableCell class="text-right tabular-nums">
|
||||||
{{ material.material_usage_formatted }}
|
{{ material.material_usage_formatted }}
|
||||||
@ -178,7 +178,7 @@ const groupedResults = computed<GroupedResults[]>(() => {
|
|||||||
{{ result.product_variant?.name }}
|
{{ result.product_variant?.name }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<MediaThumbnailCell :items="result.product_variant?.images ?? []" />
|
<MediaThumbnailCell :items="result.product_variant?.images ?? []" :max-visible="1" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="text-right tabular-nums">
|
<TableCell class="text-right tabular-nums">
|
||||||
{{ result.cutting_result }} pcs
|
{{ result.cutting_result }} pcs
|
||||||
|
|||||||
@ -13,6 +13,7 @@ defineProps<{
|
|||||||
resultCart: CuttingResultCartItem[];
|
resultCart: CuttingResultCartItem[];
|
||||||
materialLineCost: (item: CuttingMaterialCartItem) => number;
|
materialLineCost: (item: CuttingMaterialCartItem) => number;
|
||||||
totalMaterialCost: number;
|
totalMaterialCost: number;
|
||||||
|
isCreateMode: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const open = defineModel<boolean>('open', { required: true });
|
const open = defineModel<boolean>('open', { required: true });
|
||||||
@ -61,7 +62,7 @@ const open = defineModel<boolean>('open', { required: true });
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1.5 flex items-center gap-3 text-xs tabular-nums">
|
<div v-if="!isCreateMode" class="mt-1.5 flex items-center gap-3 text-xs tabular-nums">
|
||||||
<span>Hasil: <strong>{{ item.cutting_result }}</strong></span>
|
<span>Hasil: <strong>{{ item.cutting_result }}</strong></span>
|
||||||
<span class="text-muted-foreground">·</span>
|
<span class="text-muted-foreground">·</span>
|
||||||
<span>Sample: <strong>{{ item.sample }}</strong></span>
|
<span>Sample: <strong>{{ item.sample }}</strong></span>
|
||||||
|
|||||||
@ -153,16 +153,19 @@ function submit() {
|
|||||||
|
|
||||||
<CuttingPosResultCatalogPanel v-model:product-search="productSearch" :filtered-products="filteredProducts"
|
<CuttingPosResultCatalogPanel v-model:product-search="productSearch" :filtered-products="filteredProducts"
|
||||||
:get-result-cart-item="getResultCartItem" @add-result="addResult"
|
:get-result-cart-item="getResultCartItem" @add-result="addResult"
|
||||||
|
:is-create-mode="isCreateMode"
|
||||||
@decrease-result-qty="decreaseResultQty" />
|
@decrease-result-qty="decreaseResultQty" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CuttingPosSummaryPanel :form="form" :material-cart="materialCart" :result-cart="resultCart"
|
<CuttingPosSummaryPanel :form="form" :material-cart="materialCart" :result-cart="resultCart"
|
||||||
:total-result-pieces="totalResultPieces" :submit-label="submitLabel" @submit="submit"
|
:total-result-pieces="totalResultPieces" :submit-label="submitLabel" @submit="submit"
|
||||||
|
:is-create-mode="isCreateMode"
|
||||||
@open-detail="cartDetailOpen = true" @remove-material="removeMaterial"
|
@open-detail="cartDetailOpen = true" @remove-material="removeMaterial"
|
||||||
@sync-material-field="syncMaterialField" @remove-result="removeResult"
|
@sync-material-field="syncMaterialField" @remove-result="removeResult"
|
||||||
@sync-result-totals="syncResultTotals" @sync-result-field="syncResultField" />
|
@sync-result-totals="syncResultTotals" @sync-result-field="syncResultField" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CuttingPosCartDetailDialog v-model:open="cartDetailOpen" :material-cart="materialCart" :result-cart="resultCart"
|
<CuttingPosCartDetailDialog v-model:open="cartDetailOpen" :material-cart="materialCart" :result-cart="resultCart"
|
||||||
:material-line-cost="materialLineCost" :total-material-cost="totalMaterialCost" />
|
:material-line-cost="materialLineCost" :total-material-cost="totalMaterialCost"
|
||||||
|
:is-create-mode="isCreateMode" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Minus, Plus, Search } from '@lucide/vue';
|
import { Check, Minus, Plus, Search } from '@lucide/vue';
|
||||||
import PosCatalogCard from '@/components/catalog/PosCatalogCard.vue';
|
import PosCatalogCard from '@/components/catalog/PosCatalogCard.vue';
|
||||||
import PosCatalogVariantThumb from '@/components/catalog/PosCatalogVariantThumb.vue';
|
import PosCatalogVariantThumb from '@/components/catalog/PosCatalogVariantThumb.vue';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -18,6 +18,7 @@ import type { CuttingCatalogVariant } from './useCuttingPosCart';
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
filteredProducts: CuttingProductCatalogItem[];
|
filteredProducts: CuttingProductCatalogItem[];
|
||||||
getResultCartItem: (variantId: number) => CuttingResultCartItem | undefined;
|
getResultCartItem: (variantId: number) => CuttingResultCartItem | undefined;
|
||||||
|
isCreateMode: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const productSearch = defineModel<string>('productSearch', { required: true });
|
const productSearch = defineModel<string>('productSearch', { required: true });
|
||||||
@ -83,25 +84,41 @@ const emit = defineEmits<{
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="getResultCartItem(variant.id)" class="flex shrink-0 items-center gap-1.5">
|
<div v-if="getResultCartItem(variant.id)" class="flex shrink-0 items-center gap-1.5">
|
||||||
<Button
|
<template v-if="isCreateMode">
|
||||||
type="button"
|
<span class="text-primary mr-1">
|
||||||
variant="outline"
|
<Check class="size-4" />
|
||||||
size="icon-sm"
|
</span>
|
||||||
@click.stop="emit('decrease-result-qty', variant.id)"
|
<Button
|
||||||
>
|
type="button"
|
||||||
<Minus class="size-3.5" />
|
variant="outline"
|
||||||
</Button>
|
size="icon-sm"
|
||||||
<span class="min-w-[1.25rem] text-center text-xs font-semibold tabular-nums">
|
class="hover:text-destructive hover:bg-destructive/10"
|
||||||
{{ getResultCartItem(variant.id)!.cutting_result }}
|
@click.stop="emit('decrease-result-qty', variant.id)"
|
||||||
</span>
|
>
|
||||||
<Button
|
<Minus class="size-3.5" />
|
||||||
type="button"
|
</Button>
|
||||||
variant="outline"
|
</template>
|
||||||
size="icon-sm"
|
<template v-else>
|
||||||
@click.stop="emit('add-result', product, variant)"
|
<Button
|
||||||
>
|
type="button"
|
||||||
<Plus class="size-3.5" />
|
variant="outline"
|
||||||
</Button>
|
size="icon-sm"
|
||||||
|
@click.stop="emit('decrease-result-qty', variant.id)"
|
||||||
|
>
|
||||||
|
<Minus class="size-3.5" />
|
||||||
|
</Button>
|
||||||
|
<span class="min-w-[1.25rem] text-center text-xs font-semibold tabular-nums">
|
||||||
|
{{ getResultCartItem(variant.id)!.cutting_result }}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="icon-sm"
|
||||||
|
@click.stop="emit('add-result', product, variant)"
|
||||||
|
>
|
||||||
|
<Plus class="size-3.5" />
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
v-else
|
v-else
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import type { CuttingResultCartItem } from '@/types/cutting';
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
resultCart: CuttingResultCartItem[];
|
resultCart: CuttingResultCartItem[];
|
||||||
totalResultPieces: number;
|
totalResultPieces: number;
|
||||||
|
isCreateMode: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -25,7 +26,7 @@ const emit = defineEmits<{
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<p class="text-sm font-medium">Hasil Produk</p>
|
<p class="text-sm font-medium">Hasil Produk</p>
|
||||||
<Badge v-if="totalResultPieces > 0" variant="outline" class="tabular-nums text-xs">
|
<Badge v-if="!isCreateMode && totalResultPieces > 0" variant="outline" class="tabular-nums text-xs">
|
||||||
{{ totalResultPieces }} pcs
|
{{ totalResultPieces }} pcs
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
@ -52,7 +53,7 @@ const emit = defineEmits<{
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 gap-2">
|
<div v-if="!isCreateMode" class="grid grid-cols-3 gap-2">
|
||||||
<Field>
|
<Field>
|
||||||
<FieldLabel class="text-xs">Hasil</FieldLabel>
|
<FieldLabel class="text-xs">Hasil</FieldLabel>
|
||||||
<NumberInput v-model="item.cutting_result" class="h-8"
|
<NumberInput v-model="item.cutting_result" class="h-8"
|
||||||
|
|||||||
@ -24,6 +24,7 @@ defineProps<{
|
|||||||
resultCart: CuttingResultCartItem[];
|
resultCart: CuttingResultCartItem[];
|
||||||
totalResultPieces: number;
|
totalResultPieces: number;
|
||||||
submitLabel: string;
|
submitLabel: string;
|
||||||
|
isCreateMode: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -54,7 +55,7 @@ const emit = defineEmits<{
|
|||||||
>
|
>
|
||||||
Lihat Detail
|
Lihat Detail
|
||||||
</button>
|
</button>
|
||||||
<Badge v-if="totalResultPieces > 0" variant="secondary" class="tabular-nums font-semibold">
|
<Badge v-if="!isCreateMode && totalResultPieces > 0" variant="secondary" class="tabular-nums font-semibold">
|
||||||
Total: {{ totalResultPieces }} pcs
|
Total: {{ totalResultPieces }} pcs
|
||||||
</Badge>
|
</Badge>
|
||||||
</span>
|
</span>
|
||||||
@ -87,6 +88,7 @@ const emit = defineEmits<{
|
|||||||
<CuttingPosResultSummaryItems
|
<CuttingPosResultSummaryItems
|
||||||
:result-cart="resultCart"
|
:result-cart="resultCart"
|
||||||
:total-result-pieces="totalResultPieces"
|
:total-result-pieces="totalResultPieces"
|
||||||
|
:is-create-mode="isCreateMode"
|
||||||
@remove="emit('remove-result', $event)"
|
@remove="emit('remove-result', $event)"
|
||||||
@sync-totals="emit('sync-result-totals', $event)"
|
@sync-totals="emit('sync-result-totals', $event)"
|
||||||
@sync-field="emit('sync-result-field', $event)"
|
@sync-field="emit('sync-result-field', $event)"
|
||||||
|
|||||||
@ -177,9 +177,9 @@ export function useCuttingPosCart(options: {
|
|||||||
async function syncDraftResult(
|
async function syncDraftResult(
|
||||||
product: CuttingProductCatalogItem,
|
product: CuttingProductCatalogItem,
|
||||||
variant: CuttingCatalogVariant,
|
variant: CuttingCatalogVariant,
|
||||||
cuttingResult: string,
|
cuttingResult: string | null,
|
||||||
sample: string,
|
sample: string | null,
|
||||||
originalOutsideSample: string,
|
originalOutsideSample: string | null,
|
||||||
) {
|
) {
|
||||||
const { item } = await apiFetch<{ item: CuttingResultCartItem }>(draft_results.store.url(), {
|
const { item } = await apiFetch<{ item: CuttingResultCartItem }>(draft_results.store.url(), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -196,9 +196,9 @@ export function useCuttingPosCart(options: {
|
|||||||
|
|
||||||
async function syncDraftResultById(
|
async function syncDraftResultById(
|
||||||
variantId: number,
|
variantId: number,
|
||||||
cuttingResult: string,
|
cuttingResult: string | null,
|
||||||
sample: string,
|
sample: string | null,
|
||||||
originalOutsideSample: string,
|
originalOutsideSample: string | null,
|
||||||
) {
|
) {
|
||||||
const { item } = await apiFetch<{ item: CuttingResultCartItem }>(draft_results.store.url(), {
|
const { item } = await apiFetch<{ item: CuttingResultCartItem }>(draft_results.store.url(), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -388,9 +388,9 @@ export function useCuttingPosCart(options: {
|
|||||||
await syncDraftResult(
|
await syncDraftResult(
|
||||||
product,
|
product,
|
||||||
variant,
|
variant,
|
||||||
String(nextQty),
|
null,
|
||||||
String(nextsample),
|
null,
|
||||||
String(nextoriginalOutsideSample),
|
null,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(error instanceof Error ? error.message : 'Gagal menyimpan item ke keranjang.');
|
toast.error(error instanceof Error ? error.message : 'Gagal menyimpan item ke keranjang.');
|
||||||
|
|||||||
@ -111,7 +111,7 @@ const totalCompleted = computed(() => props.cuttings.length);
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="cutting.results.length" class="space-y-1.5 pt-2 border-t">
|
<div v-if="cutting.results.length" class="space-y-1.5 pt-2 border-t">
|
||||||
<div class="flex items-center justify-between text-[11px]">
|
<div v-if="cutting.results.some(res => res.cutting_result !== null && res.cutting_result !== undefined)" class="flex items-center justify-between text-[11px]">
|
||||||
<span class="text-muted-foreground">Total Hasil Cutting:</span>
|
<span class="text-muted-foreground">Total Hasil Cutting:</span>
|
||||||
<span class="font-semibold tabular-nums">{{ cutting.total_result_pieces ?? 0 }} pcs</span>
|
<span class="font-semibold tabular-nums">{{ cutting.total_result_pieces ?? 0 }} pcs</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -182,7 +182,7 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
|
|||||||
{{ material.variant }}
|
{{ material.variant }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<MediaThumbnailCell :items="material.images ?? []" />
|
<MediaThumbnailCell :items="material.images ?? []" :max-visible="1" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="tabular-nums">
|
<TableCell class="tabular-nums">
|
||||||
{{ material.material_usage_formatted }}
|
{{ material.material_usage_formatted }}
|
||||||
@ -225,7 +225,7 @@ function getGroupedResults(results: any[]): GroupedCuttingResults[] {
|
|||||||
{{ result.product_variant?.name }}
|
{{ result.product_variant?.name }}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<MediaThumbnailCell :items="result.product_variant?.images ?? []" />
|
<MediaThumbnailCell :items="result.product_variant?.images ?? []" :max-visible="1" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell class="tabular-nums">
|
<TableCell class="tabular-nums">
|
||||||
{{ result.cutting_result }} pcs
|
{{ result.cutting_result }} pcs
|
||||||
|
|||||||
@ -77,15 +77,14 @@ defineProps<{
|
|||||||
<span class="font-medium text-foreground">Hasil Produk:</span>
|
<span class="font-medium text-foreground">Hasil Produk:</span>
|
||||||
<ul class="list-disc pl-4 space-y-0.5 text-muted-foreground">
|
<ul class="list-disc pl-4 space-y-0.5 text-muted-foreground">
|
||||||
<li v-for="res in cutting.results" :key="res.id">
|
<li v-for="res in cutting.results" :key="res.id">
|
||||||
{{ res.product_variant?.product?.name }} ({{ res.product_variant?.name }}) -
|
{{ res.product_variant?.product?.name }} ({{ res.product_variant?.name }})
|
||||||
{{ res.cutting_result }} pcs
|
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!cutting.results.length">Belum ada hasil produk</li>
|
<li v-if="!cutting.results.length">Belum ada hasil produk</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="cutting.results.length" class="space-y-1.5 pt-2 border-t">
|
<div v-if="cutting.results.length" class="space-y-1.5 pt-2 border-t">
|
||||||
<div class="flex items-center justify-between text-[11px]">
|
<div v-if="cutting.results.some(res => res.cutting_result !== null && res.cutting_result !== undefined)" class="flex items-center justify-between text-[11px]">
|
||||||
<span class="text-muted-foreground">Total Hasil Cutting:</span>
|
<span class="text-muted-foreground">Total Hasil Cutting:</span>
|
||||||
<span class="font-semibold tabular-nums">{{ cutting.total_result_pieces ?? 0 }}
|
<span class="font-semibold tabular-nums">{{ cutting.total_result_pieces ?? 0 }}
|
||||||
pcs</span>
|
pcs</span>
|
||||||
|
|||||||
@ -122,9 +122,9 @@ export type CuttingResultCartItem = {
|
|||||||
product_name: string;
|
product_name: string;
|
||||||
variant_name: string;
|
variant_name: string;
|
||||||
stock: number;
|
stock: number;
|
||||||
cutting_result: string;
|
cutting_result: string | null;
|
||||||
sample: string;
|
sample: string | null;
|
||||||
original_outside_sample: string;
|
original_outside_sample: string | null;
|
||||||
images?: MediaItem[];
|
images?: MediaItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user