import type { CustomerPackageItem, CustomerPackageStatus, DeliveryOperationType, DeliveryStatus, FundOperationType, FundRecordStatus, PackageConfigStatus, PaymentStatus, RefundStatus } from "@/types/Package"; /** 生成套餐业务幂等请求号。每次新操作必须重新生成。 */ export function createPackageRequestNo(): string { return window.crypto.randomUUID(); } /** 将分转换为金额展示文本,不用浮点数做金额格式化。 */ export function formatPackageAmount(cents?: number | null): string { if (cents === undefined || cents === null) { return "未设置"; } const negative = cents < 0; const absolute = Math.abs(Math.trunc(cents)); const yuan = Math.floor(absolute / 100); const fen = String(absolute % 100).padStart(2, "0"); return `${negative ? "-" : ""}${yuan}.${fen} 元`; } /** 将分转换为金额输入框文本。 */ export function formatPackageAmountInput(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 parsePackageAmount(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 normalizePackageQuantity(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; } /** 使用 BigInt 比较定点数量,避免 JavaScript 二进制浮点误差。 */ export function comparePackageQuantity(left: number | string, right: number | string): number { const leftParts = toQuantityParts(left); const rightParts = toQuantityParts(right); if (!leftParts || !rightParts) { return 0; } const scale = Math.max(leftParts.scale, rightParts.scale); const leftValue = leftParts.value * 10n ** BigInt(scale - leftParts.scale); const rightValue = rightParts.value * 10n ** BigInt(scale - rightParts.scale); return leftValue < rightValue ? -1 : rightValue < leftValue ? 1 : 0; } export function getPackageQuantityText(value?: number | string | null): string { if (value === undefined || value === null || value === "") { return "0"; } return normalizePackageQuantity(value) || "0"; } export function getPackageConfigStatusText(status?: PackageConfigStatus): string { return ({ DRAFT: "草稿", ACTIVE: "启用", INACTIVE: "停用" } as Record)[status || ""] || "未知"; } export function getCustomerPackageStatusText(status?: CustomerPackageStatus): string { return ({ ACTIVE: "进行中", FULFILLED: "已履约", CANCELLED: "已取消" } as Record)[status || ""] || "未知"; } export function getPaymentStatusText(status?: PaymentStatus): string { return ({ UNPAID: "未收款", PARTIALLY_PAID: "部分收款", PAID: "已收款" } as Record)[status || ""] || "未知"; } export function getRefundStatusText(status?: RefundStatus): string { return ({ NOT_REQUIRED: "无需退款", PENDING: "待退款", REFUNDED: "已退款" } as Record)[status || ""] || "未知"; } export function getDeliveryStatusText(status?: DeliveryStatus): string { return ({ PENDING: "待核销", VERIFIED: "已核销", REJECTED: "已拒绝", CANCELLED: "已撤销", REVOKED: "已冲正" } as Record)[status || ""] || "未知"; } export function getDeliveryOperationText(type?: DeliveryOperationType): string { return type === "REVOKE" ? "发放冲正" : "正常发放"; } export function getFundOperationText(type?: FundOperationType): string { return ({ RECEIVE: "线下收款", REFUND: "退款", CONFIRM_NO_REFUND: "确认无需退款", REVOKE: "资金冲正" } as Record)[type || ""] || "未知"; } export function getFundStatusText(status?: FundRecordStatus): string { return ({ PENDING: "待核销", VERIFIED: "已核销", REJECTED: "已拒绝", CANCELLED: "已撤销" } as Record)[status || ""] || "未知"; } export function getPackageItemAvailableQuantity(item: CustomerPackageItem): string { return getPackageQuantityText(item.availableQuantity); } function toQuantityParts(value: number | string): { value: bigint; scale: number } | undefined { const normalized = normalizePackageQuantity(value); if (!normalized) { return undefined; } const [integerText, fractionText = ""] = normalized.split("."); return { value: BigInt(`${integerText}${fractionText}`), scale: fractionText.length }; }