Files
shanxia2.0/web-admin/src/utils/crm-package-spec.ts
T
2026-09-26 11:59:39 +08:00

148 lines
4.5 KiB
TypeScript

/** Contract / quote product package_spec codec — mirror backend `crm_package_spec.py`. */
export const MAX_PACKAGE_SPECS_PER_LINE = 20
export interface PackageQtyBreakdownItem {
packageSpec?: string
archivedQty?: number | null
arrangedUnarchivedQty?: number | null
arrangedTotalQty?: number | null
}
export function normalizePackageSpecs(values?: Iterable<unknown> | null): string[] {
const out: string[] = []
const seen = new Set<string>()
for (const raw of values || []) {
const s = String(raw ?? '').trim()
if (!s || seen.has(s)) continue
seen.add(s)
out.push(s)
if (out.length >= MAX_PACKAGE_SPECS_PER_LINE) break
}
return out
}
/** JSON array or legacy scalar → string[] */
export function decodePackageSpecs(value: unknown): string[] {
if (value == null) return []
if (Array.isArray(value)) return normalizePackageSpecs(value)
const text = String(value).trim()
if (!text) return []
if (text.startsWith('[')) {
try {
const parsed = JSON.parse(text)
if (Array.isArray(parsed)) return normalizePackageSpecs(parsed)
} catch {
// fall through to scalar
}
}
return [text]
}
/** Prefer API `packageSpecs` when present; fallback to scalar `packageSpec`. */
export function resolvePackageSpecsFromPayload(data?: {
packageSpecs?: string[] | null
packageSpec?: string | null
} | null): string[] {
if (!data) return []
if (data.packageSpecs != null) return normalizePackageSpecs(data.packageSpecs)
return decodePackageSpecs(data.packageSpec)
}
export function formatPackageSpecsJoin(
data?: { packageSpecs?: string[] | null; packageSpec?: string | null } | null,
separator = '、'
): string {
const specs = resolvePackageSpecsFromPayload(data)
return specs.length ? specs.join(separator) : ''
}
export function hasMultiPackageQtyBreakdown(product?: {
packageQtyBreakdown?: PackageQtyBreakdownItem[] | null
} | null): boolean {
return (product?.packageQtyBreakdown?.length ?? 0) > 1
}
export function resolveLineRemainingQty(product?: {
remainingQty?: number | null
quantity?: number | null
arrangedTotalQty?: number | null
} | null): number | null {
if (product?.remainingQty != null && !Number.isNaN(Number(product.remainingQty))) {
return Number(product.remainingQty)
}
const qty = Number(product?.quantity)
const arranged = Number(product?.arrangedTotalQty ?? 0)
if (!Number.isNaN(qty) && qty > 0) {
return qty - arranged
}
return null
}
/** Collect union of all line package specs (dedupe, preserve order). */
export function collectAllContractPackageSpecs(
products?: Array<{ packageSpecs?: string[] | null; packageSpec?: string | null }> | null
): string[] {
const out: string[] = []
const seen = new Set<string>()
for (const p of products || []) {
for (const spec of resolvePackageSpecsFromPayload(p)) {
if (seen.has(spec)) continue
seen.add(spec)
out.push(spec)
}
}
return out
}
export function normalizeContractProductPackageFields<
T extends { packageSpecs?: string[] | null; packageSpec?: string | null }
>(product: T): T {
const specs = resolvePackageSpecsFromPayload(product)
product.packageSpecs = specs
product.packageSpec = specs[0] || ''
return product
}
export function buildPackageSpecsPayload(product: {
packageSpecs?: string[] | null
packageSpec?: string | null
}) {
const specs = product.packageSpecs?.length
? normalizePackageSpecs(product.packageSpecs)
: resolvePackageSpecsFromPayload(product)
return {
packageSpecs: specs,
packageSpec: specs[0] || ''
}
}
export function validateContractProductPackageSpecs(
products: Array<{
productId?: string | number | null
packageSpecs?: string[] | null
packageSpec?: string | null
}>
): { ok: true } | { ok: false; message: string } {
const seenByProduct = new Map<string, Set<string>>()
for (let i = 0; i < products.length; i++) {
const product = products[i]
const rowNum = i + 1
const specs = resolvePackageSpecsFromPayload(product)
if (!specs.length) {
return { ok: false, message: `第${rowNum}行产品的包装规格不能为空` }
}
const pid = String(product.productId || '').trim()
if (!pid) continue
if (!seenByProduct.has(pid)) seenByProduct.set(pid, new Set())
const seen = seenByProduct.get(pid)!
for (const spec of specs) {
if (seen.has(spec)) {
return { ok: false, message: `同一产品的包装规格不能重复:${spec}` }
}
seen.add(spec)
}
}
return { ok: true }
}