fix(fleet): distinguish signed returned and remaining trip cargo
This commit is contained in:
@@ -42,7 +42,11 @@ const bindingsColumns: DataTableColumn[] = [{ key: 'actions', label: '操作', w
|
||||
{ key: 'boundAt', label: '关联时间', slotName: 'boundAt', minWidth: 180 }]
|
||||
const outcomeColumns: DataTableColumn[] = [{ key: 'deliveryNo', prop: 'deliveryNo', label: '派送单号', minWidth: 220 },
|
||||
{ key: 'destination', prop: 'destination', label: '目的地', minWidth: 180 }, { key: 'status', label: '状态', slotName: 'status', width: 115 },
|
||||
{ key: 'packages', prop: 'packages', label: '箱数', width: 110 }, { key: 'terminalAt', label: '完成时间', slotName: 'terminalAt', minWidth: 180 }]
|
||||
{ key: 'packages', prop: 'packages', label: '原实发箱数', width: 110 },
|
||||
{ key: 'signedPackages', prop: 'signedPackages', label: '实签箱数', width: 100 },
|
||||
{ key: 'returnedPackages', prop: 'returnedPackages', label: '本趟实退箱数', width: 120 },
|
||||
{ key: 'remainingPackages', prop: 'remainingPackages', label: '未结箱数', width: 100 },
|
||||
{ key: 'terminalAt', label: '完成时间', slotName: 'terminalAt', minWidth: 180 }]
|
||||
const outcomeLabels: Record<string, string> = { DELIVERED: '已签收', RETURNED: '已退回', RETURN_PENDING: '待退回', DELIVERING: '派送中', WAIT_DELIVERY: '待派送', CANCELLED: '已取消' }
|
||||
const historyColumns: DataTableColumn[] = [{ key: 'occurredAt', label: '操作时间', slotName: 'time', minWidth: 175 },
|
||||
{ key: 'action', label: '操作', slotName: 'action', minWidth: 145 }, { key: 'reason', prop: 'reason', label: '说明', minWidth: 230 },
|
||||
@@ -199,7 +203,7 @@ onBeforeUnmount(() => { generation++; inspectionSequence++; historySequence++ })
|
||||
<FormSection v-if="activeTab === 'loading'" class="density-form-section" title="按实物托盘确认装载"><TripLoadingPanel :key="detail.trip.tripNo" :trip="detail.trip" :editable="editable" @saved="loaded" /><div class="trip-review"><p>理货审核:{{ tripReviewLabels[detail.loadingReview.status] }}<span v-if="detail.loadingReview.reason"> · {{ detail.loadingReview.reason }}</span><span v-if="detail.loadingReview.reviewedAt"> · {{ time(detail.loadingReview.reviewedAt) }}</span></p><div v-if="planned && access.can(permission.tripLoadingReview)" class="density-actions"><el-button type="primary" :disabled="!!busy || detail.loadingReview.status === 'APPROVED'" @click="review('APPROVED')">理货审核通过</el-button><el-button type="danger" plain :disabled="!!busy" @click="review('REJECTED')">理货打回</el-button></div></div></FormSection>
|
||||
<template v-if="activeTab === 'completion'">
|
||||
<FormSection v-if="detail.trip.status === 'IN_TRANSIT'" v-loading="inspectionLoading" class="density-form-section" title="货物结案检查"><div class="trip-inline"><el-button @click="inspect">重新检查</el-button><el-button v-if="access.can(permission.tripComplete)" type="primary" :disabled="!inspection?.canComplete || !!busy" :loading="busy === 'complete'" @click="complete">确认货物结案</el-button></div><p>{{ inspection?.blockReason || (inspection?.canComplete ? '全部货物已具备结案依据' : '正在读取派送结果') }}</p>
|
||||
<DataTable :data="inspection?.outcomes || []" :columns="outcomeColumns" row-key="deliveryNo"><template #status="{ row }">{{ outcomeLabels[row.status] || '待处理' }}</template><template #terminalAt="{ row }">{{ time(row.terminalAt) }}</template></DataTable>
|
||||
<DataTable :data="inspection?.outcomes || []" :columns="outcomeColumns" row-key="deliveryNo"><template #status="{ row }">{{ row.status === 'DELIVERED' && row.returnedPackages > 0 ? '余货已签收' : outcomeLabels[row.status] || '待处理' }}</template><template #terminalAt="{ row }">{{ time(row.terminalAt) }}</template></DataTable>
|
||||
</FormSection>
|
||||
<FormSection class="density-form-section" title="车辆和司机分别结束任务"><p>货物结案与资源释放分别记录;仍有返程或转场任务时,等实际任务结束再释放。</p><dl class="density-fields"><div><dt>车辆任务</dt><dd>{{ detail.trip.vehicleRelease ? `${time(detail.trip.vehicleRelease.actualEndAt)} · ${detail.trip.vehicleRelease.endLocation}` : '尚未结束' }}</dd></div><div><dt>司机任务</dt><dd>{{ detail.trip.driverRelease ? `${time(detail.trip.driverRelease.actualEndAt)} · ${detail.trip.driverRelease.endLocation}` : '尚未结束' }}</dd></div></dl>
|
||||
<el-form v-if="detail.trip.status === 'COMPLETED' && access.can(permission.tripRelease) && (!detail.trip.vehicleRelease || !detail.trip.driverRelease)" label-position="top" class="density-form release-form" :disabled="!!busy || !!releaseRequest" @submit.prevent="release">
|
||||
|
||||
@@ -25,7 +25,7 @@ const detail: TripDetail = { trip, driverQualification: null, dispatches: [], lo
|
||||
const layout = { template: '<div><slot name="header"/><slot/></div>' }
|
||||
const input = { name: 'ElInput', props: ['modelValue'], emits: ['update:modelValue'], template: '<input :value="modelValue" @input="$emit(\'update:modelValue\',$event.target.value)" />' }
|
||||
const dialog = { name: 'AppDialog', props: ['modelValue'], emits: ['confirm'], template: '<div v-if="modelValue"><slot/><button @click="$emit(\'confirm\')">保存计划</button></div>' }
|
||||
const table = { name: 'DataTable', props: ['data', 'columns', 'page', 'pageSize'], emits: ['update:page'], template: '<div><slot name="toolbar-actions"/><div v-for="(row,index) in data" :key="index"><slot name="actions" :row="row"/><slot name="weight" :row="row"/><slot name="volume" :row="row"/></div></div>' }
|
||||
const table = { name: 'DataTable', props: ['data', 'columns', 'page', 'pageSize'], emits: ['update:page'], template: '<div><slot name="toolbar-actions"/><div v-for="(row,index) in data" :key="index"><slot name="actions" :row="row"/><slot name="status" :row="row"/><slot name="weight" :row="row"/><slot name="volume" :row="row"/></div></div>' }
|
||||
const wrappers: VueWrapper[] = []
|
||||
function mount(component: object, props: Record<string, unknown> = {}): VueWrapper {
|
||||
const wrapper = shallowMount(component, { props, global: { directives: { loading: () => undefined }, stubs: {
|
||||
@@ -46,6 +46,27 @@ beforeEach(() => {
|
||||
})
|
||||
afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount()))
|
||||
|
||||
it('shows mixed signature and return quantities from one lazy completion read', async () => {
|
||||
vi.mocked(api.getTrip).mockResolvedValue({ ...structuredClone(detail), trip: { ...trip, status: 'IN_TRANSIT' } })
|
||||
const outcome = { dispatchNo: 'DP-1', deliveryNo: 'DL-1', customerId: 'A', destination: 'ONT8', status: 'DELIVERED',
|
||||
packages: 12, signedPackages: 8, returnedPackages: 4, remainingPackages: 0, terminalAt: trip.updatedAt }
|
||||
vi.mocked(api.getTripCompletion).mockResolvedValue({ tripNo: trip.tripNo, recordVersion: 3, canComplete: true, blockReason: null, outcomes: [outcome] })
|
||||
const wrapper = mount(TripDetailView); await flushPromises()
|
||||
expect(api.getTripCompletion).not.toHaveBeenCalled()
|
||||
wrapper.findComponent({ name: 'ElTabs' }).vm.$emit('update:modelValue', 'completion'); await flushPromises()
|
||||
expect(api.getTripCompletion).toHaveBeenCalledExactlyOnceWith('TR-TEST')
|
||||
const result = wrapper.findComponent({ name: 'DataTable' })
|
||||
expect(result.props('data')).toEqual([outcome])
|
||||
expect(result.props('columns')).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ prop: 'packages', label: '原实发箱数' }),
|
||||
expect.objectContaining({ prop: 'signedPackages', label: '实签箱数' }),
|
||||
expect.objectContaining({ prop: 'returnedPackages', label: '本趟实退箱数' }),
|
||||
expect.objectContaining({ prop: 'remainingPackages', label: '未结箱数' }),
|
||||
]))
|
||||
expect(wrapper.text()).toContain('余货已签收')
|
||||
expect(api.completeTrip).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('mounts direct costs only when the costs tab is selected', async () => {
|
||||
const wrapper = mount(TripDetailView); await flushPromises()
|
||||
expect(wrapper.findComponent({ name: 'TripCostPanel' }).exists()).toBe(false)
|
||||
|
||||
Reference in New Issue
Block a user