180 lines
9.7 KiB
TypeScript
180 lines
9.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { http } from '@/shared/api/http'
|
|
import {
|
|
getConfirmedLoadingList,
|
|
confirmPreloadPlan,
|
|
getPreloadPlan,
|
|
listConfirmedLoadingLists,
|
|
listPreloadPlans,
|
|
preparePalletLabels,
|
|
printPalletLabels,
|
|
updatePreloadTransport,
|
|
updateLoadingItemRemarks,
|
|
} from './platformLoadingLists'
|
|
|
|
const preloadSummary = {
|
|
planId: 1900000000000000001n as unknown as string,
|
|
containerBookingId: 1900000000000000004n as unknown as string,
|
|
preloadNo: 'PL-001',
|
|
containerNo: 'MSCU1234567',
|
|
cabinetNo: 'YG-001',
|
|
customers: [{ accountId: 1900000000000000002n as unknown as string, customerName: '测试客户' }],
|
|
warehouseId: 1900000000000000003n as unknown as string,
|
|
warehouseName: '义乌仓',
|
|
vesselName: 'MATSON',
|
|
voyageNo: '001E',
|
|
containerType: '40HQ',
|
|
status: 'LOADING',
|
|
skuCount: '2',
|
|
totalPackageCount: '10',
|
|
totalVolumeCbm: 12.3,
|
|
totalGrossWeightKg: 1200,
|
|
createdAt: '2026-08-10T10:00:00',
|
|
version: 3,
|
|
readiness: {
|
|
containerNoReady: true,
|
|
preloadLinkCount: 1,
|
|
confirmedPreloadCount: 0,
|
|
oceanBookingId: 1900000000000000005n as unknown as string,
|
|
oceanBookingStatus: 'RELEASED',
|
|
pdaTaskId: null,
|
|
pdaTaskStatus: null,
|
|
confirmationStatus: 'PENDING',
|
|
canConfirm: true,
|
|
readyForPda: false,
|
|
blockingReasons: [],
|
|
},
|
|
}
|
|
|
|
const detail = {
|
|
summary: preloadSummary,
|
|
items: [{
|
|
itemId: 11, itemVersion: 4, accountId: 12, orderId: 15, orderNo: 'ORD-001', inboundId: 13, inboundDetailId: 14, containerNo: 'MSCU1234567', productQuantity: 20,
|
|
customerName: '测试客户', customsTaxMode: 'TAX_REFUND', taxRefundFlag: true, inboundCode: 'RK001', skuCode: 'SKU001', destinationWarehouse: 'ONT8', chineseName: '产品', englishName: null,
|
|
goodsImageUrl: null, unitType: 'SEALED_PALLET', palletId: 9007199254740993n, palletNo: 'PLT-001', indivisible: true, packageCount: 2, palletCount: 1, grossWeightKg: 10, netWeightKg: 9, volumeCbm: 1,
|
|
dimensionalWeightKg: 2, unitsPerPackage: 10, fbaTrackingNo: null, shipmentNo: null, shippingMark: null,
|
|
internalRemark: null, customerRemark: null, warehouseRemark: null, customerServiceRemark: null, loadSequence: 1, frozenAt: null,
|
|
}],
|
|
histories: [{ historyId: 21, itemId: 11, operationId: 'transport-21', actionType: 'UPDATE_TRANSPORT', fromStatus: 'LOADING', toStatus: 'LOADING', beforeSnapshot: null, afterSnapshot: null, operatorId: 22, operatorName: '操作员', operationReason: '调整', occurredAt: '2026-08-10T11:00:00' }],
|
|
}
|
|
|
|
describe('platform loading lists API', () => {
|
|
beforeEach(() => vi.restoreAllMocks())
|
|
|
|
it('queries preload plans without forcing a status so EDITING and LOADING are returned', async () => {
|
|
const get = vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'ok', data: { records: [preloadSummary], page: '1', size: '20', total: '1', pages: '1' } })
|
|
const result = await listPreloadPlans({ page: 1, size: 20, keyword: 'PL' })
|
|
|
|
expect(get).toHaveBeenCalledWith('/platform/v1/first-leg/loading-plans', { params: { page: 1, size: 20, keyword: 'PL' } })
|
|
expect(result.total).toBe(1)
|
|
expect(result.records[0]?.planId).toBe('1900000000000000001')
|
|
expect(result.records[0]?.containerBookingId).toBe('1900000000000000004')
|
|
expect(result.records[0]?.readiness.oceanBookingId).toBe('1900000000000000005')
|
|
expect(result.records[0]?.customers[0]?.accountId).toBe('1900000000000000002')
|
|
})
|
|
|
|
it('normalizes long ids in preload detail items and history', async () => {
|
|
vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'ok', data: detail })
|
|
const result = await getPreloadPlan('100', '300')
|
|
|
|
expect(result.items[0]).toMatchObject({ itemId: '11', itemVersion: 4, accountId: '12', orderId: '15', orderNo: 'ORD-001', inboundId: '13', inboundDetailId: '14', unitType: 'SEALED_PALLET', palletId: '9007199254740993', palletNo: 'PLT-001', indivisible: true, containerNo: 'MSCU1234567', productQuantity: '20', customsTaxMode: 'TAX_REFUND', taxRefundFlag: true })
|
|
expect(result.summary.containerBookingId).toBe('1900000000000000004')
|
|
expect(result.histories[0]).toMatchObject({ historyId: '21', itemId: '11', operationId: 'transport-21', operatorId: '22' })
|
|
})
|
|
|
|
it('updates transport with expectedVersion, warehouse and idempotency key', async () => {
|
|
const patch = vi.spyOn(http, 'patch').mockResolvedValue({ code: '200', message: 'ok', data: detail })
|
|
await updatePreloadTransport('100', '300', { containerNo: 'MSCU1234567', voyageNo: '001E', expectedVersion: 3, operationReason: '调整' })
|
|
|
|
expect(patch.mock.calls[0]?.[0]).toBe('/platform/v1/first-leg/loading-plans/100/transport')
|
|
expect(patch.mock.calls[0]?.[1]).toMatchObject({ expectedVersion: 3, voyageNo: '001E' })
|
|
expect(patch.mock.calls[0]?.[2]).toMatchObject({ params: { warehouseId: '300' }, headers: { 'Idempotency-Key': expect.any(String) } })
|
|
})
|
|
|
|
it('uses distinct completed list and detail endpoints', async () => {
|
|
const confirmed = { ...preloadSummary, loadingListNo: 'LL-001', totalNetWeightKg: 1100, totalQuantity: '20', remark: null, createdBy: 9, createdByName: '操作员', status: 'COMPLETED' }
|
|
const get = vi.spyOn(http, 'get')
|
|
.mockResolvedValueOnce({ code: '200', message: 'ok', data: { records: [confirmed], page: '1', size: '20', total: '1', pages: '1' } })
|
|
.mockResolvedValueOnce({ code: '200', message: 'ok', data: { ...detail, summary: confirmed } })
|
|
|
|
const page = await listConfirmedLoadingLists({ page: 1, size: 20 })
|
|
const loaded = await getConfirmedLoadingList('100', '300')
|
|
|
|
expect(get.mock.calls[0]?.[0]).toBe('/platform/v1/first-leg/loading-lists')
|
|
expect(get.mock.calls[0]?.[1]).toEqual({ params: { page: 1, size: 20 } })
|
|
expect(get.mock.calls[1]?.[0]).toBe('/platform/v1/first-leg/loading-lists/100')
|
|
expect(page.records[0]?.createdBy).toBe('9')
|
|
expect(page.records[0]?.containerBookingId).toBe('1900000000000000004')
|
|
expect(loaded.summary.status).toBe('COMPLETED')
|
|
})
|
|
|
|
it('updates one item editable remarks with both optimistic-lock versions and operation id', async () => {
|
|
const patch = vi.spyOn(http, 'patch').mockResolvedValue({ code: '200', message: 'ok', data: { id: 100, version: 4, items: [{ id: 11, internalRemark: '内部', warehouseRemark: '装柜时注意外箱', customerServiceRemark: '客服', version: 5 }] } })
|
|
const result = await updateLoadingItemRemarks('100', '11', {
|
|
internalRemark: '内部',
|
|
warehouseRemark: '装柜时注意外箱',
|
|
customerServiceRemark: '客服',
|
|
expectedPlanVersion: 3,
|
|
expectedItemVersion: 4,
|
|
operationId: 'warehouse-remark-1',
|
|
})
|
|
|
|
expect(patch).toHaveBeenCalledWith(
|
|
'/platform/v1/first-leg/smart-loading/plans/100/items/11/remarks',
|
|
{ internalRemark: '内部', warehouseRemark: '装柜时注意外箱', customerServiceRemark: '客服', expectedPlanVersion: 3, expectedItemVersion: 4, operationId: 'warehouse-remark-1' },
|
|
)
|
|
expect(result).toMatchObject({ id: '100', version: 4, items: [{ id: '11', version: 5, warehouseRemark: '装柜时注意外箱' }] })
|
|
})
|
|
|
|
it('confirms the whole preload plan with expectedVersion and the caller operation id', async () => {
|
|
const post = vi.spyOn(http, 'post').mockResolvedValue({
|
|
code: '200', message: 'ok', data: {
|
|
planId: 1900000000000000001n,
|
|
containerBookingId: 1900000000000000004n,
|
|
confirmationStatus: 'CONFIRMED',
|
|
version: 4,
|
|
readiness: {
|
|
...preloadSummary.readiness,
|
|
confirmedPreloadCount: 1,
|
|
confirmationStatus: 'CONFIRMED',
|
|
canConfirm: false,
|
|
readyForPda: true,
|
|
pdaTaskId: 1900000000000000006n,
|
|
pdaTaskStatus: 'PENDING',
|
|
blockingReasons: ['PRELOAD_ALREADY_CONFIRMED'],
|
|
},
|
|
},
|
|
})
|
|
|
|
const result = await confirmPreloadPlan('1900000000000000001', 3, '1900000000000000007', 'confirm-operation-1')
|
|
|
|
expect(post).toHaveBeenCalledWith(
|
|
'/platform/v1/first-leg/loading-plans/1900000000000000001/confirm',
|
|
{ expectedVersion: 3, assigneeId: '1900000000000000007' },
|
|
{ headers: { 'Idempotency-Key': 'confirm-operation-1' } },
|
|
)
|
|
expect(result).toMatchObject({
|
|
planId: '1900000000000000001',
|
|
containerBookingId: '1900000000000000004',
|
|
confirmationStatus: 'CONFIRMED',
|
|
version: 4,
|
|
readiness: { pdaTaskId: '1900000000000000006', readyForPda: true },
|
|
})
|
|
})
|
|
|
|
it('prepares and prints pallet labels by FBA warehouse', async () => {
|
|
const post = vi.spyOn(http, 'post')
|
|
.mockResolvedValueOnce({ code: '200', message: 'ok', data: { planId: 10, planNo: 'LP-1', containerNo: 'MSCU6639870', totalCount: 2, groups: [{ destinationWarehouseCode: 'ONT8', destinationWarehouseInternalCode: '154', destinationWarehouseName: 'ONT8', totalCount: 2, labels: [{ labelId: 1, itemId: 2, accountId: 3, inboundId: 4, inboundCode: 'RK-1', containerNo: 'MSCU6639870', destinationWarehouseCode: 'ONT8', destinationWarehouseInternalCode: '154', destinationWarehouseName: 'ONT8', palletIndex: 1, palletCode: 'MSCU6639870-154-001' }] }] } })
|
|
.mockResolvedValueOnce(new Blob(['pdf'], { type: 'application/pdf' }))
|
|
|
|
const manifest = await preparePalletLabels('10', '20')
|
|
const pdf = await printPalletLabels('10', '20', 'ONT8', 1)
|
|
|
|
expect(manifest).toMatchObject({ planId: '10', groups: [{ labels: [{ labelId: '1', itemId: '2', accountId: '3', inboundId: '4' }] }] })
|
|
expect(pdf.type).toBe('application/pdf')
|
|
expect(post).toHaveBeenNthCalledWith(1, '/platform/v1/first-leg/loading-plans/10/pallet-labels/prepare', null, { params: { warehouseId: '20' } })
|
|
expect(post).toHaveBeenNthCalledWith(2, '/platform/v1/first-leg/loading-plans/10/pallet-labels/print', { destinationWarehouseCode: 'ONT8', count: 1 }, { params: { warehouseId: '20' }, responseType: 'blob', timeout: 60_000 })
|
|
})
|
|
|
|
})
|