feat(mobile-oa-h5): align research donation protocol card with excel fields

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-10-13 13:14:09 +08:00
1 parent 0ab0774e8a
commit 99e71096ac
2 files changed
+94 -66

No files matched your search

+43 -42
View File
@@ -2,7 +2,6 @@ const SEARCH_PLACEHOLDERS = Object.freeze({
protocol: '搜索母亲姓名 / 样本编码',
'payment-chase': '搜索协议号 / 客户 / 样本编码',
payment: '搜索协议号 / 客户 / 样本编码',
sample: '搜索母亲姓名 / 样本编码',
freight: '搜索批次号 / 母亲姓名 / 样本编码'
})
@@ -19,13 +18,10 @@ const ACTION_LABELS = Object.freeze({
'view-payment': '查看缴费',
'view-payment-detail': '缴费明细',
'view-protocol': '协议单',
'view-storage-qc': '储存证质检信息',
'register-transfer': '登记转账',
'edit-transfer': '编辑转账',
submit: '提交财务确认',
withdraw: '撤回转账',
'storage-cert': '登记储存证',
insurance: '登记保险',
link: '关联样本',
unlink: '取消运费'
})
@@ -67,7 +63,7 @@ export function money(value) {
}
export function marketSearchPlaceholder(module, source) {
if (module === 'protocol' || module === 'sample') {
if (module === 'protocol') {
return PROTOCOL_SEARCH_PLACEHOLDERS[source] || SEARCH_PLACEHOLDERS[module]
}
return SEARCH_PLACEHOLDERS[module] || '搜索编号、姓名或关键词'
@@ -82,16 +78,55 @@ function allowed(item, ownActions = []) {
return ownActions.filter((action) => backend.has(action))
}
function attachmentPresence(urls) {
return String(urls || '').trim() ? '有' : '—'
}
function protocolCard(item) {
const actions = allowed(item, ['edit', 'delete'])
const isDonation = item.source === 'research_donation'
const status = label(PROTOCOL_STATUS_LABELS, item.protocolStatus || item.donationStatus || item.status)
if (isDonation) {
const collectDate = String(item.sampleCollectTime || '').slice(0, 10)
return {
title: text(item.motherName || item.customerName),
subtitle: `${label(SOURCE_LABELS, item.source)} · ${text(item.labStorageCode || item.sampleCode)}`,
status,
fields: [
field('采集日期', collectDate || item.donationDate),
field('当月核量号', item.monthlyVerifyNo),
field('实验室流转编号', item.labStorageCode || item.sampleCode, { wide: true }),
field('协议状态', status, { tag: true }),
field('废弃原因', item.voidDesc),
field('母亲姓名', item.motherName || item.customerName),
field('样本类型', item.sampleTypeName),
field('采集地区', referenceName(item.regionName, item.regionId)),
field('采集医院', referenceName(item.hospitalName, item.hospitalId), { wide: true }),
field('分娩时间', item.newbornDeliveryTime, { wide: true }),
field('采集时间', item.sampleCollectTime, { wide: true }),
field('市场专员', personName(item.salesUserName, item.salesUserId)),
field('地区经理', personName(item.regionManagerUserName || item.managerName, item.regionManagerUserId)),
field('运输方式', item.transportModeLabel || item.transportMode),
field('新生儿性别', label(NEWBORN_SEX_LABELS, item.newbornSex)),
field('新生儿体重', item.newbornWeightGram),
field('分娩方式', label(DELIVERY_MODE_LABELS, item.deliveryMode)),
field('胎次', item.birthOrder),
field('母血采集时间', item.maternalBloodCollectTime, { wide: true }),
field('知情同意书', attachmentPresence(item.consentFormUrls)),
field('传染病检测报告', attachmentPresence(item.infectiousDiseaseReportUrls)),
field('协议书', attachmentPresence(item.attachmentUrls)),
field('备注', item.remark, { wide: true })
],
actions: ['view', ...actions]
}
}
return {
title: text(item.motherName || item.customerName),
subtitle: `${label(SOURCE_LABELS, item.source)} · ${text(item.sampleCode)}`,
status,
fields: [
field('状态', status, { tag: true }),
field(item.source === 'research_donation' ? '捐赠日期' : '登记日期', item.registerDate || item.donationDate),
field('登记日期', item.registerDate || item.donationDate),
field('母亲姓名', item.motherName || item.customerName),
field('母亲电话', item.motherPhone),
field('母亲身份证号', item.motherIdCard, { wide: true }),
@@ -145,10 +180,10 @@ function paymentCard(item) {
]
})
return {
title: text(item.motherName), subtitle: text(item.protocolNo), status: progress,
title: text(item.motherName), subtitle: text(item.sampleCode), status: progress,
fields: [
field('姓名', item.motherName), field('手机号', item.motherPhone),
field('合同编号', item.protocolNo, { wide: true }), field('签署日期', item.signDate),
field('样本编码', item.sampleCode, { wide: true }), field('签署日期', item.signDate),
field('存储年限', item.storageYears), field('存储项目', item.sampleTypeName, { wide: true }),
field('合同总额', money(item.contractAmount), { money: true }),
field('缴费方式', label(PAY_MODE_LABELS, item.payMode), { tag: true }),
@@ -163,39 +198,6 @@ function paymentCard(item) {
}
}
function progressLabel(item, key, fallback) {
const value = item.progress?.[key]?.label
return value ? label({}, value) : fallback
}
function sampleCard(item) {
const status = label(PROTOCOL_STATUS_LABELS, item.protocolStatus || item.donationStatus || item.statusLabel)
return {
title: text(item.motherName || item.customerName), subtitle: text(item.sampleCode), status,
fields: [
field('协议状态', status, { tag: true }), field('预产期', item.expectedDeliveryDate),
field('母亲姓名', item.motherName || item.customerName), field('新生儿姓名', item.newbornName),
field('样本编码', item.sampleCode, { wide: true }), field('实验室入库编码', item.labStorageCode, { wide: true }),
field('样本类型', referenceName(item.sampleTypeName, item.sampleTypeId), { wide: true })
],
progressTags: [
`采集 · ${progressLabel(item, 'collection', '未采集')}`,
`接收 · ${progressLabel(item, 'reception', '未接收')}`,
`质检 · ${progressLabel(item, 'qc', '未完成')}`,
`入库 · ${progressLabel(item, 'storage', '未入库')}`,
...(item.source === 'cell_storage' ? [
`储存证 · ${item.storageCert?.label ? label({}, item.storageCert.label) : '未办理'}`,
`保险 · ${item.insurance?.label ? label({}, item.insurance.label) : '未办理'}`
] : [])
],
actions: [
'view',
...(item.source === 'cell_storage' ? ['view-storage-qc'] : []),
...allowed(item, ['storage-cert', 'insurance'])
]
}
}
function freightCard(role, item) {
const first = item.items?.[0] || {}
const transportMode = label({}, item.transportModeLabel || item.transportMode)
@@ -223,7 +225,6 @@ export function buildMarketCard(module, role, item = {}) {
if (module === 'protocol') return protocolCard(item)
if (module === 'payment-chase') return chaseCard(item)
if (module === 'payment') return paymentCard(item)
if (module === 'sample') return sampleCard(item)
if (module === 'freight') return freightCard(role, item)
return { title: text(item.title || item.name || item.code), subtitle: '', status: '', fields: [], actions: ['view'] }
}
+51 -24
View File
@@ -19,15 +19,6 @@ test('remaining modules use the approved search placeholders', () => {
)
assert.equal(marketSearchPlaceholder('payment-chase'), '搜索协议号 / 客户 / 样本编码')
assert.equal(marketSearchPlaceholder('payment'), '搜索协议号 / 客户 / 样本编码')
assert.equal(marketSearchPlaceholder('sample'), '搜索母亲姓名 / 样本编码')
assert.equal(
marketSearchPlaceholder('sample', 'cell_storage'),
'搜索母亲姓名 / 样本编码'
)
assert.equal(
marketSearchPlaceholder('sample', 'research_donation'),
'搜索母亲姓名 / 捐赠编号 / 样本编码'
)
assert.equal(marketSearchPlaceholder('freight'), '搜索批次号 / 母亲姓名 / 样本编码')
})
@@ -56,6 +47,52 @@ test('protocol card exposes web parity fields without truncating to three rows',
}).status, '转细胞存储')
})
test('research donation protocol card matches excel-parity field set', () => {
const card = buildMarketCard('protocol', 'specialist', {
source: 'research_donation',
motherName: '曹陆双',
sampleCode: 'S-D1',
donationStatus: 'sdfq',
sampleCollectTime: '2025-08-29 12:27:00',
monthlyVerifyNo: '202508001',
labStorageCode: 'A0000001',
voidDesc: '母血乙肝阳性',
sampleTypeName: '围产期造血干细胞',
regionName: '亳州',
hospitalName: '涡阳县人民医院',
newbornDeliveryTime: '2025-08-29 12:26:00',
salesUserName: '杨莉莉',
regionManagerUserName: '张颖',
managerName: '张颖',
transportMode: 'kdys',
transportModeLabel: '快递运输',
newbornSex: '0',
newbornWeightGram: 3000,
deliveryMode: 'sc',
birthOrder: 1,
maternalBloodCollectTime: '2025-08-29 13:30:00',
consentFormUrls: 'https://x/a.pdf',
infectiousDiseaseReportUrls: '',
attachmentUrls: 'https://x/b.pdf',
remark: '备注一',
allowedActions: ['edit']
})
const labels = card.fields.map((f) => f.label)
for (const label of [
'当月核量号', '实验室流转编号', '协议状态', '废弃原因', '样本类型',
'采集地区', '采集医院', '分娩时间', '采集时间', '市场专员', '地区经理',
'运输方式', '新生儿性别', '新生儿体重', '分娩方式', '胎次', '母血采集时间',
'知情同意书', '传染病检测报告', '协议书', '备注'
]) {
assert.ok(labels.includes(label), `missing ${label}`)
}
assert.equal(card.fields.find((f) => f.label === '当月核量号').value, '202508001')
assert.equal(card.fields.find((f) => f.label === '知情同意书').value, '有')
assert.equal(card.fields.find((f) => f.label === '传染病检测报告').value, '—')
assert.equal(card.fields.find((f) => f.label === '协议书').value, '有')
assert.ok(!labels.includes('是否同存'))
})
test('payment card formats summary totals and progress', () => {
const card = buildMarketCard('payment', 'manager', {
protocolNo: 'P-1', motherName: '周雨桐', sampleCode: 'S-1', payMode: 'fq',
@@ -67,6 +104,8 @@ test('payment card formats summary totals and progress', () => {
allowedActions: ['register-transfer']
})
assert.equal(card.status, '部分缴费')
assert.equal(card.subtitle, 'S-1')
assert.ok(card.fields.some((field) => field.label === '样本编码' && field.value === 'S-1'))
assert.ok(card.fields.some((field) => field.label === '签署日期' && field.value === '2026-10-01'))
assert.ok(card.fields.some((field) => field.label === '存储项目' && field.value === '脐带血'))
assert.ok(card.fields.some((field) => field.label === '第1期·应缴费金额(元)' && field.value === '¥3,000.00'))
@@ -101,8 +140,6 @@ test('specialist freight card masks batch-wide counts while manager sees visible
test('action labels cover module-specific operations', () => {
assert.equal(actionLabel('audit'), '审核')
assert.equal(actionLabel('storage-cert'), '登记储存证')
assert.equal(actionLabel('insurance'), '登记保险')
assert.equal(actionLabel('link'), '关联样本')
assert.equal(actionLabel('unlink'), '取消运费')
assert.equal(actionLabel('internal-action-code'), '操作')
@@ -132,19 +169,9 @@ test('list cards hide unresolved reference and dictionary codes while retaining
assert.equal(freight.fields.find((field) => field.label === '样本编码').value, 'S-KEEP-2')
const payment = buildMarketCard('payment', 'manager', {
protocolNo: 'P-KEEP-2', payMode: 'raw-pay-mode'
protocolNo: 'P-KEEP-2', sampleCode: 'S-KEEP-2', payMode: 'raw-pay-mode'
})
assert.equal(payment.subtitle, 'P-KEEP-2')
assert.equal(payment.subtitle, 'S-KEEP-2')
assert.equal(payment.fields.find((field) => field.label === '样本编码').value, 'S-KEEP-2')
assert.equal(payment.fields.find((field) => field.label === '缴费方式').value, '未知')
const sample = buildMarketCard('sample', 'specialist', {
sampleCode: 'S-KEEP-3', protocolStatus: 'raw-status', sampleTypeId: 'type-raw',
progress: { collection: { label: 'raw-progress' } },
source: 'cell_storage', storageCert: { label: 'raw-cert-status' }
})
assert.equal(sample.subtitle, 'S-KEEP-3')
assert.equal(sample.status, '未知')
assert.equal(sample.fields.find((field) => field.label === '样本类型').value, '未知')
assert.ok(sample.progressTags.includes('采集 · 未知'))
assert.ok(sample.progressTags.includes('储存证 · 未知'))
})