feat(mobile-oa-h5): port protocol sample payment helpers
Share web-aligned installment autofill and mode-switch helpers for mobile protocol forms. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
be22c37196
commit
66e1c8334c
3 files changed
+544
No files matched your search
@@ -0,0 +1,74 @@
|
||||
/** 分期日期/均摊金额(与 web-admin protocol-payment-installment 对齐) */
|
||||
|
||||
const DAYS_PER_MONTH = 30
|
||||
export const DEFAULT_INSTALLMENT_ADVANCE_DAYS = 30
|
||||
|
||||
export function addMonthsYmd(dateStr, months) {
|
||||
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 addDaysYmd(dateStr, days) {
|
||||
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, d)
|
||||
date.setDate(date.getDate() + days)
|
||||
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 addIntervalMonthsYmd(dateStr, totalMonths) {
|
||||
const compact = String(dateStr || '').replace(/-/g, '')
|
||||
if (compact.length !== 8 || !Number.isFinite(totalMonths) || totalMonths === 0) {
|
||||
return compact.length === 8 ? compact : ''
|
||||
}
|
||||
const wholeMonths = Math.trunc(totalMonths)
|
||||
const fracMonths = totalMonths - wholeMonths
|
||||
let result = compact
|
||||
if (wholeMonths !== 0) result = addMonthsYmd(result, wholeMonths)
|
||||
if (fracMonths !== 0) {
|
||||
const days = Math.round(fracMonths * DAYS_PER_MONTH)
|
||||
if (days !== 0) result = addDaysYmd(result, days)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function calcFqInstallmentDueDate(
|
||||
startDate,
|
||||
index,
|
||||
intervalMonths,
|
||||
advanceDays = DEFAULT_INSTALLMENT_ADVANCE_DAYS
|
||||
) {
|
||||
const base = addIntervalMonthsYmd(startDate, index * intervalMonths)
|
||||
if (index >= 1) return addDaysYmd(base, -Number(advanceDays || 0))
|
||||
return base
|
||||
}
|
||||
|
||||
export function defaultInstallmentAmounts(contractAmount, count) {
|
||||
if (contractAmount == null || !count || count < 1) return []
|
||||
const totalCents = Math.round(Number(contractAmount) * 100)
|
||||
const baseCents = Math.floor(totalCents / count)
|
||||
const amounts = []
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
/** 按样本缴费 · 工具函数(对齐 web-admin protocol-sample-payment.ts) */
|
||||
|
||||
import {
|
||||
calcFqInstallmentDueDate,
|
||||
defaultInstallmentAmounts,
|
||||
DEFAULT_INSTALLMENT_ADVANCE_DAYS
|
||||
} from './protocol-payment-installment.js'
|
||||
|
||||
export { calcFqInstallmentDueDate, DEFAULT_INSTALLMENT_ADVANCE_DAYS }
|
||||
|
||||
export function sumSampleContractAmounts(payments) {
|
||||
return (payments || []).reduce((s, p) => s + (Number(p.contractAmount) || 0), 0)
|
||||
}
|
||||
|
||||
export function parsePreferentialPrice(sample) {
|
||||
if (sample == null) return null
|
||||
const raw = sample.price
|
||||
if (raw == null || raw === '') return null
|
||||
const n = Number(raw)
|
||||
if (!Number.isFinite(n) || n < 0) return null
|
||||
return Math.round(n * 100) / 100
|
||||
}
|
||||
|
||||
export function protocolTotalMismatchHint(total, sampleSum) {
|
||||
if (total == null || Number.isNaN(Number(total))) return null
|
||||
if (Math.abs(Number(total) - sampleSum) > 0.015) {
|
||||
return `协议总金额 ${Number(total).toFixed(2)} 与样本合同合计 ${sampleSum.toFixed(2)} 不一致,请核对`
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function activeSchedules(pay) {
|
||||
return (pay.schedules || []).filter((s) => String(s.scheduleStatus || 'open') !== 'void')
|
||||
}
|
||||
|
||||
export function sampleScheduleRequiredFieldsHint(pay) {
|
||||
const mode = String(pay.payMode || '')
|
||||
if (mode !== 'fq' && mode !== 'fgd' && mode !== 'djfq') return null
|
||||
const schedules = activeSchedules(pay)
|
||||
if (!schedules.length) return null
|
||||
const code = String(pay.sampleCode || '').trim()
|
||||
const prefix = code ? `样本 ${code} ` : ''
|
||||
for (let i = 0; i < schedules.length; i++) {
|
||||
const s = schedules[i]
|
||||
const amt = Number(s.dueAmount)
|
||||
if (!Number.isFinite(amt) || amt <= 0) {
|
||||
return `${prefix}第 ${i + 1} 期应缴金额必须填写`
|
||||
}
|
||||
if (!String(s.dueDate || '').trim()) {
|
||||
return `${prefix}第 ${i + 1} 期应缴时间必须填写`
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function sampleScheduleDuesMismatchHint(pay) {
|
||||
const contract = Number(pay.contractAmount)
|
||||
if (!Number.isFinite(contract)) return null
|
||||
const schedules = activeSchedules(pay)
|
||||
if (!schedules.length) return null
|
||||
const sum = schedules.reduce((acc, x) => acc + (Number(x.dueAmount) || 0), 0)
|
||||
if (Math.abs(contract - sum) > 0.015) {
|
||||
const code = String(pay.sampleCode || '').trim()
|
||||
const prefix = code ? `样本 ${code} ` : ''
|
||||
return `${prefix}合同金额 ${contract.toFixed(2)} 与各期应缴合计 ${sum.toFixed(2)} 不一致,请调整后再保存`
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function sumTransferAmountByStatuses(transfers, statuses) {
|
||||
return (transfers || [])
|
||||
.filter((t) => statuses.has(String(t.payStatus || '')))
|
||||
.reduce((s, t) => s + (Number(t.transferAmount) || 0), 0)
|
||||
}
|
||||
|
||||
export function scheduleFinanceConfirmedPaidArrears(dueAmount, transfers) {
|
||||
const paid =
|
||||
Math.round(sumTransferAmountByStatuses(transfers, new Set(['yjf'])) * 100) / 100
|
||||
return { paid, arrears: Math.round((dueAmount - paid) * 100) / 100 }
|
||||
}
|
||||
|
||||
export function schedulePaidArrears(dueAmount, transfers) {
|
||||
const { paid, arrears } = scheduleFinanceConfirmedPaidArrears(dueAmount, transfers)
|
||||
return { paid, arrears: arrears > 0 ? arrears : 0 }
|
||||
}
|
||||
|
||||
export function resolveSampleContractAmount(sample) {
|
||||
return parsePreferentialPrice(sample)
|
||||
}
|
||||
|
||||
export function resolvePerSamplePaymentDefaults(sample, payMode) {
|
||||
const contractAmount = resolveSampleContractAmount(sample)
|
||||
if (payMode === 'fq') {
|
||||
return { contractAmount, installmentCount: 6, installmentAmounts: [] }
|
||||
}
|
||||
return { contractAmount }
|
||||
}
|
||||
|
||||
export function shouldReplaceContractWithPreferentialPrice(contractAmount, preferentialPrice) {
|
||||
if (preferentialPrice == null) return false
|
||||
if (contractAmount == null || !Number.isFinite(Number(contractAmount))) return true
|
||||
return false
|
||||
}
|
||||
|
||||
export function parseInstallmentRatios(raw) {
|
||||
if (Array.isArray(raw)) return raw.map((v) => Number(v) || 0)
|
||||
if (typeof raw === 'string' && raw.trim()) {
|
||||
try {
|
||||
const parsed = JSON.parse(raw)
|
||||
if (Array.isArray(parsed)) return parsed.map((v) => Number(v) || 0)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function roundMoney(n) {
|
||||
return Math.round(n * 100) / 100
|
||||
}
|
||||
|
||||
export function planFqToFgdMerge(schedules) {
|
||||
const schList = (schedules || [])
|
||||
.filter((s) => String(s.scheduleStatus || 'open') !== 'void' && !s._deleted)
|
||||
.slice()
|
||||
.sort((a, b) => Number(a.scheduleNo || 0) - Number(b.scheduleNo || 0))
|
||||
|
||||
const arrearsOf = (s) => {
|
||||
const { arrears } = scheduleFinanceConfirmedPaidArrears(
|
||||
Number(s.dueAmount) || 0,
|
||||
s.transfers || []
|
||||
)
|
||||
return arrears > 0 ? arrears : 0
|
||||
}
|
||||
|
||||
let target
|
||||
for (const s of schList) {
|
||||
if (arrearsOf(s) > 0) {
|
||||
target = s
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!target) {
|
||||
target = schList.find((s) => String(s.scheduleStatus || 'open') !== 'cleared')
|
||||
}
|
||||
if (!target) throw new Error('没有可合并的未清期次')
|
||||
|
||||
const targetNo = Number(target.scheduleNo || 0)
|
||||
const later = schList.filter((s) => Number(s.scheduleNo || 0) > targetNo)
|
||||
for (const s of later) {
|
||||
if ((s.transfers || []).some((t) => t.payStatus === 'yjf')) {
|
||||
throw new Error('后续期已有已确认转账,无法合并为分改趸')
|
||||
}
|
||||
}
|
||||
|
||||
const remain = roundMoney(schList.reduce((sum, s) => sum + arrearsOf(s), 0))
|
||||
const { paid: targetConfirmed } = scheduleFinanceConfirmedPaidArrears(
|
||||
Number(target.dueAmount) || 0,
|
||||
target.transfers || []
|
||||
)
|
||||
const newDueAmount = roundMoney(targetConfirmed + remain)
|
||||
const softDeleteScheduleIds = later.map((s) => String(s.id || '').trim()).filter(Boolean)
|
||||
const softDeleteScheduleNos = later.map((s) => Number(s.scheduleNo || 0))
|
||||
const softNoSet = new Set(softDeleteScheduleNos)
|
||||
const kept = schList.filter((s) => !softNoSet.has(Number(s.scheduleNo || 0)))
|
||||
|
||||
return {
|
||||
targetScheduleId: String(target.id || ''),
|
||||
targetScheduleNo: targetNo,
|
||||
newDueAmount,
|
||||
softDeleteScheduleIds,
|
||||
softDeleteScheduleNos,
|
||||
newInstallmentCount: kept.length
|
||||
}
|
||||
}
|
||||
|
||||
export function samplePaymentHasConfirmedTransfer(pay) {
|
||||
return (pay.schedules || []).some((s) =>
|
||||
(s.transfers || []).some((t) => t.payStatus === 'yjf')
|
||||
)
|
||||
}
|
||||
|
||||
export function availableSamplePayModes(pay) {
|
||||
const mode = String(pay.payMode || 'dj')
|
||||
if (mode === 'fq') return ['fq', 'dj', 'fgd']
|
||||
if (mode === 'fgd') return ['fgd', 'fq']
|
||||
return ['dj', 'fq']
|
||||
}
|
||||
|
||||
export function applyConsolidateRemainingToPayment(pay, targetMode) {
|
||||
const plan = planFqToFgdMerge(pay.schedules || [])
|
||||
const softNoSet = new Set(plan.softDeleteScheduleNos)
|
||||
const schedules = (pay.schedules || [])
|
||||
.filter(
|
||||
(s) =>
|
||||
String(s.scheduleStatus || 'open') !== 'void' &&
|
||||
!s._deleted &&
|
||||
!softNoSet.has(Number(s.scheduleNo || 0))
|
||||
)
|
||||
.map((s) => {
|
||||
if (Number(s.scheduleNo || 0) !== plan.targetScheduleNo) return { ...s }
|
||||
return { ...s, dueAmount: plan.newDueAmount }
|
||||
})
|
||||
.sort((a, b) => Number(a.scheduleNo || 0) - Number(b.scheduleNo || 0))
|
||||
|
||||
return {
|
||||
...pay,
|
||||
payMode: targetMode,
|
||||
installmentCount: plan.newInstallmentCount,
|
||||
schedules,
|
||||
installmentRatios: schedules.map((s) => {
|
||||
const n = Number(s.dueAmount)
|
||||
return Number.isFinite(n) && n > 0 ? n : 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function applyFqToFgdMergeToPayment(pay) {
|
||||
return applyConsolidateRemainingToPayment(pay, 'fgd')
|
||||
}
|
||||
|
||||
export function applyExpandRemainingToInstallment(pay, opts) {
|
||||
const schList = (pay.schedules || [])
|
||||
.filter((s) => String(s.scheduleStatus || 'open') !== 'void' && !s._deleted)
|
||||
.slice()
|
||||
.sort((a, b) => Number(a.scheduleNo || 0) - Number(b.scheduleNo || 0))
|
||||
|
||||
for (const s of schList) {
|
||||
if ((s.transfers || []).some((t) => t.payStatus === 'jfqrz')) {
|
||||
throw new Error('存在付款确认中的转账,请先完成财务确认')
|
||||
}
|
||||
}
|
||||
|
||||
let totalPaid = 0
|
||||
const kept = []
|
||||
for (const s of schList) {
|
||||
const { paid, arrears } = scheduleFinanceConfirmedPaidArrears(
|
||||
Number(s.dueAmount) || 0,
|
||||
s.transfers || []
|
||||
)
|
||||
totalPaid = roundMoney(totalPaid + paid)
|
||||
if (paid > 0) {
|
||||
kept.push({
|
||||
...s,
|
||||
dueAmount: arrears > 0 ? paid : s.dueAmount,
|
||||
scheduleStatus: arrears > 0 ? 'cleared' : s.scheduleStatus || 'cleared',
|
||||
transfers: [...(s.transfers || [])]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const contract = Number(pay.contractAmount)
|
||||
if (!Number.isFinite(contract)) throw new Error('请先填写样本合同金额')
|
||||
const remaining = roundMoney(contract - totalPaid)
|
||||
if (remaining <= 0) throw new Error('已缴清,无法改为分期')
|
||||
|
||||
const count = Math.max(1, Number(opts.installmentCount) || 6)
|
||||
const newCount = Math.max(1, count - kept.length)
|
||||
const interval = Number(opts.installmentIntervalMonths) || 1
|
||||
const startDate = String(opts.installmentStartDate || '').trim()
|
||||
if (!startDate) throw new Error('请先填写分期开始时间')
|
||||
const advanceDays = Number(opts.installmentAdvanceDays ?? DEFAULT_INSTALLMENT_ADVANCE_DAYS)
|
||||
const amounts = defaultInstallmentAmounts(remaining, newCount)
|
||||
const maxNo = kept.reduce((m, s) => Math.max(m, Number(s.scheduleNo) || 0), 0)
|
||||
const created = amounts.map((amt, i) => ({
|
||||
scheduleNo: maxNo + 1 + i,
|
||||
dueAmount: amt,
|
||||
dueDate: calcFqInstallmentDueDate(startDate, i, interval, advanceDays),
|
||||
scheduleStatus: 'open',
|
||||
transfers: []
|
||||
}))
|
||||
|
||||
const schedules = [...kept, ...created]
|
||||
return {
|
||||
...pay,
|
||||
payMode: 'fq',
|
||||
installmentCount: schedules.length,
|
||||
installmentIntervalMonths: interval,
|
||||
installmentStartDate: startDate,
|
||||
installmentAdvanceDays: advanceDays,
|
||||
schedules,
|
||||
installmentRatios: schedules.map((s) => {
|
||||
const n = Number(s.dueAmount)
|
||||
return Number.isFinite(n) && n > 0 ? n : 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function calcLastPeriodDueAmount(contractAmount, priorAmounts) {
|
||||
if (contractAmount == null) return null
|
||||
const contract = Number(contractAmount)
|
||||
if (!Number.isFinite(contract)) return null
|
||||
if (!priorAmounts.length) return null
|
||||
let anyFilled = false
|
||||
let sumCents = 0
|
||||
for (const raw of priorAmounts) {
|
||||
if (raw === null || raw === undefined || raw === '') continue
|
||||
const n = Number(raw)
|
||||
if (!Number.isFinite(n)) continue
|
||||
anyFilled = true
|
||||
sumCents += Math.round(n * 100)
|
||||
}
|
||||
if (!anyFilled) return null
|
||||
const last = Math.round(contract * 100 - sumCents) / 100
|
||||
return last < 0 ? 0 : last
|
||||
}
|
||||
|
||||
export function shouldAutoFillLastPeriodDue(opts) {
|
||||
return !opts.lastManualOverride && !opts.lastLocked
|
||||
}
|
||||
|
||||
export function applyLumpSumDeadlineToLastDueDate(schedules, deadline, isLocked) {
|
||||
const due = String(deadline || '').trim()
|
||||
const activeIdx = []
|
||||
schedules.forEach((s, i) => {
|
||||
if (String(s.scheduleStatus || 'open') !== 'void') activeIdx.push(i)
|
||||
})
|
||||
if (!activeIdx.length) return schedules.map((s) => ({ ...s }))
|
||||
const lastIdx = activeIdx[activeIdx.length - 1]
|
||||
if (isLocked(schedules[lastIdx])) return schedules.map((s) => ({ ...s }))
|
||||
return schedules.map((s, i) => (i === lastIdx ? { ...s, dueDate: due } : { ...s }))
|
||||
}
|
||||
|
||||
/** 按缴费参数重建应缴批次(对齐 SamplePaymentPanel.rebuildSchedules 精简版) */
|
||||
export function rebuildSampleSchedules(pay) {
|
||||
const mode = pay.payMode || 'dj'
|
||||
if (mode === 'dj') {
|
||||
const prev = (pay.schedules || []).find((s) => Number(s.scheduleNo || 0) === 1)
|
||||
return {
|
||||
...pay,
|
||||
schedules: [
|
||||
{
|
||||
id: prev?.id,
|
||||
scheduleNo: 1,
|
||||
dueAmount: pay.contractAmount,
|
||||
dueDate: pay.lumpSumDeadline || '',
|
||||
scheduleStatus: prev?.scheduleStatus || 'open',
|
||||
transfers: [...(prev?.transfers || [])]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
const count = Number(pay.installmentCount) || 0
|
||||
const interval = Number(pay.installmentIntervalMonths) || 0
|
||||
const startDate = pay.installmentStartDate || ''
|
||||
const advanceDays = Number(pay.installmentAdvanceDays ?? DEFAULT_INSTALLMENT_ADVANCE_DAYS)
|
||||
if (!count || !interval || !startDate) {
|
||||
return { ...pay, schedules: [] }
|
||||
}
|
||||
const prevByNo = new Map(
|
||||
(pay.schedules || []).map((s) => [Number(s.scheduleNo || 0), s])
|
||||
)
|
||||
return {
|
||||
...pay,
|
||||
schedules: Array.from({ length: count }, (_, i) => {
|
||||
const scheduleNo = i + 1
|
||||
const prev = prevByNo.get(scheduleNo)
|
||||
const prevAmt = prev?.dueAmount
|
||||
return {
|
||||
id: prev?.id,
|
||||
scheduleNo,
|
||||
dueAmount: prevAmt != null && Number(prevAmt) > 0 ? Number(prevAmt) : null,
|
||||
dueDate: prev?.dueDate || calcFqInstallmentDueDate(startDate, i, interval, advanceDays) || '',
|
||||
scheduleStatus: prev?.scheduleStatus || 'open',
|
||||
transfers: [...(prev?.transfers || [])]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import {
|
||||
calcLastPeriodDueAmount,
|
||||
shouldAutoFillLastPeriodDue,
|
||||
applyLumpSumDeadlineToLastDueDate,
|
||||
availableSamplePayModes,
|
||||
applyConsolidateRemainingToPayment,
|
||||
sampleScheduleRequiredFieldsHint,
|
||||
sampleScheduleDuesMismatchHint
|
||||
} from './protocol-sample-payment.js'
|
||||
|
||||
describe('calcLastPeriodDueAmount', () => {
|
||||
it('last = contract − priors', () => {
|
||||
assert.equal(calcLastPeriodDueAmount(20000, [10000, 5000]), 5000)
|
||||
assert.equal(calcLastPeriodDueAmount(100, [33.33, 33.33]), 33.34)
|
||||
})
|
||||
it('returns null when contract invalid or priors empty', () => {
|
||||
assert.equal(calcLastPeriodDueAmount(null, [1000]), null)
|
||||
assert.equal(calcLastPeriodDueAmount(20000, [null, '']), null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('shouldAutoFillLastPeriodDue', () => {
|
||||
it('blocks override/locked', () => {
|
||||
assert.equal(
|
||||
shouldAutoFillLastPeriodDue({ lastManualOverride: true, lastLocked: false }),
|
||||
false
|
||||
)
|
||||
assert.equal(
|
||||
shouldAutoFillLastPeriodDue({ lastManualOverride: false, lastLocked: true }),
|
||||
false
|
||||
)
|
||||
})
|
||||
it('allows otherwise', () => {
|
||||
assert.equal(
|
||||
shouldAutoFillLastPeriodDue({ lastManualOverride: false, lastLocked: false }),
|
||||
true
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyLumpSumDeadlineToLastDueDate', () => {
|
||||
it('writes last dueDate', () => {
|
||||
const next = applyLumpSumDeadlineToLastDueDate(
|
||||
[{ scheduleNo: 1, dueDate: '', scheduleStatus: 'open' }],
|
||||
'20261231',
|
||||
() => false
|
||||
)
|
||||
assert.equal(next[0].dueDate, '20261231')
|
||||
})
|
||||
})
|
||||
|
||||
describe('availableSamplePayModes', () => {
|
||||
it('fq can switch to dj/fgd', () => {
|
||||
assert.deepEqual(availableSamplePayModes({ payMode: 'fq' }), ['fq', 'dj', 'fgd'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('applyConsolidateRemainingToPayment', () => {
|
||||
it('fq→fgd consolidates unpaid', () => {
|
||||
const next = applyConsolidateRemainingToPayment(
|
||||
{
|
||||
payMode: 'fq',
|
||||
installmentCount: 3,
|
||||
contractAmount: 30000,
|
||||
schedules: [
|
||||
{ scheduleNo: 1, dueAmount: 10000, transfers: [] },
|
||||
{ scheduleNo: 2, dueAmount: 10000, transfers: [] },
|
||||
{ scheduleNo: 3, dueAmount: 10000, transfers: [] }
|
||||
]
|
||||
},
|
||||
'fgd'
|
||||
)
|
||||
assert.equal(next.payMode, 'fgd')
|
||||
assert.equal(next.schedules.length, 1)
|
||||
assert.equal(next.schedules[0].dueAmount, 30000)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hints', () => {
|
||||
it('requires fq amounts', () => {
|
||||
const hint = sampleScheduleRequiredFieldsHint({
|
||||
payMode: 'fq',
|
||||
sampleCode: 'S1',
|
||||
schedules: [{ dueAmount: null, dueDate: '20261001' }]
|
||||
})
|
||||
assert.match(hint, /应缴金额/)
|
||||
})
|
||||
it('dues must match contract', () => {
|
||||
const hint = sampleScheduleDuesMismatchHint({
|
||||
payMode: 'fq',
|
||||
contractAmount: 20000,
|
||||
schedules: [
|
||||
{ dueAmount: 10000, dueDate: '20261001' },
|
||||
{ dueAmount: 5000, dueDate: '20261101' }
|
||||
]
|
||||
})
|
||||
assert.match(hint, /不一致/)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user