46 lines
4.8 KiB
TypeScript
46 lines
4.8 KiB
TypeScript
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
|
import { shallowMount, flushPromises, type VueWrapper } from '@vue/test-utils'
|
|
import TripBookingFulfillmentDialog from './TripBookingFulfillmentDialog.vue'
|
|
import * as api from '../api/tripBookings'
|
|
import type { FleetTrip } from '../tripTypes'
|
|
import type { TripBooking, TripIncomingPreview } from '../bookingTypes'
|
|
vi.mock('element-plus', () => ({ ElMessage: { success: vi.fn(), warning: vi.fn() } }))
|
|
vi.mock('../api/tripBookings', () => ({ getTripIncoming: vi.fn(), startTripIncoming: vi.fn(), acceptTripIncoming: vi.fn(), bindTripBookingDispatch: vi.fn() }))
|
|
const trip = { tripNo: 'TR-1', recordVersion: 4, plannedDepartureAt: '2026-10-03T10:00:00Z' } as FleetTrip
|
|
const booking: TripBooking = { bookingNo: 'FB-1', tripNo: 'TR-1', customerId: '9', customerCode: '00009', customerName: '客户甲', status: 'CONFIRMED', incomingNo: 'WT-1', dispatchNo: null, receipt: null, quoteSource: null,
|
|
destinationLabel: 'ONT8', agreedAmount: 100, recordVersion: 2, updatedAt: '2026-10-03T00:00:00Z', cargo: { destinationKey: 'FBA:ONT8', cargoName: null, packageCount: 20, palletSpaces: 2, weightKg: 100, volumeCbm: 1, priceBasis: 'PALLET', unitPrice: 50, currency: 'USD', contactName: null, contactPhone: null, remark: null } }
|
|
const preview: TripIncomingPreview = { tripRecordVersion: 5, recordVersion: 2, incoming: { incomingNo: 'WT-1', inboundCode: 'WT-1', warehouseId: '92', destinationKey: 'FBA:ONT8', status: 'WAIT_CUSTOMER_CONFIRMATION', receiptHash: 'receipt-hash', receivedPackages: 22,
|
|
pallets: [{ warehousePalletId: '902', palletNo: 'PLT-1', packages: 22, weightKg: 150, volumeCbm: 1.5 }] } }
|
|
const wrappers: VueWrapper[] = []
|
|
function mount(mode: 'incoming' | 'receipt' | 'bind' | 'view') {
|
|
const field = { props: ['modelValue'], emits: ['update:modelValue'], template: '<input />' }
|
|
const wrapper = shallowMount(TripBookingFulfillmentDialog, { props: { modelValue: true, trip, booking, mode }, global: { directives: { loading: () => undefined }, stubs: {
|
|
AppDialog: { name: 'AppDialog', props: ['modelValue', 'confirmDisabled'], emits: ['confirm'], template: '<div><slot/></div>' },
|
|
DataTable: { props: ['data'], template: '<div><div v-for="row in data" :key="row.warehousePalletId"><slot name="spaces" :row="row"/></div></div>' },
|
|
ElForm: { template: '<div><slot/></div>' }, ElFormItem: { template: '<div><slot/></div>' }, ElInputNumber: { ...field, name: 'ElInputNumber' }, ElDatePicker: field, ElInput: field,
|
|
LoadErrorState: true, ApiErrorMeta: true,
|
|
} } }); wrappers.push(wrapper); return wrapper
|
|
}
|
|
beforeEach(() => { vi.clearAllMocks(); vi.mocked(api.getTripIncoming).mockResolvedValue(structuredClone(preview)) })
|
|
afterEach(() => wrappers.splice(0).forEach(wrapper => wrapper.unmount()))
|
|
it('uses server actual pallets and preview versions while requiring explicit price confirmation', async () => {
|
|
vi.mocked(api.acceptTripIncoming).mockResolvedValue({ tripRecordVersion: 6, booking: { ...booking, status: 'RECEIVED' }, replayed: false })
|
|
const wrapper = mount('receipt'); await flushPromises()
|
|
const inputs = wrapper.findAllComponents({ name: 'ElInputNumber' }); inputs[1]!.vm.$emit('update:modelValue', 55); await flushPromises()
|
|
wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises()
|
|
expect(api.acceptTripIncoming).toHaveBeenCalledWith('TR-1', 'FB-1', expect.objectContaining({ tripRecordVersion: 5, recordVersion: 2, receiptHash: 'receipt-hash', unitPrice: 55, pallets: [{ warehousePalletId: '902', palletSpaces: 1 }] }))
|
|
expect(api.startTripIncoming).not.toHaveBeenCalled(); expect(api.bindTripBookingDispatch).not.toHaveBeenCalled()
|
|
})
|
|
it('does not accept a receipt while the warehouse is still waiting for actual goods', async () => {
|
|
vi.mocked(api.getTripIncoming).mockResolvedValue({ ...preview, incoming: { ...preview.incoming!, status: 'WAIT_PDA_RECEIVING', pallets: [], receivedPackages: 0 } })
|
|
const wrapper = mount('receipt'); await flushPromises(); wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises()
|
|
expect(api.acceptTripIncoming).not.toHaveBeenCalled(); expect(wrapper.findComponent({ name: 'AppDialog' }).props('confirmDisabled')).toBe(true)
|
|
})
|
|
it('retries the same incoming registration after an uncertain result without creating a new command', async () => {
|
|
vi.mocked(api.startTripIncoming).mockRejectedValueOnce(new Error('network')).mockResolvedValueOnce({ tripRecordVersion: 5, booking, replayed: true })
|
|
const wrapper = mount('incoming'); await flushPromises(); wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises()
|
|
const first = vi.mocked(api.startTripIncoming).mock.calls[0]![2]
|
|
wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises()
|
|
expect(api.startTripIncoming).toHaveBeenNthCalledWith(2, 'TR-1', 'FB-1', first); expect(api.getTripIncoming).not.toHaveBeenCalled()
|
|
})
|