This commit is contained in:
Your Name
2026-09-21 10:26:14 +08:00
parent bbe3e870a1
commit dbf474ddd7
67 changed files with 10636 additions and 279 deletions
+1 -1
View File
@@ -467,7 +467,7 @@ export function prescriptionOrderEditTime(params: { id: number; create_time: str
return request.post({ url: '/tcm.prescriptionOrder/editTime', params })
}
/** 仅修改业务订单的承运商与快递单号;所有履约状态均可使用 */
/** 仅修改业务订单的承运商与快递单号;处方和支付单均审核通过后,所有履约状态均可使用 */
export function prescriptionOrderDdcode(params: {
id: number
express_company: string
@@ -871,8 +871,17 @@
<el-option label="京东快递" value="jd" />
<el-option label="极兔速递" value="jt" />
</el-select>
<el-input v-model="editForm.tracking_number" maxlength="80" placeholder="快递单号" class="flex-1 min-w-0" />
</div>
<el-input
v-model="editForm.tracking_number"
disabled
maxlength="80"
placeholder="快递单号"
class="flex-1 min-w-0"
/>
</div>
<div class="text-xs text-gray-400 mt-1">
编辑订单后需重新审核双审通过后请通过列表的单号操作填写或修改
</div>
</el-form-item>
</el-col>
</el-row>
@@ -1067,8 +1076,17 @@
<el-option label="京东快递" value="jd" />
<el-option label="极兔速递" value="jt" />
</el-select>
<el-input v-model="editForm.tracking_number" maxlength="80" placeholder="快递单号" class="flex-1 min-w-0" />
</div>
<el-input
v-model="editForm.tracking_number"
disabled
maxlength="80"
placeholder="快递单号"
class="flex-1 min-w-0"
/>
</div>
<div class="text-xs text-gray-400 mt-1">
编辑订单后需重新审核双审通过后请通过列表的单号操作填写或修改
</div>
</el-form-item>
</el-col>
<el-col :span="24">
@@ -2975,9 +2993,13 @@ function canWithdrawRow(row: { fulfillment_status?: number }) {
return Number(row.fulfillment_status) === 1
}
function canShipRow(row: { fulfillment_status?: number }) {
// 履约中(2) 可发货
return Number(row.fulfillment_status) === 2
function canShipRow(row: {
fulfillment_status?: number
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
// 履约中(2) 且处方、支付单均审核通过才可发货
return Number(row.fulfillment_status) === 2 && isDualAuditPassed(row)
}
type ShipMode = 'gancao' | 'direct'
@@ -3073,8 +3095,11 @@ function canRefundRow(row: { fulfillment_status?: number; payment_slip_audit_sta
return (fs === 5 || fs === 6 || fs === 3 || fs === 9) && Number(row.payment_slip_audit_status) === 1
}
function canQuickTrackRow(_row: { fulfillment_status?: number }) {
return true
function canQuickTrackRow(row: {
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
return isDualAuditPassed(row)
}
function canUploadPharmacyRow(row: {
@@ -3270,7 +3295,7 @@ const editSaving = ref(false)
/** 编辑订单分步:0 收货 / 1 服务与支付单 / 2 金额与确认(与处方列表「创建业务订单」一致) */
const editOrderStep = ref(0)
/** 顶部「关联处方」卡片数据(来自业务订单详情中的 prescription */
const editOrderPrescription = ref<Record<string, any> | null>(null)
const editOrderPrescription = ref<Record<string, any> | null>(null)
/** 甘草 SCM 已提交:弹窗仅展示并提交快递单号与承运商 */
const editGancaoLogisticsOnlyMode = ref(false)
/** 用于提示文案展示甘草处方单号 */
@@ -3455,7 +3480,7 @@ const editRules = computed<FormRules>(() => {
return rules
})
function resetEditOrderDialog() {
function resetEditOrderDialog() {
editOrderStep.value = 0
editOrderPrescription.value = null
editGancaoLogisticsOnlyMode.value = false
@@ -3507,8 +3532,8 @@ async function openEdit(row: {
}
editOrderStep.value = 0
editOrderPrescription.value = null
editVisible.value = true
editDialogLoading.value = true
editVisible.value = true
editDialogLoading.value = true
try {
const res: any = await prescriptionOrderDetail({ id: row.id })
const d = res?.data ?? res
@@ -3531,7 +3556,7 @@ async function openEdit(row: {
editOrderPrescription.value = null
}
editForm.id = d.id
editForm.id = d.id
editForm.prescription_id = Number(d.prescription_id) || 0
editForm.diagnosis_id = Number(d.diagnosis_id) || 0
editForm.assistant_id = Number(d.assistant_id) || 0
@@ -3627,7 +3652,6 @@ async function submitEdit() {
service_channel: editForm.service_channel || '',
service_package: Array.isArray(editForm.service_package) ? editForm.service_package.join(',') : '',
express_company: editForm.express_company || 'auto',
tracking_number: editForm.tracking_number || '',
fee_type: editForm.fee_type,
amount: editForm.amount,
remark_extra: editForm.remark_extra || '',
@@ -3915,7 +3939,17 @@ const quickTrackForm = reactive({
tracking_number: ''
})
function openQuickTrack(row: { id: number; express_company?: unknown; tracking_number?: unknown }) {
function openQuickTrack(row: {
id: number
express_company?: unknown
tracking_number?: unknown
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
if (!canQuickTrackRow(row)) {
feedback.msgWarning('处方审核和支付单审核均通过后,才可填写或修改快递单号')
return
}
quickTrackRowId.value = row.id
quickTrackForm.express_company = String(row.express_company || 'auto') || 'auto'
quickTrackForm.tracking_number = String(row.tracking_number || '')
@@ -3963,7 +3997,19 @@ function resolveShipModeForRow(row: { id: number; ship_mode?: unknown }) {
return normalizeShipMode(row.ship_mode)
}
function openShip(row: { id: number; express_company?: unknown; tracking_number?: unknown; ship_mode?: unknown }) {
function openShip(row: {
id: number
express_company?: unknown
tracking_number?: unknown
ship_mode?: unknown
fulfillment_status?: number
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
if (!canShipRow(row)) {
feedback.msgWarning('仅处方审核和支付单审核均通过的履约中订单可填写单号并发货')
return
}
shipRowId.value = Number(row.id)
shipForm.ship_mode = resolveShipModeForRow(row)
shipForm.express_company = String(row.express_company || 'auto') || 'auto'
@@ -1624,8 +1624,17 @@
<el-option label="京东快递" value="jd" />
<el-option label="极兔速递" value="jt" />
</el-select>
<el-input v-model="editForm.tracking_number" maxlength="80" placeholder="快递单号" class="flex-1 min-w-0" />
</div>
<el-input
v-model="editForm.tracking_number"
disabled
maxlength="80"
placeholder="快递单号"
class="flex-1 min-w-0"
/>
</div>
<div class="text-xs text-gray-400 mt-1">
编辑订单后需重新审核双审通过后请通过列表的单号操作填写或修改
</div>
</el-form-item>
</el-col>
<el-col :span="24">
@@ -3422,9 +3431,13 @@ function canWithdrawRow(row: { fulfillment_status?: number }) {
return Number(row.fulfillment_status) === 1
}
function canShipRow(row: { fulfillment_status?: number }) {
// (2)
return Number(row.fulfillment_status) === 2
function canShipRow(row: {
fulfillment_status?: number
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
// (2)
return Number(row.fulfillment_status) === 2 && isDualAuditPassed(row)
}
function canAddPayOrderRow(row: { fulfillment_status?: number }) {
@@ -3444,8 +3457,11 @@ function canRefundRow(row: { fulfillment_status?: number; payment_slip_audit_sta
return (fs === 5 || fs === 6 || fs === 3 || fs === 9) && Number(row.payment_slip_audit_status) === 1
}
function canQuickTrackRow(_row: { fulfillment_status?: number }) {
return true
function canQuickTrackRow(row: {
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
return isDualAuditPassed(row)
}
function canUploadPharmacyRow(row: {
@@ -4181,7 +4197,7 @@ const editSaving = ref(false)
/** 编辑订单分步:0 收货 / 1 服务与支付单 / 2 金额与确认(与处方列表「创建业务订单」一致) */
const editOrderStep = ref(0)
/** 顶部「关联处方」卡片数据(来自业务订单详情中的 prescription */
const editOrderPrescription = ref<Record<string, any> | null>(null)
const editOrderPrescription = ref<Record<string, any> | null>(null)
const editOrderStepLead = computed(() => {
const texts = [
@@ -4381,7 +4397,7 @@ const editRules = computed<FormRules>(() => {
return rules
})
function resetEditOrderDialog() {
function resetEditOrderDialog() {
editOrderStep.value = 0
editOrderPrescription.value = null
editFormRef.value?.clearValidate()
@@ -4431,8 +4447,8 @@ async function openEdit(row: {
}
editOrderStep.value = 0
editOrderPrescription.value = null
editVisible.value = true
editDialogLoading.value = true
editVisible.value = true
editDialogLoading.value = true
try {
const res: any = await prescriptionOrderDetail({ id: row.id })
const d = res?.data ?? res
@@ -4455,7 +4471,7 @@ async function openEdit(row: {
editOrderPrescription.value = null
}
editForm.id = d.id
editForm.id = d.id
editForm.prescription_id = Number(d.prescription_id) || 0
editForm.diagnosis_id = Number(d.diagnosis_id) || 0
editForm.assistant_id = Number(d.assistant_id) || 0
@@ -4540,7 +4556,6 @@ async function submitEdit() {
service_channel: editForm.service_channel || '',
service_package: Array.isArray(editForm.service_package) ? editForm.service_package.join(',') : '',
express_company: editForm.express_company || 'auto',
tracking_number: editForm.tracking_number || '',
fee_type: editForm.fee_type,
amount: editForm.amount,
remark_extra: editForm.remark_extra || '',
@@ -4788,7 +4803,17 @@ const quickTrackForm = reactive({
tracking_number: ''
})
function openQuickTrack(row: { id: number; express_company?: unknown; tracking_number?: unknown }) {
function openQuickTrack(row: {
id: number
express_company?: unknown
tracking_number?: unknown
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
if (!canQuickTrackRow(row)) {
feedback.msgWarning('处方审核和支付单审核均通过后,才可填写或修改快递单号')
return
}
quickTrackRowId.value = row.id
quickTrackForm.express_company = String(row.express_company || 'auto') || 'auto'
quickTrackForm.tracking_number = String(row.tracking_number || '')
@@ -4840,7 +4865,19 @@ const shipDialogModeDisplay = computed(() =>
shipForm.ship_mode === 'direct' ? '洛阳药房直发' : '甘草药房直发'
)
function openShip(row: { id: number; express_company?: unknown; tracking_number?: unknown; ship_mode?: unknown }) {
function openShip(row: {
id: number
express_company?: unknown
tracking_number?: unknown
ship_mode?: unknown
fulfillment_status?: number
prescription_audit_status?: number
payment_slip_audit_status?: number
}) {
if (!canShipRow(row)) {
feedback.msgWarning('仅处方审核和支付单审核均通过的履约中订单可填写单号并发货')
return
}
shipRowId.value = row.id
shipForm.ship_mode = String(row.ship_mode || 'gancao') || 'gancao'
shipForm.express_company = String(row.express_company || 'auto') || 'auto'
@@ -0,0 +1,156 @@
const assert = require('node:assert/strict')
const fs = require('node:fs')
const path = require('node:path')
const test = require('node:test')
const ts = require('typescript')
const vue = require('vue')
const { parse, compileScript, compileTemplate } = require('@vue/compiler-sfc')
const handlers = [
'isDualAuditPassed', 'canQuickTrackRow', 'canShipRow', 'openQuickTrack',
'submitQuickTrack', 'openShip', 'openEdit', 'submitEdit', 'resetEditOrderDialog'
]
const stateNames = [
'quickTrackVisible', 'quickTrackSaving', 'quickTrackRowId', 'quickTrackForm',
'shipVisible', 'shipRowId', 'shipForm', 'editVisible', 'editDialogLoading',
'editSaving', 'editOrderStep', 'editOrderPrescription',
'editFormRef', 'editForm', 'editDepositMin'
]
for (const page of ['order_list.vue', 'order_list_h5.vue']) {
const filename = path.join(__dirname, '../src/views/consumer/prescription', page)
const { descriptor, errors } = parse(fs.readFileSync(filename, 'utf8'), { filename })
assert.deepEqual(errors, [])
const script = compileScript(descriptor, { id: page })
const ast = ts.createSourceFile(filename + '.ts', descriptor.scriptSetup.content, ts.ScriptTarget.Latest, true)
// Execute the actual order handlers with only network and unrelated UI dependencies stubbed.
const declarations = new Map()
for (const node of ast.statements) {
if (ts.isFunctionDeclaration(node) && node.name) declarations.set(node.name.text, node.getText(ast))
if (ts.isVariableStatement(node)) {
for (const declaration of node.declarationList.declarations) {
declarations.set(declaration.name.getText(ast), `const ${declaration.getText(ast)}`)
}
}
}
const selected = [...handlers, ...stateNames]
for (const name of selected) assert.ok(declarations.has(name), `${page}: missing ${name}`)
const compiled = ts.transpileModule(selected.map(name => declarations.get(name)).join('\n'), {
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS }
}).outputText
function instance(detail = {}, saveTracking = async () => {}) {
const warnings = []
const errors = []
const edits = []
const tracks = []
const globals = {
ref: vue.ref, reactive: vue.reactive, nextTick: vue.nextTick,
feedback: { msgWarning: message => warnings.push(message), msgError: message => errors.push(message), msgSuccess() {} },
prescriptionOrderDetail: async () => ({ data: detail }),
prescriptionOrderEdit: async payload => edits.push(payload),
prescriptionOrderDdcode: async payload => { tracks.push(payload); await saveTracking(payload) },
canEditRow: () => true,
parseServicePackageValues: () => [],
loadEditPaidOrders: async () => {},
resolveShipModeForRow: row => row.ship_mode || 'gancao',
editGancaoLogisticsOnlyMode: vue.ref(false),
editGancaoDisplayNo: vue.ref(''),
detailDrawerRef: vue.ref(null),
detailVisible: vue.ref(false),
getLists() {}
}
const state = new Function(...Object.keys(globals), `${compiled}\nreturn { ${selected.join(', ')} }`)(...Object.values(globals))
state.editFormRef.value = { validate: async () => {}, clearValidate() {} }
return { state, warnings, errors, edits, tracks }
}
test(`${page}: only two approved audits unlock tracking, including legacy string statuses`, () => {
const { state } = instance()
for (const rx of [undefined, null, 0, 1, 2, '0', '1', '2']) {
for (const pay of [undefined, null, 0, 1, 2, '0', '1', '2']) {
const row = { prescription_audit_status: rx, payment_slip_audit_status: pay, fulfillment_status: 2 }
const allowed = [1, '1'].includes(rx) && [1, '1'].includes(pay)
assert.equal(state.canQuickTrackRow(row), allowed, `tracking: ${rx}/${pay}`)
assert.equal(state.canShipRow(row), allowed, `shipping: ${rx}/${pay}`)
}
}
for (const status of [1, 3, 4, 5, 6, 9]) {
assert.equal(state.canShipRow({ prescription_audit_status: 1, payment_slip_audit_status: 1, fulfillment_status: status }), false)
}
})
test(`${page}: direct handler calls cannot open tracking or shipping before both approvals`, () => {
for (const [rx, pay] of [[0, 0], [1, 0], [0, 1], [2, 1], [1, 2]]) {
const { state, warnings } = instance()
const row = { id: 42, prescription_audit_status: rx, payment_slip_audit_status: pay, fulfillment_status: 2 }
state.openQuickTrack(row)
state.openShip(row)
assert.equal(state.quickTrackVisible.value, false)
assert.equal(state.shipVisible.value, false)
assert.equal(state.quickTrackRowId.value, 0)
assert.equal(state.shipRowId.value, 0)
assert.equal(warnings.length, 2)
}
})
test(`${page}: approved orders can fill or replace the number and preserve the carrier`, async () => {
for (const oldNumber of ['', 'SF-OLD']) {
const { state, tracks } = instance()
const row = { id: 42, prescription_audit_status: 1, payment_slip_audit_status: 1, fulfillment_status: 2, express_company: 'sf', tracking_number: oldNumber }
state.openQuickTrack(row)
assert.equal(state.quickTrackVisible.value, true)
assert.equal(state.quickTrackForm.tracking_number, oldNumber)
state.quickTrackForm.tracking_number = ' SF-NEW '
await state.submitQuickTrack()
assert.deepEqual(tracks, [{ id: 42, express_company: 'sf', tracking_number: 'SF-NEW' }])
assert.equal(state.quickTrackVisible.value, false)
state.openShip(row)
assert.equal(state.shipVisible.value, true)
}
})
test(`${page}: a server rejection after audit revocation retains the dialog and entered number`, async () => {
const { state } = instance({}, async () => { throw new Error('audit revoked') })
state.openQuickTrack({ id: 42, prescription_audit_status: 1, payment_slip_audit_status: 1 })
state.quickTrackForm.tracking_number = 'SF-NEW'
await state.submitQuickTrack()
assert.equal(state.quickTrackVisible.value, true)
assert.equal(state.quickTrackSaving.value, false)
assert.equal(state.quickTrackForm.tracking_number, 'SF-NEW')
})
test(`${page}: editing preserves the existing number while saving other fields before approval`, async () => {
const { state, edits, errors } = instance({ id: 42, prescription_audit_status: 1, payment_slip_audit_status: 0, tracking_number: 'SF-EXISTING' })
await state.openEdit({ id: 42, prescription_audit_status: 1, payment_slip_audit_status: 1 })
assert.deepEqual(errors, [])
assert.equal(state.editForm.tracking_number, 'SF-EXISTING')
state.editForm.tracking_number = 'FORGED'
state.editForm.recipient_name = '修改后的收货人'
state.editOrderStep.value = 2
await state.submitEdit()
assert.equal(edits.length, 1)
assert.equal(edits[0].recipient_name, '修改后的收货人')
assert.equal(Object.hasOwn(edits[0], 'tracking_number'), false)
})
test(`${page}: ordinary editing cannot change tracking while resetting an approved payment audit`, async () => {
const { state, edits, errors } = instance({ id: 42, prescription_audit_status: '1', payment_slip_audit_status: '1' })
await state.openEdit({ id: 42 })
assert.deepEqual(errors, [])
state.editForm.tracking_number = 'SF-NEW'
state.editOrderStep.value = 2
await state.submitEdit()
assert.equal(edits.length, 1)
assert.equal(Object.hasOwn(edits[0], 'tracking_number'), false)
})
test(`${page}: template compiles with tracking controls bound to audit restrictions`, () => {
const template = compileTemplate({ source: descriptor.template.content, filename, id: page, compilerOptions: { bindingMetadata: script.bindings } })
assert.deepEqual(template.errors, [])
const inputs = [...descriptor.template.content.matchAll(/<el-input\b[^>]*v-model="editForm\.tracking_number"[^>]*>/g)]
assert.equal(inputs.length, page === 'order_list.vue' ? 2 : 1)
for (const [input] of inputs) assert.match(input, /\sdisabled(?:\s|\/?>)/)
assert.match(descriptor.template.content, /v-if="canQuickTrackRow\(row\)"/)
})
}