feat(mobile-oa-h5): align transfer payStatus labels with 付款 wording
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
727002761d
commit
cc0b217c89
4 files changed
+792
-5
No files matched your search
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<article class="presign-transfer-card">
|
||||
<header class="presign-transfer-card__header">
|
||||
<div>
|
||||
<strong>意向金转账 {{ index + 1 }}</strong>
|
||||
</div>
|
||||
<van-tag class="market-tag" :type="statusTone" :title="statusLabel">{{ statusLabel }}</van-tag>
|
||||
</header>
|
||||
|
||||
<div class="presign-form-field">
|
||||
<label>转账方式</label>
|
||||
<PresignSelectField
|
||||
:model-value="modelValue.transferType"
|
||||
:options="transferTypes"
|
||||
:disabled="!editable"
|
||||
title="选择转账方式"
|
||||
@update:model-value="updateField('transferType', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="presign-form-field">
|
||||
<label>转账金额(元)</label>
|
||||
<OaMoneyField
|
||||
:model-value="modelValue.transferAmount"
|
||||
:disabled="!editable"
|
||||
:show-upper="false"
|
||||
@update:model-value="updateField('transferAmount', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="presign-form-field">
|
||||
<label>转账时间</label>
|
||||
<OaDatePicker
|
||||
:model-value="modelValue.transferTime"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
:disabled="!editable"
|
||||
@update:model-value="updateField('transferTime', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="presign-form-field is-wide">
|
||||
<label>转账凭证</label>
|
||||
<PresignAttachmentUploader
|
||||
:model-value="modelValue.attachments"
|
||||
:disabled="!editable"
|
||||
:uploading="uploading"
|
||||
@upload="$emit('upload', { transfer: modelValue, file: $event })"
|
||||
@remove="removeAttachment"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="presign-form-field is-wide">
|
||||
<label>备注</label>
|
||||
<van-field
|
||||
:model-value="modelValue.remark"
|
||||
type="textarea"
|
||||
rows="2"
|
||||
maxlength="512"
|
||||
show-word-limit
|
||||
:disabled="!editable"
|
||||
placeholder="选填"
|
||||
@update:model-value="updateField('remark', $event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="modelValue.financeRemark" class="presign-transfer-card__finance">
|
||||
<span>财务备注</span>
|
||||
<strong>{{ modelValue.financeRemark }}</strong>
|
||||
</div>
|
||||
|
||||
<footer v-if="showActions" class="presign-transfer-card__actions">
|
||||
<van-button
|
||||
v-if="canDelete"
|
||||
size="small"
|
||||
type="danger"
|
||||
plain
|
||||
icon="delete-o"
|
||||
:disabled="busy"
|
||||
@click="$emit('remove', modelValue)"
|
||||
>删除</van-button>
|
||||
<van-button
|
||||
v-if="can('submit')"
|
||||
size="small"
|
||||
type="primary"
|
||||
icon="passed"
|
||||
:loading="busyAction === 'submit-transfer'"
|
||||
:disabled="busy"
|
||||
@click="$emit('submit-transfer', modelValue)"
|
||||
>提交财务确认</van-button>
|
||||
<van-button
|
||||
v-if="can('withdraw')"
|
||||
size="small"
|
||||
type="warning"
|
||||
plain
|
||||
icon="revoke"
|
||||
:loading="busyAction === 'withdraw-transfer'"
|
||||
:disabled="busy"
|
||||
@click="$emit('withdraw-transfer', modelValue)"
|
||||
>撤回</van-button>
|
||||
</footer>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import OaDatePicker from '../../oa/components/OaDatePicker.vue'
|
||||
import OaMoneyField from '../../oa/components/OaMoneyField.vue'
|
||||
import PresignAttachmentUploader from './PresignAttachmentUploader.vue'
|
||||
import PresignSelectField from './PresignSelectField.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: Object, required: true },
|
||||
index: { type: Number, required: true },
|
||||
transferTypes: { type: Array, default: () => [] },
|
||||
disabled: { type: Boolean, default: false },
|
||||
uploading: { type: Boolean, default: false },
|
||||
busyAction: { type: String, default: '' }
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'change',
|
||||
'remove',
|
||||
'upload',
|
||||
'submit-transfer',
|
||||
'withdraw-transfer'
|
||||
])
|
||||
|
||||
const actionSet = computed(() => new Set(
|
||||
(props.modelValue.allowedActions || []).map((action) =>
|
||||
typeof action === 'string' ? action : action?.code
|
||||
)
|
||||
))
|
||||
const newRow = computed(() => !String(props.modelValue.id || '').trim())
|
||||
const editable = computed(() => !props.disabled && (newRow.value || actionSet.value.has('edit')))
|
||||
const canDelete = computed(() => !props.disabled && (newRow.value || actionSet.value.has('delete')))
|
||||
const busy = computed(() => Boolean(props.busyAction) || props.uploading)
|
||||
const showActions = computed(() => canDelete.value || can('submit') || can('withdraw'))
|
||||
|
||||
const statusLabel = computed(() => ({
|
||||
dfj: '已付款',
|
||||
jfqrz: '付款确认中',
|
||||
yjf: '付款完成',
|
||||
tfqrz: '退费确认中',
|
||||
ytf: '已退费'
|
||||
}[String(props.modelValue.payStatus || 'dfj')] || '已付款'))
|
||||
|
||||
const statusTone = computed(() => ({
|
||||
dfj: 'default',
|
||||
jfqrz: 'warning',
|
||||
yjf: 'success',
|
||||
tfqrz: 'warning',
|
||||
ytf: 'default'
|
||||
}[String(props.modelValue.payStatus || 'dfj')] || 'default'))
|
||||
|
||||
function can(action) {
|
||||
return !props.disabled && actionSet.value.has(action)
|
||||
}
|
||||
|
||||
function updateField(key, value) {
|
||||
if (!editable.value) return
|
||||
const next = { ...props.modelValue, [key]: value }
|
||||
emit('update:modelValue', next)
|
||||
emit('change', next)
|
||||
}
|
||||
|
||||
function removeAttachment(index) {
|
||||
if (!editable.value) return
|
||||
const attachments = (props.modelValue.attachments || []).filter((_, itemIndex) => itemIndex !== index)
|
||||
updateField('attachments', attachments)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.presign-transfer-card {
|
||||
padding: 14px;
|
||||
border: 1px solid #dce9e4;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.presign-transfer-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #edf3f0;
|
||||
}
|
||||
|
||||
.presign-transfer-card__header > div {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.presign-transfer-card__finance {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin-top: 12px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
background: #f4f8f6;
|
||||
}
|
||||
|
||||
.presign-transfer-card__finance span {
|
||||
color: #6b7f77;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.presign-transfer-card__actions {
|
||||
display: flex;
|
||||
min-height: 44px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #edf3f0;
|
||||
}
|
||||
|
||||
.presign-transfer-card__actions .van-button {
|
||||
min-height: 36px;
|
||||
border-radius: 9px;
|
||||
}
|
||||
</style>
|
||||
@@ -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}`)
|
||||
}
|
||||
})
|
||||
@@ -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 || [])
|
||||
|
||||
@@ -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' },
|
||||
|
||||
Reference in new issue
Block a user