This commit is contained in:
zlwu committed 2026-06-15 11:43:16 +08:00
1 parent 427bcb1551
commit 31b04575e2
247 files changed
+39284 -20367

No files matched your search

@@ -43,6 +43,114 @@ export function isInstallmentRatiosValid(ratios: number[]): boolean {
return Math.abs(total - 100) < 0.01
}
/** 历史数据:installmentRatios 存的是百分比(合计约 100) */
export function isLegacyInstallmentRatios(
values: number[],
contractAmount?: number | null
): boolean {
if (!values.length) return false
const nums = values.map((v) => Number(v || 0))
const sum = nums.reduce((s, v) => s + v, 0)
const max = Math.max(...nums)
if (contractAmount != null && Math.abs(sum - Number(contractAmount)) < 0.02) {
return false
}
return max <= 100 && Math.abs(sum - 100) < 0.05
}
/** 将存储值规范为各期金额(元);兼容旧比例数据 */
export function normalizeInstallmentAmounts(
values: number[],
contractAmount: number | null | undefined,
count: number
): number[] {
if (!values.length) {
return defaultInstallmentAmounts(contractAmount, count)
}
if (isLegacyInstallmentRatios(values, contractAmount)) {
const sized =
values.length === count ? values : resizeInstallmentRatios(values, count)
return calcInstallmentDueAmountsByRatios(contractAmount, finalizeInstallmentRatios(sized))
}
const amounts = values.map((v) => Number(v || 0))
if (amounts.length === count) return amounts
return resizeInstallmentAmounts(amounts, count, contractAmount)
}
export function defaultInstallmentAmounts(
contractAmount: number | null | undefined,
count: number
): number[] {
return calcInstallmentDueAmounts(contractAmount, count)
}
export function resizeInstallmentAmounts(
oldAmounts: number[],
newCount: number,
contractAmount: number | null | undefined
): number[] {
if (newCount <= 0) return []
const head = oldAmounts.map((v) => Number(v || 0))
if (newCount > head.length) {
const existingSum = sumInstallmentAmounts(head)
const remaining =
contractAmount != null
? Math.max(0, Math.round((Number(contractAmount) - existingSum) * 100) / 100)
: 0
const extraCount = newCount - head.length
const extra =
remaining > 0 && contractAmount != null
? defaultInstallmentAmounts(remaining, extraCount)
: Array(extraCount).fill(0)
return [...head, ...extra]
}
if (newCount < head.length) {
return head.slice(0, newCount)
}
return head
}
export function isInstallmentAmountsValid(
amounts: number[],
contractAmount: number | null | undefined
): boolean {
if (!amounts.length || contractAmount == null) return false
if (amounts.some((v) => Number(v) < 0)) return false
const sum = amounts.reduce((s, v) => s + Number(v || 0), 0)
return Math.abs(sum - Number(contractAmount)) < 0.01
}
export function sumInstallmentAmounts(amounts: number[]): number {
const total = amounts.reduce((s, v) => s + Number(v || 0), 0)
return Math.round(total * 100) / 100
}
/** 合同金额变更时,按原各期占比等比缩放金额 */
export function scaleInstallmentAmountsToContract(
amounts: number[],
contractAmount: number | null | undefined
): number[] {
if (!amounts.length || contractAmount == null) return amounts
const oldSum = sumInstallmentAmounts(amounts)
if (oldSum <= 0) {
return defaultInstallmentAmounts(contractAmount, amounts.length)
}
const target = Number(contractAmount)
const scaled: number[] = []
let sumCents = 0
const totalCents = Math.round(target * 100)
for (let i = 0; i < amounts.length; i++) {
if (i === amounts.length - 1) {
scaled.push((totalCents - sumCents) / 100)
} else {
const cents = Math.floor((totalCents * Number(amounts[i] || 0)) / oldSum)
scaled.push(cents / 100)
sumCents += cents
}
}
return scaled
}
export function addMonthsYmd(dateStr: string, months: number): string {
const compact = String(dateStr || '').replace(/-/g, '')
if (compact.length !== 8) return ''
@@ -175,29 +283,23 @@ type DetailLike = Record<string, any>
function resolveFqAmounts(payment: PaymentLike, count: number, details?: DetailLike[]): number[] {
const contract = payment?.contractAmount != null ? Number(payment.contractAmount) : null
const ratios = Array.isArray(payment?.installmentRatios)
const stored = Array.isArray(payment?.installmentRatios)
? (payment!.installmentRatios as number[])
: []
if (ratios.length === count && contract != null) {
if (stored.length === count && contract != null) {
const baseAmounts = isLegacyInstallmentRatios(stored, contract)
? calcInstallmentDueAmountsByRatios(contract, finalizeInstallmentRatios(stored))
: stored.map((v) => Number(v || 0))
const paidRows = (details || []).filter((d) => String(d.payStatus) === 'yjf')
if (paidRows.length > 0) {
const paidSum = paidRows.reduce((s, d) => s + (Number(d.dueAmount) || 0), 0)
const remaining = Math.round((contract - paidSum) * 100) / 100
const unpaidIndices = Array.from({ length: count }, (_, i) => i).filter(
(i) => !paidRows.some((d) => Number(d.installmentNo) === i + 1)
)
const unpaidAmounts = calcUnpaidAmountsByRatios(remaining, ratios, unpaidIndices)
const amounts = Array(count).fill(0)
unpaidIndices.forEach((idx, j) => {
amounts[idx] = unpaidAmounts[j] ?? 0
})
const amounts = [...baseAmounts]
paidRows.forEach((d) => {
const no = Number(d.installmentNo)
if (no >= 1 && no <= count) amounts[no - 1] = Number(d.dueAmount) || 0
})
return amounts
}
return calcInstallmentDueAmountsByRatios(contract, ratios)
return baseAmounts
}
return calcInstallmentDueAmounts(contract, count)
}