fix(pda): bind loading evidence to cabinet tasks
This commit is contained in:
+40
-74
@@ -19,9 +19,8 @@ const EVIDENCE_SLOTS = Object.freeze([
|
||||
{ type: 'SEAL', label: '带锁码照' }
|
||||
])
|
||||
|
||||
// C02 supplies inboundId/inboundVersion and loading.pickingVersion. C04 supplies
|
||||
// inbound-scoped task and evidence operations. No local task is promoted to a
|
||||
// server success when any required identifier or version is absent.
|
||||
// Picking and cabinet evidence are both scoped by the canonical PDA task. The
|
||||
// task warehouse is always sent as an authorization boundary and never guessed.
|
||||
|
||||
function debugLog(event, payload = {}) {
|
||||
try { console.info(`[PDA] ${event}`, payload) } catch (_) { /* console may be unavailable on a device */ }
|
||||
@@ -29,10 +28,6 @@ function debugLog(event, payload = {}) {
|
||||
|
||||
const request = apiRequest
|
||||
|
||||
function idempotencyKey(operation, inboundId) {
|
||||
return `pda-${operation}-${inboundId}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
||||
}
|
||||
|
||||
function stableIdempotencyKey(operationId, suffix = '') {
|
||||
const raw = `pda-${operationId}${suffix ? `-${suffix}` : ''}`
|
||||
let hash = 2166136261
|
||||
@@ -44,12 +39,6 @@ function stableIdempotencyKey(operationId, suffix = '') {
|
||||
return `${prefix}-${(hash >>> 0).toString(36)}-${raw.length}`.slice(0, 128)
|
||||
}
|
||||
|
||||
function tenantQuery(tenantId) {
|
||||
const value = tenantId ?? session.tenantId
|
||||
if (!value) throw new Error('缺少客户隔离标识 tenantId,无法请求现场任务')
|
||||
return String(value)
|
||||
}
|
||||
|
||||
export function isRemoteReady(inboundId) {
|
||||
return Boolean(session.token && (session.tenantId || inboundId))
|
||||
}
|
||||
@@ -190,41 +179,18 @@ export function normalizeInventorySnapshot(snapshot = {}) {
|
||||
return normalizePickingTask({ ...snapshot, version: snapshot.taskVersion ?? snapshot.version })
|
||||
}
|
||||
|
||||
export function getPickingTask(inboundId, tenantId) {
|
||||
export function getEvidence(taskId, warehouseId) {
|
||||
if (!taskId) throw new Error('缺少装柜任务标识,无法加载装柜证据')
|
||||
if (!warehouseId) throw new Error('缺少任务仓库标识,无法加载装柜证据')
|
||||
return request({
|
||||
url: `/inbounds/${encodeURIComponent(inboundId)}/loading/picking?tenantId=${encodeURIComponent(tenantQuery(tenantId))}`,
|
||||
url: `/first-leg/pda-loading/tasks/${encodeURIComponent(taskId)}/evidence?warehouseId=${encodeURIComponent(warehouseId)}`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function startPicking(inboundId, payload, tenantId) {
|
||||
return request({
|
||||
url: `/inbounds/${encodeURIComponent(inboundId)}/loading/picking/start?tenantId=${encodeURIComponent(tenantQuery(tenantId))}`,
|
||||
method: 'POST',
|
||||
header: { 'Idempotency-Key': idempotencyKey('start-picking', inboundId) },
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
|
||||
export function completePicking(inboundId, payload, tenantId) {
|
||||
return request({
|
||||
url: `/inbounds/${encodeURIComponent(inboundId)}/loading/picking/complete?tenantId=${encodeURIComponent(tenantQuery(tenantId))}`,
|
||||
method: 'POST',
|
||||
header: { 'Idempotency-Key': idempotencyKey('complete-picking', inboundId) },
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
|
||||
export function getEvidence(inboundId, tenantId) {
|
||||
return request({
|
||||
url: `/inbounds/${encodeURIComponent(inboundId)}/loading/evidence/pda?tenantId=${encodeURIComponent(tenantQuery(tenantId))}`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
export function getEvidencePreviewUrl(inboundId, evidenceId, tenantId) {
|
||||
if (!inboundId || !evidenceId) return ''
|
||||
return `/api/platform/v1/inbounds/${encodeURIComponent(inboundId)}/loading/evidence/${encodeURIComponent(evidenceId)}/content/pda?tenantId=${encodeURIComponent(tenantQuery(tenantId))}`
|
||||
export function getEvidencePreviewUrl(taskId, evidenceId, warehouseId) {
|
||||
if (!taskId || !evidenceId || !warehouseId) return ''
|
||||
return `/api/platform/v1/first-leg/pda-loading/tasks/${encodeURIComponent(taskId)}/evidence/${encodeURIComponent(evidenceId)}/content?warehouseId=${encodeURIComponent(warehouseId)}`
|
||||
}
|
||||
|
||||
export function downloadEvidencePreview(previewUrl) {
|
||||
@@ -260,56 +226,56 @@ export function downloadEvidencePreview(previewUrl) {
|
||||
})
|
||||
}
|
||||
|
||||
export function upsertEvidence(inboundId, payload, tenantId) {
|
||||
if (!EVIDENCE_TYPES.includes(payload.evidenceType)) throw new Error('不支持的装柜证据类型')
|
||||
return request({
|
||||
url: `/inbounds/${encodeURIComponent(inboundId)}/loading/evidence/pda?tenantId=${encodeURIComponent(tenantQuery(tenantId))}`,
|
||||
method: 'POST',
|
||||
header: { 'Idempotency-Key': stableIdempotencyKey(payload.operationId || `evidence-${inboundId}-${payload.evidenceType}-${payload.fileId || 'seal'}`) },
|
||||
data: payload
|
||||
})
|
||||
}
|
||||
|
||||
export function voidEvidence(inboundId, evidenceId, payload = {}, tenantId) {
|
||||
if (!inboundId || !evidenceId) throw new Error('缺少装柜证据标识,无法作废')
|
||||
export function voidEvidence(taskId, evidenceId, warehouseId, payload = {}) {
|
||||
if (!taskId || !evidenceId) throw new Error('缺少装柜任务或证据标识,无法作废')
|
||||
if (!warehouseId) throw new Error('缺少任务仓库标识,无法作废装柜证据')
|
||||
if (!String(payload.reason || '').trim()) throw new Error('作废原因不能为空')
|
||||
return request({
|
||||
url: `/inbounds/${encodeURIComponent(inboundId)}/loading/evidence/${encodeURIComponent(evidenceId)}/void/pda?tenantId=${encodeURIComponent(tenantQuery(tenantId))}`,
|
||||
url: `/first-leg/pda-loading/tasks/${encodeURIComponent(taskId)}/evidence/${encodeURIComponent(evidenceId)}/void`,
|
||||
method: 'POST',
|
||||
header: { 'Idempotency-Key': stableIdempotencyKey(payload.operationId || `void-evidence-${inboundId}-${evidenceId}-${payload.expectedVersion ?? 0}`) },
|
||||
data: payload
|
||||
data: { warehouseId, expectedVersion: payload.expectedVersion ?? 0, reason: String(payload.reason).trim() }
|
||||
})
|
||||
}
|
||||
|
||||
/** Upload a local photo to the file service and return its durable fileId. */
|
||||
export function uploadEvidenceFile(inboundId, evidenceType, localPath, tenantId, onProgress) {
|
||||
export function uploadEvidenceFile(taskId, warehouseId, payload, localPath, onProgress) {
|
||||
requireOnline('当前网络不可用,无法上传装柜照片')
|
||||
if (!taskId) throw new Error('缺少装柜任务标识,无法上传装柜照片')
|
||||
if (!warehouseId) throw new Error('缺少任务仓库标识,无法上传装柜照片')
|
||||
const evidenceType = payload?.evidenceType
|
||||
if (!EVIDENCE_TYPES.includes(evidenceType)) throw new Error('不支持的装柜证据类型')
|
||||
if (!localPath) throw new Error('缺少待上传的照片')
|
||||
const tenant = tenantQuery(tenantId)
|
||||
if (payload.expectedVersion == null) throw new Error('缺少装柜证据版本,无法上传装柜照片')
|
||||
if (evidenceType === 'SEAL' && !String(payload.sealNo || '').trim()) throw new Error('带锁码照必须填写封签号')
|
||||
const baseUrl = session.apiBase || '/api/platform/v1'
|
||||
debugLog('evidence.upload.start', { inboundId, evidenceType, tenantId: tenant, hasLocalPath: Boolean(localPath) })
|
||||
debugLog('evidence.upload.start', { taskId, evidenceType, hasLocalPath: Boolean(localPath) })
|
||||
const query = [
|
||||
`warehouseId=${encodeURIComponent(warehouseId)}`,
|
||||
`expectedVersion=${encodeURIComponent(payload.expectedVersion)}`,
|
||||
`evidenceType=${encodeURIComponent(evidenceType)}`
|
||||
]
|
||||
if (payload.sealNo) query.push(`sealNo=${encodeURIComponent(String(payload.sealNo).trim())}`)
|
||||
if (payload.remark) query.push(`remark=${encodeURIComponent(payload.remark)}`)
|
||||
return apiUpload({
|
||||
url: `${baseUrl}/files/loading-evidence/${encodeURIComponent(tenant)}/${encodeURIComponent(inboundId)}?evidenceType=${encodeURIComponent(evidenceType)}`,
|
||||
url: `${baseUrl}/first-leg/pda-loading/tasks/${encodeURIComponent(taskId)}/evidence?${query.join('&')}`,
|
||||
filePath: localPath,
|
||||
name: 'file',
|
||||
header: { 'Idempotency-Key': stableIdempotencyKey(payload.operationId || `evidence-${taskId}-${evidenceType}-${payload.expectedVersion}`) }
|
||||
}, onProgress).then((response) => {
|
||||
let body = response.data
|
||||
try { body = typeof body === 'string' ? JSON.parse(body) : body } catch (_) { /* handled below */ }
|
||||
const code = String(body?.code ?? response.statusCode)
|
||||
debugLog('evidence.upload.response', { inboundId, evidenceType, statusCode: response.statusCode, code, message: body?.message || '', requestId: body?.requestId || response.header?.['X-Request-Id'] || response.header?.['x-request-id'] || '', traceId: body?.traceId || response.header?.['X-Trace-Id'] || response.header?.['x-trace-id'] || '', fileId: body?.data?.fileId || null })
|
||||
if (code !== '200') { const error = new Error(body?.message || '文件上传失败'); error.code = body?.code || response.statusCode; throw error }
|
||||
debugLog('evidence.upload.response', { taskId, evidenceType, statusCode: response.statusCode, code, message: body?.message || '', requestId: body?.requestId || response.header?.['X-Request-Id'] || response.header?.['x-request-id'] || '', traceId: body?.traceId || response.header?.['X-Trace-Id'] || response.header?.['x-trace-id'] || '', fileId: body?.data?.fileId || null })
|
||||
if (code !== '200') {
|
||||
const error = new Error(body?.message || '文件上传失败')
|
||||
error.code = body?.code || response.statusCode
|
||||
error.statusCode = response.statusCode
|
||||
error.requestId = body?.requestId || response.header?.['X-Request-Id'] || response.header?.['x-request-id'] || ''
|
||||
error.traceId = body?.traceId || response.header?.['X-Trace-Id'] || response.header?.['x-trace-id'] || ''
|
||||
throw error
|
||||
}
|
||||
return body?.data || body
|
||||
}).catch((error) => { debugLog('evidence.upload.error', { inboundId, evidenceType, message: error.message || error.errMsg || '' }); throw error })
|
||||
}
|
||||
|
||||
export function completeLoading(inboundId, payload, tenantId) {
|
||||
return request({
|
||||
url: `/inbounds/${encodeURIComponent(inboundId)}/loading/complete?tenantId=${encodeURIComponent(tenantQuery(tenantId))}`,
|
||||
method: 'POST',
|
||||
header: { 'Idempotency-Key': idempotencyKey('complete-loading', inboundId) },
|
||||
data: payload
|
||||
})
|
||||
}).catch((error) => { debugLog('evidence.upload.error', { taskId, evidenceType, message: error.message || error.errMsg || '' }); throw error })
|
||||
}
|
||||
|
||||
/** Complete the cabinet PDA task after the operator has captured all evidence. */
|
||||
|
||||
Reference in New Issue
Block a user