Files
admin-web/src/modules/appointment-calendar/batchApi.test.ts
T

42 lines
3.6 KiB
TypeScript

import { beforeEach, expect, it, vi } from 'vitest'
import { http } from '@/shared/api/http'
import { allocationKey, getAppointmentBatch, mergeBatchSources, repartitionAppointments, splitBatch, type RepartitionFbaAppointmentsRequest } from './batchApi'
import { batchContext } from './batch.test-support'
vi.mock('@/shared/api/http', () => ({ http: { get: vi.fn(), post: vi.fn() }, unwrapApiResponse: <T>(value: { data: T }) => value.data }))
beforeEach(() => vi.clearAllMocks())
it('reads the immutable batch key and submits the actual repartition contract including nullable versions', async () => {
const context = batchContext(); vi.mocked(http.get).mockResolvedValue({ data: context })
expect(await getAppointmentBatch(context.appointment.batchKey!)).toEqual(context)
expect(http.get).toHaveBeenCalledWith('/platform/v1/last-leg/appointment-planning/batches/FBA_APPOINTMENT%3A1')
const split = splitBatch(context, { [allocationKey(context.sources[0]!)]: 20 })
const request: RepartitionFbaAppointmentsRequest = { originals: [{ appointmentNo: 'AP-001', version: 3 }], batches: split.map(allocations => ({ allocations, plannedDate: null, remark: null })), reason: '分两次送仓', idempotencyKey: 'same-request' }
vi.mocked(http.post).mockResolvedValue({ data: { replacedBatchKeys: ['FBA_APPOINTMENT:1'], replacedAppointmentNos: ['AP-001'], appointments: [] } })
expect((await repartitionAppointments(request)).replacedBatchKeys).toEqual(['FBA_APPOINTMENT:1'])
expect(http.post).toHaveBeenCalledWith('/platform/v1/last-leg/appointment-planning/appointments/repartition', request)
expect(request.batches[0]!.allocations.find(row => row.customerId === 'customer-b')).toMatchObject({ expectedSourceVersion: null, expectedVerificationVersion: 2, packageCount: 30 })
})
it('conserves each source separately, omits zero allocations and preserves all customers and snapshot versions', () => {
const context = batchContext()
const [remaining, moved] = splitBatch(context, { [allocationKey(context.sources[0]!)]: 70 })
expect(remaining).toEqual([{ customerId: 'customer-b', sourceKey: 'BOOKING:2', packageCount: 30, expectedSourceVersion: null, expectedVerificationVersion: 2 }])
expect(moved).toEqual([{ customerId: 'customer-a', sourceKey: 'ORDER:1', packageCount: 70, expectedSourceVersion: 'source-a-v2', expectedVerificationVersion: 4 }])
expect(() => splitBatch(context, { [allocationKey(context.sources[0]!)]: 71 })).toThrow('每票')
expect(() => splitBatch(context, {})).toThrow('两批')
expect(() => splitBatch(context, { [allocationKey(context.sources[0]!)]: 70, [allocationKey(context.sources[1]!)]: 30 })).toThrow('两批')
})
it('merges only the same origin and destination, preserving per-source amounts and detecting stale verification', () => {
const first = batchContext(), second = batchContext('AP-002', 'FBA_APPOINTMENT:2')
second.sources = [{ ...first.sources[0]!, packageCount: 10 }]
const merged = mergeBatchSources([first, second]); expect(merged.map(row => row.packageCount)).toEqual([80, 30])
second.sources[0]!.cargoVerificationVersion = 9
expect(() => mergeBatchSources([first, second])).toThrow('核实版本')
second.appointment.originWarehouseId = 'another-origin'
expect(() => mergeBatchSources([first, second])).toThrow('同一起运仓')
})
it('does not turn unknown quantities or superseded snapshots into allocatable cargo', () => {
const context = batchContext(); context.sources[0]!.packageCount = null
expect(() => splitBatch(context, {})).toThrow('核实每个来源')
context.appointment.replacementBatchKeys = ['FBA_APPOINTMENT:3']
expect(() => mergeBatchSources([context])).toThrow('已被替代')
})