提交
This commit is contained in:
1 parent
cdaa0ac273
commit
b7cd657f32
32 files changed
+2885
-1936
No files matched your search
@@ -529,10 +529,19 @@
|
||||
<div class="section-subtitle">缴费主表</div>
|
||||
<div class="form-grid form-grid--col6">
|
||||
<el-form-item label="缴费模式">
|
||||
<el-radio-group v-model="formData.payMode" @change="onPayModeChange">
|
||||
<el-radio label="dj">趸交</el-radio>
|
||||
<el-radio label="fq">分期</el-radio>
|
||||
</el-radio-group>
|
||||
<el-select
|
||||
v-model="formData.payMode"
|
||||
placeholder="请选择缴费模式"
|
||||
style="width: 100%"
|
||||
@change="onPayModeChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in payModeOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同金额(元)">
|
||||
<AmountInput
|
||||
@@ -556,7 +565,7 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-if="formData.payMode === 'fq'">
|
||||
<template v-if="isInstallmentPayMode(formData.payMode)">
|
||||
<el-form-item label="分期期数">
|
||||
<el-input
|
||||
v-model="formData.installmentCount"
|
||||
@@ -602,39 +611,6 @@
|
||||
</el-form-item>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="formData.payMode === 'fq'" class="installment-amounts-row">
|
||||
<el-form-item label="分期金额(元)" class="installment-amounts-field">
|
||||
<div class="installment-amounts-cells">
|
||||
<div
|
||||
v-for="(_amount, rIdx) in formData.installmentRatios"
|
||||
:key="rIdx"
|
||||
class="installment-amount-cell"
|
||||
>
|
||||
<span class="installment-amount-cell-no">{{ rIdx + 1 }}期</span>
|
||||
<AmountInput
|
||||
v-model="formData.installmentRatios[rIdx]"
|
||||
:min="0"
|
||||
placeholder="金额"
|
||||
@change="onInstallmentAmountChange(rIdx)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<div class="installment-amounts-summary-row">
|
||||
<span
|
||||
class="installment-amounts-sum"
|
||||
:class="{ 'is-error': !!installmentAmountsError }"
|
||||
>
|
||||
合计 {{ fmtMoney(installmentAmountsSum) }}
|
||||
<template v-if="formData.contractAmount != null">
|
||||
/ 合同 {{ fmtMoney(formData.contractAmount) }}
|
||||
</template>
|
||||
</span>
|
||||
<div v-if="installmentAmountsError" class="form-tip form-tip--error">
|
||||
{{ installmentAmountsError }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 缴费明细 -->
|
||||
@@ -693,7 +669,7 @@
|
||||
type="danger"
|
||||
size="small"
|
||||
class="detail-delete"
|
||||
@click.stop="formData.paymentDetails.splice(dIdx, 1)"
|
||||
@click.stop="removePaymentDetail(dIdx)"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
@@ -963,22 +939,28 @@
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import {
|
||||
DEFAULT_INSTALLMENT_ADVANCE_DAYS,
|
||||
isInstallmentAmountsValid,
|
||||
normalizeInstallmentAmounts,
|
||||
resizeInstallmentAmounts,
|
||||
scaleInstallmentAmountsToContract,
|
||||
sumInstallmentAmounts,
|
||||
syncFqPaymentDetailRowCount
|
||||
} from '@/utils/protocol-payment-installment'
|
||||
import {
|
||||
applyPayModeSwitch,
|
||||
canAddPaymentDetail,
|
||||
createPaymentDetailRow,
|
||||
evaluatePayModeSwitch,
|
||||
isPayDetailLocked,
|
||||
markPayDetailLocks,
|
||||
sumPaidAmount,
|
||||
type PayMode
|
||||
} from '@/utils/protocol-pay-mode-switch'
|
||||
import {
|
||||
availablePayModes,
|
||||
getActiveSampleCount,
|
||||
isInstallmentPayMode,
|
||||
isPayModeValidForSampleCount,
|
||||
resolvePaymentPreset,
|
||||
type PayMode as PresetPayMode
|
||||
} from '@/utils/protocol-payment-presets'
|
||||
import {
|
||||
applyFqParamsChange,
|
||||
evaluateFqParamsChange,
|
||||
@@ -1019,6 +1001,21 @@
|
||||
/** 上次确认生效的缴费模式(用于切换取消/拦截时回滚) */
|
||||
const lastConfirmedPayMode = ref<PayMode>('dj')
|
||||
|
||||
const activeSampleCount = computed(() => getActiveSampleCount(formData.samples))
|
||||
|
||||
const payModeOptions = computed(() => {
|
||||
const modes = [...availablePayModes(activeSampleCount.value)]
|
||||
const current = String(formData.payMode || '') as PresetPayMode
|
||||
if (current && !modes.includes(current)) {
|
||||
modes.push(current)
|
||||
}
|
||||
return modes.map((value) => ({ value, label: payModeLabel(value) }))
|
||||
})
|
||||
|
||||
function hasLockedPaymentDetails() {
|
||||
return formData.paymentDetails.some((d) => isPayDetailLocked(d))
|
||||
}
|
||||
|
||||
/** 上次确认生效的分期参数(用于编辑时修改期数/间隔/开始时间回滚) */
|
||||
const lastConfirmedFqParams = ref<FqParams>({
|
||||
installmentCount: null,
|
||||
@@ -1357,6 +1354,9 @@
|
||||
|
||||
function scrollToSection(key: string) {
|
||||
activeFormTab.value = key
|
||||
if (key === 'payment') {
|
||||
maybeApplyPaymentPresetOnTabEnter()
|
||||
}
|
||||
const el = document.getElementById(`form-section-${key}`)
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
@@ -1386,6 +1386,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function removePaymentDetail(dIdx: number) {
|
||||
const detail = formData.paymentDetails[dIdx]
|
||||
if (detail && isPayDetailLocked(detail)) return
|
||||
formData.paymentDetails.splice(dIdx, 1)
|
||||
syncContractAmountFromPaymentDetails()
|
||||
}
|
||||
|
||||
function addPaymentDetail() {
|
||||
if (!canAddPaymentDetail(formData.payMode, formData.paymentDetails)) return
|
||||
const now = new Date()
|
||||
@@ -1446,8 +1453,13 @@
|
||||
*/
|
||||
function applyFqPaymentDefaults(options?: { resetCountInterval?: boolean }) {
|
||||
const reset = options?.resetCountInterval ?? false
|
||||
const sampleCount = getActiveSampleCount(formData.samples)
|
||||
const preset = resolvePaymentPreset(sampleCount, formData.payMode as PresetPayMode)
|
||||
if (preset?.contractAmount != null) {
|
||||
formData.contractAmount = preset.contractAmount
|
||||
}
|
||||
if (reset || isFqNumericFieldEmpty(formData.installmentCount)) {
|
||||
formData.installmentCount = 6
|
||||
formData.installmentCount = preset?.installmentCount ?? 6
|
||||
}
|
||||
if (reset || isFqNumericFieldEmpty(formData.installmentIntervalMonths)) {
|
||||
formData.installmentIntervalMonths = 6
|
||||
@@ -1459,11 +1471,58 @@
|
||||
formData.installmentAdvanceDays = DEFAULT_INSTALLMENT_ADVANCE_DAYS
|
||||
}
|
||||
const count = toIntOrUndefined(formData.installmentCount) || 6
|
||||
formData.installmentRatios = normalizeInstallmentAmounts(
|
||||
formData.installmentRatios || [],
|
||||
formData.contractAmount,
|
||||
count
|
||||
)
|
||||
if (preset?.installmentAmounts?.length) {
|
||||
formData.installmentRatios = [...preset.installmentAmounts]
|
||||
} else {
|
||||
formData.installmentRatios = normalizeInstallmentAmounts(
|
||||
formData.installmentRatios || [],
|
||||
formData.contractAmount,
|
||||
count
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function applyPaymentPresetForMode(payMode: PresetPayMode) {
|
||||
const sampleCount = getActiveSampleCount(formData.samples)
|
||||
const preset = resolvePaymentPreset(sampleCount, payMode)
|
||||
if (!preset) return
|
||||
formData.contractAmount = preset.contractAmount
|
||||
if (isInstallmentPayMode(payMode) && preset.installmentCount) {
|
||||
formData.installmentCount = preset.installmentCount
|
||||
if (isFqNumericFieldEmpty(formData.installmentIntervalMonths)) {
|
||||
formData.installmentIntervalMonths = 6
|
||||
}
|
||||
if (!formData.installmentStartDate) {
|
||||
formData.installmentStartDate = defaultInstallmentStartDateYmd()
|
||||
}
|
||||
if (formData.installmentAdvanceDays == null) {
|
||||
formData.installmentAdvanceDays = DEFAULT_INSTALLMENT_ADVANCE_DAYS
|
||||
}
|
||||
formData.installmentRatios = [...(preset.installmentAmounts || [])]
|
||||
formData.paymentDetails = generateInitialFqDetails(
|
||||
getFqParamsFromForm(),
|
||||
preset.contractAmount
|
||||
)
|
||||
initPaymentDetailExpandFlags(formData.paymentDetails)
|
||||
markPayDetailLocks(formData.paymentDetails)
|
||||
syncLastConfirmedFqParams()
|
||||
} else {
|
||||
formData.paymentDetails = [
|
||||
createPaymentDetailRow({
|
||||
dueAmount: preset.contractAmount,
|
||||
installmentDueDate: formData.lumpSumDeadline || '',
|
||||
expanded: true
|
||||
})
|
||||
]
|
||||
initPaymentDetailExpandFlags(formData.paymentDetails)
|
||||
markPayDetailLocks(formData.paymentDetails)
|
||||
}
|
||||
}
|
||||
|
||||
function maybeApplyPaymentPresetOnTabEnter() {
|
||||
if (hasLockedPaymentDetails()) return
|
||||
if (formData.paymentDetails.length > 0) return
|
||||
applyPaymentPresetForMode(formData.payMode as PresetPayMode)
|
||||
}
|
||||
|
||||
function toIntOrUndefined(val: unknown): number | undefined {
|
||||
@@ -1529,7 +1588,7 @@
|
||||
* 与缴费模式切换独立;保留 yjf 已缴行,作废原 dfj 后按剩余金额重建。
|
||||
*/
|
||||
async function handleFqParamsChange() {
|
||||
if (formData.payMode !== 'fq') return
|
||||
if (formData.payMode !== 'fq' && formData.payMode !== 'djfq') return
|
||||
|
||||
const newParams = getFqParamsFromForm()
|
||||
const oldParams = lastConfirmedFqParams.value
|
||||
@@ -1625,25 +1684,35 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (toMode === 'fq') {
|
||||
applyFqPaymentDefaults({ resetCountInterval: false })
|
||||
if (!hasLockedPaymentDetails()) {
|
||||
applyPaymentPresetForMode(toMode as PresetPayMode)
|
||||
} else {
|
||||
if (isInstallmentPayMode(toMode)) {
|
||||
applyFqPaymentDefaults({ resetCountInterval: fromMode === 'dj' })
|
||||
}
|
||||
formData.paymentDetails = applyPayModeSwitch(
|
||||
fromMode,
|
||||
toMode,
|
||||
formData.paymentDetails,
|
||||
payModeSwitchContext()
|
||||
)
|
||||
initPaymentDetailExpandFlags(formData.paymentDetails)
|
||||
markPayDetailLocks(formData.paymentDetails)
|
||||
const preset = resolvePaymentPreset(
|
||||
getActiveSampleCount(formData.samples),
|
||||
toMode as PresetPayMode
|
||||
)
|
||||
if (preset?.contractAmount != null) {
|
||||
formData.contractAmount = preset.contractAmount
|
||||
}
|
||||
}
|
||||
|
||||
formData.paymentDetails = applyPayModeSwitch(
|
||||
fromMode,
|
||||
toMode,
|
||||
formData.paymentDetails,
|
||||
payModeSwitchContext()
|
||||
)
|
||||
initPaymentDetailExpandFlags(formData.paymentDetails)
|
||||
markPayDetailLocks(formData.paymentDetails)
|
||||
|
||||
if (toMode === 'dj') {
|
||||
syncLumpSumDueDate()
|
||||
}
|
||||
|
||||
lastConfirmedPayMode.value = toMode
|
||||
if (toMode === 'fq') {
|
||||
if (isInstallmentPayMode(toMode)) {
|
||||
syncLastConfirmedFqParams()
|
||||
}
|
||||
}
|
||||
@@ -1770,7 +1839,7 @@
|
||||
if (loadedAmounts.length) {
|
||||
formData.installmentRatios = loadedAmounts.map((v: unknown) => Number(v))
|
||||
}
|
||||
if (formData.payMode === 'fq') {
|
||||
if (isInstallmentPayMode(formData.payMode)) {
|
||||
applyFqPaymentDefaults()
|
||||
const count = toIntOrUndefined(formData.installmentCount) || 6
|
||||
formData.installmentRatios = normalizeInstallmentAmounts(
|
||||
@@ -1793,7 +1862,9 @@
|
||||
markPayDetailLocks(formData.paymentDetails)
|
||||
}
|
||||
syncFqPaymentDetailRowCount(fqPaymentContext(), formData.paymentDetails)
|
||||
lastConfirmedPayMode.value = (formData.payMode === 'fq' ? 'fq' : 'dj') as PayMode
|
||||
lastConfirmedPayMode.value = (
|
||||
['fq', 'djfq'].includes(formData.payMode) ? formData.payMode : 'dj'
|
||||
) as PayMode
|
||||
|
||||
const pa =
|
||||
(row as any).protocolAttachments || (bundle.protocol as any)?.protocolAttachments || ''
|
||||
@@ -1880,16 +1951,16 @@
|
||||
async function handleSubmit() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate()
|
||||
if (formData.payMode === 'fq') {
|
||||
if (installmentAmountsError.value) {
|
||||
await ElMessageBox.alert(installmentAmountsError.value, '分期金额无效', { type: 'warning' })
|
||||
return
|
||||
}
|
||||
const amounts = (formData.installmentRatios || []).map((v) => Number(v || 0))
|
||||
if (!isInstallmentAmountsValid(amounts, formData.contractAmount)) {
|
||||
await ElMessageBox.alert('分期金额合计须等于合同金额', '分期金额无效', { type: 'warning' })
|
||||
return
|
||||
}
|
||||
const sampleCount = getActiveSampleCount(formData.samples)
|
||||
if (!isPayModeValidForSampleCount(formData.payMode as PresetPayMode, sampleCount)) {
|
||||
await ElMessageBox.alert(
|
||||
formData.payMode === 'djfq'
|
||||
? '「趸交+分期」仅适用于 2 个样本,请切换缴费模式'
|
||||
: '当前缴费模式与样本数量不匹配,请切换缴费模式',
|
||||
'无法保存',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!isEdit.value) {
|
||||
for (const sampleRow of formData.samples) {
|
||||
@@ -1969,8 +2040,7 @@
|
||||
installmentIntervalMonths: toFloatOrUndefined(formData.installmentIntervalMonths),
|
||||
installmentStartDate: formData.installmentStartDate || undefined,
|
||||
installmentAdvanceDays: toIntOrUndefined(formData.installmentAdvanceDays),
|
||||
installmentRatios:
|
||||
formData.payMode === 'fq'
|
||||
installmentRatios: isInstallmentPayMode(formData.payMode)
|
||||
? (formData.installmentRatios || []).map((v) => Number(v || 0))
|
||||
: undefined
|
||||
},
|
||||
@@ -2596,7 +2666,7 @@
|
||||
formData.payMode = parsed.payMode || 'dj'
|
||||
lastConfirmedPayMode.value = formData.payMode as PayMode
|
||||
|
||||
if (parsed.payMode === 'fq') {
|
||||
if (isInstallmentPayMode(parsed.payMode || 'dj')) {
|
||||
applyFqPaymentDefaults()
|
||||
}
|
||||
|
||||
@@ -3111,38 +3181,32 @@
|
||||
handleFqParamsChange()
|
||||
}
|
||||
|
||||
function onInstallmentAmountChange(_changedIdx: number) {
|
||||
syncPaymentDueAmounts()
|
||||
handleFqParamsChange()
|
||||
/** 缴费明细应缴金额之和回写合同金额 */
|
||||
function syncContractAmountFromPaymentDetails() {
|
||||
if (!formData.paymentDetails.length) return
|
||||
const total = formData.paymentDetails.reduce(
|
||||
(sum, d) => sum + (Number(d.dueAmount) || 0),
|
||||
0
|
||||
)
|
||||
formData.contractAmount = Math.round(total * 100) / 100
|
||||
}
|
||||
|
||||
/** 缴费明细应缴金额变更:分期模式下回写分期金额,保持与主表一致 */
|
||||
/** 缴费明细应缴金额变更:分期回写 installmentRatios,并同步合同金额 */
|
||||
function onPaymentDetailDueAmountChange(dIdx: number) {
|
||||
if (formData.payMode !== 'fq') return
|
||||
const detail = formData.paymentDetails[dIdx]
|
||||
if (!detail || isPayDetailLocked(detail)) return
|
||||
const installmentNo = Number(detail.installmentNo)
|
||||
const ratioIdx = installmentNo >= 1 ? installmentNo - 1 : dIdx
|
||||
if (!formData.installmentRatios) formData.installmentRatios = []
|
||||
while (formData.installmentRatios.length <= ratioIdx) {
|
||||
formData.installmentRatios.push(0)
|
||||
if (formData.payMode === 'fq' || formData.payMode === 'djfq') {
|
||||
const installmentNo = Number(detail.installmentNo)
|
||||
const ratioIdx = installmentNo >= 1 ? installmentNo - 1 : dIdx
|
||||
if (!formData.installmentRatios) formData.installmentRatios = []
|
||||
while (formData.installmentRatios.length <= ratioIdx) {
|
||||
formData.installmentRatios.push(0)
|
||||
}
|
||||
formData.installmentRatios[ratioIdx] = Number(detail.dueAmount) || 0
|
||||
}
|
||||
formData.installmentRatios[ratioIdx] = Number(detail.dueAmount) || 0
|
||||
syncContractAmountFromPaymentDetails()
|
||||
}
|
||||
|
||||
const installmentAmountsError = computed(() => {
|
||||
const amounts = formData.installmentRatios || []
|
||||
if (!amounts.length || formData.contractAmount == null) return ''
|
||||
if (amounts.some((v) => Number(v) < 0)) return '分期金额不能为负数'
|
||||
const sum = sumInstallmentAmounts(amounts)
|
||||
if (Math.abs(sum - Number(formData.contractAmount)) > 0.01) {
|
||||
return `分期金额合计须等于合同金额(${fmtMoney(formData.contractAmount)})`
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const installmentAmountsSum = computed(() => sumInstallmentAmounts(formData.installmentRatios || []))
|
||||
|
||||
/** 按缴费模式同步各明细应缴费金额(跳过已锁定明细) */
|
||||
function syncPaymentDueAmounts() {
|
||||
const contract = formData.contractAmount
|
||||
@@ -3159,14 +3223,14 @@
|
||||
})
|
||||
return
|
||||
}
|
||||
if (formData.payMode === 'fq') {
|
||||
const count = formData.installmentCount || formData.paymentDetails.length
|
||||
if (formData.payMode === 'fq' || formData.payMode === 'djfq') {
|
||||
const count = Number(formData.installmentCount || formData.paymentDetails.length)
|
||||
if (!count) return
|
||||
const amounts = normalizeInstallmentAmounts(
|
||||
formData.installmentRatios || [],
|
||||
contract,
|
||||
Number(count)
|
||||
)
|
||||
const ratios = formData.installmentRatios || []
|
||||
const amounts =
|
||||
ratios.length === count
|
||||
? ratios.map((v) => Number(v || 0))
|
||||
: normalizeInstallmentAmounts(ratios, null, count)
|
||||
const paidRows = formData.paymentDetails.filter((d) => String(d.payStatus) === 'yjf')
|
||||
paidRows.forEach((d) => {
|
||||
const no = Number(d.installmentNo)
|
||||
@@ -3179,37 +3243,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
/** 合同金额手动变更:等比缩放分期金额并同步应缴费金额 */
|
||||
/** 合同金额手动变更:趸交时同步待缴明细应缴金额 */
|
||||
function onContractAmountChange() {
|
||||
if (formData.payMode === 'fq' && (formData.installmentRatios || []).length) {
|
||||
formData.installmentRatios = scaleInstallmentAmountsToContract(
|
||||
formData.installmentRatios,
|
||||
formData.contractAmount
|
||||
)
|
||||
if (formData.payMode === 'dj') {
|
||||
syncPaymentDueAmounts()
|
||||
}
|
||||
syncPaymentDueAmounts()
|
||||
}
|
||||
|
||||
/** 根据非作废样本价格之和同步合同金额,并联动应缴费金额 */
|
||||
function syncContractAmount() {
|
||||
const total = formData.samples.reduce((sum, s) => {
|
||||
if (s.sampleStatus === 'yzf') return sum
|
||||
return sum + (Number(s.price) || 0)
|
||||
}, 0)
|
||||
const nextContract = Math.round(total * 100) / 100
|
||||
if (formData.payMode === 'fq' && (formData.installmentRatios || []).length) {
|
||||
formData.installmentRatios = scaleInstallmentAmountsToContract(
|
||||
formData.installmentRatios,
|
||||
nextContract
|
||||
)
|
||||
}
|
||||
formData.contractAmount = nextContract
|
||||
syncPaymentDueAmounts()
|
||||
}
|
||||
|
||||
/** 样本价格或状态变更 */
|
||||
/** 样本价格或状态变更(不自动改合同金额) */
|
||||
function onSamplePriceChange() {
|
||||
syncContractAmount()
|
||||
// 保留样本价格字段,不与合同金额联动
|
||||
}
|
||||
|
||||
/* ====== 母亲身份证号 ↔ 出生年月联动 ====== */
|
||||
@@ -3232,6 +3275,22 @@
|
||||
() => calcBirthFromIdCard()
|
||||
)
|
||||
|
||||
/** 样本数变化时按当前缴费模式刷新默认定价(新增单;无已缴锁定明细时整单重填) */
|
||||
watch(activeSampleCount, () => {
|
||||
if (isEdit.value) return
|
||||
if (hasLockedPaymentDetails()) {
|
||||
const preset = resolvePaymentPreset(
|
||||
getActiveSampleCount(formData.samples),
|
||||
formData.payMode as PresetPayMode
|
||||
)
|
||||
if (preset?.contractAmount != null) {
|
||||
formData.contractAmount = preset.contractAmount
|
||||
}
|
||||
return
|
||||
}
|
||||
applyPaymentPresetForMode(formData.payMode as PresetPayMode)
|
||||
})
|
||||
|
||||
/* ====== 趸交截止时间联动 ====== */
|
||||
watch(
|
||||
() => formData.lumpSumDeadline,
|
||||
|
||||
Reference in new issue
Block a user