diff --git a/mobile-oa-h5/src/market/components/PaymentDetailTransfersPanel.vue b/mobile-oa-h5/src/market/components/PaymentDetailTransfersPanel.vue new file mode 100644 index 00000000..2c29a640 --- /dev/null +++ b/mobile-oa-h5/src/market/components/PaymentDetailTransfersPanel.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/mobile-oa-h5/src/market/mobile-parity-ui.test.js b/mobile-oa-h5/src/market/mobile-parity-ui.test.js index 2d4f6efb..2b13151e 100644 --- a/mobile-oa-h5/src/market/mobile-parity-ui.test.js +++ b/mobile-oa-h5/src/market/mobile-parity-ui.test.js @@ -463,7 +463,7 @@ test('market forms share attachment cards and preserve their existing value shap assert.match(presignForm, /createPendingMarketAttachment/) }) -test('payment detail presents every web field and nested transfer field with Chinese labels', () => { +test('payment detail presents summary overview fields with Chinese labels', () => { const sections = getMarketModule('payment').detailSections({ protocolNo: 'P-1', motherName: '张三', @@ -498,24 +498,21 @@ test('payment detail presents every web field and nested transfer field with Chi }] }) - assert.deepEqual(sections.map((section) => section.title), ['缴费概览', '应缴批次']) + assert.deepEqual(sections.map((section) => section.title), ['缴费概览']) + assert.equal(sections.find((section) => section.key === 'schedules'), undefined) assert.deepEqual( sections[0].fields.map((field) => field.label), ['客户', '联系电话', '样本编码', '缴费方式', '合同额', '已缴 / 待缴', '整体进度', '协议状态', '缴费进度'] ) - assert.equal(sections[1].cards[0].variant, 'schedule') - assert.deepEqual( - sections[1].cards[0].fields.map((field) => field.label), - ['应缴金额', '应缴日', '已缴金额', '欠缴金额'] - ) - const transfer = sections[1].cards[0].cards[0] - assert.equal(transfer.variant, 'transfer') - assert.equal(transfer.amount, '¥3,000.00') - assert.match(transfer.meta, /微信转账/) - assert.deepEqual( - transfer.fields.map((field) => field.label), - ['备注', '财务备注', '转账凭证'] - ) +}) + +test('payment detail hosts PaymentDetailTransfersPanel and hides transfer bottom actions', () => { + const detail = source('../views/market/MarketModuleDetail.vue') + assert.match(detail, /PaymentDetailTransfersPanel/) + assert.match(detail, /filterPaymentDetailBottomActions/) + const panel = source('./components/PaymentDetailTransfersPanel.vue') + assert.match(panel, /ScheduleTransferEditor/) + assert.match(panel, /应缴批次/) }) test('payment cards reproduce the Web Excel columns and operation labels', () => { diff --git a/mobile-oa-h5/src/market/module-contracts.test.js b/mobile-oa-h5/src/market/module-contracts.test.js index a1e051e9..a97bb94d 100644 --- a/mobile-oa-h5/src/market/module-contracts.test.js +++ b/mobile-oa-h5/src/market/module-contracts.test.js @@ -27,7 +27,7 @@ test('payment chase renders payment and protocol payloads with their native deta }) assert.equal(paymentSections[0].key, 'summary') - assert.equal(paymentSections[1].cards.length, 1) + assert.equal(paymentSections.find((section) => section.key === 'schedules'), undefined) assert.equal(protocolSections[0].key, 'protocol') }) @@ -111,12 +111,16 @@ test('protocol adapters preserve nested detail sections', () => { 'customer', 'samples', 'payment', - 'storageCert', - 'insurance', + 'refundRecords', 'audits', - 'statusChange', - 'attachments' + 'attachments', + 'statusChange' ]) + const protocolLabels = protocol.flatMap((section) => (section.fields || []).map((field) => field.label)) + assert.ok(!protocolLabels.includes('协议编号')) + assert.ok(protocolLabels.includes('母亲姓名')) + assert.ok(!protocolLabels.includes('新生儿评分(5分钟)')) + assert.equal(protocol.at(-1).key, 'statusChange') }) test('attachment-bearing detail adapters expose typed values instead of path text', () => { @@ -135,14 +139,9 @@ test('attachment-bearing detail adapters expose typed values instead of path tex 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' - }) + assert.equal(payment.find((section) => section.key === 'schedules'), undefined) + assert.equal(payment.length, 1) + assert.equal(payment[0].key, 'summary') const presign = getMarketModule('presign').detailSections({ transfers: [{ transferAttachments: 'deposit-proof.png' }] @@ -217,7 +216,7 @@ test('payment detail recognizes the converted cell-storage protocol status', () assert.equal(detail[0].fields.find((field) => field.label === '协议状态').value, '转细胞存储') }) -test('payment detail presents real backend progress, schedule statuses, and combined pay mode', () => { +test('payment detail presents real backend progress and combined pay mode in summary only', () => { const sections = getMarketModule('payment').detailSections({ payMode: 'djfq', paymentProgress: '部分已缴', @@ -228,13 +227,10 @@ test('payment detail presents real backend progress, schedule statuses, and comb ] }) const summary = sections.find((section) => section.key === 'summary') - const schedules = sections.find((section) => section.key === 'schedules').cards + assert.equal(sections.find((section) => section.key === 'schedules'), undefined) 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('payment progress dfj aligns with web-admin samplePaymentProgress 待缴费', () => { @@ -278,13 +274,12 @@ test('protocol read-only history uses Web audit and void type labels', () => { assert.equal(audits.cards[0].statusText, '审核退回') assert.equal( - audits.cards[0].fields.find((field) => field.label === '审核状态').value, - '审核退回' - ) - assert.equal( - status.fields.find((field) => field.label === '作废类型').value, - '租户自定义作废' + audits.cards[0].fields.find((field) => field.label === '审核状态'), + undefined ) + assert.equal(status.fields.length, 1) + assert.equal(status.fields[0].value, '暂无状态变更记录') + assert.equal(status.cards.length, 0) }) test('donation detail shows void type and preserves unknown original values', () => { @@ -292,15 +287,19 @@ test('donation detail shows void type and preserves unknown original values', () source: 'research_donation', basic: { donationStatus: 'yzf', + voidTime: '2026-10-01 09:30:00', + voidDesc: '客户申请', voidType: 'legacy-unknown', voidTypeLabel: 'legacy-unknown' } }) const status = sections.find((section) => section.key === 'statusChange') + assert.equal(status.cards.length, 1) + assert.equal(status.cards[0].statusText, '作废') assert.equal( - status.fields.find((field) => field.label === '作废类型').value, - 'legacy-unknown' + status.cards[0].fields.find((field) => field.label === '变更说明').value, + '作废类型:legacy-unknown;客户申请' ) }) @@ -342,7 +341,7 @@ test('detail adapters hide unresolved references and dictionary codes but preser ...(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('P-RAW-1')) assert.ok(values.includes('S-RAW-1')) assert.ok(values.includes('未知人员')) assert.ok(values.includes('未知')) @@ -360,6 +359,7 @@ test('detail adapters hide unresolved references and dictionary codes but preser 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, '未知') + assert.ok(!freight[1].cards[0].fields.some((field) => field.label === '协议单号')) const presign = getMarketModule('presign').detailSections({ protocolNo: 'P-RAW-2', regionId: 'region-raw', regionName: 'region-raw', @@ -368,7 +368,7 @@ test('detail adapters hide unresolved references and dictionary codes but preser 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.ok(!presign[0].fields.some((field) => field.label === '协议编号')) 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, '未知') @@ -377,14 +377,23 @@ test('detail adapters hide unresolved references and dictionary codes but preser 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' }] }] + protocolNo: 'P-RAW-3', + motherName: '客户甲', + sampleCode: 'S-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 paymentLabels = payment.flatMap((section) => (section.fields || []).map((field) => field.label)) 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')) + assert.ok(!paymentLabels.includes('协议号')) + assert.ok(!paymentValues.includes('P-RAW-3')) + assert.ok(paymentValues.includes('客户甲')) + assert.ok(paymentValues.includes('S-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 571894a5..8c76ca6d 100644 --- a/mobile-oa-h5/src/market/modules/payment.js +++ b/mobile-oa-h5/src/market/modules/payment.js @@ -2,48 +2,19 @@ import { formatMoney } from '../money.js' import { resolveStatusTag, samplePaymentProgress, - paymentScheduleStatus, - payStatus, payMode, protocolStatus, donationStatus } from '../status-tag-registry.js' -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) { return formatMoney(value) } -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 progressTag(code) { return resolveStatusTag(samplePaymentProgress, code) } -function scheduleTag(code) { - return resolveStatusTag(paymentScheduleStatus, code) -} - -function transferPayTag(code) { - return resolveStatusTag(payStatus, code) -} - function payModeTag(code) { return resolveStatusTag(payMode, code) } @@ -57,47 +28,6 @@ function protocolStatusTag(code) { return resolveStatusTag(protocolStatus, key) } -function transferCard(transfer, index) { - const pay = transferPayTag(transfer?.payStatus) - const transferType = label(TRANSFER_TYPE, transfer?.transferType) - const transferTime = transfer?.transferTime || '—' - return { - key: String(transfer?.id || `transfer-${index}`), - title: `转账笔 ${index + 1}`, - variant: 'transfer', - transferIndex: index + 1, - amount: money(transfer?.transferAmount), - meta: `${transferType} · ${transferTime}`, - statusText: pay.label, - statusTone: pay.tagType, - fields: [ - { label: '备注', value: transfer?.remark || '—' }, - { label: '财务备注', value: transfer?.financeRemark || '—' }, - { label: '转账凭证', type: 'attachments', value: transfer?.transferAttachments || '' } - ] - } -} - -function scheduleCard(schedule, index) { - const status = scheduleTag(schedule?.scheduleStatus) - const transfers = schedule?.transfers || [] - return { - key: String(schedule?.id || `schedule-${index}`), - title: `第 ${schedule?.scheduleNo || index + 1} 期`, - variant: 'schedule', - cardsLabel: '转账记录', - statusText: status.label, - statusTone: status.tagType, - fields: [ - { label: '应缴金额', value: money(schedule?.dueAmount), money: true }, - { label: '应缴日', value: displayDate(schedule?.dueDate) }, - { label: '已缴金额', value: money(schedule?.paidAmount), money: true }, - { label: '欠缴金额', value: money(schedule?.arrearsAmount), money: true } - ], - cards: transfers.map(transferCard) - } -} - export default { key: 'payment', resource: 'payments', @@ -139,7 +69,6 @@ export default { key: 'summary', title: '缴费概览', fields: [ - { label: '协议号', value: detail?.protocolNo || '—' }, { label: '客户', value: detail?.motherName || '—' }, { label: '联系电话', value: detail?.motherPhone || '—' }, { label: '样本编码', value: detail?.sampleCode || '—' }, @@ -150,12 +79,6 @@ export default { { label: '协议状态', value: protocol.label, tag: true, tagType: protocol.tagType }, { label: '缴费进度', value: progress.label, tag: true, tagType: progress.tagType } ] - }, - { - key: 'schedules', - title: '应缴批次', - fields: [], - cards: (detail?.schedules || []).map(scheduleCard) } ] }, diff --git a/mobile-oa-h5/src/market/payment-entry.js b/mobile-oa-h5/src/market/payment-entry.js index b261a1e6..d273bf34 100644 --- a/mobile-oa-h5/src/market/payment-entry.js +++ b/mobile-oa-h5/src/market/payment-entry.js @@ -1,6 +1,15 @@ /** 缴费信息入口进入协议详情时的只读压制 */ const PROTOCOL_WRITE_CODES = new Set(['edit', 'delete', 'create']) +/** 缴费详情底栏隐藏的转账动作(改由 PaymentDetailTransfersPanel 内联办理) */ +const PAYMENT_TRANSFER_BOTTOM_CODES = new Set([ + 'register-transfer', + 'edit-transfer', + 'delete-payment-transfer', + 'submit', + 'withdraw' +]) + export function isPaymentEntryView(query) { return String(query?.from || '') === 'payment' } @@ -16,3 +25,9 @@ export function filterProtocolActionsForPaymentEntry(actions) { (action) => !PROTOCOL_WRITE_CODES.has(actionCode(action)) ) } + +export function filterPaymentDetailBottomActions(actions) { + return (Array.isArray(actions) ? actions : []).filter( + (action) => !PAYMENT_TRANSFER_BOTTOM_CODES.has(actionCode(action)) + ) +} diff --git a/mobile-oa-h5/src/market/payment-entry.test.js b/mobile-oa-h5/src/market/payment-entry.test.js index 02e08c3f..fbe6ed45 100644 --- a/mobile-oa-h5/src/market/payment-entry.test.js +++ b/mobile-oa-h5/src/market/payment-entry.test.js @@ -2,7 +2,8 @@ import test from 'node:test' import assert from 'node:assert/strict' import { isPaymentEntryView, - filterProtocolActionsForPaymentEntry + filterProtocolActionsForPaymentEntry, + filterPaymentDetailBottomActions } from './payment-entry.js' test('isPaymentEntryView detects from=payment', () => { @@ -26,3 +27,24 @@ test('filterProtocolActionsForPaymentEntry hides edit and delete', () => { [{ code: 'view' }] ) }) + +test('filterPaymentDetailBottomActions hides transfer bottom actions', () => { + assert.deepEqual( + filterPaymentDetailBottomActions([ + 'view', + 'register-transfer', + 'edit-transfer', + 'delete-payment-transfer', + 'submit', + 'withdraw' + ]), + ['view'] + ) + assert.deepEqual( + filterPaymentDetailBottomActions([ + { code: 'register-transfer', scheduleId: 's1' }, + { code: 'view' } + ]), + [{ code: 'view' }] + ) +}) diff --git a/mobile-oa-h5/src/views/market/MarketModuleDetail.vue b/mobile-oa-h5/src/views/market/MarketModuleDetail.vue index 9162fabf..36aa954f 100644 --- a/mobile-oa-h5/src/views/market/MarketModuleDetail.vue +++ b/mobile-oa-h5/src/views/market/MarketModuleDetail.vue @@ -29,6 +29,13 @@ :cards="section.cards" :raw="section.raw || section" /> +