|
- import type { ItemLotTraceStockTakeEvent, ItemLotTraceStockTakeRecordDetail } from "@/app/api/itemTracing";
-
- export const formatStockTakeRoundLabel = (event: ItemLotTraceStockTakeEvent): string => {
- const name = event.stockTakeRoundName?.trim();
- if (name) return name;
- if (event.stockTakeRoundId != null) return `#${event.stockTakeRoundId}`;
- return "";
- };
-
- export const stockTakeEventSubtitle = (event: ItemLotTraceStockTakeEvent): string => {
- const parts = [
- formatStockTakeRoundLabel(event),
- event.stockTakeSection?.trim(),
- event.warehouseCode?.trim(),
- event.lotNo?.trim() || undefined,
- ].filter(Boolean);
- return parts.length > 0 ? parts.join(" · ") : event.stockTakeCode || "—";
- };
-
- /** Book (system) qty at count time — prefer record snapshot over line initialQty. */
- export const resolveStockTakeBookQty = (
- detail: ItemLotTraceStockTakeRecordDetail | null | undefined,
- fallbackBeforeQty?: number | null,
- ): number | null => {
- if (detail?.bookQty != null && !Number.isNaN(Number(detail.bookQty))) {
- return Number(detail.bookQty);
- }
- if (fallbackBeforeQty != null && !Number.isNaN(Number(fallbackBeforeQty))) {
- return Number(fallbackBeforeQty);
- }
- return null;
- };
-
- /**
- * Accepted physical qty used for variance / posting decision.
- * Prefer lastSelect (1=first, 2=second, 3=approver), then fallback chain, then line finalQty.
- */
- export const resolveStockTakeAcceptedQty = (
- detail: ItemLotTraceStockTakeRecordDetail | null | undefined,
- fallbackAfterQty?: number | null,
- ): number | null => {
- if (detail) {
- const pick = (n: number | null | undefined) =>
- n != null && !Number.isNaN(Number(n)) ? Number(n) : null;
- switch (detail.lastSelect) {
- case 3: {
- const q = pick(detail.approverQty);
- if (q != null) return q;
- break;
- }
- case 2: {
- const q = pick(detail.pickerSecondQty);
- if (q != null) return q;
- break;
- }
- case 1: {
- const q = pick(detail.pickerFirstQty);
- if (q != null) return q;
- break;
- }
- default:
- break;
- }
- const fromApprover = pick(detail.approverQty);
- if (fromApprover != null) return fromApprover;
- const fromSecond = pick(detail.pickerSecondQty);
- if (fromSecond != null) return fromSecond;
- const fromFirst = pick(detail.pickerFirstQty);
- if (fromFirst != null) return fromFirst;
- }
- if (fallbackAfterQty != null && !Number.isNaN(Number(fallbackAfterQty))) {
- return Number(fallbackAfterQty);
- }
- return null;
- };
-
- export interface StockTakeLifecycleStage {
- key: string;
- label: string;
- qty?: number | null;
- badQty?: number | null;
- handler?: string | null;
- timestamp?: string | null;
- isActive: boolean;
- }
-
- /** Build ordered lifecycle stages from stocktake record detail. */
- export const buildStockTakeLifecycleStages = (
- detail: ItemLotTraceStockTakeRecordDetail | null | undefined,
- ): StockTakeLifecycleStage[] => {
- if (!detail) return [];
- const stages: StockTakeLifecycleStage[] = [];
-
- // Stage 1: Round created — show book (帳面) qty for context
- stages.push({
- key: "round-created",
- label: "stockTakeStageCreated",
- qty: detail.bookQty,
- handler: null,
- timestamp: detail.stockTakeStartTime ?? null,
- isActive: true,
- });
-
- // Stage 2: Picker first count
- const hasFirstCount =
- detail.pickerFirstQty != null ||
- detail.pickerFirstBadQty != null ||
- Boolean(detail.stockTakerName?.trim());
- stages.push({
- key: "first-count",
- label: "stockTakeStageFirstCount",
- qty: detail.pickerFirstQty,
- badQty: detail.pickerFirstBadQty,
- handler: detail.stockTakerName,
- timestamp: detail.stockTakeStartTime ?? null,
- isActive: hasFirstCount,
- });
-
- // Stage 3: Picker second count (re-count) — only if present
- const hasSecondCount =
- detail.pickerSecondQty != null || detail.pickerSecondBadQty != null;
- if (hasSecondCount) {
- stages.push({
- key: "second-count",
- label: "stockTakeStageSecondCount",
- qty: detail.pickerSecondQty,
- badQty: detail.pickerSecondBadQty,
- handler: detail.stockTakerName,
- timestamp: detail.stockTakeEndTime ?? null,
- isActive: true,
- });
- }
-
- // Stage 4: Approver manual input — only when lastSelect === 3 (admin entered own count)
- const hasApproverCount =
- detail.approverQty != null || detail.approverBadQty != null;
- if (detail.lastSelect === 3 && hasApproverCount) {
- stages.push({
- key: "approver-count",
- label: "stockTakeStageApproverCount",
- qty: detail.approverQty,
- badQty: detail.approverBadQty,
- handler: detail.approverName,
- timestamp: detail.approverTime ?? null,
- isActive: true,
- });
- }
-
- // Stage 5: Accepted — show accepted physical qty; variance via badQty for secondary line
- const isAccepted =
- detail.recordStatus?.toUpperCase() === "ACCEPTED" ||
- detail.recordStatus?.toUpperCase() === "COMPLETED" ||
- detail.recordStatus?.toUpperCase() === "COMPLETE";
- const acceptedQty = resolveStockTakeAcceptedQty(detail, null);
- stages.push({
- key: "accepted",
- label: isAccepted ? "stockTakeStageAccepted" : "stockTakeStagePending",
- qty: acceptedQty,
- badQty: detail.varianceQty,
- handler: detail.approverName,
- timestamp: detail.approverTime ?? detail.stockTakeEndTime ?? null,
- isActive: isAccepted || Boolean(detail.approverName?.trim()),
- });
-
- return stages;
- };
|