提交
This commit is contained in:
1 parent
afd517ce10
commit
eec61cb5dd
82 files changed
+16585
-9026
No files matched your search
@@ -758,7 +758,7 @@
|
||||
step="any"
|
||||
:min="1"
|
||||
style="width: 100%"
|
||||
@change="tryGenerateInstallmentDetails"
|
||||
@change="handleFqParamsChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="每期间隔(月)">
|
||||
@@ -766,9 +766,9 @@
|
||||
v-model="formData.installmentIntervalMonths"
|
||||
type="number"
|
||||
step="any"
|
||||
:min="1"
|
||||
:min="0.1"
|
||||
style="width: 100%"
|
||||
@change="tryGenerateInstallmentDetails"
|
||||
@change="handleFqParamsChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="分期开始时间">
|
||||
@@ -780,7 +780,7 @@
|
||||
placeholder="选择开始时间"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
@change="tryGenerateInstallmentDetails"
|
||||
@change="handleFqParamsChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
@@ -790,7 +790,11 @@
|
||||
<!-- 缴费明细 -->
|
||||
<div class="section-subtitle" style="margin-top: 20px">缴费明细</div>
|
||||
<div style="margin-bottom: 14px">
|
||||
<el-button type="primary" @click="addPaymentDetail">
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="!canAddPaymentDetail(formData.payMode, formData.paymentDetails)"
|
||||
@click="addPaymentDetail"
|
||||
>
|
||||
<el-icon><Plus /></el-icon>
|
||||
添加明细
|
||||
</el-button>
|
||||
@@ -834,6 +838,7 @@
|
||||
{{ paymentDetailSummary(detail) }}
|
||||
</span>
|
||||
<el-button
|
||||
v-if="!isPayDetailLocked(detail)"
|
||||
link
|
||||
type="danger"
|
||||
size="small"
|
||||
@@ -872,6 +877,7 @@
|
||||
<AmountInput
|
||||
v-model="detail.transferAmount"
|
||||
:min="0"
|
||||
:disabled="isPayDetailLocked(detail)"
|
||||
placeholder="请输入转账金额"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -882,6 +888,7 @@
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择时间"
|
||||
clearable
|
||||
:disabled="isPayDetailLocked(detail)"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -889,6 +896,7 @@
|
||||
<el-select
|
||||
v-model="detail.payStatus"
|
||||
placeholder="状态"
|
||||
:disabled="isPayDetailLocked(detail)"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="待缴费" value="dfj" />
|
||||
@@ -906,12 +914,13 @@
|
||||
format="YYYY-MM-DD"
|
||||
placeholder="选择应缴费时间"
|
||||
clearable
|
||||
:disabled="isPayDetailLocked(detail)"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<!-- 每条明细的附件上传区 -->
|
||||
<div class="payment-detail-card__attachments">
|
||||
<div v-if="!isPayDetailLocked(detail)" class="payment-detail-card__attachments">
|
||||
<div class="attachment-upload">
|
||||
<div class="attachment-list">
|
||||
<el-upload
|
||||
@@ -1175,10 +1184,24 @@
|
||||
} from '@/api/admin'
|
||||
import { fetchEthnicList, type EthnicOption } from '@/api/ethnic'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { syncFqPaymentDetailRowCount } from '@/utils/protocol-payment-installment'
|
||||
import {
|
||||
calcFqInstallmentDueDate,
|
||||
syncFqPaymentDetailRowCount
|
||||
} from '@/utils/protocol-payment-installment'
|
||||
applyPayModeSwitch,
|
||||
canAddPaymentDetail,
|
||||
evaluatePayModeSwitch,
|
||||
isPayDetailLocked,
|
||||
markPayDetailLocks,
|
||||
sumPaidAmount,
|
||||
type PayMode
|
||||
} from '@/utils/protocol-pay-mode-switch'
|
||||
import {
|
||||
applyFqParamsChange,
|
||||
evaluateFqParamsChange,
|
||||
fqParamsSnapshot,
|
||||
generateInitialFqDetails,
|
||||
isSameFqParams,
|
||||
type FqParams
|
||||
} from '@/utils/protocol-fq-params-change'
|
||||
import AmountInput from '@/components/common/amount-input/index.vue'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import {
|
||||
@@ -1199,6 +1222,16 @@
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
/** 上次确认生效的缴费模式(用于切换取消/拦截时回滚) */
|
||||
const lastConfirmedPayMode = ref<PayMode>('dj')
|
||||
|
||||
/** 上次确认生效的分期参数(用于编辑时修改期数/间隔/开始时间回滚) */
|
||||
const lastConfirmedFqParams = ref<FqParams>({
|
||||
installmentCount: null,
|
||||
installmentIntervalMonths: null,
|
||||
installmentStartDate: ''
|
||||
})
|
||||
|
||||
const customerId = ref<string | null>(null)
|
||||
const customerOptions = ref<
|
||||
Array<{ id: string; customerName: string; phoneNo: string; idCardNo: string }>
|
||||
@@ -1544,6 +1577,12 @@
|
||||
batchTransportVisible.value = false
|
||||
Object.assign(batchTransport, createEmptyTransport())
|
||||
activeFormTab.value = 'basic'
|
||||
lastConfirmedPayMode.value = 'dj'
|
||||
lastConfirmedFqParams.value = {
|
||||
installmentCount: null,
|
||||
installmentIntervalMonths: null,
|
||||
installmentStartDate: ''
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToSection(key: string) {
|
||||
@@ -1627,6 +1666,7 @@
|
||||
}
|
||||
|
||||
function addPaymentDetail() {
|
||||
if (!canAddPaymentDetail(formData.payMode, formData.paymentDetails)) return
|
||||
const now = new Date()
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
const defaultTime = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
|
||||
@@ -1702,77 +1742,180 @@
|
||||
return Number.isFinite(n) ? Math.trunc(n) : undefined
|
||||
}
|
||||
|
||||
function toFloatOrUndefined(val: unknown): number | undefined {
|
||||
if (val == null || val === '') return undefined
|
||||
const n = Number(val)
|
||||
return Number.isFinite(n) ? n : undefined
|
||||
}
|
||||
|
||||
function fqPaymentContext() {
|
||||
return {
|
||||
payMode: formData.payMode,
|
||||
installmentCount: toIntOrUndefined(formData.installmentCount) ?? null,
|
||||
contractAmount: formData.contractAmount,
|
||||
installmentStartDate: formData.installmentStartDate,
|
||||
installmentIntervalMonths: toIntOrUndefined(formData.installmentIntervalMonths) ?? null
|
||||
installmentIntervalMonths: toFloatOrUndefined(formData.installmentIntervalMonths) ?? null
|
||||
}
|
||||
}
|
||||
|
||||
/** 分期模式:三个参数都填好后生成/补齐缴费明细行(已有明细时仅补齐期次,不整表覆盖) */
|
||||
function tryGenerateInstallmentDetails() {
|
||||
if (formData.payMode !== 'fq') return
|
||||
const count = formData.installmentCount
|
||||
const interval = formData.installmentIntervalMonths
|
||||
const startDate = formData.installmentStartDate
|
||||
if (!count || !interval || !startDate) return
|
||||
function payModeSwitchContext() {
|
||||
return {
|
||||
contractAmount: formData.contractAmount,
|
||||
lumpSumDeadline: formData.lumpSumDeadline,
|
||||
installmentCount: toIntOrUndefined(formData.installmentCount) ?? null,
|
||||
installmentIntervalMonths: toFloatOrUndefined(formData.installmentIntervalMonths) ?? null,
|
||||
installmentStartDate: formData.installmentStartDate
|
||||
}
|
||||
}
|
||||
|
||||
if (formData.paymentDetails.length) {
|
||||
syncFqPaymentDetailRowCount(fqPaymentContext(), formData.paymentDetails)
|
||||
syncPaymentDueAmounts()
|
||||
formData.paymentDetails.forEach((d, i) => {
|
||||
d.installmentNo = i + 1
|
||||
d.installmentDueDate = calcFqInstallmentDueDate(startDate, i, interval)
|
||||
})
|
||||
function getFqParamsFromForm(): FqParams {
|
||||
return {
|
||||
installmentCount: toIntOrUndefined(formData.installmentCount) ?? null,
|
||||
installmentIntervalMonths: toFloatOrUndefined(formData.installmentIntervalMonths) ?? null,
|
||||
installmentStartDate: formData.installmentStartDate || ''
|
||||
}
|
||||
}
|
||||
|
||||
function restoreFqParams(params: FqParams) {
|
||||
formData.installmentCount = params.installmentCount
|
||||
formData.installmentIntervalMonths = params.installmentIntervalMonths
|
||||
formData.installmentStartDate = params.installmentStartDate || ''
|
||||
}
|
||||
|
||||
function syncLastConfirmedFqParams() {
|
||||
lastConfirmedFqParams.value = fqParamsSnapshot(getFqParamsFromForm())
|
||||
}
|
||||
|
||||
/**
|
||||
* 分期参数变更(期数 / 间隔 / 开始时间):校验 → 确认 → 重建未缴期次
|
||||
* 与缴费模式切换独立;保留 yjf 已缴行,作废原 dfj 后按剩余金额重建。
|
||||
*/
|
||||
async function handleFqParamsChange() {
|
||||
if (formData.payMode !== 'fq') return
|
||||
|
||||
const newParams = getFqParamsFromForm()
|
||||
const oldParams = lastConfirmedFqParams.value
|
||||
|
||||
if (isSameFqParams(oldParams, newParams)) return
|
||||
|
||||
if (
|
||||
!newParams.installmentCount ||
|
||||
!newParams.installmentIntervalMonths ||
|
||||
!newParams.installmentStartDate
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const dueAmounts = calcInstallmentDueAmounts(formData.contractAmount, count)
|
||||
const details = []
|
||||
for (let i = 0; i < count; i++) {
|
||||
details.push({
|
||||
transferType: '',
|
||||
dueAmount: dueAmounts[i] ?? null,
|
||||
transferAmount: null,
|
||||
transferTime: '',
|
||||
payStatus: 'dfj',
|
||||
installmentNo: i + 1,
|
||||
installmentDueDate: calcFqInstallmentDueDate(startDate, i, interval),
|
||||
attachments: [] as Array<{
|
||||
id?: string
|
||||
fileName: string
|
||||
fileUrl: string
|
||||
fileSize: number
|
||||
fileSizeDisplay?: string
|
||||
fileType?: string
|
||||
_uploading?: boolean
|
||||
}>
|
||||
})
|
||||
if (formData.paymentDetails.length === 0) {
|
||||
formData.paymentDetails = generateInitialFqDetails(newParams, formData.contractAmount)
|
||||
initPaymentDetailExpandFlags(formData.paymentDetails)
|
||||
markPayDetailLocks(formData.paymentDetails)
|
||||
syncLastConfirmedFqParams()
|
||||
return
|
||||
}
|
||||
initPaymentDetailExpandFlags(details)
|
||||
formData.paymentDetails = details
|
||||
|
||||
const evaluation = evaluateFqParamsChange(
|
||||
oldParams,
|
||||
newParams,
|
||||
formData.paymentDetails,
|
||||
formData.contractAmount
|
||||
)
|
||||
|
||||
if (evaluation.action === 'noop') {
|
||||
syncLastConfirmedFqParams()
|
||||
return
|
||||
}
|
||||
|
||||
if (evaluation.action === 'blocked') {
|
||||
restoreFqParams(oldParams)
|
||||
await ElMessageBox.alert(evaluation.reason, '无法修改分期参数', { type: 'warning' })
|
||||
return
|
||||
}
|
||||
|
||||
if (evaluation.action === 'confirm') {
|
||||
try {
|
||||
await ElMessageBox.confirm(evaluation.summary, '修改分期参数', {
|
||||
confirmButtonText: '确认修改',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
restoreFqParams(oldParams)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
formData.paymentDetails = applyFqParamsChange(
|
||||
newParams,
|
||||
formData.paymentDetails,
|
||||
formData.contractAmount
|
||||
)
|
||||
initPaymentDetailExpandFlags(formData.paymentDetails)
|
||||
markPayDetailLocks(formData.paymentDetails)
|
||||
syncLastConfirmedFqParams()
|
||||
}
|
||||
|
||||
/** 缴费模式切换回调 */
|
||||
function onPayModeChange(val: string) {
|
||||
if (val === 'fq') {
|
||||
applyFqPaymentDefaults({ resetCountInterval: true })
|
||||
tryGenerateInstallmentDetails()
|
||||
} else {
|
||||
// 趸交模式:将趸交截止时间同步到所有明细的应缴费时间
|
||||
syncPaymentDueAmounts()
|
||||
/** 缴费模式切换:校验 → 确认 → 变换明细 */
|
||||
async function onPayModeChange(val: string) {
|
||||
const toMode = val as PayMode
|
||||
const fromMode = lastConfirmedPayMode.value
|
||||
if (toMode === fromMode) return
|
||||
|
||||
const evaluation = evaluatePayModeSwitch(
|
||||
fromMode,
|
||||
toMode,
|
||||
formData.paymentDetails,
|
||||
payModeSwitchContext()
|
||||
)
|
||||
|
||||
if (evaluation.action === 'blocked') {
|
||||
formData.payMode = fromMode
|
||||
await ElMessageBox.alert(evaluation.reason, '无法切换缴费模式', { type: 'warning' })
|
||||
return
|
||||
}
|
||||
|
||||
if (evaluation.action === 'confirm') {
|
||||
try {
|
||||
await ElMessageBox.confirm(evaluation.summary, '切换缴费模式', {
|
||||
confirmButtonText: '确认切换',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
} catch {
|
||||
formData.payMode = fromMode
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (toMode === 'fq') {
|
||||
applyFqPaymentDefaults({ resetCountInterval: false })
|
||||
}
|
||||
|
||||
formData.paymentDetails = applyPayModeSwitch(
|
||||
fromMode,
|
||||
toMode,
|
||||
formData.paymentDetails,
|
||||
payModeSwitchContext()
|
||||
)
|
||||
initPaymentDetailExpandFlags(formData.paymentDetails)
|
||||
markPayDetailLocks(formData.paymentDetails)
|
||||
|
||||
if (toMode === 'dj') {
|
||||
syncLumpSumDueDate()
|
||||
}
|
||||
|
||||
lastConfirmedPayMode.value = toMode
|
||||
if (toMode === 'fq') {
|
||||
syncLastConfirmedFqParams()
|
||||
}
|
||||
}
|
||||
|
||||
/** 趸交模式:将趸交截止时间同步到所有缴费明细的应缴费时间 */
|
||||
/** 趸交模式:将趸交截止时间同步到待缴明细的应缴费时间 */
|
||||
function syncLumpSumDueDate() {
|
||||
if (formData.payMode !== 'dj') return
|
||||
const deadline = formData.lumpSumDeadline || ''
|
||||
formData.paymentDetails.forEach((d) => {
|
||||
if (isPayDetailLocked(d)) return
|
||||
d.installmentDueDate = deadline
|
||||
})
|
||||
}
|
||||
@@ -1885,6 +2028,7 @@
|
||||
formData.installmentStartDate = String(pay.installmentStartDate || '')
|
||||
if (formData.payMode === 'fq') {
|
||||
applyFqPaymentDefaults()
|
||||
syncLastConfirmedFqParams()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1896,8 +2040,10 @@
|
||||
}))
|
||||
initPaymentDetailExpandFlags(details)
|
||||
formData.paymentDetails = details
|
||||
markPayDetailLocks(formData.paymentDetails)
|
||||
}
|
||||
syncFqPaymentDetailRowCount(fqPaymentContext(), formData.paymentDetails)
|
||||
lastConfirmedPayMode.value = (formData.payMode === 'fq' ? 'fq' : 'dj') as PayMode
|
||||
|
||||
const pa =
|
||||
(row as any).protocolAttachments || (bundle.protocol as any)?.protocolAttachments || ''
|
||||
@@ -2057,11 +2203,13 @@
|
||||
contractAmount: formData.contractAmount ?? undefined,
|
||||
lumpSumDeadline: formData.lumpSumDeadline || undefined,
|
||||
installmentCount: toIntOrUndefined(formData.installmentCount),
|
||||
installmentIntervalMonths: toIntOrUndefined(formData.installmentIntervalMonths),
|
||||
installmentIntervalMonths: toFloatOrUndefined(formData.installmentIntervalMonths),
|
||||
installmentStartDate: formData.installmentStartDate || undefined
|
||||
},
|
||||
paymentDetails: formData.paymentDetails.map(({ _expanded, ...d }) => ({
|
||||
transferType: d.transferType,
|
||||
paymentDetails: formData.paymentDetails.map(
|
||||
({ _expanded, _payModeLocked, _voided, ...d }) => ({
|
||||
id: d.id,
|
||||
transferType: d.transferType,
|
||||
dueAmount: d.dueAmount,
|
||||
transferAmount: d.transferAmount,
|
||||
transferTime: d.transferTime,
|
||||
@@ -2087,7 +2235,7 @@
|
||||
fileSizeDisplay: a.fileSizeDisplay,
|
||||
fileType: a.fileType
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
if (isEdit.value && editingId.value) {
|
||||
@@ -2527,21 +2675,29 @@
|
||||
return amounts
|
||||
}
|
||||
|
||||
/** 按缴费模式同步各明细应缴费金额 */
|
||||
/** 按缴费模式同步各明细应缴费金额(跳过已锁定明细) */
|
||||
function syncPaymentDueAmounts() {
|
||||
const contract = formData.contractAmount
|
||||
if (contract == null) return
|
||||
if (formData.payMode === 'dj') {
|
||||
formData.paymentDetails.forEach((d) => {
|
||||
d.dueAmount = contract
|
||||
const paidSum = sumPaidAmount(formData.paymentDetails)
|
||||
const remaining = Math.round((Number(contract) - paidSum) * 100) / 100
|
||||
const dfjRows = formData.paymentDetails.filter(
|
||||
(d) => !isPayDetailLocked(d) && String(d.payStatus || 'dfj') === 'dfj'
|
||||
)
|
||||
const targetAmount = paidSum > 0 ? remaining : contract
|
||||
dfjRows.forEach((d, i) => {
|
||||
if (i === 0) d.dueAmount = targetAmount
|
||||
})
|
||||
return
|
||||
}
|
||||
if (formData.payMode === 'fq') {
|
||||
if (formData.paymentDetails.some((d) => isPayDetailLocked(d))) return
|
||||
const count = formData.installmentCount || formData.paymentDetails.length
|
||||
if (!count) return
|
||||
const amounts = calcInstallmentDueAmounts(Number(contract), count)
|
||||
const amounts = calcInstallmentDueAmounts(Number(contract), Number(count))
|
||||
formData.paymentDetails.forEach((d, i) => {
|
||||
if (isPayDetailLocked(d)) return
|
||||
if (amounts[i] !== undefined) d.dueAmount = amounts[i]
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user