Files
baiyue-firstleg/scripts/admin-test-env-modules/phase3-evidence-gate.mjs
T

87 lines
6.6 KiB
JavaScript
Raw Normal View History

const MODULE = 'phase3-evidence-gate'
const OPTIONS = { scenario: 'loading-evidence-gate', deploymentBlocker: true }
function id(value) { return value == null ? '' : String(value) }
2026-08-15 09:49:09 +08:00
function successful(response) { return response?.http === 200 && String(response?.body?.code ?? '') === '200' }
export async function run(context) {
const fixture = context.createdResources?.phase3Loading
2026-08-15 09:49:09 +08:00
const tenantId = id(fixture?.tenantId)
const inboundId = id(fixture?.inboundId)
const taskId = id(fixture?.canonicalPdaTaskId || fixture?.pickingTaskId)
const loadingPlanId = id(fixture?.loadingPlanId)
const containerBookingId = id(fixture?.containerBookingId)
const warehouseId = id(fixture?.warehouseId)
if (!tenantId || !inboundId || !taskId || !loadingPlanId || !containerBookingId || !warehouseId
|| fixture.marker !== context.marker) {
2026-08-24 19:58:35 +08:00
context.addStep(MODULE, '取得同 marker 的五证据门禁前置', null, false,
`tenantId=${tenantId || 'missing'}, inboundId=${inboundId || 'missing'}, taskId=${taskId || 'missing'}, loadingPlanId=${loadingPlanId || 'missing'}, containerBookingId=${containerBookingId || 'missing'}, fixtureMarker=${fixture?.marker || 'missing'}, runMarker=${context.marker}`,
OPTIONS)
context.addCoverageGap(MODULE, OPTIONS.scenario, '五类证据多图追加、作废审计和有效证据门禁',
2026-08-15 09:49:09 +08:00
'本轮未取得带 taskId/inboundId/loadingPlanId/containerBookingId 的 phase3-loading marker 隔离证据 fixture,未使用历史资源', 'fixture')
return
}
2026-08-15 09:49:09 +08:00
// AWAITING_REVIEW is a PDA workflow state, not evidence permission. Evidence is
// opened only after the server-side inbound aggregate has entered LOADING.
2026-08-15 10:35:54 +08:00
// The list endpoint only exposes tasks that are currently in the loading queue.
// Once PDA completion advances the task, use its stable task identity instead.
2026-08-15 09:49:09 +08:00
const taskResponse = await context.request('GET',
2026-08-15 10:35:54 +08:00
`/api/platform/v1/first-leg/pda-loading/tasks/${encodeURIComponent(taskId)}?warehouseId=${encodeURIComponent(warehouseId)}`)
2026-08-15 09:49:09 +08:00
const task = successful(taskResponse) ? taskResponse.body?.data : null
const taskItem = task?.items?.find?.((item) => id(item?.inboundId) === inboundId)
const taskIdentityValid = id(task?.id) === taskId
&& id(task?.loadingPlanId) === loadingPlanId
&& id(task?.containerBookingId) === containerBookingId
&& Boolean(taskItem) && id(taskItem?.inboundId) === inboundId
context.assert(MODULE, '证据前置任务身份使用本轮四元稳定关联', taskResponse,
taskIdentityValid,
`taskId=${id(task?.id) || 'missing'}, inboundId=${id(taskItem?.inboundId) || 'missing'}, loadingPlanId=${id(task?.loadingPlanId) || 'missing'}, containerBookingId=${id(task?.containerBookingId) || 'missing'}`, OPTIONS)
if (!taskIdentityValid) return
const inboundResponse = await context.request('GET',
`/api/platform/inbounds/${encodeURIComponent(inboundId)}?tenantId=${encodeURIComponent(tenantId)}`)
const inbound = successful(inboundResponse) ? inboundResponse.body?.data : null
2026-08-15 11:05:58 +08:00
const evidenceReadable = ['LOADING', 'LOADED'].includes(inbound?.summary?.processStatus)
context.assert(MODULE, '服务端入库进入 LOADING 或 LOADED 后开放装柜证据只读回查', inboundResponse,
evidenceReadable,
2026-08-15 09:49:09 +08:00
`processStatus=${inbound?.summary?.processStatus || 'missing'}, taskWorkflow=${task?.workflowStatus || 'missing'}`, OPTIONS)
2026-08-15 11:05:58 +08:00
if (!evidenceReadable) {
2026-08-24 19:58:35 +08:00
context.addStep(MODULE, '装柜证据读取前置必须由服务端进入 LOADING', inboundResponse, false,
`processStatus=${inbound?.summary?.processStatus || 'missing'};不能以 AWAITING_REVIEW 或 coverageGap 代替装柜阶段事实`, OPTIONS)
2026-08-15 09:49:09 +08:00
context.addCoverageGap(MODULE, OPTIONS.scenario, '服务端装柜阶段前不请求证据',
`taskId=${taskId}, inboundId=${inboundId}, loadingPlanId=${loadingPlanId}, containerBookingId=${containerBookingId}, processStatus=${inbound?.summary?.processStatus || 'missing'};AWAITING_REVIEW 不作为证据开放条件`, 'deployment-blocker')
return
}
const path = `/api/platform/v1/first-leg/pda-loading/tasks/${encodeURIComponent(taskId)}/evidence?warehouseId=${encodeURIComponent(warehouseId)}`
const response = await context.request('GET', path)
const data = context.apiSuccess(MODULE, '复核本轮 task 柜级装柜证据聚合', response,
`taskId=${taskId}, containerBookingId=${containerBookingId}`, OPTIONS)
const evidences = Array.isArray(data?.evidences) ? data.evidences : []
const required = ['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL']
const active = evidences.filter((item) => item.status !== 'VOID')
const voided = evidences.find((item) => id(item.evidenceId) === id(fixture.voidedEvidenceId))
const missingTypeVoided = evidences.find((item) => id(item.evidenceId) === id(fixture.missingTypeVoidedEvidenceId))
context.assert(MODULE, 'task 证据聚合归属稳定 PDA 任务和物理柜次', response,
id(data?.taskId) === taskId && id(data?.containerBookingId) === containerBookingId,
`taskId=${id(data?.taskId) || 'missing'}, containerBookingId=${id(data?.containerBookingId) || 'missing'}`, OPTIONS)
context.assert(MODULE, '五类有效证据齐全且同类型多图追加不覆盖历史', response,
required.every((type) => active.some((item) => item.evidenceType === type))
2026-08-12 10:59:39 +08:00
&& evidences.filter((item) => item.evidenceType === 'PHOTO_STEP_1').length >= 2
&& active.some((item) => item.evidenceType === 'SEAL' && item.sealNo === fixture.sealNo)
&& active.every((item) => String(item.fileName || '').includes(context.marker)
&& String(item.remark || '').includes(context.marker)),
`active=${active.length}, types=${[...new Set(active.map((item) => item.evidenceType))].join(',')}`, OPTIONS)
context.assert(MODULE, '作废证据保留稳定记录与更新时间且不计入有效门禁', response,
Boolean(voided) && voided.status === 'VOID' && Boolean(voided.updatedAt)
&& !active.some((item) => id(item.evidenceId) === id(fixture.voidedEvidenceId)),
`voidedEvidenceId=${id(fixture.voidedEvidenceId) || 'missing'}, status=${voided?.status || 'missing'}`, OPTIONS)
2026-08-12 12:35:29 +08:00
context.assert(MODULE, '缺类作废审计保留且补传证据恢复有效门禁', response,
Boolean(missingTypeVoided) && missingTypeVoided.status === 'VOID'
&& Boolean(missingTypeVoided.updatedAt)
2026-08-12 12:35:29 +08:00
&& active.some((item) => item.evidenceType === 'PHOTO_STEP_4'
&& id(item.fileId) === id(fixture.replacementEvidenceFileId)),
`voidedEvidenceId=${id(fixture.missingTypeVoidedEvidenceId) || 'missing'}, replacementFileId=${id(fixture.replacementEvidenceFileId) || 'missing'}`, OPTIONS)
}