|
|
@@ -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}`)
|
|
|
+ }
|
|
|
+})
|