diff --git a/mobile-oa-h5/src/market/components/PresignTransferCard.vue b/mobile-oa-h5/src/market/components/PresignTransferCard.vue new file mode 100644 index 00000000..61d9c8a3 --- /dev/null +++ b/mobile-oa-h5/src/market/components/PresignTransferCard.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/mobile-oa-h5/src/market/module-contracts.test.js b/mobile-oa-h5/src/market/module-contracts.test.js new file mode 100644 index 00000000..4cccbdab --- /dev/null +++ b/mobile-oa-h5/src/market/module-contracts.test.js @@ -0,0 +1,332 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import { getMarketModule } from './module-registry.js' + +test('payment chase uses the singular read-only resource and opens payment detail', () => { + const module = getMarketModule('payment-chase') + assert.equal(module.resource, 'payment-chase') + assert.deepEqual(module.actions.specialist, ['view-payment']) + assert.deepEqual(module.detailTarget({ samplePaymentId: 'pay 1', scheduleId: 'sch-1' }), { + module: 'payment', + id: 'pay 1', + query: { scheduleId: 'sch-1' } + }) +}) + +test('module adapters map list filters to the actual backend query names', () => { + assert.deepEqual(getMarketModule('presign').listParams({ status: 'draft' }), { + orderStatus: 'draft' + }) + assert.deepEqual(getMarketModule('protocol').listParams({ status: 'cell_storage' }), { + source: 'cell_storage' + }) + assert.deepEqual(getMarketModule('sample').listParams({ status: 'research_donation' }), { + source: 'research_donation' + }) + assert.deepEqual(getMarketModule('freight').listParams({ status: 'air' }), { + transportMode: 'air', + includeItems: true + }) +}) + +test('protocol list tabs match Web dual-source tabs without an all-sources option', () => { + assert.deepEqual(getMarketModule('protocol').statusOptions, [ + { text: '细胞存储', value: 'cell_storage' }, + { text: '科研捐赠', value: 'research_donation' } + ]) + assert.deepEqual(getMarketModule('protocol').listParams({}), { + source: 'cell_storage' + }) + assert.deepEqual(getMarketModule('protocol').listParams({ status: '' }), { + source: 'cell_storage' + }) +}) + +test('sample list tabs match Web dual-source tabs without an all-sources option', () => { + assert.deepEqual(getMarketModule('sample').statusOptions, [ + { text: '细胞存储', value: 'cell_storage' }, + { text: '科研捐赠', value: 'research_donation' } + ]) + assert.deepEqual(getMarketModule('sample').listParams({}), { + source: 'cell_storage' + }) + assert.deepEqual(getMarketModule('sample').listParams({ status: '' }), { + source: 'cell_storage' + }) + assert.deepEqual(getMarketModule('sample').listParams({ status: 'research_donation' }), { + source: 'research_donation' + }) +}) + +test('presign filters use the backend order status codes', () => { + assert.deepEqual(getMarketModule('presign').statusOptions, [ + { text: '全部状态', value: '' }, + { text: '草稿', value: 'draft' }, + { text: '进行中', value: 'active' }, + { text: '已转正式', value: 'converted' }, + { text: '已作废', value: 'void' } + ]) +}) + +test('protocol summary status resolves real codes without exposing unknown codes', () => { + const protocol = getMarketModule('protocol') + assert.equal(protocol.statusLabel({ source: 'cell_storage', basic: { protocolStatus: 'sxz' } }), '生效中') + assert.equal(protocol.statusLabel({ source: 'research_donation', basic: { donationStatus: 'yzf' } }), '已作废') + assert.equal(protocol.statusLabel({ source: 'cell_storage', basic: { protocolStatus: 'raw-status' } }), '未知') + assert.equal(protocol.statusLabel({ source: 'cell_storage', basic: { protocolStatus: '' } }), '—') +}) + +test('protocol and sample adapters preserve nested detail sections', () => { + const protocol = getMarketModule('protocol').detailSections({ + source: 'cell_storage', + protocol: { protocolNo: 'P-1' }, + customer: { motherName: '张三' }, + collection: { regionName: '华东' }, + samples: [{ sampleCode: 'S-1' }], + payment: { payMode: 'yearly' }, + attachments: { protocolAttachments: 'a.pdf' } + }) + assert.deepEqual(protocol.map((section) => section.key), [ + 'protocol', 'collection', 'customer', 'samples', 'payment', 'attachments' + ]) + + const sample = getMarketModule('sample').detailSections({ + sections: [{ key: 'market-handles', title: '市场办理', storageCert: { statusLabel: '已完成' } }] + }) + assert.equal(sample[0].storageCert.statusLabel, '已完成') +}) + +test('attachment-bearing detail adapters expose typed values instead of path text', () => { + const protocol = getMarketModule('protocol').detailSections({ + source: 'cell_storage', + basic: { protocolNo: 'P-1' }, + attachments: { + protocolAttachments: 'protocol.pdf', + consentFormUrls: 'consent.jpg' + } + }) + const protocolFiles = protocol.find((section) => section.key === 'attachments').fields + assert.ok(protocolFiles.every((field) => field.type === 'attachments')) + assert.equal(protocolFiles[0].value, 'protocol.pdf') + + const payment = getMarketModule('payment').detailSections({ + schedules: [{ transfers: [{ transferAttachments: 'payment-proof.png' }] }] + }) + const paymentFiles = payment[1].cards[0].cards[0].fields.find( + (field) => field.label === '转账凭证' + ) + assert.deepEqual(paymentFiles, { + label: '转账凭证', + type: 'attachments', + value: 'payment-proof.png' + }) + + const sample = getMarketModule('sample').detailSections({ + sections: [{ + key: 'market-handles', + storageCert: { attachments: 'certificate.pdf' }, + insurance: { attachments: 'insurance.jpg' } + }] + }) + assert.equal(sample[0].cards[0].fields.find((field) => field.label === '附件').type, 'attachments') + + const presign = getMarketModule('presign').detailSections({ + transfers: [{ transferAttachments: 'deposit-proof.png' }] + }) + assert.equal(presign[1].cards[0].fields.find((field) => field.label === '转账附件').type, 'attachments') +}) + +test('presign detail prefers attachment metadata so original names remain visible', () => { + const attachments = [ + { fileUrl: '/minio/uuid.pdf', fileName: '意向金凭证.pdf' } + ] + const section = getMarketModule('presign').detailSections({ + transfers: [{ id: 't1', transferAttachments: '/minio/uuid.pdf', attachments }] + }).find((item) => item.key === 'transfers') + + assert.deepEqual( + section.cards[0].fields.find((field) => field.type === 'attachments').value, + attachments + ) +}) + +test('presign detail keeps the Web order fields and transfer cards', () => { + const sections = getMarketModule('presign').detailSections({ + orderDate: '20261011', + protocolNo: 'P-1', + motherName: '张三', + expectedDeliveryDate: '20261231', + contractAmount: '28800', + confirmedDepositAmount: '5000', + orderStatus: 'active', + transfers: [{ + id: 't-1', transferType: 'wx', transferAmount: '5000', + transferTime: '2026-10-11 10:00:00', payStatus: 'yjf' + }] + }) + + assert.deepEqual(sections.map((section) => section.key), ['base', 'transfers']) + assert.ok(sections[0].fields.some((field) => field.label === '母亲姓名' && field.value === '张三')) + assert.equal(sections[1].raw.transfers[0].statusLabel, '付款完成') +}) + +test('payment and sample adapters initialize only editable fields from real detail shapes', () => { + const payment = getMarketModule('payment') + const paymentDetail = { + id: 'payment-1', + schedules: [{ id: 'schedule-1', transfers: [{ + id: 'transfer-1', transferAmount: '20', transferType: 'bank', + payStatus: 'dfj', statusLabel: '待提交', statusTone: 'warning' + }] }] + } + assert.deepEqual(payment.initialForm(paymentDetail, { transferId: 'transfer-1' }), { + transfer: { transferAmount: '20', transferType: 'bank' } + }) + + const sample = getMarketModule('sample') + const sampleDetail = { sections: [{ key: 'market-handles', storageCert: { + handleTime: '2026-10-11', handlerUserId: 'u1', certCode: 'CERT-1', statusLabel: '已完成' + } }] } + assert.deepEqual(sample.initialForm(sampleDetail, 'storage-cert'), { + handle: { handleTime: '2026-10-11', handlerUserId: 'u1' } + }) +}) + +test('sample form options are scoped by the exact source-qualified sample id and action', () => { + const sample = getMarketModule('sample') + assert.deepEqual(sample.formOptionsParams('cell_storage:C-001', 'insurance'), { + sampleId: 'cell_storage:C-001', + action: 'insurance' + }) +}) + +test('protocol personnel fields use isolated hydrated option collections', () => { + const fields = getMarketModule('protocol').formSections.flatMap((section) => section.fields) + assert.equal(fields.find((field) => field.key === 'collectorUserId').optionKey, 'collectorStaff') + assert.equal(fields.find((field) => field.key === 'salesUserId').optionKey, 'salesStaff') + assert.equal(fields.find((field) => field.key === 'regionManagerUserId').optionKey, 'managerStaff') +}) + +test('payment detail recognizes the converted cell-storage protocol status', () => { + const detail = getMarketModule('payment').detailSections({ protocolStatus: 'zckc' }) + assert.equal(detail[0].fields.find((field) => field.label === '协议状态').value, '转细胞存储') +}) + +test('payment detail presents real backend progress, schedule statuses, and combined pay mode', () => { + const sections = getMarketModule('payment').detailSections({ + payMode: 'djfq', + paymentProgress: '部分缴费', + schedules: [ + { id: 'open', scheduleStatus: 'open' }, + { id: 'cleared', scheduleStatus: 'cleared' }, + { id: 'void', scheduleStatus: 'void' } + ] + }) + const summary = sections.find((section) => section.key === 'summary') + const schedules = sections.find((section) => section.key === 'schedules').cards + + assert.equal(summary.fields.find((field) => field.label === '缴费方式').value, '趸交+分期') + assert.equal(summary.fields.find((field) => field.label === '缴费进度').value, '部分缴费') + assert.equal(schedules.find((card) => card.key === 'open').statusText, '待缴费') + assert.equal(schedules.find((card) => card.key === 'cleared').statusText, '已缴清') + assert.equal(schedules.find((card) => card.key === 'void').statusText, '已作废') +}) + +test('protocol detail recognizes the combined lump-sum and installment pay mode', () => { + const sections = getMarketModule('protocol').detailSections({ + source: 'cell_storage', + payment: { payMode: 'djfq' } + }) + const payment = sections.find((section) => section.key === 'payment') + assert.equal(payment.fields.find((field) => field.label === '缴费模式').value, '趸交+分期') +}) + +test('protocol source is selectable only while creating and exposes all attachment fields', () => { + const fields = getMarketModule('protocol').formSections.flatMap((section) => section.fields) + assert.equal(fields.find((field) => field.key === 'source').createOnly, true) + assert.equal(fields.find((field) => field.key === 'source').type, 'tabs') + assert.ok(fields.some((field) => field.key === 'protocolTotalAmount')) + assert.ok(fields.some((field) => field.key === 'healthSurveyFormUrls')) + assert.ok(fields.some((field) => field.key === 'infectiousDiseaseReportUrls')) +}) + +test('freight unlink options come from complete detail items instead of a global first page', () => { + const freight = getMarketModule('freight') + const detailItems = [ + { sampleSource: 'cell_storage', sampleId: 'old-1', freightId: 'f1' }, + { sampleSource: 'research_donation', sampleId: 'old-2', freightId: 'f1' } + ] + const firstPage = [{ sampleSource: 'cell_storage', sampleId: 'new-1', freightId: '' }] + assert.deepEqual(freight.sampleOptions('unlink', detailItems, firstPage), detailItems) + assert.deepEqual(freight.sampleOptions('link', detailItems, firstPage), firstPage) + assert.equal(freight.usesPagedSampleOptions('unlink'), false) + assert.equal(freight.usesPagedSampleOptions('link'), true) +}) + +test('detail adapters hide unresolved references and dictionary codes but preserve business numbers', () => { + const protocol = getMarketModule('protocol').detailSections({ + source: 'cell_storage', + basic: { protocolNo: 'P-RAW-1', protocolStatus: 'raw-status', collectorUserId: 'user-raw', collectorUserName: 'user-raw' }, + collection: { regionId: 'region-raw', hospitalId: 'hospital-raw', salesUserId: 'sales-raw', salesUserName: 'sales-raw' }, + customer: { newbornSex: 'raw-sex', deliveryMode: 'raw-delivery' }, + samples: [{ sampleCode: 'S-RAW-1', sampleTypeId: 'type-raw', unitId: 'unit-raw', sampleStatus: 'ycj' }], + payment: { payMode: 'raw-pay-mode' } + }) + const values = protocol.flatMap((section) => [ + ...(section.fields || []).map((field) => field.value), + ...(section.cards || []).flatMap((card) => card.fields.map((field) => field.value)) + ]) + assert.ok(values.includes('P-RAW-1')) + assert.ok(values.includes('S-RAW-1')) + assert.ok(values.includes('未知人员')) + assert.ok(values.includes('未知')) + for (const raw of ['raw-status', 'user-raw', 'region-raw', 'hospital-raw', 'sales-raw', 'raw-sex', 'raw-delivery', 'type-raw', 'unit-raw', 'raw-pay-mode']) { + assert.ok(!values.includes(raw), `must not display ${raw}`) + } + assert.equal(protocol.find((section) => section.key === 'samples').cards[0].statusText, '已采集') + + const sample = getMarketModule('sample').detailSections({ + sections: [{ key: 'market-handles', storageCert: { handlerUserId: 'handler-raw', handlerUserName: 'handler-raw', statusLabel: 'raw-handle-status' } }] + }) + assert.equal(sample[0].cards[0].fields.find((field) => field.label === '办理人员').value, '未知人员') + assert.equal(sample[0].cards[0].statusText, '未知') + + const freight = getMarketModule('freight').detailSections({ + freightNo: 'F-RAW-1', transportMode: 'raw-transport', createBy: 'creator-raw', creatorName: 'creator-raw', + items: [{ sampleSource: 'raw-source', sampleCode: 'S-RAW-2' }] + }) + assert.equal(freight[0].fields.find((field) => field.label === '批次号').value, 'F-RAW-1') + assert.equal(freight[0].fields.find((field) => field.label === '运输方式').value, '未知') + assert.equal(freight[0].fields.find((field) => field.label === '创建人').value, '未知人员') + assert.equal(freight[1].cards[0].fields.find((field) => field.label === '样本编码').value, 'S-RAW-2') + assert.equal(freight[1].cards[0].fields.find((field) => field.label === '来源').value, '未知') + + const presign = getMarketModule('presign').detailSections({ + protocolNo: 'P-RAW-2', regionId: 'region-raw', regionName: 'region-raw', + hospitalId: 'hospital-raw', hospitalName: 'hospital-raw', + collectorUserId: 'collector-raw', collectorUserName: 'collector-raw', + payMode: 'raw-pay-mode', orderStatus: 'raw-order-status', + transfers: [{ transferType: 'raw-transfer', payStatus: 'raw-pay-status' }] + }) + assert.equal(presign[0].fields.find((field) => field.label === '协议编号').value, 'P-RAW-2') + assert.equal(presign[0].fields.find((field) => field.label === '采集地区').value, '未知') + assert.equal(presign[0].fields.find((field) => field.label === '采集专员').value, '未知人员') + assert.equal(presign[0].fields.find((field) => field.label === '缴费方式').value, '未知') + assert.equal(presign[0].fields.find((field) => field.label === '订单状态').value, '未知') + assert.equal(presign[1].cards[0].fields.find((field) => field.label === '转账方式').value, '未知') + assert.equal(presign[1].cards[0].statusText, '未知') + + const payment = getMarketModule('payment').detailSections({ + protocolNo: 'P-RAW-3', payMode: 'raw-pay-mode', protocolStatus: 'raw-status', + progressStatus: 'raw-progress', schedules: [{ scheduleStatus: 'raw-schedule', transfers: [{ transferType: 'raw-transfer', payStatus: 'raw-pay-status' }] }] + }) + const paymentValues = payment.flatMap((section) => [ + ...(section.fields || []).map((field) => field.value), + ...(section.cards || []).flatMap((card) => [card.statusText, ...(card.cards || []).flatMap((nested) => [nested.statusText, ...nested.fields.map((field) => field.value)])]) + ]) + assert.ok(paymentValues.includes('P-RAW-3')) + for (const raw of ['raw-pay-mode', 'raw-status', 'raw-progress', 'raw-schedule', 'raw-transfer', 'raw-pay-status']) { + assert.ok(!paymentValues.includes(raw), `must not display ${raw}`) + } +}) diff --git a/mobile-oa-h5/src/market/modules/payment.js b/mobile-oa-h5/src/market/modules/payment.js index 88b565df..f5f49fec 100644 --- a/mobile-oa-h5/src/market/modules/payment.js +++ b/mobile-oa-h5/src/market/modules/payment.js @@ -1,3 +1,79 @@ +const PAY_MODE = Object.freeze({ dj: '趸交', fq: '分期', fgd: '分改趸', djfq: '趸交+分期' }) +const PROTOCOL_STATUS = Object.freeze({ + dsx: '待生效', dqr: '待确认', sxz: '生效中', ygq: '已过期', + yzf: '已作废', sdfq: '属地废弃', zckc: '转细胞存储', zky: '转科研' +}) +const PAYMENT_PROGRESS = Object.freeze({ + dfj: '待缴费', jfqrz: '付款确认中', bfyj: '部分已缴', yjq: '已缴清', + 待缴费: '待缴费', 部分缴费: '部分缴费', 已缴清: '已缴清' +}) +const SCHEDULE_STATUS = Object.freeze({ + open: '待缴费', cleared: '已缴清', void: '已作废', + pending: '待缴费', partial: '部分到账', paid: '已缴清', closed: '已关闭' +}) +const PAY_STATUS = Object.freeze({ + dfj: '已付款', jfqrz: '付款确认中', yjf: '付款完成', tfqrz: '退费确认中', ytf: '已退费' +}) +const TRANSFER_TYPE = Object.freeze({ + wxzz: '微信转账', wx: '微信转账', wechat: '微信转账', + zzbzz: '支付宝转账', zfb: '支付宝转账', alipay: '支付宝转账', + yhkzz: '银行卡转账', bank: '银行卡转账', + xj: '现金', cash: '现金', lszz: '乐刷' +}) + +function label(map, value) { + const key = String(value || '').trim() + return map[key] || (key ? '未知' : '—') +} + +function money(value) { + if (value == null || value === '') return '—' + const amount = Number(value) + if (!Number.isFinite(amount)) return String(value) + return `¥${amount.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}` +} + +function displayDate(value) { + const text = String(value || '').trim() + return /^\d{8}$/.test(text) + ? `${text.slice(0, 4)}-${text.slice(4, 6)}-${text.slice(6, 8)}` + : text || '—' +} + +function transferCard(transfer, index) { + const statusText = label(PAY_STATUS, transfer?.payStatus) + return { + key: String(transfer?.id || `transfer-${index}`), + title: `转账笔 ${index + 1}`, + statusText, + statusTone: statusText.includes('确认中') ? 'warning' : statusText === '付款完成' ? 'success' : 'info', + fields: [ + { label: '转账方式', value: label(TRANSFER_TYPE, transfer?.transferType) }, + { label: '转账金额', value: money(transfer?.transferAmount) }, + { label: '转账时间', value: transfer?.transferTime || '—' }, + { label: '转账凭证', type: 'attachments', value: transfer?.transferAttachments || '' }, + { label: '备注', value: transfer?.remark || '—' } + ] + } +} + +function scheduleCard(schedule, index) { + const statusText = label(SCHEDULE_STATUS, schedule?.scheduleStatus) + return { + key: String(schedule?.id || `schedule-${index}`), + title: `第 ${schedule?.scheduleNo || index + 1} 期`, + statusText, + statusTone: statusText === '已缴清' ? 'success' : statusText === '部分到账' ? 'primary' : 'warning', + fields: [ + { label: '应缴金额', value: money(schedule?.dueAmount) }, + { label: '应缴日', value: displayDate(schedule?.dueDate) }, + { label: '已缴金额', value: money(schedule?.paidAmount) }, + { label: '欠缴金额', value: money(schedule?.arrearsAmount) } + ], + cards: (schedule?.transfers || []).map(transferCard) + } +} + export default { key: 'payment', resource: 'payments', @@ -7,6 +83,34 @@ export default { }, listParams: ({ status } = {}) => ({ payMode: status || '' }), statusOptions: [{ text: '全部缴费模式', value: '' }], + detailSections(detail) { + const progress = label(PAYMENT_PROGRESS, detail?.progressStatus || detail?.paymentProgress) + const protocolStatus = label(PROTOCOL_STATUS, detail?.protocolStatus) + return [ + { + key: 'summary', + title: '缴费概览', + fields: [ + { label: '协议号', value: detail?.protocolNo || '—' }, + { label: '客户', value: detail?.motherName || '—' }, + { label: '联系电话', value: detail?.motherPhone || '—' }, + { label: '样本编码', value: detail?.sampleCode || '—' }, + { label: '缴费方式', value: label(PAY_MODE, detail?.payMode), tag: true, tagType: 'primary' }, + { label: '合同额', value: money(detail?.contractAmount) }, + { label: '已缴 / 待缴', value: `${money(detail?.paidAmount)} / ${money(detail?.dueRemainAmount ?? detail?.remainAmount)}` }, + { label: '整体进度', value: `已缴清 ${Number(detail?.clearedScheduleCount || 0)}/${Number(detail?.activeScheduleCount || 0)} 期` }, + { label: '协议状态', value: protocolStatus, tag: true }, + { label: '缴费进度', value: progress, tag: true } + ] + }, + { + key: 'schedules', + title: '应缴批次', + fields: [], + cards: (detail?.schedules || []).map(scheduleCard) + } + ] + }, initialForm(detail, target = {}) { const transfer = (detail?.schedules || []) .flatMap((row) => row.transfers || []) diff --git a/mobile-oa-h5/src/market/modules/presign.js b/mobile-oa-h5/src/market/modules/presign.js index 9032d8e3..cf6f504b 100644 --- a/mobile-oa-h5/src/market/modules/presign.js +++ b/mobile-oa-h5/src/market/modules/presign.js @@ -1,4 +1,55 @@ import { commonFields } from './shared.js' +import { + formatPresignDate, + formatPresignDateTime, + formatPresignMoney, + presignStatusLabel, + presignStatusTone +} from '../presign/presign-presenter.js' + +const paymentStatus = Object.freeze({ + dfj: { label: '已付款', tone: 'default' }, + jfqrz: { label: '付款确认中', tone: 'warning' }, + yjf: { label: '付款完成', tone: 'success' }, + tfqrz: { label: '退费确认中', tone: 'warning' }, + ytf: { label: '已退费', tone: 'default' } +}) +const transferTypeLabels = Object.freeze({ + wxzz: '微信转账', wx: '微信转账', wechat: '微信转账', + zzbzz: '支付宝转账', zfb: '支付宝转账', alipay: '支付宝转账', + yhkzz: '银行卡转账', bank: '银行卡转账', + xj: '现金', cash: '现金', lszz: '乐刷' +}) + +function dictionaryLabel(map, value) { + const key = String(value || '').trim() + return map[key] || (key ? '未知' : '—') +} + +function person(name, id) { + const displayName = String(name || '').trim() + const referenceId = String(id || '').trim() + if (displayName && displayName !== referenceId) return displayName + return referenceId ? '未知人员' : '—' +} + +function referenceName(name, id) { + const displayName = String(name || '').trim() + const referenceId = String(id || '').trim() + if (displayName && displayName !== referenceId) return displayName + return referenceId ? '未知' : '—' +} + +function presignStatus(value) { + const code = String(value || '').trim() + if (!code) return '—' + const result = presignStatusLabel(code) + return result === code ? '未知' : result +} + +function displayValue(value) { + return value && value !== '-' ? value : '—' +} export default { key: 'presign', @@ -10,16 +61,88 @@ export default { listParams: ({ status } = {}) => ({ orderStatus: status || '' }), statusOptions: [ { text: '全部状态', value: '' }, - { text: '预签约', value: 'yqy' }, - { text: '已转正式', value: 'yzs' } + { text: '草稿', value: 'draft' }, + { text: '进行中', value: 'active' }, + { text: '已转正式', value: 'converted' }, + { text: '已作废', value: 'void' } ], + statusLabel(detail) { + return presignStatus(detail?.orderStatus) + }, + statusTone(detail) { + return presignStatusTone(detail?.orderStatus) + }, + detailSections(detail) { + const status = String(detail?.orderStatus || '') + const transfers = (detail?.transfers || []).map((transfer, index) => { + const paymentCode = String(transfer?.payStatus || 'dfj') + const payment = paymentStatus[paymentCode] || { label: '未知', tone: 'default' } + return { + ...transfer, + title: `意向金转账 ${index + 1}`, + statusLabel: payment.label, + statusTone: payment.tone, + transferAmount: formatPresignMoney(transfer?.transferAmount), + transferTime: formatPresignDateTime(transfer?.transferTime) + } + }) + return [ + { + key: 'base', + title: '预签信息', + fields: [ + { label: '业务日期', value: displayValue(formatPresignDate(detail?.orderDate)) }, + { label: '协议编号', value: detail?.protocolNo || '—' }, + { label: '母亲姓名', value: detail?.motherName || '—' }, + { label: '预产期', value: displayValue(formatPresignDate(detail?.expectedDeliveryDate)) }, + { label: '采集地区', value: referenceName(detail?.regionName, detail?.regionId) }, + { label: '采集医院', value: referenceName(detail?.hospitalName, detail?.hospitalId) }, + { label: '采集专员', value: person(detail?.collectorUserName, detail?.collectorUserId) }, + { label: '采集经理', value: person(detail?.regionManagerUserName, detail?.regionManagerUserId) }, + { label: '采集时间', value: displayValue(formatPresignDateTime(detail?.sampleCollectTime)) }, + { label: '缴费方式', value: dictionaryLabel({ fq: '分期', dj: '趸交' }, detail?.payMode) }, + { label: '合同金额', value: displayValue(formatPresignMoney(detail?.contractAmount)) }, + { label: '到账金额', value: displayValue(formatPresignMoney(detail?.confirmedDepositAmount)) }, + { label: '订单状态', value: presignStatus(status), tag: true, tagType: presignStatusTone(status) }, + { label: '备注', value: detail?.remark || '—' }, + { label: '创建时间', value: displayValue(formatPresignDateTime(detail?.createTime)) } + ], + raw: {}, + cards: [] + }, + { + key: 'transfers', + title: '意向金转账', + fields: [], + raw: { transfers }, + cards: transfers.map((transfer, index) => ({ + key: String(transfer?.id || `transfer-${index}`), + title: transfer.title, + statusText: transfer.statusLabel, + statusTone: transfer.statusTone, + fields: [ + { label: '转账方式', value: dictionaryLabel(transferTypeLabels, transfer.transferType) }, + { label: '转账金额', value: transfer.transferAmount || '—' }, + { label: '转账时间', value: transfer.transferTime || '—' }, + { + label: '转账附件', + type: 'attachments', + value: transfer.attachments?.length + ? transfer.attachments + : transfer.transferAttachments || '' + }, + { label: '备注', value: transfer.remark || '—' } + ] + })) + } + ] + }, formSections: [ { key: 'base', title: '预签信息', fields: [ - { key: 'orderDate', label: '预签日期', type: 'date' }, - { key: 'protocolNo', label: '协议编号' }, + { key: 'orderDate', label: '预签日期', type: 'date', valueFormat: 'YYYYMMDD' }, { key: 'motherName', label: '客户姓名', required: true }, - { key: 'expectedDeliveryDate', label: '预产期', type: 'date' }, + { key: 'expectedDeliveryDate', label: '预产期', type: 'date', valueFormat: 'YYYYMMDD' }, { key: 'regionId', label: '采集地区', type: 'select', optionKey: 'regions' }, { key: 'hospitalId', label: '采集医院', type: 'select', optionKey: 'hospitals' }, { key: 'sampleCollectTime', label: '采集时间', type: 'datetime' },