This commit is contained in:
zlwu committed 2026-07-06 19:41:24 +08:00
1 parent cdaa0ac273
commit b7cd657f32
32 files changed
+2885 -1936

No files matched your search

+61 -37
View File
@@ -1,5 +1,5 @@
/**
* 采集协议单 · 缴费模式切换(dj ↔ fq)
* 采集协议单 · 缴费模式切换(dj / fq / djfq)
* 规则见 docs/superpowers/specs/2026-06-04-protocol-pay-mode-switch-design.md
*/
@@ -8,8 +8,9 @@ import {
DEFAULT_INSTALLMENT_ADVANCE_DAYS,
defaultInstallmentAmounts
} from '@/utils/protocol-payment-installment'
import { type PayMode, isInstallmentPayMode } from '@/utils/protocol-payment-presets'
export type PayMode = 'dj' | 'fq'
export type { PayMode }
export type DetailLike = Record<string, any>
const LOCKED_STATUSES = new Set(['jfqrz', 'yjf', 'tfqrz', 'ytf'])
@@ -69,6 +70,35 @@ function countByStatus(details: DetailLike[], status: string): number {
return details.filter((d) => String(d.payStatus || 'dfj') === status).length
}
function buildInstallmentDetails(
ctx: PayModeSwitchContext,
rows: DetailLike[]
): DetailLike[] {
const count = Number(ctx.installmentCount) || 6
const interval = Number(ctx.installmentIntervalMonths) || 6
const startDate = ctx.installmentStartDate || ''
const advanceDays = Number(ctx.installmentAdvanceDays ?? DEFAULT_INSTALLMENT_ADVANCE_DAYS)
const amounts =
ctx.installmentRatios?.length && sumPaidAmount(rows) === 0
? ctx.installmentRatios.map((v) => Number(v || 0))
: defaultInstallmentAmounts(ctx.contractAmount, count)
const result: DetailLike[] = []
for (let i = 0; i < count; i++) {
result.push(
createPaymentDetailRow({
dueAmount: amounts[i] ?? null,
installmentNo: i + 1,
installmentDueDate:
startDate && interval
? calcFqInstallmentDueDate(startDate, i, interval, advanceDays)
: '',
expanded: i === 0
})
)
}
return result
}
export function createPaymentDetailRow(options: {
dueAmount: number | null
installmentDueDate?: string
@@ -110,7 +140,7 @@ export function evaluatePayModeSwitch(
}
}
if (fromMode === 'dj' && toMode === 'fq') {
if (fromMode === 'dj' && isInstallmentPayMode(toMode)) {
if (rows.some((d) => isPayDetailLocked(d))) {
return {
action: 'blocked',
@@ -118,13 +148,14 @@ export function evaluatePayModeSwitch(
}
}
const count = ctx.installmentCount || 6
const label = toMode === 'djfq' ? '趸交+分期' : '分期'
return {
action: 'confirm',
summary: `将清除当前趸交计划,按 ${count} 期分期重新生成缴费明细。是否继续?`
summary: `将清除当前趸交计划,按 ${count} 期${label}重新生成缴费明细。是否继续?`
}
}
if (fromMode === 'fq' && toMode === 'dj') {
if (isInstallmentPayMode(fromMode) && toMode === 'dj') {
const dfjCount = countByStatus(rows, 'dfj')
const yjfCount = countByStatus(rows, 'yjf')
@@ -156,6 +187,21 @@ export function evaluatePayModeSwitch(
}
}
if (isInstallmentPayMode(fromMode) && isInstallmentPayMode(toMode) && fromMode !== toMode) {
if (rows.some((d) => isPayDetailLocked(d))) {
return {
action: 'blocked',
reason: '分期已有缴费记录,无法切换分期类型。'
}
}
const count = ctx.installmentCount || 6
const label = toMode === 'djfq' ? '趸交+分期' : '分期'
return {
action: 'confirm',
summary: `将按 ${count} 期${label}重新生成缴费明细。是否继续?`
}
}
return { action: 'noop' }
}
@@ -167,33 +213,11 @@ export function applyPayModeSwitch(
): DetailLike[] {
const rows = activeDetails(details)
if (fromMode === 'dj' && toMode === 'fq') {
const count = Number(ctx.installmentCount) || 6
const interval = Number(ctx.installmentIntervalMonths) || 6
const startDate = ctx.installmentStartDate || ''
const advanceDays = Number(ctx.installmentAdvanceDays ?? DEFAULT_INSTALLMENT_ADVANCE_DAYS)
const amounts =
ctx.installmentRatios?.length && sumPaidAmount(rows) === 0
? ctx.installmentRatios.map((v) => Number(v || 0))
: defaultInstallmentAmounts(ctx.contractAmount, count)
const result: DetailLike[] = []
for (let i = 0; i < count; i++) {
result.push(
createPaymentDetailRow({
dueAmount: amounts[i] ?? null,
installmentNo: i + 1,
installmentDueDate:
startDate && interval
? calcFqInstallmentDueDate(startDate, i, interval, advanceDays)
: '',
expanded: i === 0
})
)
}
return result
if (fromMode === 'dj' && isInstallmentPayMode(toMode)) {
return buildInstallmentDetails(ctx, rows)
}
if (fromMode === 'fq' && toMode === 'dj') {
if (isInstallmentPayMode(fromMode) && toMode === 'dj') {
const yjfRows = rows
.filter((d) => String(d.payStatus) === 'yjf')
.map((d) => ({ ...d, _payModeLocked: true }))
@@ -209,14 +233,14 @@ export function applyPayModeSwitch(
return [...yjfRows, newDjRow]
}
if (isInstallmentPayMode(fromMode) && isInstallmentPayMode(toMode) && fromMode !== toMode) {
return buildInstallmentDetails(ctx, rows)
}
return rows
}
/** 趸交模式下是否还可添加待缴明细 */
export function canAddPaymentDetail(payMode: string, details: DetailLike[]): boolean {
if (payMode === 'fq') return true
const dfjCount = details.filter(
(d) => !isPayDetailLocked(d) && String(d.payStatus || 'dfj') === 'dfj'
).length
return dfjCount < 1
/** 趸交与分期均允许多条缴费明细(合计须等于合同金额) */
export function canAddPaymentDetail(_payMode: string, _details: DetailLike[]): boolean {
return true
}