protocol-payment-installment.ts 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. /** 分期缴费:期数对齐、应缴日/金额计算(与 ProtocolFormDialog 逻辑一致) */
  2. export function addMonthsYmd(dateStr: string, months: number): string {
  3. const compact = String(dateStr || '').replace(/-/g, '')
  4. if (compact.length !== 8) return ''
  5. const y = parseInt(compact.slice(0, 4), 10)
  6. const m = parseInt(compact.slice(4, 6), 10) - 1
  7. const d = parseInt(compact.slice(6, 8), 10)
  8. const date = new Date(y, m + months, d)
  9. const yy = date.getFullYear()
  10. const mm = String(date.getMonth() + 1).padStart(2, '0')
  11. const dd = String(date.getDate()).padStart(2, '0')
  12. return `${yy}${mm}${dd}`
  13. }
  14. export function calcInstallmentDueAmounts(
  15. contractAmount: number | null | undefined,
  16. count: number
  17. ): number[] {
  18. if (contractAmount == null || !count || count < 1) return []
  19. const totalCents = Math.round(Number(contractAmount) * 100)
  20. const baseCents = Math.floor(totalCents / count)
  21. const amounts: number[] = []
  22. let sumCents = 0
  23. for (let i = 0; i < count; i++) {
  24. if (i === count - 1) {
  25. amounts.push((totalCents - sumCents) / 100)
  26. } else {
  27. amounts.push(baseCents / 100)
  28. sumCents += baseCents
  29. }
  30. }
  31. return amounts
  32. }
  33. type PaymentLike = Record<string, unknown> | null | undefined
  34. type DetailLike = Record<string, any>
  35. /**
  36. * 分期模式下按 installmentCount 补齐缺失期次(保留已有明细数据),仅用于编辑表单。
  37. */
  38. export function alignFqPaymentDetailsForDisplay(
  39. payment: PaymentLike,
  40. details: DetailLike[]
  41. ): DetailLike[] {
  42. if (!payment || payment.payMode !== 'fq') return details
  43. const count = Number(payment.installmentCount)
  44. if (!count || count < 1) return details
  45. const rows = [...details]
  46. const byNo = new Map<number, DetailLike>()
  47. const unnumbered: DetailLike[] = []
  48. for (const row of rows) {
  49. const no = row.installmentNo
  50. if (no != null && no !== '') {
  51. byNo.set(Number(no), row)
  52. } else {
  53. unnumbered.push(row)
  54. }
  55. }
  56. const needed = new Set(Array.from({ length: count }, (_, i) => i + 1))
  57. const have = new Set(byNo.keys())
  58. let missing = false
  59. for (const n of needed) {
  60. if (!have.has(n)) {
  61. missing = true
  62. break
  63. }
  64. }
  65. if (!missing && rows.length >= count) {
  66. return sortFqPaymentDetails(rows).slice(0, count)
  67. }
  68. const contract = payment.contractAmount != null ? Number(payment.contractAmount) : null
  69. const amounts = calcInstallmentDueAmounts(contract, count)
  70. const startDate = String(payment.installmentStartDate || '')
  71. const interval = Number(payment.installmentIntervalMonths || 0)
  72. const result: DetailLike[] = []
  73. for (let i = 0; i < count; i++) {
  74. const no = i + 1
  75. if (byNo.has(no)) {
  76. result.push(byNo.get(no)!)
  77. continue
  78. }
  79. if (i < unnumbered.length) {
  80. const raw = { ...unnumbered[i], installmentNo: no }
  81. if (!raw.installmentDueDate && startDate && interval) {
  82. raw.installmentDueDate = addMonthsYmd(startDate, i * interval)
  83. }
  84. result.push(raw)
  85. continue
  86. }
  87. result.push({
  88. installmentNo: no,
  89. installmentDueDate: startDate && interval ? addMonthsYmd(startDate, i * interval) : '',
  90. dueAmount: amounts[i] ?? null,
  91. payStatus: 'dfj',
  92. transferType: '',
  93. transferAmount: null,
  94. attachments: []
  95. })
  96. }
  97. return result
  98. }
  99. export function sortFqPaymentDetails(details: DetailLike[]): DetailLike[] {
  100. return [...details].sort((a, b) => {
  101. const na = a.installmentNo != null && a.installmentNo !== '' ? Number(a.installmentNo) : 9999
  102. const nb = b.installmentNo != null && b.installmentNo !== '' ? Number(b.installmentNo) : 9999
  103. if (na !== nb) return na - nb
  104. return Number(a.orderNum || 0) - Number(b.orderNum || 0)
  105. })
  106. }
  107. /** 编辑表单:将缴费明细行数与分期期数对齐(不覆盖已有行) */
  108. export function syncFqPaymentDetailRowCount(
  109. payment: {
  110. payMode: string
  111. installmentCount: number | null
  112. contractAmount: number | null
  113. installmentStartDate: string
  114. installmentIntervalMonths: number | null
  115. },
  116. paymentDetails: DetailLike[]
  117. ): void {
  118. if (payment.payMode !== 'fq') return
  119. const count = Number(payment.installmentCount)
  120. if (!count || count < 1) return
  121. const aligned = alignFqPaymentDetailsForDisplay(payment, paymentDetails)
  122. paymentDetails.splice(0, paymentDetails.length, ...aligned)
  123. }