feat(transfer): expose stock outbound and durable receipt recovery UI

This commit is contained in:
2026-10-05 06:16:21 +08:00
parent 578df0f927
commit 93f193c5d6
7 changed files with 432 additions and 2 deletions
@@ -2,6 +2,7 @@ import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrders'
import { normalizeWarehouseTransferQuantity } from './warehouseTransferQuantities'
import type { ReceivingOverseasWarehouseDto } from './pallets'
export interface WarehouseTransferQuery {
page: number
@@ -85,8 +86,20 @@ export interface WarehouseTransferDetailDto extends WarehouseTransferSummaryDto
relabelBindings: WarehouseTransferRelabelBindingDto[]
customerConfirmedAt?: string | null
actualShipAt?: string | null
destinationWarehouseId?: string | null
destinationWarehouseCode?: string | null
destinationWarehouseName?: string | null
sourceOrders?: Array<{ lastlegOrderId: string; lastlegOrderNo?: string | null; inboundOrderId: string; inboundCode?: string | null }>
allocations?: Array<{ inventoryPositionId: string; inboundOrderId: string; inboundCode?: string | null; palletId: string; palletNo?: string | null; packageCount: number; status: string; destinationInventoryPositionId?: string | null; skuCode?: string | null }>
receiptCommands?: WarehouseTransferReceiptCommandDto[]
}
export interface WarehouseTransferReceiptCommandDto {
sourceInventoryPositionId: string
status: 'PREPARED' | 'RESERVED' | 'COMPLETED'
requestHash: string
request: ReceiveWarehouseTransferPayload
target: { warehouseId: string; warehouseCode: string; warehouseName: string; palletId: string; palletNo: string; locationId: string; locationCode: string; fbaId?: string | null; fbaCode: string; fbaName?: string | null }
}
export interface AllocateWarehouseTransferPayload {
@@ -94,6 +107,57 @@ export interface AllocateWarehouseTransferPayload {
allocations: Array<{ inventoryPositionId: string; palletId: string; packageCount: number }>
}
export interface OutboundWarehouseTransferPayload { idempotencyKey: string; remark?: string }
export interface ReceiveWarehouseTransferPayload {
sourceInventoryPositionId: string
destinationOverseasWarehouseId: string
destinationOverseasWarehouseCode: string
destinationOverseasWarehouseName: string
destinationWarehouseId?: string
destinationWarehouseCode: string
destinationWarehouseName?: string
destinationPalletId: string
destinationPalletNo: string
warehouseLocationId: string
warehouseLocationCode: string
batchNo: string
idempotencyKey: string
remark?: string
}
export async function outboundWarehouseTransfer(customerId: string, transferNo: string, payload: OutboundWarehouseTransferPayload) {
const response = await http.post<ApiEnvelope<WarehouseTransferDetailWire>, ApiEnvelope<WarehouseTransferDetailWire>>(
`/platform/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/outbound`, payload, { params: { customerId } },
)
return normalizeTransferDetail(unwrapApiResponse(response))
}
export async function receiveWarehouseTransfer(customerId: string, transferNo: string, payload: ReceiveWarehouseTransferPayload) {
const response = await http.post<ApiEnvelope<WarehouseTransferDetailWire>, ApiEnvelope<WarehouseTransferDetailWire>>(
`/platform/v1/last-leg/warehouse-transfers/${encodeURIComponent(transferNo)}/receipts`, payload, { params: { customerId } },
)
return normalizeTransferDetail(unwrapApiResponse(response))
}
export async function listTransferReceivingWarehouses() {
const records: ReceivingOverseasWarehouseDto[] = []
let total: number | undefined
for (let page = 1; page <= 100; page++) {
const response = await http.get<ApiEnvelope<OverseasPagePayload<ReceivingOverseasWarehouseDto>>, ApiEnvelope<OverseasPagePayload<ReceivingOverseasWarehouseDto>>>(
'/platform/v1/warehouse-admin/overseas-receiving-options', { params: { page, size: 200 } },
)
const value = normalizeOverseasPage(unwrapApiResponse(response))
if (total !== undefined && total !== value.total) throw new Error('收货仓列表已变化,请重新查询')
total = value.total
records.push(...value.records.map(w => ({ ...w, id: String(w.id), customerId: String(w.customerId) })))
if (records.length === total) {
if (new Set(records.map(w => w.id)).size !== records.length || records.some(w => w.customerId !== '0' || w.warehouseType !== 'OVERSEAS' || w.status !== 'ACTIVE')) throw new Error('收货仓返回范围不符合要求')
return { records }
}
if (!value.records.length || records.length > total) throw new Error('收货仓分页不完整,请重新查询')
}
throw new Error('收货仓分页超出查询限制')
}
type WarehouseTransferDetailWire = Omit<WarehouseTransferDetailDto, 'packageCount'> & { packageCount: number | string }
function normalizeTransferDetail(value: WarehouseTransferDetailWire): WarehouseTransferDetailDto {
return { ...value, packageCount: normalizeWarehouseTransferQuantity(value.packageCount, '本票转运箱数') }