This commit is contained in:
2026-08-17 19:13:08 +08:00
parent 5199146e97
commit cddd23e4c7
40 changed files with 1203 additions and 71 deletions
@@ -40,6 +40,37 @@ async function request(context, method, path, body, headers = {}) {
}
}
async function uploadLoadingEvidence(context, tenantId, inboundId, evidenceType) {
const path = `/api/platform/v1/files/loading-evidence/${tenantId}/${inboundId}?evidenceType=${encodeURIComponent(evidenceType)}`
const form = new FormData()
const png = Uint8Array.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,6,0,0,0,31,21,196,137,0,0,0,13,73,68,65,84,8,215,99,248,207,192,240,31,0,5,0,1,255,137,153,61,29,0,0,0,0,73,69,78,68,174,66,96,130])
form.append('file', new Blob([png], { type: 'image/png' }), `${context.marker}-${evidenceType.toLowerCase()}.png`)
try {
const response = await fetch(`${context.options.gateway}${path}`, {
method: 'POST',
headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}` },
body: form,
signal: AbortSignal.timeout(60_000),
})
const raw = await response.text(); let payload = null
try { payload = raw ? JSON.parse(raw) : null } catch { /* bounded raw evidence */ }
return { method: 'POST', path, http: response.status, body: payload,
raw: payload === null ? raw.slice(0, 500) : null,
requestId: payload?.requestId || response.headers.get('x-request-id'),
traceId: payload?.traceId || response.headers.get('x-trace-id') }
} catch (error) {
return { method: 'POST', path, http: 0, body: null, raw: String(error), requestId: null, traceId: null }
}
}
async function bindLoadingEvidence(context, inboundId, tenantId, evidenceType, fileId, sealNo) {
return request(context, 'POST',
`/api/platform/v1/inbounds/${inboundId}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}`,
{ expectedVersion: 0, evidenceType, fileId, fileName: 'client-must-not-override.png',
sealNo: evidenceType === 'SEAL' ? sealNo : null, remark: context.marker },
{ 'Idempotency-Key': `${context.marker}-cab-evidence-${evidenceType}`.slice(0, 128) })
}
export function selectContainerLoadingFixture(context) {
const candidates = [
context.createdResources?.orchestrationFixtures?.phase3Container,
@@ -662,6 +693,52 @@ export async function run(context) {
&& pickReplay?.items?.every?.((item) => Number(item.pickedPackageCount) === Number(item.plannedPackageCount)),
`taskStatus=${pickReplay?.taskStatus || 'missing'}`, OPTIONS)
const evidenceTypes = ['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL']
const sealNo = `SEAL-${suffix}`.slice(0, 64)
for (const evidenceType of evidenceTypes) {
const uploadResponse = await uploadLoadingEvidence(context, tenantId, inboundId, evidenceType)
const uploaded = context.apiSuccess(MODULE, `上传柜次 ${evidenceType} 真实证据文件`, uploadResponse,
responseEvidence(uploadResponse), OPTIONS)
const fileId = id(uploaded?.fileId)
context.assert(MODULE, `${evidenceType} 文件类型、归属和二进制元数据完整`, uploadResponse,
Boolean(fileId) && uploaded?.businessType === `LOADING_${evidenceType}`
&& uploaded?.contentType === 'image/png' && Number(uploaded?.sizeBytes) > 0,
`fileId=${fileId || 'missing'}, businessType=${uploaded?.businessType || 'missing'}, size=${uploaded?.sizeBytes ?? 'missing'}`, OPTIONS)
if (!fileId) return
const bindResponse = await bindLoadingEvidence(context, inboundId, tenantId, evidenceType, fileId, sealNo)
const bound = context.apiSuccess(MODULE, `绑定柜次 ${evidenceType} 装柜证据`, bindResponse,
responseEvidence(bindResponse), OPTIONS)
const boundItem = bound?.evidences?.evidences?.find?.((item) =>
item.evidenceType === evidenceType && id(item.fileId) === fileId && item.status !== 'VOID')
context.assert(MODULE, `${evidenceType} 有效证据归属稳定且锁码门禁完整`, bindResponse,
bound?.mainStatus === 'LOADING' && Boolean(boundItem?.id)
&& boundItem?.remark === context.marker
&& (evidenceType !== 'SEAL' || boundItem?.sealNo === sealNo),
`mainStatus=${bound?.mainStatus || 'missing'}, evidenceId=${id(boundItem?.id) || 'missing'}, sealNo=${boundItem?.sealNo || 'n/a'}`, OPTIONS)
if (!boundItem?.id) return
const downloadResponse = await context.requestBinary('GET',
`/api/platform/v1/files/loading-evidence/${tenantId}/${inboundId}/${fileId}`)
context.assert(MODULE, `下载柜次 ${evidenceType} 证据为非空受控二进制`, downloadResponse,
downloadResponse.http === 200 && downloadResponse.bytes.length > 0
&& downloadResponse.contentType.toLowerCase().includes('image/png'),
`bytes=${downloadResponse.bytes.length}, contentType=${downloadResponse.contentType || 'missing'}`, OPTIONS)
}
const evidenceListResponse = await context.request('GET',
`/api/platform/v1/inbounds/${inboundId}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}`)
const evidenceList = context.apiSuccess(MODULE, 'PDA 完成前回查五类有效装柜证据与锁码', evidenceListResponse,
'', OPTIONS)
const activeEvidence = Array.isArray(evidenceList?.evidences)
? evidenceList.evidences.filter((item) => item.status !== 'VOID') : []
context.assert(MODULE, '五类有效照片和 SEAL 锁码齐全后才调用 PDA complete', evidenceListResponse,
evidenceTypes.every((type) => activeEvidence.some((item) => item.evidenceType === type))
&& activeEvidence.some((item) => item.evidenceType === 'SEAL' && item.sealNo === sealNo),
`types=${[...new Set(activeEvidence.map((item) => item.evidenceType))].join(',') || 'missing'}, sealNo=${sealNo}`, OPTIONS)
if (!evidenceTypes.every((type) => activeEvidence.some((item) => item.evidenceType === type))
|| !activeEvidence.some((item) => item.evidenceType === 'SEAL' && item.sealNo === sealNo)) return
const completePath = `${PDA}/tasks/${taskId}/complete`
const completePayload = {
expectedTaskVersion: Number(picked?.version),