2026-10-14 11:43:44 +08:00
|
|
|
import { overseasRequest, overseasUpload, overseasDownload } from './overseas.js'
|
2026-09-01 14:23:55 +08:00
|
|
|
|
|
|
|
|
const DISPATCH_BASE = '/pda/v1/last-leg/dispatches'
|
|
|
|
|
const OUTBOUND_BASE = '/pda/v1/last-leg/outbound-tasks'
|
2026-09-28 17:26:50 +08:00
|
|
|
const CARGO_DISCARD_BASE = '/pda/v1/last-leg/cargo-discards'
|
2026-10-26 13:43:39 +08:00
|
|
|
const STORAGE_PREPARATION_BASE = '/pda/v1/last-leg/storage-rent/preparations'
|
2026-09-01 14:23:55 +08:00
|
|
|
|
|
|
|
|
const encoded = (value) => encodeURIComponent(String(value || '').trim())
|
|
|
|
|
|
|
|
|
|
function asList(value) {
|
|
|
|
|
if (Array.isArray(value)) return value
|
|
|
|
|
if (Array.isArray(value?.records)) return value.records
|
|
|
|
|
if (Array.isArray(value?.items)) return value.items
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function count(value) {
|
|
|
|
|
const number = Number(value || 0)
|
|
|
|
|
return Number.isFinite(number) ? Math.max(0, number) : 0
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-19 17:56:30 +08:00
|
|
|
export function requireDispatchVersion(value) {
|
|
|
|
|
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) return value
|
|
|
|
|
if (typeof value === 'string' && /^\d+$/.test(value)) return value
|
|
|
|
|
throw new Error('排车操作版本缺失或无效,请刷新任务')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function dispatchVersionAtLeast(current, confirmed) {
|
|
|
|
|
const latest = String(requireDispatchVersion(current)).replace(/^0+(?=\d)/, '')
|
|
|
|
|
const original = String(requireDispatchVersion(confirmed)).replace(/^0+(?=\d)/, '')
|
|
|
|
|
return latest.length > original.length || latest.length === original.length && latest >= original
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function dispatchCount(value) {
|
|
|
|
|
if (typeof value !== 'number' && (typeof value !== 'string' || !/^(?:0|[1-9]\d*)$/.test(value))) throw new Error('排车数量不是有效非负整数,请重新读取任务')
|
|
|
|
|
const number = typeof value === 'number' ? value : Number(value)
|
|
|
|
|
if (!Number.isSafeInteger(number) || number < 0) throw new Error('排车数量超出安全整数范围或无效,请重新读取任务')
|
|
|
|
|
return number
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-01 14:23:55 +08:00
|
|
|
export function normalizeDispatchTask(value) {
|
2026-10-19 17:56:30 +08:00
|
|
|
if (!value || !value.dispatchNo || !Array.isArray(value.items)) throw new Error('排车任务返回结构不完整')
|
2026-09-01 14:23:55 +08:00
|
|
|
return {
|
2026-10-19 17:56:30 +08:00
|
|
|
...value, taskNo: String(value.taskNo || ''), dispatchNo: String(value.dispatchNo),
|
|
|
|
|
recordVersion: value.recordVersion, pickingReconciled: value.pickingReconciled === true,
|
|
|
|
|
actions: Object.fromEntries(Object.entries(value.actions || {}).map(([key, gate]) => [key, { ...gate, allowed: gate?.allowed === true }])),
|
2026-10-20 10:36:04 +08:00
|
|
|
items: value.items.map(item => ({ ...item, dispatchItemId: String(item.dispatchItemId || ''), sourceKey: String(item.sourceKey || ''), sourceBusinessType: item.sourceBusinessType == null ? null : String(item.sourceBusinessType), sourceBusinessNo: item.sourceBusinessNo == null ? null : String(item.sourceBusinessNo), palletNo: String(item.palletNo || ''),
|
2026-10-19 17:56:30 +08:00
|
|
|
scheduledPackages: dispatchCount(item.scheduledPackages), pickedPackages: dispatchCount(item.pickedPackages),
|
|
|
|
|
returnCartons: (item.returnCartons || []).map(carton => ({ ...carton, returnCartonId: String(carton.returnCartonId || ''), cartonMark: String(carton.cartonMark || ''), productQuantity: dispatchCount(carton.productQuantity) })),
|
|
|
|
|
positions: (item.positions || []).map(position => ({ ...position, positionId: String(position.positionId || ''), balanceId: String(position.balanceId || ''), availablePackages: dispatchCount(position.availablePackages), allocatedPackages: dispatchCount(position.allocatedPackages), pickedPackages: dispatchCount(position.pickedPackages) }))
|
2026-09-01 14:23:55 +08:00
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function normalizeOutboundTask(value) {
|
|
|
|
|
const header = value?.task || value || {}
|
|
|
|
|
return {
|
|
|
|
|
...header,
|
|
|
|
|
taskNo: String(header?.taskNo || ''),
|
|
|
|
|
outboundNo: String(header?.outboundNo || ''),
|
2026-09-29 20:52:44 +08:00
|
|
|
outboundType: String(header?.outboundType || ''),
|
2026-09-01 14:23:55 +08:00
|
|
|
status: String(header?.status || ''),
|
|
|
|
|
items: asList(value?.items).map((item) => ({
|
|
|
|
|
...item,
|
|
|
|
|
outboundPalletItemId: String(item?.outboundPalletItemId || ''),
|
|
|
|
|
palletNo: String(item?.palletNo || ''),
|
|
|
|
|
plannedPackages: count(item?.packageCount),
|
|
|
|
|
pickedPackages: count(item?.pickedPackageCount),
|
|
|
|
|
positions: asList(item?.positions).map((position) => ({
|
|
|
|
|
...position,
|
|
|
|
|
inventoryPositionId: String(position?.inventoryPositionId || ''),
|
|
|
|
|
palletNo: String(position?.palletNo || item?.palletNo || ''),
|
|
|
|
|
cartonMark: String(position?.cartonMark || ''),
|
|
|
|
|
skuCode: String(position?.skuCode || position?.sku || ''),
|
|
|
|
|
inboundCode: String(position?.inboundCode || ''),
|
|
|
|
|
availablePackages: count(position?.availablePackages),
|
|
|
|
|
pickedPackages: count(position?.pickedPackages)
|
|
|
|
|
}))
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-19 17:56:30 +08:00
|
|
|
export async function listDispatchTasks() {
|
|
|
|
|
const result = await overseasRequest('GET', DISPATCH_BASE)
|
|
|
|
|
if (!Array.isArray(result)) throw new Error('排车任务列表返回结构无效')
|
|
|
|
|
return result.map(normalizeDispatchTask)
|
2026-09-01 14:23:55 +08:00
|
|
|
}
|
|
|
|
|
export async function getDispatchTask(dispatchNo) {
|
|
|
|
|
return normalizeDispatchTask(await overseasRequest('GET', `${DISPATCH_BASE}/${encoded(dispatchNo)}`))
|
|
|
|
|
}
|
2026-10-19 17:56:30 +08:00
|
|
|
export function getDispatchLoading(dispatchNo) {
|
|
|
|
|
return overseasRequest('GET', `${DISPATCH_BASE}/${encoded(dispatchNo)}/loading`)
|
|
|
|
|
}
|
|
|
|
|
export function getDispatchCommand(dispatchNo, key) {
|
|
|
|
|
return overseasRequest('GET', `${DISPATCH_BASE}/${encoded(dispatchNo)}/commands/${encoded(key)}`)
|
|
|
|
|
}
|
|
|
|
|
function dispatchWrite(path, request) {
|
|
|
|
|
requireDispatchVersion(request?.recordVersion)
|
|
|
|
|
return overseasRequest('POST', path, request)
|
|
|
|
|
}
|
2026-10-26 13:43:39 +08:00
|
|
|
async function prepareStorageRent(mode, businessNo, request, extra = {}) {
|
|
|
|
|
try {
|
|
|
|
|
if (!request?.idempotencyKey) throw new Error('出库请求标识缺失,请重新进入任务')
|
|
|
|
|
const result = await overseasRequest('POST', STORAGE_PREPARATION_BASE, {
|
|
|
|
|
mode, businessNo, idempotencyKey: request.idempotencyKey, ...extra
|
|
|
|
|
})
|
|
|
|
|
if (result?.ready !== true || !['NOT_REQUIRED', 'READY', 'CONSUMED'].includes(result?.status)) {
|
|
|
|
|
const error = new Error(result?.message || (result?.status === 'FREEZE_PENDING' ? '费用确认中,请稍后重试' : '费用尚未确认,请稍后重试'))
|
|
|
|
|
error.code = `STORAGE_RENT_${result?.status || 'INVALID_RESPONSE'}`
|
|
|
|
|
throw error
|
|
|
|
|
}
|
|
|
|
|
} catch (cause) {
|
|
|
|
|
// 预扣可能已受理,保留完整原请求及幂等键继续核对。
|
|
|
|
|
cause.storageRentPreparation = true
|
|
|
|
|
throw cause
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function pickDispatchItem(dispatchNo, dispatchItemId, request) {
|
|
|
|
|
requireDispatchVersion(request?.recordVersion)
|
|
|
|
|
await prepareStorageRent('DISPATCH', dispatchNo, request, {
|
|
|
|
|
itemId: dispatchItemId, positionId: request.positionId, packages: request.packages,
|
|
|
|
|
cartonMark: request.cartonMark, productQuantity: request.productQuantity
|
|
|
|
|
})
|
2026-10-19 17:56:30 +08:00
|
|
|
return dispatchWrite(`${DISPATCH_BASE}/${encoded(dispatchNo)}/items/${encoded(dispatchItemId)}/pick`, request)
|
2026-09-01 14:23:55 +08:00
|
|
|
}
|
2026-10-19 17:56:30 +08:00
|
|
|
export function completeDispatchLoading(dispatchNo, request) {
|
|
|
|
|
return dispatchWrite(`${DISPATCH_BASE}/${encoded(dispatchNo)}/loading-complete`, request)
|
2026-09-01 14:23:55 +08:00
|
|
|
}
|
2026-10-21 14:30:45 +08:00
|
|
|
export function reviewDispatchLoading(dispatchNo, request) {
|
|
|
|
|
return dispatchWrite(`${DISPATCH_BASE}/${encoded(dispatchNo)}/loading-review`, request)
|
2026-10-03 04:35:58 +08:00
|
|
|
}
|
|
|
|
|
|
2026-09-01 14:23:55 +08:00
|
|
|
export async function listOutboundTasks(status = '') {
|
|
|
|
|
const suffix = status ? `?status=${encoded(status)}` : ''
|
|
|
|
|
return asList(await overseasRequest('GET', `${OUTBOUND_BASE}${suffix}`)).map(normalizeOutboundTask)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getOutboundTask(taskNo) {
|
|
|
|
|
return normalizeOutboundTask(await overseasRequest('GET', `${OUTBOUND_BASE}/${encoded(taskNo)}`))
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-07 20:47:55 +08:00
|
|
|
export function resolveOutboundTask(businessNo) {
|
|
|
|
|
return overseasRequest('GET', `${OUTBOUND_BASE}/resolve/${encoded(businessNo)}`)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-26 13:43:39 +08:00
|
|
|
export async function pickOutboundTask(taskNo, request) {
|
|
|
|
|
await prepareStorageRent('OUTBOUND', taskNo, request, {
|
|
|
|
|
itemId: request.outboundPalletItemId, positionId: request.inventoryPositionId,
|
|
|
|
|
packages: request.packages, cartonMarks: request.cartonMarks,
|
|
|
|
|
skuCode: request.skuCode, productQuantity: request.productQuantity
|
|
|
|
|
})
|
2026-09-01 14:23:55 +08:00
|
|
|
return overseasRequest('POST', `${OUTBOUND_BASE}/${encoded(taskNo)}/pick`, request)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-26 13:43:39 +08:00
|
|
|
export async function pickOutboundPallet(taskNo, palletNo, request) {
|
|
|
|
|
await prepareStorageRent('OUTBOUND_PALLET', taskNo, request, { palletNo })
|
2026-09-15 21:05:09 +08:00
|
|
|
return overseasRequest('POST', `${OUTBOUND_BASE}/${encoded(taskNo)}/pallets/${encoded(palletNo)}/pick`, request)
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-29 20:52:44 +08:00
|
|
|
export function outboundPickPayload(item, position, packages, scannedCode = '') {
|
|
|
|
|
const skuScan = item.skuCode && scannedCode === item.skuCode
|
2026-09-15 11:40:56 +08:00
|
|
|
return {
|
|
|
|
|
outboundPalletItemId: item.outboundPalletItemId,
|
2026-10-02 17:06:08 +08:00
|
|
|
inventoryPositionId: position.inventoryPositionId || null,
|
2026-09-15 11:40:56 +08:00
|
|
|
packages: Number(packages),
|
2026-10-09 20:39:46 +08:00
|
|
|
...(item.returnSkuQuantity != null ? { productQuantity: Number(item.returnSkuQuantity) } : {}),
|
2026-09-29 20:52:44 +08:00
|
|
|
cartonMarks: skuScan ? [] : position.cartonMark ? [position.cartonMark] : [],
|
|
|
|
|
...(skuScan ? { skuCode: item.skuCode } : {})
|
2026-09-15 11:40:56 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-01 14:23:55 +08:00
|
|
|
export function completeOutboundTask(taskNo, request) {
|
|
|
|
|
return overseasRequest('POST', `${OUTBOUND_BASE}/${encoded(taskNo)}/complete`, request)
|
|
|
|
|
}
|
2026-09-28 17:26:50 +08:00
|
|
|
|
|
|
|
|
export function normalizeCargoDiscardTask(value) {
|
|
|
|
|
const task = value || {}
|
|
|
|
|
return {
|
|
|
|
|
...task,
|
|
|
|
|
discardNo: String(task?.discardNo || ''),
|
2026-10-14 17:41:11 +08:00
|
|
|
outboundNo: String(task?.outboundNo || ''),
|
2026-09-28 17:26:50 +08:00
|
|
|
palletNo: String(task?.palletNo || ''),
|
2026-10-14 17:41:11 +08:00
|
|
|
scanMode: task?.scanMode === 'CARTON' ? 'CARTON' : 'PALLET',
|
|
|
|
|
cartonMarks: asList(task?.cartonMarks).map(String),
|
2026-09-28 17:26:50 +08:00
|
|
|
status: String(task?.status || 'PENDING'),
|
|
|
|
|
skus: asList(task?.skus).map((sku) => ({
|
|
|
|
|
...sku,
|
|
|
|
|
itemId: String(sku?.itemId || ''),
|
|
|
|
|
skuCode: String(sku?.skuCode || ''),
|
|
|
|
|
productName: String(sku?.productName || ''),
|
2026-10-19 16:40:39 +08:00
|
|
|
quantityUnit: sku?.quantityUnit === '件' ? '件' : '箱',
|
2026-09-28 17:26:50 +08:00
|
|
|
plannedPackages: count(sku?.plannedPackages),
|
|
|
|
|
actualPackages: sku?.actualPackages == null ? null : count(sku.actualPackages),
|
2026-10-20 14:01:10 +08:00
|
|
|
actualQuantityEditable: sku?.actualQuantityEditable === true,
|
2026-09-28 17:26:50 +08:00
|
|
|
status: String(sku?.status || 'PENDING')
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function listCargoDiscardTasks() {
|
|
|
|
|
return asList(await overseasRequest('GET', CARGO_DISCARD_BASE)).map(normalizeCargoDiscardTask)
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-14 17:41:11 +08:00
|
|
|
export async function resolveCargoDiscardPallet(palletNo, discardNo) {
|
|
|
|
|
return normalizeCargoDiscardTask(await overseasRequest('POST', `${CARGO_DISCARD_BASE}/resolve`, { palletNo, ...(discardNo ? { discardNo } : {}) }))
|
2026-09-28 17:26:50 +08:00
|
|
|
}
|
|
|
|
|
|
2026-10-26 13:43:39 +08:00
|
|
|
export async function confirmCargoDiscard(discardNo, palletNo, lines, photoFileIds, scannedCartonMarks = [], confirmAllCartons = false, request = {}) {
|
|
|
|
|
const body = { lines, photoFileIds, scannedCartonMarks, confirmAllCartons, idempotencyKey: request.idempotencyKey }
|
|
|
|
|
await prepareStorageRent('DISCARD', discardNo, request, { palletNo, lines, photoFileIds, scannedCartonMarks, confirmAllCartons })
|
|
|
|
|
return normalizeCargoDiscardTask(await overseasRequest('POST', `${CARGO_DISCARD_BASE}/${encoded(discardNo)}/pallets/${encoded(palletNo)}/confirm`, body))
|
2026-09-28 17:26:50 +08:00
|
|
|
}
|
2026-10-14 11:43:44 +08:00
|
|
|
|
|
|
|
|
const discardPhotoPath = (discardNo, palletNo) => `${CARGO_DISCARD_BASE}/${encoded(discardNo)}/pallets/${encoded(palletNo)}/photos/files`
|
|
|
|
|
export const listCargoDiscardPhotos = (discardNo, palletNo) => overseasRequest('GET', discardPhotoPath(discardNo, palletNo))
|
|
|
|
|
export const uploadCargoDiscardPhoto = (discardNo, palletNo, filePath) => overseasUpload(discardPhotoPath(discardNo, palletNo), filePath)
|
|
|
|
|
export const downloadCargoDiscardPhoto = (discardNo, palletNo, fileId) => overseasDownload(`${discardPhotoPath(discardNo, palletNo)}/${encoded(fileId)}/download`, { expectedContentTypePrefix: 'image/' })
|