From 618d8fe21cb99c3a2a77e8c17fc6ee1923106fc3 Mon Sep 17 00:00:00 2001 From: zlwu <704541015@qq.com> Date: Tue, 13 Oct 2026 14:53:11 +0800 Subject: [PATCH] feat(web-admin): wire fq last-period and dj deadline autofill in SamplePaymentPanel Auto-recalculate the final installment due amount when earlier periods change (with manual override), and sync lump-sum deadline to due date. Co-authored-by: Cursor --- .../protocol/SamplePaymentPanel.vue | 66 ++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/web-admin/src/views/cell-storage/protocol/SamplePaymentPanel.vue b/web-admin/src/views/cell-storage/protocol/SamplePaymentPanel.vue index 54328679..6f6ecc53 100644 --- a/web-admin/src/views/cell-storage/protocol/SamplePaymentPanel.vue +++ b/web-admin/src/views/cell-storage/protocol/SamplePaymentPanel.vue @@ -59,7 +59,7 @@ placeholder="选择截止时间" clearable style="width: 100%" - @update:model-value="(v: string) => patchPay(pIdx, { lumpSumDeadline: v || '' })" + @update:model-value="(v: string) => onLumpSumDeadlineChange(pIdx, v || '')" /> @@ -364,12 +364,15 @@ import { applyConsolidateRemainingToPayment, applyExpandRemainingToInstallment, + applyLumpSumDeadlineToLastDueDate, availableSamplePayModes, + calcLastPeriodDueAmount, parseInstallmentRatios, parsePreferentialPrice, resolvePerSamplePaymentDefaults, samplePaymentHasConfirmedTransfer, schedulePaidArrears, + shouldAutoFillLastPeriodDue, shouldReplaceContractWithPreferentialPrice } from '@/utils/protocol-sample-payment' import { isMultiSchedulePayMode } from '@/utils/protocol-payment-presets' @@ -430,6 +433,8 @@ const transferTypeOptions = ref([]) const submittingId = ref('') const savingKey = ref('') + /** 按样本 key:用户手改过最后一期应缴金额则 true,前几期再变时清除 */ + const lastPeriodDueOverrideByKey = ref>({}) async function ensureTransferTypes() { await loadTransferTypeDict() @@ -442,6 +447,18 @@ return '¥' + formatMoneyDisplay(v) } + function samplePayKey(pay: Record, pIdx: number): string { + return String(pay.sampleCode || pay.protocolSampleId || pIdx) + } + + function activeScheduleIndices(schedules: Record[]): number[] { + const idx: number[] = [] + schedules.forEach((s, i) => { + if (String(s.scheduleStatus || 'open') !== 'void' && !s._deleted) idx.push(i) + }) + return idx + } + /** 仅财务已确认(yjf)锁定;付款确认中仍可改,直至财务确认 */ function isTransferLocked(tr: Record) { return tr.payStatus === 'yjf' @@ -458,6 +475,37 @@ const pay = { ...list[pIdx] } const schedules = [...(pay.schedules || [])] schedules[sIdx] = { ...schedules[sIdx], ...patch } + const mode = String(pay.payMode || '') + const key = samplePayKey(pay, pIdx) + const activeIdx = activeScheduleIndices(schedules) + + if ('dueAmount' in patch && mode === 'fq' && activeIdx.length >= 2) { + const lastIdx = activeIdx[activeIdx.length - 1] + if (sIdx === lastIdx) { + lastPeriodDueOverrideByKey.value = { + ...lastPeriodDueOverrideByKey.value, + [key]: true + } + } else if (activeIdx.slice(0, -1).includes(sIdx)) { + lastPeriodDueOverrideByKey.value = { + ...lastPeriodDueOverrideByKey.value, + [key]: false + } + const lastSch = schedules[lastIdx] + const canFill = shouldAutoFillLastPeriodDue({ + lastManualOverride: false, + lastLocked: isScheduleDueLocked(lastSch) + }) + if (canFill) { + const priors = activeIdx.slice(0, -1).map((i) => schedules[i]?.dueAmount) + const lastAmt = calcLastPeriodDueAmount(pay.contractAmount, priors) + if (lastAmt != null) { + schedules[lastIdx] = { ...schedules[lastIdx], dueAmount: lastAmt } + } + } + } + } + pay.schedules = schedules if ('dueAmount' in patch) { pay.installmentRatios = schedules.map((s: Record) => { @@ -469,6 +517,22 @@ emit('update:modelValue', list) } + function onLumpSumDeadlineChange(pIdx: number, raw: string) { + const list = [...(props.modelValue || [])] + const pay = { ...list[pIdx] } + const deadline = raw || '' + pay.lumpSumDeadline = deadline + if (String(pay.payMode || '') === 'dj') { + pay.schedules = applyLumpSumDeadlineToLastDueDate( + [...(pay.schedules || [])], + deadline, + isScheduleDueLocked + ) + } + list[pIdx] = pay + emit('update:modelValue', list) + } + function sampleTitle(pay: Record) { const sample = (props.samples || []).find( (s) => String(s.sampleCode || '') === String(pay.sampleCode || '')