add product

This commit is contained in:
Timi
2026-09-03 14:27:40 +08:00
parent 7f669621fb
commit 333999fe21
63 changed files with 5662 additions and 2210 deletions
+85
View File
@@ -0,0 +1,85 @@
import type { ProductCategoryStatus, ProductStatus, ProductType, StockOperationType } from "@/types/Product";
/** 生成产品域写操作的幂等请求号。一次失败重试应复用调用方已经生成的请求号。 */
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;
}
export function getProductTypeText(type?: ProductType): string {
return ({ SERVICE: "服务", GOODS: "实物", CONSUMABLE: "消耗品" } as Record<string, string>)[type || ""] || "未知";
}
export function getProductStatusText(status?: ProductStatus): string {
return ({ ACTIVE: "启用", INACTIVE: "停用" } as Record<string, string>)[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<string, string>)[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: "手工出库",
ORDER_RESERVE: "履约预占",
ORDER_RELEASE: "履约释放预占",
ORDER_OUT: "履约出库",
ORDER_REVOKE: "履约冲正入库",
STOCKTAKE_GAIN: "盘盈",
STOCKTAKE_LOSS: "盘亏"
} as Record<string, string>)[type || ""] || "未知操作";
}
/** 将数量转换成稳定的展示文本,兼容后端 BigDecimal 字符串。 */
export function getProductQuantityText(value?: number | string | null): string {
if (value === undefined || value === null || value === "") {
return "0";
}
return normalizeProductQuantity(value) || "0";
}