Merge branch 'release/test' of http://8.163.30.128:10082/html/admin-web into release/test

This commit is contained in:
2026-10-28 11:30:02 +08:00
21 changed files with 522 additions and 150 deletions
@@ -4,7 +4,7 @@ import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, expect, it, vi } from 'vitest'
import DispatchDocumentsDialog from './DispatchDocumentsDialog.vue'
import DeliveryDocumentsDialog from './DeliveryDocumentsDialog.vue'
import { detail } from '../dispatchTestFixtures'
import { destination, detail } from '../dispatchTestFixtures'
import { getDispatch } from '../api/dispatches'
import { deliveryDocumentCategories, getDeliveryDocuments, saveDeliveryDocuments, type DeliveryDocuments } from '../api/deliveryDocuments'
const can = vi.hoisted(() => vi.fn(() => true))
@@ -37,8 +37,13 @@ it.each(['LOADING', 'AFTER_LOADING'] as const)('opens loading documents in %s be
expect(wrapper.findAll('[role="tab"]').find(tab => tab.text() === '装车资料')!.attributes('aria-selected')).toBe('true')
expect(getDeliveryDocuments).not.toHaveBeenCalled(); wrapper.unmount()
})
it('reads existing delivery files in one dialog and saves receipt without requiring loading or BOL files', async () => {
it('hides the single delivery selector and saves receipt files to that delivery without requiring loading or BOL files', async () => {
const value = detail({ stage: 'DELIVERING' })
value.documents.deliveries = [{ deliveryNo: 'DL-1', customerId: '101', customerName: '客户甲', groupKey: 'A', outboundNo: 'OB-1', destination: destination(), status: 'DELIVERING', packageCount: 1, palletCount: 1 }]
vi.mocked(getDispatch).mockResolvedValueOnce(value)
const wrapper = setup(); await flushPromises()
expect(wrapper.findComponent({ name: 'ElSelect' }).exists()).toBe(false)
expect(wrapper.find('[aria-label="选择派送单"]').exists()).toBe(false)
expect(getDeliveryDocuments).toHaveBeenCalledWith('DL-1', '101')
expect(wrapper.text()).toContain('existing-pod.pdf')
expect(wrapper.text()).not.toContain('资料核对')
@@ -52,6 +57,7 @@ it('reads existing delivery files in one dialog and saves receipt without requir
})
it('switches customer scope exactly and keeps unsaved delivery edits when switching tabs', async () => {
const wrapper = setup(); await flushPromises()
expect(wrapper.findComponent({ name: 'ElSelect' }).exists()).toBe(true)
const receipt = wrapper.getComponent(DeliveryDocumentsDialog)
await receipt.findAll('button').find(button => button.text() === '删除')!.trigger('click')
expect(receipt.text()).not.toContain('existing-pod.pdf')
@@ -63,7 +63,7 @@ watch(() => [open.value, props.dispatchNo] as const, ([visible]) => {
<div class="density-status-tabs" role="tablist" aria-label="排车资料类别"><button v-for="item in [{ key: 'RECEIPT', label: '签收文件' }, { key: 'LOADING', label: '装车资料' }]" :key="item.key" role="tab" :aria-selected="tab === item.key" :disabled="busy" :class="{ 'is-active': tab === item.key }" @click="selectTab(item.key)">{{ item.label }}</button></div>
<DispatchLoadingDocuments v-if="tab === 'LOADING'" ref="loadingEditor" :key="dispatchNo" :dispatch-no="dispatchNo" @saved="loadingSaved" />
<div v-show="tab === 'RECEIPT'" v-if="!beforeDeparture">
<el-form class="density-form" label-position="left" label-width="80px"><el-form-item label="派送单"><el-select :model-value="selected" :disabled="busy || !access.can(permission.deliveryList)" aria-label="选择派送单" @update:model-value="selectDelivery"><el-option v-for="item in deliveries" :key="item.deliveryNo" :value="item.deliveryNo" :label="item.deliveryNo" /></el-select></el-form-item></el-form>
<el-form v-if="deliveries.length > 1" class="density-form" label-position="left" label-width="80px"><el-form-item label="派送单"><el-select :model-value="selected" :disabled="busy || !access.can(permission.deliveryList)" aria-label="选择派送单" @update:model-value="selectDelivery"><el-option v-for="item in deliveries" :key="item.deliveryNo" :value="item.deliveryNo" :label="item.deliveryNo" /></el-select></el-form-item></el-form>
<DeliveryDocumentsDialog v-if="delivery && access.can(permission.deliveryList)" ref="editor" :key="delivery.customerId + ':' + delivery.deliveryNo" :model-value="open" embedded :delivery-no="delivery.deliveryNo" :customer-id="delivery.customerId" :categories="['RECEIPT']" @saved="saved" />
<p v-else>{{ access.can(permission.deliveryList) ? '暂无关联派送单' : '当前账号无派送资料查看权限' }}</p>
</div>
@@ -199,7 +199,7 @@ describe('dispatch workflow native layout', () => {
expect(dialog.get('[aria-label="本车审核依据"]').text()).toContain('联系电话')
expect(dialog.get('[aria-label="排车与预约"]').findAll('input')).toHaveLength(0)
const photos = dialog.findAllComponents(DispatchEvidenceFiles)
expect(photos.map(photo => photo.props('label'))).toEqual(['车辆图片', '司机照片', '驾驶证照片'])
expect(photos.map(photo => photo.props('label'))).toEqual(['车牌图片', '司机照片', '驾驶证照片'])
expect(photos.every(photo => photo.props('readonly'))).toBe(true)
expect(dialog.get('[aria-label="操作原因与意见"] h3').text()).toBe('审核意见')
expect(dialog.text()).not.toContain('请核对本车资源与本趟证据')
@@ -246,7 +246,7 @@ describe('dispatch workflow native layout', () => {
vi.mocked(api.updateDispatch).mockRejectedValueOnce(new Error('unknown edit')).mockImplementationOnce(async (_, body) => command('EDIT', body.idempotencyKey))
const { wrapper, dialog } = await render('EDIT', undefined, true)
const fields = dialog.findAllComponents(DispatchEvidenceFiles)
expect(fields.map(item => item.props('label'))).toEqual(['车辆图片', '司机照片', '驾驶证照片'])
expect(fields.map(item => item.props('label'))).toEqual(['车牌图片', '司机照片', '驾驶证照片'])
expect(fields[0]!.props('modelValue')).toEqual(['301'])
expect(dialog.get('.workflow-photo-grid').findAll('figure')).toHaveLength(3)
for (const [index, files] of fields.entries()) {
@@ -280,7 +280,7 @@ describe('dispatch workflow native layout', () => {
vi.mocked(api.getDispatch).mockResolvedValueOnce(value)
await wrapper.setProps({ modelValue: false }); await wrapper.setProps({ modelValue: true }); await flushPromises()
const fields = dialog.findAllComponents(DispatchEvidenceFiles)
expect(fields.map(item => item.props('label'))).toEqual(['车辆图片', '司机照片', '驾驶证照片'])
expect(fields.map(item => item.props('label'))).toEqual(['车牌图片', '司机照片', '驾驶证照片'])
expect(fields.every(item => item.props('readonly') && item.props('photoCard'))).toBe(true)
expect(fields[1]!.props('modelValue')).toEqual(['402', '403'])
expect(dialog.findAll('input[type="file"]')).toHaveLength(0)
@@ -137,7 +137,7 @@ function payload(): object {
if (!exception.exceptionType) throw new DispatchInputError('请选择异常类型')
return { ...common, exceptionType: exception.exceptionType, returnDirection: exception.returnDirection || null, description: exception.description.trim() || null, imageFileIds: [...exception.imageFileIds] } satisfies MarkDispatchException
case 'EDIT':
if (missingVehicle.value && vehicle.value.evidenceFileIds.length) throw new DispatchInputError('请先选择车辆,再保存车辆图片;当前图片尚未保存')
if (missingVehicle.value && vehicle.value.evidenceFileIds.length) throw new DispatchInputError('请先选择车辆,再保存车牌图片;当前图片尚未保存')
if (missingDriver.value && (driver.value.evidence?.fileIds.length || driverPhotos.value.length)) throw new DispatchInputError('请先选择司机,再保存司机照片和驾驶证照片;当前图片尚未保存')
return { ...common, vehicle: missingVehicle.value ? null : vehicle.value, driver: missingDriver.value ? null : driver.value, maintenance: vehicleMaintenancePayload(form.maintenance), plannedDepartureAt: form.plannedDepartureAt || null, estimatedArrivalAt: form.estimatedArrivalAt || null, remark: form.remark || null } satisfies UpdateDispatch
case 'DISPATCH_REVIEW': case 'LOADING_REVIEW':
@@ -268,7 +268,7 @@ watch([open, () => props.dispatchNo, () => props.action, () => props.operation],
<template #fees><DispatchVehicleMaintenanceFees :dispatch-no="currentNo" :deliveries="detail.documents.deliveries" /></template>
<template #attachments>
<div class="workflow-photo-grid density-dialog-sections" aria-label="车辆司机照片">
<DispatchEvidenceFiles photo-card v-model="vehicle.evidenceFileIds" :dispatch-no="currentNo" kind="VEHICLE" label="车辆图片" :master-resource="vehicle.mode === 'MASTER' && vehicle.vehicleId ? { kind: 'VEHICLE', id: vehicle.vehicleId } : null" :disabled="frozen || loading || uploadingEvidence || !allowed" @busy="evidenceBusy.VEHICLE = $event" />
<DispatchEvidenceFiles photo-card v-model="vehicle.evidenceFileIds" :dispatch-no="currentNo" kind="VEHICLE" label="车牌图片" :master-resource="vehicle.mode === 'MASTER' && vehicle.vehicleId ? { kind: 'VEHICLE', id: vehicle.vehicleId } : null" :disabled="frozen || loading || uploadingEvidence || !allowed" @busy="evidenceBusy.VEHICLE = $event" />
<DispatchEvidenceFiles photo-card v-model="driverPhotos" :dispatch-no="currentNo" kind="DRIVER_PHOTO" label="司机照片" :master-resource="driver.mode === 'MASTER' && driver.driverId ? { kind: 'DRIVER', id: driver.driverId } : null" :disabled="frozen || loading || uploadingEvidence || !allowed" @busy="evidenceBusy.DRIVER_PHOTO = $event" />
<DispatchEvidenceFiles photo-card v-if="driver.evidence" v-model="driver.evidence.fileIds" :dispatch-no="currentNo" kind="DRIVER" label="驾驶证照片" :reference-ids="masterLicenseReference ? [masterLicenseReference] : []" :master-license="unsavedMasterLicense" :disabled="frozen || loading || uploadingEvidence || !allowed" @busy="evidenceBusy.DRIVER = $event" />
</div>