feat(appointment): complete customer receiving and trip continuation workflows
This commit is contained in:
@@ -97,7 +97,9 @@ export async function getBatchDispatchCandidates(batchKey: string) {
|
||||
return unwrapApiResponse(await http.get<ApiEnvelope<BatchDispatchCandidates>, ApiEnvelope<BatchDispatchCandidates>>(`/platform/v1/last-leg/appointment-planning/batches/${encodeURIComponent(batchKey)}/dispatch-candidates`))
|
||||
}
|
||||
|
||||
export interface DispatchBatchReference { batchKey: string; batchNo: string | null; customerId: string; sources: Array<{ sourceKey: string; destinationKey: string; packageCount: number }> }
|
||||
export interface DispatchSummaryDto {
|
||||
appointmentBatches: DispatchBatchReference[]
|
||||
workflow?: DispatchWorkflow | null
|
||||
id: string
|
||||
dispatchNo: string
|
||||
|
||||
@@ -46,8 +46,8 @@ const expectedBatch = { draftToken: 'original-draft', groups: [
|
||||
{ draftContainerId: 'container-1', planPalletIds: ['11', '13'], returnReceiptIds: [] },
|
||||
{ draftContainerId: 'container-2', planPalletIds: ['14'], returnReceiptIds: ['22'] },
|
||||
] }
|
||||
const goodResult = [{ draftContainerId: 'container-2', dispatch: { id: '102', dispatchNo: 'DS-2' } },
|
||||
{ draftContainerId: 'container-1', dispatch: { id: '101', dispatchNo: 'DS-1' } }]
|
||||
const goodResult = [{ draftContainerId: 'container-2', dispatch: { id: '102', dispatchNo: 'DS-2', appointmentBatches: [] } },
|
||||
{ draftContainerId: 'container-1', dispatch: { id: '101', dispatchNo: 'DS-1', appointmentBatches: [] } }]
|
||||
async function submitDraft(wrapper: ReturnType<typeof mountDraft>) {
|
||||
await wrapper.findAll('button').find(node => node.text() === '预排车')!.trigger('click')
|
||||
await flushPromises()
|
||||
@@ -70,7 +70,7 @@ it.each([
|
||||
['foreign container', [{ ...goodResult[0], draftContainerId: 'foreign' }, goodResult[1]]],
|
||||
['duplicate container', [goodResult[0], goodResult[0]]],
|
||||
['duplicate dispatch', [goodResult[0], { ...goodResult[1], dispatch: goodResult[0].dispatch }]],
|
||||
['missing business number', [goodResult[0], { ...goodResult[1], dispatch: { id: '101', dispatchNo: '' } }]],
|
||||
['missing business number', [goodResult[0], { ...goodResult[1], dispatch: { id: '101', dispatchNo: '', appointmentBatches: [] } }]],
|
||||
])('retains the original groups and token after %s', async (_label, result) => {
|
||||
vi.mocked(listDispatchDrafts).mockResolvedValue([twoGroups])
|
||||
vi.mocked(listDispatchCandidates).mockResolvedValue([plan, secondPlan, thirdPlan, returned])
|
||||
@@ -115,7 +115,7 @@ it('submits relabel physical pallet identity without requiring an inbound plan p
|
||||
{ id: 'container', capacity: 30, palletIds: ['101'] },
|
||||
] }])
|
||||
vi.mocked(listDispatchCandidates).mockResolvedValue([{ ...plan, planPalletId: null, sourceStage: 'RELABEL', orderRefs: 'RL-1' }])
|
||||
vi.mocked(preArrangeDispatch).mockResolvedValue({ id: '1', dispatchNo: 'DS-1' })
|
||||
vi.mocked(preArrangeDispatch).mockResolvedValue({ id: '1', dispatchNo: 'DS-1', appointmentBatches: [] })
|
||||
const wrapper = mountDraft(); await flushPromises(); await submitDraft(wrapper)
|
||||
expect(preArrangeDispatch).toHaveBeenCalledExactlyOnceWith({ planPalletIds: ['101'], returnReceiptIds: [], relabelNo: 'RL-1' })
|
||||
expect(preArrangeDraftDispatch).not.toHaveBeenCalled()
|
||||
@@ -197,7 +197,7 @@ it('keeps one complete relabel order on the single-dispatch endpoint', async ()
|
||||
{ id: 'c1', capacity: 30, palletIds: ['11', '13'] },
|
||||
] }])
|
||||
vi.mocked(listDispatchCandidates).mockResolvedValue([plan, secondPlan].map(p => ({ ...p, sourceStage: 'RELABEL', orderRefs: 'RL-1' })))
|
||||
vi.mocked(preArrangeDispatch).mockResolvedValue({ id: '1', dispatchNo: 'DS-RL' })
|
||||
vi.mocked(preArrangeDispatch).mockResolvedValue({ id: '1', dispatchNo: 'DS-RL', appointmentBatches: [] })
|
||||
const wrapper = mountDraft(); await flushPromises(); await submitDraft(wrapper)
|
||||
expect(preArrangeDispatch).toHaveBeenCalledExactlyOnceWith({ planPalletIds: ['11', '13'], returnReceiptIds: [], relabelNo: 'RL-1' })
|
||||
expect(preArrangeDraftDispatch).not.toHaveBeenCalled()
|
||||
@@ -206,7 +206,7 @@ it('keeps one complete relabel order on the single-dispatch endpoint', async ()
|
||||
})
|
||||
|
||||
it('retains every restored selection on candidate failure and submits both sources only after retry hydration', async () => {
|
||||
vi.mocked(preArrangeDraftDispatch).mockResolvedValue([{ draftContainerId: 'container', dispatch: { id: '1', dispatchNo: 'DS-1' } }])
|
||||
vi.mocked(preArrangeDraftDispatch).mockResolvedValue([{ draftContainerId: 'container', dispatch: { id: '1', dispatchNo: 'DS-1', appointmentBatches: [] } }])
|
||||
vi.mocked(listDispatchDrafts).mockResolvedValue([{ draftToken: 'original-draft', containers: [{ id: 'container', capacity: 30, palletIds: ['11', 'RETURN:22'] }] }])
|
||||
vi.mocked(listDispatchCandidates)
|
||||
.mockResolvedValueOnce([plan])
|
||||
|
||||
@@ -7,7 +7,7 @@ export { default as FbaAppointmentFields } from './components/FbaAppointmentFiel
|
||||
export { createAppointmentForm, appointmentPayload, appointmentFormError } from './appointmentForm'
|
||||
export { overseasWarehousePermissions } from './permissions'
|
||||
export { getDispatch, getBatchDispatchCandidates, createDispatchDraftContainer, listDispatchDrafts, lockDispatchDraftPallet, preArrangeDraftDispatch, releaseDispatchDraft } from './api/dispatches'
|
||||
export type { BatchDispatchCandidate, BatchDispatchCandidates, DispatchBatchAllocation, DispatchSummaryDto, PreArrangeDispatchDraftPayload } from './api/dispatches'
|
||||
export type { DispatchBatchReference, BatchDispatchCandidate, BatchDispatchCandidates, DispatchBatchAllocation, DispatchSummaryDto, PreArrangeDispatchDraftPayload } from './api/dispatches'
|
||||
export { dispatchVehicleSpecs } from './dispatchVehicleSpecs'
|
||||
export { getContainerPlanProgress, getContainerInboundPlanPreview, convertLastMileContainerToInboundPlans } from './api/lastMileOrders'
|
||||
export type { ContainerPlanProgressDetailDto, ContainerInboundPlanPreviewDto, ConvertContainerInboundPlanPayload } from './api/lastMileOrders'
|
||||
|
||||
@@ -15,7 +15,7 @@ describe('DispatchDetailView', () => {
|
||||
routerPush.mockReset()
|
||||
Object.keys(sourceQuery).forEach(key => delete sourceQuery[key])
|
||||
vi.mocked(getDispatch).mockReset().mockResolvedValue({
|
||||
id: '1', dispatchNo: 'PC/1', vehiclePlate: 'CA-100', driverName: '张师傅', status: 'PENDING_DEPARTURE',
|
||||
id: '1', dispatchNo: 'PC/1', appointmentBatches: [], vehiclePlate: 'CA-100', driverName: '张师傅', status: 'PENDING_DEPARTURE',
|
||||
route: 'LAX → ONT8', items: [{ id: '11', planPalletId: '20', orderNo: 'LM-1', warehousePalletId: '21', fbaWarehouseCode: 'ONT8', grossWeightKg: 88.5, volumeCbm: 1.25 }],
|
||||
})
|
||||
})
|
||||
@@ -33,7 +33,7 @@ describe('DispatchDetailView', () => {
|
||||
})
|
||||
|
||||
it('keeps related order weight unknown when one pallet lacks physical weight', async () => {
|
||||
vi.mocked(getDispatch).mockResolvedValueOnce({ id: '1', dispatchNo: 'PC/1', status: 'PENDING_DEPARTURE', items: [
|
||||
vi.mocked(getDispatch).mockResolvedValueOnce({ id: '1', dispatchNo: 'PC/1', appointmentBatches: [], status: 'PENDING_DEPARTURE', items: [
|
||||
{ id: '11', planPalletId: '31', orderNo: 'LM-1', warehousePalletId: '21', grossWeightKg: 88.5, volumeCbm: 1 },
|
||||
{ id: '12', planPalletId: '32', orderNo: 'LM-1', warehousePalletId: '22', grossWeightKg: null, volumeWeightKg: 999, volumeCbm: 1 },
|
||||
] })
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('DispatchesView', () => {
|
||||
vi.stubGlobal('localStorage', memoryStorage())
|
||||
vi.mocked(listDispatches).mockReset().mockResolvedValue({
|
||||
records: [{
|
||||
id: '190000000000000099', dispatchNo: 'PC-1', vehiclePlate: 'CA-100', driverName: '张师傅',
|
||||
id: '190000000000000099', dispatchNo: 'PC-1', appointmentBatches: [], vehiclePlate: 'CA-100', driverName: '张师傅',
|
||||
driverPhone: '555-0100', carrierName: 'Carrier A', orderNos: 'LM-1,LM-2', route: 'ONT8,LAX9',
|
||||
overseasWarehouseId: '321', overseasWarehouseName: '洛杉矶仓', departAt: '2026-08-20T09:00:00', fbaAppointmentNo: 'FBA-1',
|
||||
fbaAppointmentStatus: 'CONFIRMED', vehicleAppointmentStatus: 'BOOKED', estimatedArrivalAt: '2026-08-20T18:00:00',
|
||||
@@ -219,7 +219,7 @@ describe('DispatchesView', () => {
|
||||
|
||||
it('keeps long dispatch identifiers as strings', async () => {
|
||||
vi.mocked(listDispatches).mockResolvedValueOnce({
|
||||
records: [{ id: '190000000000000099', dispatchNo: '190000000000000099' }],
|
||||
records: [{ id: '190000000000000099', dispatchNo: '190000000000000099', appointmentBatches: [] }],
|
||||
page: 1, size: 20, total: 1, pages: 1,
|
||||
})
|
||||
const wrapper = mount(DispatchesView, { global: { plugins: [createPinia(), ElementPlus] } })
|
||||
|
||||
Reference in New Issue
Block a user