/** 分期缴费:期数对齐、应缴日/金额计算(与 ProtocolFormDialog 逻辑一致) */ export function addMonthsYmd(dateStr: string, months: number): string { const compact = String(dateStr || '').replace(/-/g, '') if (compact.length !== 8) return '' const y = parseInt(compact.slice(0, 4), 10) const m = parseInt(compact.slice(4, 6), 10) - 1 const d = parseInt(compact.slice(6, 8), 10) const date = new Date(y, m + months, d) const yy = date.getFullYear() const mm = String(date.getMonth() + 1).padStart(2, '0') const dd = String(date.getDate()).padStart(2, '0') return `${yy}${mm}${dd}` } export function calcInstallmentDueAmounts( contractAmount: number | null | undefined, count: number ): number[] { if (contractAmount == null || !count || count < 1) return [] const totalCents = Math.round(Number(contractAmount) * 100) const baseCents = Math.floor(totalCents / count) const amounts: number[] = [] let sumCents = 0 for (let i = 0; i < count; i++) { if (i === count - 1) { amounts.push((totalCents - sumCents) / 100) } else { amounts.push(baseCents / 100) sumCents += baseCents } } return amounts } type PaymentLike = Record | null | undefined type DetailLike = Record /** * 分期模式下按 installmentCount 补齐缺失期次(保留已有明细数据),仅用于编辑表单。 */ export function alignFqPaymentDetailsForDisplay( payment: PaymentLike, details: DetailLike[] ): DetailLike[] { if (!payment || payment.payMode !== 'fq') return details const count = Number(payment.installmentCount) if (!count || count < 1) return details const rows = [...details] const byNo = new Map() const unnumbered: DetailLike[] = [] for (const row of rows) { const no = row.installmentNo if (no != null && no !== '') { byNo.set(Number(no), row) } else { unnumbered.push(row) } } const needed = new Set(Array.from({ length: count }, (_, i) => i + 1)) const have = new Set(byNo.keys()) let missing = false for (const n of needed) { if (!have.has(n)) { missing = true break } } if (!missing && rows.length >= count) { return sortFqPaymentDetails(rows).slice(0, count) } const contract = payment.contractAmount != null ? Number(payment.contractAmount) : null const amounts = calcInstallmentDueAmounts(contract, count) const startDate = String(payment.installmentStartDate || '') const interval = Number(payment.installmentIntervalMonths || 0) const result: DetailLike[] = [] for (let i = 0; i < count; i++) { const no = i + 1 if (byNo.has(no)) { result.push(byNo.get(no)!) continue } if (i < unnumbered.length) { const raw = { ...unnumbered[i], installmentNo: no } if (!raw.installmentDueDate && startDate && interval) { raw.installmentDueDate = addMonthsYmd(startDate, i * interval) } result.push(raw) continue } result.push({ installmentNo: no, installmentDueDate: startDate && interval ? addMonthsYmd(startDate, i * interval) : '', dueAmount: amounts[i] ?? null, payStatus: 'dfj', transferType: '', transferAmount: null, attachments: [] }) } return result } export function sortFqPaymentDetails(details: DetailLike[]): DetailLike[] { return [...details].sort((a, b) => { const na = a.installmentNo != null && a.installmentNo !== '' ? Number(a.installmentNo) : 9999 const nb = b.installmentNo != null && b.installmentNo !== '' ? Number(b.installmentNo) : 9999 if (na !== nb) return na - nb return Number(a.orderNum || 0) - Number(b.orderNum || 0) }) } /** 编辑表单:将缴费明细行数与分期期数对齐(不覆盖已有行) */ export function syncFqPaymentDetailRowCount( payment: { payMode: string installmentCount: number | null contractAmount: number | null installmentStartDate: string installmentIntervalMonths: number | null }, paymentDetails: DetailLike[] ): void { if (payment.payMode !== 'fq') return const count = Number(payment.installmentCount) if (!count || count < 1) return const aligned = alignFqPaymentDetailsForDisplay(payment, paymentDetails) paymentDetails.splice(0, paymentDetails.length, ...aligned) }