import type { Product, ProductCategoryStatus, ProductSku, ProductSpec, ProductSpecValue, ProductStatus, ProductType, StockOperationType } from "@/types/Product"; import { getAttachmentReadURL } from "@/utils/attachment.ts"; /** 生成产品域写操作的幂等请求号。一次失败重试应复用调用方已经生成的请求号。 */ export function createProductRequestNo(): string { return window.crypto.randomUUID(); } /** 将分转换成金额展示文本。金额不使用浮点数格式化。 */ export function formatProductAmount(cents?: number | null): string { if (cents === undefined || cents === null) { return "未设置"; } const negative = cents < 0; const absolute = Math.abs(Math.trunc(cents)); return `${negative ? "-" : ""}${Math.floor(absolute / 100)}.${String(absolute % 100).padStart(2, "0")}`; } /** 将元金额输入精确转换为分。 */ export function parseProductAmount(value: string): number | undefined { const text = value.trim(); if (!/^\d+(?:\.\d{1,2})?$/.test(text)) { return undefined; } const [yuanText, fenText = ""] = text.split("."); const cents = BigInt(yuanText) * 100n + BigInt(fenText.padEnd(2, "0") || "0"); const result = Number(cents); return Number.isSafeInteger(result) ? result : undefined; } /** 规范化最多三位小数的非负数量。 */ export function normalizeProductQuantity(value: number | string): string | undefined { const text = String(value).trim(); if (!/^\d+(?:\.\d{1,3})?$/.test(text)) { return undefined; } const [integerText, fractionText = ""] = text.split("."); const integer = integerText.replace(/^0+(?=\d)/, "") || "0"; const fraction = fractionText.replace(/0+$/, ""); return fraction ? `${integer}.${fraction}` : integer; } /** 根据产品数量规则返回数量控件步长。允许小数时固定步进 0.1。 */ export function getProductQuantityStep(sku?: Pick): number { return sku?.allowDecimalQuantity ? .1 : 1; } export function getProductTypeText(type?: ProductType): string { return ({ SERVICE: "服务", GOODS: "实物", CONSUMABLE: "消耗品" } as Record)[type || ""] || "未知"; } export function getProductStatusText(status?: ProductStatus): string { return ({ ACTIVE: "启用", INACTIVE: "停用" } as Record)[status || ""] || "未知"; } export function getProductStatusTheme(status?: ProductStatus): "success" | "warning" | "default" { return status === "ACTIVE" ? "success" : status === "INACTIVE" ? "warning" : "default"; } export function getProductCategoryStatusText(status?: ProductCategoryStatus): string { return ({ ACTIVE: "启用", INACTIVE: "停用" } as Record)[status || ""] || "未知"; } export function getProductCategoryStatusTheme(status?: ProductCategoryStatus): "success" | "warning" | "default" { return status === "ACTIVE" ? "success" : status === "INACTIVE" ? "warning" : "default"; } export function getStockOperationText(type?: StockOperationType): string { return ({ INITIAL_IN: "期初入库", MANUAL_IN: "手工入库", MANUAL_OUT: "手工出库", MANUAL_REVOKE: "手工流水冲正", ORDER_RESERVE: "履约预占", ORDER_RELEASE: "履约释放预占", ORDER_OUT: "履约出库", ORDER_REVOKE: "履约冲正入库", STOCKTAKE_GAIN: "盘盈", STOCKTAKE_LOSS: "盘亏" } as Record)[type || ""] || "未知操作"; } export function getProductPhotoURL(item: Pick): string { const attachment = item.attachmentList?.find(item => item.attachType === "PHOTO" && !!item.id); return attachment?.id ? getAttachmentReadURL(attachment.id, 128) : ""; } /** 将数量转换成稳定的展示文本,兼容后端 BigDecimal 字符串。 */ export function getProductQuantityText(value?: number | string | null): string { if (value === undefined || value === null || value === "") { return "0"; } return normalizeProductQuantity(value) || "0"; } /** 从产品单位列表中获取基础单位名称。 */ export function getProductBaseUnitName(sku?: ProductSku): string { return sku?.unitList?.find(unit => unit.isBase)?.name || ""; } /** 组合产品名、SKU 名称和规格摘要,供选品后的业务页面统一展示。 */ export function getProductSkuText(productName: string | undefined, sku?: Pick): string { const skuText = sku?.specSummary || sku?.name; return skuText && skuText !== productName ? `${productName || "未命名产品"} · ${skuText}` : productName || "未命名产品"; } export type ProductSkuSpecEntry = { specName: string; valueName: string; }; type ProductEditorSpec = Pick & { valueList?: Array & { clientKey?: string }>; }; /** 根据 SKU 保存的规格值 ID,返回可直接展示的规格项和规格值。 */ export function getProductSkuSpecEntries(sku: Pick, specList: ProductEditorSpec[]): ProductSkuSpecEntry[] { const valueIdSet = new Set(sku.specValueIdList || []); return specList.flatMap((spec, specIndex) => { const value = (spec.valueList || []).find(item => valueIdSet.has(item.id || item.clientKey || "")); if (!value) { return []; } return [{ specName: spec.name.trim() || `规格项 ${specIndex + 1}`, valueName: value.name.trim() || "未命名规格值" }]; }); }