Merge release/test into dev
This commit is contained in:
@@ -27,10 +27,10 @@ describe('customer visit API', () => {
|
||||
expect(overseasHttp.get).toHaveBeenNthCalledWith(2, '/platform/v1/last-leg/customer-visits/9', { params: { customerId: '100' } })
|
||||
})
|
||||
|
||||
it('updates delivery status so selecting delivered can generate a visit', async () => {
|
||||
vi.mocked(overseasHttp.put).mockResolvedValue({ code: '200', data: { deliveryStatus: 'DELIVERED' } })
|
||||
await updateDeliveryOrderStatus('DEL/1', '100', { status: 'DELIVERED', expectedVersion: 7, remark: '完成' })
|
||||
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/last-leg/delivery-orders/DEL%2F1/status', { status: 'DELIVERED', expectedVersion: 7, remark: '完成' }, { params: { customerId: '100' } })
|
||||
it('updates a delivery to an operator-managed nonterminal status', async () => {
|
||||
vi.mocked(overseasHttp.put).mockResolvedValue({ code: '200', data: { deliveryStatus: 'DELIVERING' } })
|
||||
await updateDeliveryOrderStatus('DEL/1', '100', { status: 'DELIVERING', expectedVersion: 7, remark: '开始派送' })
|
||||
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/last-leg/delivery-orders/DEL%2F1/status', { status: 'DELIVERING', expectedVersion: 7, remark: '开始派送' }, { params: { customerId: '100' } })
|
||||
})
|
||||
|
||||
it('uploads and saves repeatable visit material', async () => {
|
||||
|
||||
@@ -99,14 +99,6 @@ export async function uploadCustomerVisitAttachment(visitId: string, customerId:
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export async function completeDeliveryOrder(deliveryNo: string, customerId: string) {
|
||||
const response = await overseasHttp.post<ApiEnvelope<{ visitId: string; deliveryNo: string; deliveryStatus: string; visitStatus: string; replayed: boolean }>, ApiEnvelope<{ visitId: string; deliveryNo: string; deliveryStatus: string; visitStatus: string; replayed: boolean }>>(
|
||||
`/platform/v1/last-leg/delivery-orders/${encodeURIComponent(deliveryNo)}/complete`, undefined,
|
||||
{ params: { customerId } },
|
||||
)
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export async function updateDeliveryOrderStatus(deliveryNo: string, customerId: string, payload: { status: string; expectedVersion: number; remark?: string }) {
|
||||
const response = await overseasHttp.put<ApiEnvelope<{ visitId?: string | null; deliveryNo: string; deliveryStatus: string; visitStatus?: string | null; replayed: boolean }>, ApiEnvelope<{ visitId?: string | null; deliveryNo: string; deliveryStatus: string; visitStatus?: string | null; replayed: boolean }>>(
|
||||
`/platform/v1/last-leg/delivery-orders/${encodeURIComponent(deliveryNo)}/status`, payload,
|
||||
|
||||
@@ -30,6 +30,9 @@ export interface DeliveryOrderRow {
|
||||
deliveryNo: string
|
||||
outboundNo: string
|
||||
dispatchNo: string
|
||||
destinationWarehouseId?: string | null
|
||||
destinationWarehouseCode?: string | null
|
||||
destinationWarehouseName?: string | null
|
||||
overseasWarehouseId?: string | null
|
||||
overseasWarehouseName?: string | null
|
||||
status: string
|
||||
|
||||
@@ -48,9 +48,9 @@ describe('dispatch overseas API', () => {
|
||||
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: [] })
|
||||
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: { id: '1', dispatchNo: 'PC-1' } })
|
||||
await listDispatchCandidates({ planIds: '101,102', fbaWarehouseCode: 'ONT8', sourceStage: 'PLAN' })
|
||||
await preArrangeDispatch({ planPalletIds: ['190000000000000099'], route: 'LAX -> ONT8' })
|
||||
await preArrangeDispatch({ planPalletIds: ['190000000000000099'] })
|
||||
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/candidates', { params: { planIds: '101,102', fbaWarehouseCode: 'ONT8', sourceStage: 'PLAN' } })
|
||||
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/pre-arrange', { planPalletIds: ['190000000000000099'], route: 'LAX -> ONT8' })
|
||||
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/pre-arrange', { planPalletIds: ['190000000000000099'] })
|
||||
})
|
||||
|
||||
it('loads platform candidates without selected inbound plans', async () => {
|
||||
@@ -78,15 +78,15 @@ describe('dispatch overseas API', () => {
|
||||
it('edits and cancels only by dispatchNo without customer scope or status mutation', async () => {
|
||||
vi.mocked(overseasHttp.put).mockResolvedValue({ code: '200', message: 'success', data: { id: '1', dispatchNo: 'PC/1' } })
|
||||
vi.mocked(overseasHttp.delete).mockResolvedValue({ code: '200', message: 'success', data: null })
|
||||
await updateDispatch('PC/1', { vehiclePlate: 'CA-100', route: 'LAX → ONT8' })
|
||||
await updateDispatch('PC/1', { vehiclePlate: 'CA-100' })
|
||||
await cancelDispatch('PC/1')
|
||||
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/PC%2F1', { vehiclePlate: 'CA-100', route: 'LAX → ONT8' })
|
||||
expect(overseasHttp.put).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/PC%2F1', { vehiclePlate: 'CA-100' })
|
||||
expect(overseasHttp.put).not.toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ status: expect.anything() }))
|
||||
expect(overseasHttp.delete).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/PC%2F1')
|
||||
})
|
||||
|
||||
it('transfers to delivery only by dispatchNo without customerId', async () => {
|
||||
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: { dispatchNo: 'PC/1', outboundNo: 'OUT-1', deliveryNo: 'DEL-1' } })
|
||||
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: { dispatchNo: 'PC/1', outboundNo: 'OUT-1', outboundStatus: 'PICKED', palletCount: 2, packageCount: 20, deliveries: [{ deliveryNo: 'DEL-1', deliveryStatus: 'WAIT_DELIVERY', destinationWarehouseCode: 'ACY2', palletCount: 2, packageCount: 20 }], replayed: false } })
|
||||
const payload = { idempotencyKey: 'transfer-1', plannedDeliveryAt: '2026-09-01T10:00:00', deliveryMethod: 'TRUCK_PALLET', pickupMethod: 'WAREHOUSE_PICKUP', loadingType: 'PALLET', fees: [{ feeType: 'DELIVERY_FEE', feeName: '派送费用', direction: 'RECEIVABLE' as const, currency: 'USD' as const, amount: 10 }] }
|
||||
await transferDispatchToDelivery('PC/1', payload)
|
||||
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/PC%2F1/transfer-delivery', payload)
|
||||
@@ -94,15 +94,15 @@ describe('dispatch overseas API', () => {
|
||||
|
||||
it('loads drafts and preserves long container ids in create and delete paths', async () => {
|
||||
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', message: 'success', data: [] })
|
||||
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: { id: '190000000000000099', capacity: 68, palletIds: [] } })
|
||||
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: { id: '190000000000000099', capacity: 30, palletIds: [] } })
|
||||
vi.mocked(overseasHttp.delete).mockResolvedValue({ code: '200', message: 'success', data: null })
|
||||
|
||||
await listDispatchDrafts()
|
||||
await createDispatchDraftContainer('draft/1', 68)
|
||||
await createDispatchDraftContainer('draft/1', 30)
|
||||
await deleteDispatchDraftContainer('draft/1', '190000000000000099')
|
||||
|
||||
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/drafts')
|
||||
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/drafts/containers', { draftToken: 'draft/1', capacity: 68 })
|
||||
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/drafts/containers', { draftToken: 'draft/1', capacity: 30 })
|
||||
expect(overseasHttp.delete).toHaveBeenCalledWith('/platform/v1/last-leg/dispatches/drafts/draft%2F1/containers/190000000000000099')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -53,7 +53,6 @@ export interface DispatchDraftDto { draftToken: string; containers?: DispatchDra
|
||||
|
||||
export interface PreArrangeDispatchPayload {
|
||||
planPalletIds: string[]
|
||||
route?: string
|
||||
fbaAppointmentNo?: string
|
||||
fbaAppointmentStatus?: string
|
||||
vehicleAppointmentStatus?: string
|
||||
@@ -106,7 +105,6 @@ export interface UpdateDispatchPayload {
|
||||
driverName?: string | null
|
||||
driverPhone?: string | null
|
||||
carrierName?: string | null
|
||||
route?: string | null
|
||||
departAt?: string | null
|
||||
fbaAppointmentNo?: string | null
|
||||
fbaAppointmentStatus?: string | null
|
||||
@@ -225,7 +223,8 @@ export async function releaseDispatchDraft(draftToken: string) {
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export interface TransferDeliveryResult { dispatchNo: string; outboundNo: string; outboundStatus: string; deliveryNo: string; deliveryStatus: string; palletCount: number; packageCount: number; replayed: boolean }
|
||||
export interface TransferDeliveryItem { deliveryNo: string; deliveryStatus: string; destinationWarehouseId?: string | null; destinationWarehouseCode?: string | null; destinationWarehouseName?: string | null; palletCount: number; packageCount: number }
|
||||
export interface TransferDeliveryResult { dispatchNo: string; outboundNo: string; outboundStatus: string; palletCount: number; packageCount: number; deliveries: TransferDeliveryItem[]; replayed: boolean }
|
||||
export interface TransferDeliveryPayload {
|
||||
idempotencyKey: string
|
||||
plannedDeliveryAt: string
|
||||
@@ -247,7 +246,6 @@ export interface TransferDeliveryPayload {
|
||||
paidAt?: string; paymentMethod?: 'ONLINE' | 'BANK_TRANSFER' | 'CASH' | 'OTHER'; paymentReferenceNo?: string
|
||||
paymentVoucherFileIds?: string[]; chargeableQuantity?: number; quantityUnit?: string; unitPrice?: number
|
||||
periodStart?: string; periodEnd?: string; remark?: string }>
|
||||
palletCartonMarks?: Record<string, string[]>
|
||||
}
|
||||
export async function transferDispatchToDelivery(dispatchNo: string, payload: TransferDeliveryPayload) {
|
||||
const response = await overseasHttp.post<ApiEnvelope<TransferDeliveryResult>, ApiEnvelope<TransferDeliveryResult>>(
|
||||
|
||||
@@ -87,12 +87,13 @@ describe('last-mile order overseas API', () => {
|
||||
it('uses the manual inbound-plan endpoint with the complete form and idempotency evidence', async () => {
|
||||
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', message: 'success', data: {} })
|
||||
const payload = { orderId: '190000000000000099', customerId: '190000000000000001', expectedOrderVersion: 4,
|
||||
plannedArrivalDate: '2026-09-08', palletCount: 8, palletType: 'STANDARD' as const,
|
||||
palletCount: 8, palletType: 'STANDARD' as const,
|
||||
assigned: false, mergeRequired: false, urgent: true, remark: '加急' }
|
||||
await createManualInboundPlan(payload, 'plan-key-1')
|
||||
expect(overseasHttp.post).toHaveBeenCalledWith('/platform/v1/last-leg/inbound-plans/manual', payload,
|
||||
{ headers: { 'Idempotency-Key': 'plan-key-1' } })
|
||||
expect(payload).not.toHaveProperty('inboundCode')
|
||||
expect(payload).not.toHaveProperty('plannedArrivalDate')
|
||||
expect(payload).not.toHaveProperty('plannedWarehouseId')
|
||||
expect(payload).not.toHaveProperty('plannedWarehouseCode')
|
||||
expect(payload).not.toHaveProperty('plannedWarehouseName')
|
||||
|
||||
@@ -294,7 +294,6 @@ export interface ManualInboundPlanPayload {
|
||||
customerId: string
|
||||
orderId: string
|
||||
expectedOrderVersion: number
|
||||
plannedArrivalDate: string
|
||||
palletCount: number
|
||||
palletType: 'STANDARD' | 'WOOD' | 'PAPER'
|
||||
assigned: boolean
|
||||
|
||||
@@ -14,7 +14,7 @@ describe('outbound overseas API', () => {
|
||||
})
|
||||
|
||||
it('loads detail and cancels only by outboundNo without customerId', async () => {
|
||||
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', data: { outbound: { id: '1', outboundNo: 'OUT/1', status: 'PICKED' }, delivery: null } })
|
||||
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', data: { outbound: { id: '1', outboundNo: 'OUT/1', status: 'PICKED' }, deliveries: [] } })
|
||||
vi.mocked(overseasHttp.post).mockResolvedValue({ code: '200', data: null })
|
||||
await getOutbound('OUT/1')
|
||||
await cancelOutbound('OUT/1', '运营取消')
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface OutboundPalletRow {
|
||||
pickedPackageCount?: number | null
|
||||
status?: string | null
|
||||
}
|
||||
export interface OutboundDetail { outbound: OutboundRow; pallets?: OutboundPalletRow[]; delivery?: DeliveryOrderRow | null }
|
||||
export interface OutboundDetail { outbound: OutboundRow; pallets?: OutboundPalletRow[]; deliveries?: DeliveryOrderRow[] }
|
||||
export interface OutboundQuery { page: number; size: number; documentNo?: string; lxCode?: string; warehouseId?: string; status?: string; outboundType?: string }
|
||||
export interface ReturnOutboundCandidate { returnNo?: string | null; overseasWarehouseId?: string | null; cartonMark?: string | null; fbaCartonMark?: string | null; destinationWarehouseCode?: string | null; destinationWarehouseName?: string | null; totalQuantity?: number | null; status?: string | null }
|
||||
export interface OneClickOutboundCandidate { inventoryPositionId: string; orderNo?: string | null; customerId?: string | null; customerName?: string | null; overseasWarehouseId?: string | null; overseasWarehouseName?: string | null; palletNo?: string | null; inboundCode?: string | null; skuCode?: string | null; productName?: string | null; availablePackages: number }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { listSharedReceivingOverseasWarehouseOptions } from '@/modules/iam'
|
||||
import { overseasHttp } from '@/shared/api/overseasHttp'
|
||||
import { createPallet, getPallet, listPallets, listReceivingOverseasWarehouses, updatePallet } from './pallets'
|
||||
import { createPallet, getPallet, getPalletLabelPdf, listPallets, listReceivingOverseasWarehouses, updatePallet } from './pallets'
|
||||
|
||||
vi.mock('@/modules/iam', () => ({ listSharedReceivingOverseasWarehouseOptions: vi.fn() }))
|
||||
vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post: vi.fn(), put: vi.fn() } }))
|
||||
@@ -9,6 +9,19 @@ vi.mock('@/shared/api/overseasHttp', () => ({ overseasHttp: { get: vi.fn(), post
|
||||
describe('overseas pallet API', () => {
|
||||
beforeEach(() => { vi.clearAllMocks() })
|
||||
|
||||
it('downloads only the selected pallet through the overseas gateway', async () => {
|
||||
const pdf = new Blob(['%PDF-label'], { type: 'application/pdf' })
|
||||
vi.mocked(overseasHttp.get).mockResolvedValueOnce(pdf)
|
||||
await expect(getPalletLabelPdf('200', '9007199254740993')).resolves.toBe(pdf)
|
||||
expect(overseasHttp.get).toHaveBeenCalledWith('/platform/v1/warehouse-admin/200/pallets/9007199254740993/label', { responseType: 'blob', timeout: 60_000 })
|
||||
})
|
||||
|
||||
it('surfaces a JSON business failure instead of saving it as a PDF', async () => {
|
||||
const error = new Blob([JSON.stringify({ code: 'A0422', message: '托盘编码不支持打印', requestId: 'r1' })], { type: 'application/json' })
|
||||
vi.mocked(overseasHttp.get).mockResolvedValueOnce(error)
|
||||
await expect(getPalletLabelPdf('200', '9')).rejects.toMatchObject({ message: '托盘编码不支持打印', code: 'A0422', requestId: 'r1' })
|
||||
})
|
||||
|
||||
it('uses the public warehouse scoped pallet CRUD contract', async () => {
|
||||
const page = { records: [], page: 1, size: 20, total: 0, pages: 0 }
|
||||
vi.mocked(overseasHttp.get).mockResolvedValue({ code: '200', data: page })
|
||||
|
||||
@@ -78,6 +78,16 @@ export async function getPallet(warehouseId: string, palletId: string) {
|
||||
return unwrapApiResponse(response)
|
||||
}
|
||||
|
||||
export async function getPalletLabelPdf(warehouseId: string, palletId: string) {
|
||||
const blob = await overseasHttp.get<Blob, Blob>(
|
||||
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets/${encodeURIComponent(palletId)}/label`,
|
||||
{ responseType: 'blob', timeout: 60_000 },
|
||||
)
|
||||
if (blob.type.includes('application/json')) unwrapApiResponse(JSON.parse(await blob.text()) as ApiEnvelope<unknown>)
|
||||
if (!blob.size || !blob.type.includes('application/pdf')) throw new Error('打印服务未返回有效 PDF,请重试')
|
||||
return blob
|
||||
}
|
||||
|
||||
export async function createPallet(warehouseId: string, payload: SavePalletPayload) {
|
||||
const response = await overseasHttp.post<ApiEnvelope<string>, ApiEnvelope<string>>(
|
||||
`/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets`, payload,
|
||||
|
||||
Reference in New Issue
Block a user