feat(web-admin): per-sample payment UI and refund registration on protocol form
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
a328b9ff2d
commit
4e71a13165
5 files changed
+1280
-19
No files matched your search
@@ -683,6 +683,43 @@
|
||||
<div class="section-title-left"><h3>缴费信息</h3></div>
|
||||
</div>
|
||||
|
||||
<!-- v2: 按样本缴费 -->
|
||||
<template v-if="paymentModelVersion >= 2">
|
||||
<el-alert
|
||||
v-if="presignDepositHint"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="presign-deposit-hint"
|
||||
:title="presignDepositHintText"
|
||||
/>
|
||||
<div class="payment-main-card" style="margin-bottom: 16px">
|
||||
<div class="section-subtitle">协议总金额</div>
|
||||
<div class="form-grid form-grid--col4">
|
||||
<el-form-item label="协议总金额(元)">
|
||||
<AmountInput
|
||||
v-model="formData.protocolTotalAmount"
|
||||
:min="0"
|
||||
placeholder="手填协议总金额"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="样本合同合计(元)">
|
||||
<el-input :model-value="fmtMoney(sampleContractSum)" disabled />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<p v-if="protocolTotalHint" class="muted-hint warning-hint">{{ protocolTotalHint }}</p>
|
||||
</div>
|
||||
<SamplePaymentPanel
|
||||
v-model="formData.samplePayments"
|
||||
:samples="formData.samples"
|
||||
:protocol-id="editingId || undefined"
|
||||
/>
|
||||
<ProtocolRefundRecordBlock :protocol-id="editingId || undefined" />
|
||||
</template>
|
||||
|
||||
<!-- v1: 旧整单缴费主表(历史协议) -->
|
||||
<template v-else>
|
||||
<!-- 缴费主表 -->
|
||||
<div class="payment-main-card">
|
||||
<div class="section-subtitle">缴费主表</div>
|
||||
@@ -998,6 +1035,7 @@
|
||||
:image-size="48"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -1142,6 +1180,13 @@
|
||||
payStatusLabel,
|
||||
payStatusTagType
|
||||
} from '@/utils/protocol-payment-labels'
|
||||
import SamplePaymentPanel from './SamplePaymentPanel.vue'
|
||||
import ProtocolRefundRecordBlock from './ProtocolRefundRecordBlock.vue'
|
||||
import {
|
||||
parseInstallmentRatios,
|
||||
protocolTotalMismatchHint,
|
||||
sumSampleContractAmounts
|
||||
} from '@/utils/protocol-sample-payment'
|
||||
|
||||
const transferTypeOptions = ref<Api.Dict.DictDataOption[]>([])
|
||||
|
||||
@@ -1449,7 +1494,33 @@
|
||||
installmentAdvanceDays: DEFAULT_INSTALLMENT_ADVANCE_DAYS as number | null,
|
||||
installmentRatios: [] as number[] as number[],
|
||||
// Payment Details
|
||||
paymentDetails: [] as Array<Record<string, any>>
|
||||
paymentDetails: [] as Array<Record<string, any>>,
|
||||
// v2 per-sample payment
|
||||
paymentModelVersion: 2,
|
||||
protocolTotalAmount: null as number | null,
|
||||
sourcePresignOrderId: '',
|
||||
samplePayments: [] as Array<Record<string, any>>
|
||||
})
|
||||
|
||||
const paymentModelVersion = computed(() => Number(formData.paymentModelVersion || 2))
|
||||
const presignDepositHint = ref<Record<string, any> | null>(null)
|
||||
const sampleContractSum = computed(() => sumSampleContractAmounts(formData.samplePayments))
|
||||
const protocolTotalHint = computed(() =>
|
||||
protocolTotalMismatchHint(formData.protocolTotalAmount, sampleContractSum.value)
|
||||
)
|
||||
const presignDepositHintText = computed(() => {
|
||||
const h = presignDepositHint.value
|
||||
if (!h) return ''
|
||||
const amt = Number(h.confirmedDepositAmount || 0)
|
||||
const cnt = Number(h.confirmedTransferCount || 0)
|
||||
return `来源预签约已确认意向金合计 ¥${formatMoneyDisplay(amt)}(${cnt} 笔),仅提示不自动写入样本缴费`
|
||||
})
|
||||
|
||||
watch(sampleContractSum, (sum) => {
|
||||
if (paymentModelVersion.value < 2) return
|
||||
if (formData.protocolTotalAmount == null && sum > 0) {
|
||||
formData.protocolTotalAmount = Math.round(sum * 100) / 100
|
||||
}
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
@@ -1506,8 +1577,13 @@
|
||||
installmentStartDate: '',
|
||||
installmentAdvanceDays: DEFAULT_INSTALLMENT_ADVANCE_DAYS,
|
||||
installmentRatios: [] as number[],
|
||||
paymentDetails: []
|
||||
paymentDetails: [],
|
||||
paymentModelVersion: 2,
|
||||
protocolTotalAmount: null,
|
||||
sourcePresignOrderId: '',
|
||||
samplePayments: []
|
||||
})
|
||||
presignDepositHint.value = null
|
||||
customerId.value = null
|
||||
customerOptions.value = []
|
||||
customerSnapshot.value = null
|
||||
@@ -1966,6 +2042,18 @@
|
||||
formData.protocolStatus = String(prot.protocolStatus || formData.protocolStatus || 'dsx')
|
||||
formData.registerDate = String(prot.registerDate || formData.registerDate || '')
|
||||
formData.remark = String(prot.remark ?? formData.remark ?? '')
|
||||
formData.paymentModelVersion = Number(prot.paymentModelVersion || 1)
|
||||
formData.protocolTotalAmount =
|
||||
prot.protocolTotalAmount != null ? Number(prot.protocolTotalAmount) : null
|
||||
formData.sourcePresignOrderId = String(prot.sourcePresignOrderId || '')
|
||||
}
|
||||
presignDepositHint.value = (bundle.presignDepositHint as Record<string, any>) || null
|
||||
const samplePays = bundle.samplePayments as Array<Record<string, any>> | null
|
||||
if (samplePays && samplePays.length) {
|
||||
formData.samplePayments = samplePays.map((p) => ({
|
||||
...p,
|
||||
installmentRatios: parseInstallmentRatios(p.installmentRatios)
|
||||
}))
|
||||
}
|
||||
|
||||
const pc = bundle.partyCustomer as Record<string, any> | null
|
||||
@@ -2154,16 +2242,33 @@
|
||||
return
|
||||
}
|
||||
const sampleCount = getActiveSampleCount(formData.samples)
|
||||
if (!isPayModeValidForSampleCount(formData.payMode as PresetPayMode, sampleCount)) {
|
||||
submitLoading.value = false
|
||||
await ElMessageBox.alert(
|
||||
formData.payMode === 'djfq'
|
||||
? '「趸交+分期」仅适用于 2 个样本,请切换缴费模式'
|
||||
: '当前缴费模式与样本数量不匹配,请切换缴费模式',
|
||||
'无法保存',
|
||||
{ type: 'warning' }
|
||||
const isV2 = paymentModelVersion.value >= 2
|
||||
if (!isV2) {
|
||||
if (!isPayModeValidForSampleCount(formData.payMode as PresetPayMode, sampleCount)) {
|
||||
submitLoading.value = false
|
||||
await ElMessageBox.alert(
|
||||
formData.payMode === 'djfq'
|
||||
? '「趸交+分期」仅适用于 2 个样本,请切换缴费模式'
|
||||
: '当前缴费模式与样本数量不匹配,请切换缴费模式',
|
||||
'无法保存',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
const missingCode = formData.samples.some(
|
||||
(s) => String(s.sampleStatus || '') !== 'yzf' && !String(s.sampleCode || '').trim()
|
||||
)
|
||||
return
|
||||
if (missingCode && formData.samplePayments.length) {
|
||||
submitLoading.value = false
|
||||
ElMessage.warning('请先为样本分配样本编码,再保存按样本缴费方案')
|
||||
return
|
||||
}
|
||||
const mismatch = protocolTotalMismatchHint(
|
||||
formData.protocolTotalAmount,
|
||||
sumSampleContractAmounts(formData.samplePayments)
|
||||
)
|
||||
if (mismatch) ElMessage.warning(mismatch)
|
||||
}
|
||||
if (!isEdit.value) {
|
||||
for (const sampleRow of formData.samples) {
|
||||
@@ -2176,14 +2281,16 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
if (formData.payMode === 'dj') {
|
||||
if (!isV2 && formData.payMode === 'dj') {
|
||||
const dueHint = djDueAmountMismatchHint(formData.contractAmount, formData.paymentDetails)
|
||||
if (dueHint) {
|
||||
ElMessage.warning(dueHint)
|
||||
}
|
||||
}
|
||||
try {
|
||||
syncFqPaymentDetailRowCount(fqPaymentContext(), formData.paymentDetails)
|
||||
if (!isV2) {
|
||||
syncFqPaymentDetailRowCount(fqPaymentContext(), formData.paymentDetails)
|
||||
}
|
||||
const protocolAttachmentUrls = formData.attachments
|
||||
.map((a) => a.fileUrl)
|
||||
.filter(Boolean)
|
||||
@@ -2267,8 +2374,47 @@
|
||||
? String(s.sampleStatus).trim()
|
||||
: null,
|
||||
transport: transport?.transportType ? { ...transport } : undefined
|
||||
})),
|
||||
payment: {
|
||||
}))
|
||||
}
|
||||
|
||||
if (isV2) {
|
||||
payload.paymentModelVersion = 2
|
||||
payload.protocolTotalAmount = formData.protocolTotalAmount
|
||||
payload.sourcePresignOrderId = formData.sourcePresignOrderId || undefined
|
||||
payload.samplePayments = formData.samplePayments.map((p) => ({
|
||||
id: p.id,
|
||||
sampleCode: p.sampleCode,
|
||||
protocolSampleId: p.protocolSampleId || undefined,
|
||||
payMode: p.payMode,
|
||||
contractAmount: p.contractAmount ?? undefined,
|
||||
lumpSumDeadline: p.lumpSumDeadline || undefined,
|
||||
installmentCount: toIntOrUndefined(p.installmentCount),
|
||||
installmentIntervalMonths: toFloatOrUndefined(p.installmentIntervalMonths),
|
||||
installmentStartDate: p.installmentStartDate || undefined,
|
||||
installmentAdvanceDays: toIntOrUndefined(p.installmentAdvanceDays),
|
||||
installmentRatios: Array.isArray(p.installmentRatios)
|
||||
? p.installmentRatios.map((v: any) => Number(v || 0))
|
||||
: parseInstallmentRatios(p.installmentRatios),
|
||||
remark: p.remark || undefined,
|
||||
schedules: (p.schedules || []).map((sch: any) => ({
|
||||
scheduleNo: sch.scheduleNo,
|
||||
dueAmount: sch.dueAmount,
|
||||
dueDate: sch.dueDate || undefined,
|
||||
scheduleStatus: sch.scheduleStatus || 'open',
|
||||
remark: sch.remark,
|
||||
transfers: (sch.transfers || []).map((t: any) => ({
|
||||
id: t.id,
|
||||
transferType: t.transferType,
|
||||
transferAmount: t.transferAmount,
|
||||
transferTime: t.transferTime,
|
||||
transferAttachments: t.transferAttachments,
|
||||
payStatus: t.payStatus || 'dfj',
|
||||
remark: t.remark
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
} else {
|
||||
payload.payment = {
|
||||
payMode: formData.payMode,
|
||||
contractAmount: formData.contractAmount ?? undefined,
|
||||
lumpSumDeadline: formData.lumpSumDeadline || undefined,
|
||||
@@ -2277,10 +2423,10 @@
|
||||
installmentStartDate: formData.installmentStartDate || undefined,
|
||||
installmentAdvanceDays: toIntOrUndefined(formData.installmentAdvanceDays),
|
||||
installmentRatios: isInstallmentPayMode(formData.payMode)
|
||||
? (formData.installmentRatios || []).map((v) => Number(v || 0))
|
||||
: undefined
|
||||
},
|
||||
paymentDetails: formData.paymentDetails.map(
|
||||
? (formData.installmentRatios || []).map((v) => Number(v || 0))
|
||||
: undefined
|
||||
}
|
||||
payload.paymentDetails = formData.paymentDetails.map(
|
||||
({ _expanded, _payModeLocked, _voided, ...d }) => ({
|
||||
id: d.id,
|
||||
transferType: d.transferType,
|
||||
@@ -3894,6 +4040,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
.presign-deposit-hint {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.muted-hint {
|
||||
margin: 4px 0 0;
|
||||
font-size: 12px;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.warning-hint {
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════
|
||||
FORM GRID
|
||||
═══════════════════════════════════════════ */
|
||||
|
||||
Reference in new issue
Block a user