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: (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('已被替代') })