const MODULE = 'phase3-loading' const OPTIONS = { scenario: 'phase3-split-preload-loading', deploymentBlocker: true } function id(value) { return value == null ? '' : String(value) } function codeOf(response) { return response?.body?.code == null ? 'missing' : String(response.body.code) } function failure(response) { return [400, 401, 403, 404, 409, 503].includes(response?.http) || (response?.http === 200 && codeOf(response) !== '200') } async function request(context, method, path, body, headers = {}) { try { const response = await fetch(`${context.options.gateway}${path}`, { method, headers: { Accept: 'application/json', Authorization: `Bearer ${context.token}`, ...(body === undefined ? {} : { 'Content-Type': 'application/json; charset=utf-8' }), ...headers }, body: body === undefined ? undefined : JSON.stringify(body), 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, 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, path, http: 0, body: null, raw: String(error), requestId: null, traceId: null } } } async function uploadEvidence(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]) const fileName = `${context.marker}-${evidenceType.toLowerCase()}.png` form.append('file', new Blob([png], { type: 'image/png' }), fileName) 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 bindEvidence(context, base, tenantId, evidenceType, fileId, suffix, keySuffix = evidenceType) { return request(context, 'POST', `${base}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: 0, evidenceType, fileId, fileName: 'client-must-not-override.png', sealNo: evidenceType === 'SEAL' ? `SEAL-${suffix}` : null, remark: context.marker, }, { 'Idempotency-Key': `${context.marker}-p3-evidence-${keySuffix}`.slice(0, 128) }) } function fixture(context) { return context.createdResources?.orchestrationFixtures?.inboundMeasurement || context.createdResources?.inboundMeasurement || context.createdResources?.orders } export async function run(context) { const source = fixture(context) const tenantId = id(source?.tenantId) const inboundId = id(source?.inboundId) if (!tenantId || !inboundId) { context.addStep(MODULE, '取得本轮 WAIT_SPLIT 隔离入库单', null, false, '未找到 orders/inbound-measurement 本轮隔离 fixture,未使用历史入库单', OPTIONS) return } const detailPath = `/api/platform/inbounds/${inboundId}?tenantId=${encodeURIComponent(tenantId)}` const initialResponse = await context.request('GET', detailPath) const initial = context.apiSuccess(MODULE, '读取三期隔离入库单状态与版本', initialResponse, `tenantId=${tenantId}, inboundId=${inboundId}`, OPTIONS) const initialStatus = initial?.summary?.processStatus const initialVersion = Number(initial?.summary?.version) context.assert(MODULE, '隔离入库单处于智能分柜门禁状态', initialResponse, ['INBOUNDED', 'WAIT_SPLIT'].includes(initialStatus) && Number.isInteger(initialVersion), `status=${initialStatus || 'missing'}, version=${initial?.summary?.version ?? 'missing'}`, OPTIONS) if (!['INBOUNDED', 'WAIT_SPLIT'].includes(initialStatus) || !Number.isInteger(initialVersion)) return const base = `/api/platform/v1/inbounds/${inboundId}` const suffix = context.marker.replace(/\D/g, '').slice(-10) const warehouseId = id(initial?.summary?.warehouseId) const smartLoadingBase = '/api/platform/v1/first-leg/smart-loading' const pdaBase = '/api/platform/v1/first-leg/pda-loading' const candidateResponse = await context.request('GET', `${smartLoadingBase}/candidates?warehouseId=${encodeURIComponent(warehouseId)}`) const candidates = context.apiSuccess(MODULE, '查询本轮入库单柜次装柜候选', candidateResponse, '', OPTIONS) const candidate = Array.isArray(candidates) ? candidates.find((item) => id(item.inboundId) === inboundId && Number(item.availablePackageCount) > 0) : null context.assert(MODULE, '柜次装柜候选与入库单、客户和仓库一致', candidateResponse, Boolean(candidate?.inboundDetailId) && id(candidate?.accountId) === tenantId && id(candidate?.warehouseId) === warehouseId, `candidate=${Boolean(candidate)}, warehouseId=${warehouseId || 'missing'}`, OPTIONS) if (!candidate?.inboundDetailId || !warehouseId) return const containerNo = `P3B-${suffix}` const containerResponse = await request(context, 'POST', '/api/platform/v1/containers', { containerNo, cabinetNo: `P3BI-${suffix}`, warehouseId, warehouseName: initial?.summary?.warehouseName || null, billOfLadingNo: `P3BL-${suffix}`, vesselName: `P3-BRIDGE-${suffix}`, voyageNo: `P3V-${suffix}`, containerType: '40HQ', shippingCompanyName: `P3-SHIPPING-${suffix}`, departurePortCode: 'CNSZX', departurePortName: '深圳', destinationPortCode: 'USLAX', destinationPortName: '洛杉矶', destinationWarehouseCode: candidate.destinationWarehouseCode || null, destinationWarehouseName: candidate.destinationWarehouseName || null, capacityCbm: 100, maxWeightKg: 100000, loadingAt: '2026-09-02T08:00:00', estimatedSailingAt: '2026-09-03T08:00:00', estimatedPortArrivalAt: '2026-09-20T08:00:00', estimatedWarehouseArrivalAt: '2026-09-21T08:00:00', }) const container = context.apiSuccess(MODULE, '创建与入库预装串联的柜次', containerResponse, '', OPTIONS) const containerBookingId = id(container?.id) if (!containerBookingId) return const loadingPlanResponse = await request(context, 'POST', `${smartLoadingBase}/plans`, { bookingId: containerBookingId, remark: context.marker, }) const loadingPlan = context.apiSuccess(MODULE, '创建柜次级智能装柜计划', loadingPlanResponse, '', OPTIONS) const loadingPlanId = id(loadingPlan?.id) if (!loadingPlanId) return const assignmentResponse = await request(context, 'POST', `${smartLoadingBase}/plans/${loadingPlanId}/assignments`, { inboundId, inboundDetailId: id(candidate.inboundDetailId), packageCount: 1, expectedVersion: Number(loadingPlan.version), operationId: `${context.marker}-bridge-assign`.slice(0, 128), }) const assignedLoadingPlan = context.apiSuccess(MODULE, '将本轮入库货物分配到柜次计划', assignmentResponse, '', OPTIONS) context.assert(MODULE, '柜次计划明细和版本真实持久化', assignmentResponse, assignedLoadingPlan?.items?.some?.((item) => id(item.inboundId) === inboundId && Number(item.packageCount) === 1) && Number(assignedLoadingPlan?.version) === 1, `loadingPlanId=${loadingPlanId}, version=${assignedLoadingPlan?.version ?? 'missing'}`, OPTIONS) const smart = await request(context, 'POST', `${base}/split/smart?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: 0, items: [{ containerCode: `P3-${suffix}`, cargoReference: `REF-${suffix}`, packageCount: 1, volumeCbm: 0.01, remark: context.marker }], remark: context.marker, }, { 'Idempotency-Key': `${context.marker}-p3-smart`.slice(0, 128) }) const smartData = context.apiSuccess(MODULE, '智能分柜并生成预装清单', smart, `inboundId=${inboundId}`, OPTIONS) const planId = id(smartData?.preloadPlan?.id) context.assert(MODULE, '智能分柜预装计划 PENDING、版本和明细正确', smart, smartData?.mainStatus === 'INBOUNDED' && Boolean(planId) && smartData?.preloadPlan?.status === 'PENDING' && Number(smartData?.preloadPlan?.version) === 0 && smartData?.preloadPlan?.items?.length === 1, `mainStatus=${smartData?.mainStatus || 'missing'}, planId=${planId || 'missing'}, status=${smartData?.preloadPlan?.status || 'missing'}`, OPTIONS) if (!planId) return const replay = await request(context, 'POST', `${base}/split/smart?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: 0, items: [], remark: 'replay must not replace', }, { 'Idempotency-Key': `${context.marker}-p3-smart`.slice(0, 128) }) context.assert(MODULE, '智能分柜幂等重放返回同一预装计划', replay, codeOf(replay) === '200' && id(replay.body?.data?.preloadPlan?.id) === planId && replay.body?.data?.preloadPlan?.items?.length === 1, `HTTP=${replay.http}, code=${codeOf(replay)}, planId=${id(replay.body?.data?.preloadPlan?.id) || 'missing'}`, OPTIONS) const bindResponse = await request(context, 'POST', `${base}/preload/loading-plans/${loadingPlanId}?tenantId=${encodeURIComponent(tenantId)}`, { expectedPreloadVersion: Number(smartData.preloadPlan.version), expectedLoadingPlanVersion: Number(assignedLoadingPlan.version), }, { 'Idempotency-Key': `${context.marker}-bridge-bind`.slice(0, 128) }) const link = context.apiSuccess(MODULE, '原子关联入库预装与柜次装柜计划', bindResponse, '', OPTIONS) context.assert(MODULE, '桥接双版本递增且关联归属唯一', bindResponse, id(link?.inboundId) === inboundId && id(link?.loadingPlanId) === loadingPlanId && id(link?.preloadPlanId) === planId && Number(link?.allocatedPackageCount) === 1 && Number(link?.preloadVersion) === 1 && Number(link?.loadingPlanVersion) === 2, `linkId=${id(link?.id) || 'missing'}, preloadVersion=${link?.preloadVersion ?? 'missing'}, loadingVersion=${link?.loadingPlanVersion ?? 'missing'}`, OPTIONS) if (!link?.id) return const pushResponse = await request(context, 'POST', `${smartLoadingBase}/plans/${loadingPlanId}/push-preload`, { expectedVersion: Number(link.loadingPlanVersion) }, { 'Idempotency-Key': `${context.marker}-bridge-push`.slice(0, 128) }) const pushedLoadingPlan = context.apiSuccess(MODULE, '桥接后推送柜次计划并生成真实 PDA 任务', pushResponse, '', OPTIONS) context.assert(MODULE, '关联柜次计划进入 LOADING', pushResponse, pushedLoadingPlan?.status === 'LOADING', `status=${pushedLoadingPlan?.status || 'missing'}`, OPTIONS) const confirming = await request(context, 'POST', `${base}/preload/confirming?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: Number(link.preloadVersion) }, { 'Idempotency-Key': `${context.marker}-p3-confirming`.slice(0, 128) }) const confirmingData = context.apiSuccess(MODULE, '提交预装清单确认', confirming, '', OPTIONS) context.assert(MODULE, '预装清单进入 CONFIRMING 且版本递增', confirming, confirmingData?.preloadPlan?.status === 'CONFIRMING' && Number(confirmingData?.preloadPlan?.version) === 2, `status=${confirmingData?.preloadPlan?.status || 'missing'}, version=${confirmingData?.preloadPlan?.version ?? 'missing'}`, OPTIONS) if (confirmingData?.preloadPlan?.status !== 'CONFIRMING') return const confirm = await request(context, 'POST', `${base}/preload/confirm?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: Number(confirmingData.preloadPlan.version) }, { 'Idempotency-Key': `${context.marker}-p3-confirm`.slice(0, 128) }) const confirmed = context.apiSuccess(MODULE, '确认预装并进入装柜中', confirm, '', OPTIONS) context.assert(MODULE, '预装确认推进主状态 LOADING 和计划 CONFIRMED', confirm, confirmed?.mainStatus === 'LOADING' && confirmed?.preloadPlan?.status === 'CONFIRMED' && Number(confirmed?.preloadPlan?.version) === 3, `mainStatus=${confirmed?.mainStatus || 'missing'}, planStatus=${confirmed?.preloadPlan?.status || 'missing'}, version=${confirmed?.preloadPlan?.version ?? 'missing'}`, OPTIONS) if (confirmed?.mainStatus !== 'LOADING') return const preload = await context.request('GET', `${base}/preload?tenantId=${encodeURIComponent(tenantId)}`) const preloadData = context.apiSuccess(MODULE, '回查已确认预装清单', preload, '', OPTIONS) context.assert(MODULE, '预装清单归属、明细和 marker 持久化', preload, id(preloadData?.id) === planId && id(preloadData?.inboundId) === inboundId && id(preloadData?.tenantId) === tenantId && preloadData?.items?.[0]?.containerCode === `P3-${suffix}` && preloadData?.remark === context.marker, `planId=${id(preloadData?.id) || 'missing'}, items=${preloadData?.items?.length ?? 'n/a'}`, OPTIONS) const loadingResponse = await context.request('GET', detailPath) const loading = context.apiSuccess(MODULE, '读取装柜中入库单版本', loadingResponse, '', OPTIONS) const loadingVersion = Number(loading?.summary?.version) context.assert(MODULE, '入库单已进入 LOADING 且版本有效', loadingResponse, loading?.summary?.processStatus === 'LOADING' && Number.isInteger(loadingVersion), `status=${loading?.summary?.processStatus || 'missing'}, version=${loading?.summary?.version ?? 'missing'}`, OPTIONS) if (!Number.isInteger(loadingVersion)) return const blockedBeforePicking = await request(context, 'POST', `${base}/loading/complete?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: loadingVersion, remark: 'must require picking' }, { 'Idempotency-Key': `${context.marker}-p3-complete-before-picking`.slice(0, 128) }) context.addStep(MODULE, '未完成 PDA 拣货时拒绝完成装柜', blockedBeforePicking, failure(blockedBeforePicking), `HTTP=${blockedBeforePicking.http}, code=${codeOf(blockedBeforePicking)}`, OPTIONS) const canonicalTaskResponse = await context.request('GET', `${pdaBase}/tasks?containerNo=${encodeURIComponent(containerNo)}&warehouseId=${encodeURIComponent(warehouseId)}`) const canonicalTask = context.apiSuccess(MODULE, '读取柜次级 PDA 唯一事实任务', canonicalTaskResponse, '', OPTIONS) const canonicalTaskId = id(canonicalTask?.id) const canonicalItem = canonicalTask?.items?.find?.((item) => id(item.inboundId) === inboundId) context.assert(MODULE, '柜次 PDA 任务与桥接入库明细一致', canonicalTaskResponse, Boolean(canonicalTaskId) && id(canonicalTask?.loadingPlanId) === loadingPlanId && canonicalTask?.workflowStatus === 'UNASSIGNED' && Boolean(canonicalItem?.id), `taskId=${canonicalTaskId || 'missing'}, itemId=${id(canonicalItem?.id) || 'missing'}`, OPTIONS) if (!canonicalTaskId || !canonicalItem?.id) return const claim = await request(context, 'POST', `${pdaBase}/tasks/${canonicalTaskId}/claim`, { expectedAssignmentVersion: 0, operationId: `${context.marker}-bridge-claim`.slice(0, 128), }) const claimed = context.apiSuccess(MODULE, '领取柜次 PDA 任务', claim, '', OPTIONS) context.assert(MODULE, '柜次 PDA 任务绑定当前作业员', claim, claimed?.workflowStatus === 'IN_PROGRESS' && Boolean(claimed?.assignment?.assigneeId), `workflow=${claimed?.workflowStatus || 'missing'}`, OPTIONS) const canonicalPick = await request(context, 'POST', `${pdaBase}/tasks/${canonicalTaskId}/items/${id(canonicalItem.id)}/pick`, { warehouseId, pickedPackageCount: 1, locationIds: [], version: Number(canonicalItem.version), operationId: `${context.marker}-bridge-pick`.slice(0, 128), }) const pickedCanonical = context.apiSuccess(MODULE, '执行柜次 PDA 明细拣货', canonicalPick, '', OPTIONS) context.assert(MODULE, '柜次 PDA 明细完成后等待复核', canonicalPick, pickedCanonical?.workflowStatus === 'AWAITING_REVIEW' && pickedCanonical?.taskStatus === 'LOADING', `workflow=${pickedCanonical?.workflowStatus || 'missing'}`, OPTIONS) const canonicalReview = await request(context, 'POST', `${pdaBase}/tasks/${canonicalTaskId}/review`, { expectedTaskVersion: Number(pickedCanonical?.version), operationId: `${context.marker}-bridge-review`.slice(0, 128), remark: context.marker, }) const reviewedCanonical = context.apiSuccess(MODULE, '复核柜次 PDA 并完成柜次计划', canonicalReview, '', OPTIONS) context.assert(MODULE, '柜次 PDA 复核成为装柜完成真实门禁', canonicalReview, reviewedCanonical?.taskStatus === 'COMPLETED' && reviewedCanonical?.workflowStatus === 'COMPLETED' && reviewedCanonical?.review?.status === 'APPROVED', `taskStatus=${reviewedCanonical?.taskStatus || 'missing'}`, OPTIONS) const forgedPickingStart = await request(context, 'POST', `${base}/loading/picking/start?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: 0, expectedCount: 1, pickedCount: 0, remark: 'forged-client-count' }, { 'Idempotency-Key': `${context.marker}-p3-picking-forged-start`.slice(0, 128) }) context.addStep(MODULE, '入库级 PDA 汇总拒绝客户端伪造数量', forgedPickingStart, failure(forgedPickingStart), `HTTP=${forgedPickingStart.http}, code=${codeOf(forgedPickingStart)}`, OPTIONS) const pickingStart = await request(context, 'POST', `${base}/loading/picking/start?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: 0, expectedCount: 1, pickedCount: 1, remark: context.marker }, { 'Idempotency-Key': `${context.marker}-p3-picking-start`.slice(0, 128) }) const pickingStarted = context.apiSuccess(MODULE, '开始本轮 PDA 拣货任务', pickingStart, '', OPTIONS) context.assert(MODULE, 'PDA 拣货任务处于 IN_PROGRESS 且计划数量正确', pickingStart, pickingStarted?.status === 'IN_PROGRESS' && Number(pickingStarted?.version) === 0 && Number(pickingStarted?.expectedCount) === 1 && Number(pickingStarted?.pickedCount) === 1, `status=${pickingStarted?.status || 'missing'}, version=${pickingStarted?.version ?? 'missing'}`, OPTIONS) const pickingMismatch = await request(context, 'POST', `${base}/loading/picking/complete?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: 0, expectedCount: 1, pickedCount: 0, remark: context.marker }, { 'Idempotency-Key': `${context.marker}-p3-picking-mismatch`.slice(0, 128) }) context.addStep(MODULE, 'PDA 拣货数量不一致时拒绝完成', pickingMismatch, failure(pickingMismatch), `HTTP=${pickingMismatch.http}, code=${codeOf(pickingMismatch)}`, OPTIONS) const pickingComplete = await request(context, 'POST', `${base}/loading/picking/complete?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: 0, expectedCount: 1, pickedCount: 1, remark: context.marker }, { 'Idempotency-Key': `${context.marker}-p3-picking-complete`.slice(0, 128) }) const pickingCompleted = context.apiSuccess(MODULE, '完成本轮 PDA 拣货复核', pickingComplete, '', OPTIONS) context.assert(MODULE, 'PDA 拣货任务 COMPLETED 且数量一致', pickingComplete, pickingCompleted?.status === 'COMPLETED' && Number(pickingCompleted?.version) === 1 && Number(pickingCompleted?.expectedCount) === 1 && Number(pickingCompleted?.pickedCount) === 1, `status=${pickingCompleted?.status || 'missing'}, version=${pickingCompleted?.version ?? 'missing'}`, OPTIONS) const evidenceTypes = ['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL'] const evidenceItems = [] for (const evidenceType of evidenceTypes) { const upload = await uploadEvidence(context, tenantId, inboundId, evidenceType) const uploaded = context.apiSuccess(MODULE, `上传本轮 ${evidenceType} 真实证据文件`, upload, '', OPTIONS) const fileId = id(uploaded?.fileId) context.assert(MODULE, `${evidenceType} 文件归属类型和媒体元数据正确`, upload, Boolean(fileId) && uploaded?.businessType === `LOADING_${evidenceType}` && uploaded?.contentType === 'image/png' && Number(uploaded?.sizeBytes) > 0, `fileId=${fileId || 'missing'}, businessType=${uploaded?.businessType || 'missing'}`, OPTIONS) if (!fileId) return const evidence = await bindEvidence(context, base, tenantId, evidenceType, fileId, suffix) const evidenceData = context.apiSuccess(MODULE, `绑定本轮 ${evidenceType} 装柜证据`, evidence, '', OPTIONS) const evidenceItem = evidenceData?.evidences?.evidences?.find?.((item) => item.evidenceType === evidenceType) context.assert(MODULE, `${evidenceType} 证据归属、marker 和版本正确`, evidence, evidenceData?.mainStatus === 'LOADING' && id(evidenceItem?.fileId) === fileId && evidenceItem?.fileName === `${context.marker}-${evidenceType.toLowerCase()}.png` && evidenceItem?.remark === context.marker && Number(evidenceItem?.version) === 0 && (evidenceType !== 'SEAL' || evidenceItem?.sealNo === `SEAL-${suffix}`), `mainStatus=${evidenceData?.mainStatus || 'missing'}, evidenceId=${id(evidenceItem?.id) || 'missing'}`, OPTIONS) if (!evidenceItem?.id) return evidenceItems.push(evidenceItem) const download = await context.requestBinary('GET', `/api/platform/v1/files/loading-evidence/${tenantId}/${inboundId}/${fileId}`) context.assert(MODULE, `下载本轮 ${evidenceType} 证据文件`, download, download.http === 200 && download.contentType.toLowerCase().includes('image/png') && download.bytes.length > 0, `bytes=${download.bytes.length}, contentType=${download.contentType || 'missing'}`, OPTIONS) if (evidenceType === 'PHOTO_STEP_4') { const missingSeal = await request(context, 'POST', `${base}/loading/complete?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: loadingVersion, remark: 'must require seal' }, { 'Idempotency-Key': `${context.marker}-p3-complete-before-seal`.slice(0, 128) }) context.addStep(MODULE, '缺铅封照片和锁码时拒绝完成装柜', missingSeal, failure(missingSeal), `HTTP=${missingSeal.http}, code=${codeOf(missingSeal)}`, OPTIONS) } } const firstPhoto = evidenceItems.find((item) => item.evidenceType === 'PHOTO_STEP_1') const sealEvidence = evidenceItems.find((item) => item.evidenceType === 'SEAL') const wrongType = await bindEvidence(context, base, tenantId, 'PHOTO_STEP_2', id(firstPhoto?.fileId), suffix, 'wrong-type') context.addStep(MODULE, '不同证据类型的真实 fileId 不可交叉绑定', wrongType, failure(wrongType), `HTTP=${wrongType.http}, code=${codeOf(wrongType)}`, OPTIONS) const forged = await request(context, 'POST', `${base}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: Number(sealEvidence?.version), evidenceType: 'SEAL', fileId: '999999999999999999', fileName: `${context.marker}-forged.png`, sealNo: `FORGED-${suffix}`, remark: context.marker, }, { 'Idempotency-Key': `${context.marker}-p3-forged-evidence`.slice(0, 128) }) context.addStep(MODULE, '伪造装柜证据 fileId 归属校验拒绝', forged, failure(forged), `HTTP=${forged.http}, code=${codeOf(forged)}`, OPTIONS) const evidenceList = await context.request('GET', `${base}/loading/evidence?tenantId=${encodeURIComponent(tenantId)}`) const evidenceListData = context.apiSuccess(MODULE, '回查五类装柜证据列表', evidenceList, '', OPTIONS) context.assert(MODULE, '五类装柜证据完整且铅封号持久化', evidenceList, evidenceListData?.evidences?.length === evidenceTypes.length && evidenceTypes.every((type) => evidenceListData.evidences.some((item) => item.evidenceType === type)) && evidenceListData.evidences.some((item) => item.evidenceType === 'SEAL' && item.sealNo === `SEAL-${suffix}`), `evidences=${evidenceListData?.evidences?.length ?? 'n/a'}`, OPTIONS) const complete = await request(context, 'POST', `${base}/loading/complete?tenantId=${encodeURIComponent(tenantId)}`, { expectedVersion: loadingVersion, remark: context.marker }, { 'Idempotency-Key': `${context.marker}-p3-complete`.slice(0, 128) }) const completed = context.apiSuccess(MODULE, '确认本轮入库单装柜完成', complete, '', OPTIONS) context.assert(MODULE, '装柜完成推进主状态 LOADED 且保留五类证据', complete, completed?.mainStatus === 'LOADED' && completed?.evidences?.evidences?.length === evidenceTypes.length, `mainStatus=${completed?.mainStatus || 'missing'}, evidences=${completed?.evidences?.evidences?.length ?? 'n/a'}`, OPTIONS) const finalResponse = await context.request('GET', detailPath) const finalData = context.apiSuccess(MODULE, '回查三期装柜完成终态', finalResponse, '', OPTIONS) context.assert(MODULE, '入库单终态 LOADED、版本递增且审计历史存在', finalResponse, finalData?.summary?.processStatus === 'LOADED' && Number(finalData?.summary?.version) === loadingVersion + 1 && finalData?.statusHistory?.some?.((item) => item.action === 'COMPLETE_LOADING' && item.operatorName), `status=${finalData?.summary?.processStatus || 'missing'}, version=${finalData?.summary?.version ?? 'missing'}, histories=${finalData?.statusHistory?.length ?? 'n/a'}`, OPTIONS) context.createdResources.phase3Loading = { tenantId, inboundId, preloadPlanId: planId, evidenceIds: evidenceItems.map((item) => id(item.id)), loadingPlanId, containerBookingId, containerNo, warehouseId, canonicalPdaTaskId: canonicalTaskId, pickingTaskId: id(pickingCompleted?.id), marker: context.marker, finalStatus: 'LOADED', } }