98 lines
6.2 KiB
JavaScript
98 lines
6.2 KiB
JavaScript
import { readFile } from 'node:fs/promises'
|
|
import { resolve } from 'node:path'
|
|
|
|
const MODULE = 'manual-order-splitting-verify'
|
|
const OPTIONS = { scenario: 'manual-order-splitting-readonly-verification', deploymentBlocker: true }
|
|
const SMART = '/api/platform/v1/first-leg/smart-loading'
|
|
const CONTAINERS = '/api/platform/v1/containers'
|
|
|
|
function id(value) { return value == null ? '' : String(value) }
|
|
function rows(value) { return Array.isArray(value) ? value : [] }
|
|
|
|
async function loadManifest(context) {
|
|
const path = resolve(context.options.manualSplitFixtureReport)
|
|
const report = JSON.parse(await readFile(path, 'utf8'))
|
|
const manifest = report?.createdResources?.manualOrderSplittingFixture
|
|
const sourceGateway = String(report?.gateway || '').replace(/\/$/, '')
|
|
const sameGateway = sourceGateway && sourceGateway === context.options.gateway
|
|
context.assert(MODULE, '读取人工分柜准备报告', null,
|
|
manifest?.schemaVersion === 1 && manifest?.stage === 'WAIT_SPLIT_READY_FOR_MANUAL_ASSIGNMENT'
|
|
&& Array.isArray(manifest.assignments) && manifest.assignments.length === 3 && sameGateway,
|
|
`sourceMarker=${report?.marker || 'missing'}, schemaVersion=${manifest?.schemaVersion ?? 'missing'}, assignments=${manifest?.assignments?.length ?? 'missing'}, sameGateway=${Boolean(sameGateway)}`,
|
|
OPTIONS)
|
|
return sameGateway ? manifest : null
|
|
}
|
|
|
|
export async function run(context) {
|
|
const manifest = await loadManifest(context)
|
|
if (!manifest?.assignments?.length) return
|
|
|
|
const planId = id(context.options.manualSplitPlanId)
|
|
const planResponse = await context.request('GET', `${SMART}/plans/${encodeURIComponent(planId)}`)
|
|
const plan = context.apiSuccess(MODULE, '读取人工操作后的智能装柜计划', planResponse,
|
|
`loadingPlanId=${planId}`, OPTIONS)
|
|
if (!plan) return
|
|
|
|
const bookingId = id(plan.bookingId || plan.containerBookingId)
|
|
const planItems = rows(plan.items)
|
|
context.assert(MODULE, '智能装柜计划身份与状态可继续人工验收', planResponse,
|
|
id(plan.id) === planId && bookingId && ['EDITING', 'LOADING'].includes(plan.status),
|
|
`planId=${id(plan.id) || 'missing'}, bookingId=${bookingId || 'missing'}, status=${plan.status || 'missing'}, version=${plan.version ?? 'missing'}`,
|
|
OPTIONS)
|
|
if (!bookingId) return
|
|
|
|
const containerResponse = await context.request('GET', `${CONTAINERS}/domestic/${encodeURIComponent(bookingId)}`)
|
|
const container = context.apiSuccess(MODULE, '读取人工分柜关联柜次', containerResponse, '', OPTIONS)?.container
|
|
context.assert(MODULE, '柜次保持准备清单的仓库和渠道范围', containerResponse,
|
|
id(container?.id) === bookingId && id(container?.warehouseId) === id(manifest.warehouseId)
|
|
&& container?.channelCode === manifest.channelCode,
|
|
`containerId=${id(container?.id) || 'missing'}, warehouseId=${id(container?.warehouseId) || 'missing'}, channelCode=${container?.channelCode || 'missing'}`,
|
|
OPTIONS)
|
|
|
|
const expectedByInbound = new Map(manifest.assignments.map((item) => [id(item.inboundId), item]))
|
|
for (const expected of manifest.assignments) {
|
|
const matches = planItems.filter((item) => id(item.inboundId) === id(expected.inboundId))
|
|
const allocated = matches.reduce((sum, item) => sum + Number(item.packageCount || 0), 0)
|
|
context.assert(MODULE, `${expected.role} 按清单完成计划分配`, planResponse,
|
|
matches.length >= 1 && allocated === Number(expected.expectedAllocatedPackageCount),
|
|
`inboundCode=${expected.inboundCode || 'missing'}, expected=${expected.expectedAllocatedPackageCount}, actual=${allocated}, itemCount=${matches.length}`,
|
|
OPTIONS)
|
|
}
|
|
|
|
const foreignItems = planItems.filter((item) => !expectedByInbound.has(id(item.inboundId)))
|
|
const controlItems = planItems.filter((item) => id(item.inboundId) === id(manifest.unassignedControl?.inboundId))
|
|
context.assert(MODULE, '计划未混入清单外或未分配对照入库单', planResponse,
|
|
foreignItems.length === 0 && controlItems.length === 0,
|
|
`foreignInboundIds=${foreignItems.map((item) => id(item.inboundId)).join(',') || 'none'}, controlItemCount=${controlItems.length}`,
|
|
OPTIONS)
|
|
|
|
const candidatesPath = `${SMART}/inbound-candidates?bookingId=${encodeURIComponent(bookingId)}&warehouseId=${encodeURIComponent(manifest.warehouseId)}`
|
|
const candidatesResponse = await context.request('GET', candidatesPath)
|
|
const candidates = context.apiSuccess(MODULE, '回查人工分柜后的入仓码候选余量', candidatesResponse, '', OPTIONS)
|
|
const candidateRows = rows(candidates)
|
|
const candidateScopeValid = candidateRows.every((item) => (!item.channelCode
|
|
|| item.channelCode === manifest.channelCode)
|
|
&& (!item.warehouseId || id(item.warehouseId) === id(manifest.warehouseId)))
|
|
context.assert(MODULE, '候选列表未出现异渠道或异仓入库单', candidatesResponse,
|
|
candidateScopeValid,
|
|
`candidateCount=${candidateRows.length}, channelCode=${manifest.channelCode}, warehouseId=${manifest.warehouseId}`,
|
|
OPTIONS)
|
|
for (const expected of [...manifest.assignments, manifest.unassignedControl]) {
|
|
const candidate = candidateRows.find((item) => id(item.inboundId) === id(expected.inboundId))
|
|
const actualRemaining = candidate ? Number(candidate.availablePackageCount) : 0
|
|
context.assert(MODULE, `${expected.role} 分配后剩余箱数正确`, candidatesResponse,
|
|
actualRemaining === Number(expected.expectedRemainingPackageCount),
|
|
`inboundCode=${expected.inboundCode || 'missing'}, expectedRemaining=${expected.expectedRemainingPackageCount}, actualRemaining=${actualRemaining}`,
|
|
OPTIONS)
|
|
}
|
|
|
|
context.createdResources.manualOrderSplittingVerification = {
|
|
sourceMarker: manifest.marker, verifiedAt: new Date().toISOString(), loadingPlanId: planId,
|
|
containerBookingId: bookingId, tenantId: id(manifest.tenantId), warehouseId: id(manifest.warehouseId),
|
|
channelCode: manifest.channelCode, planStatus: plan.status, planVersion: Number(plan.version),
|
|
verifiedAssignments: manifest.assignments.map((item) => ({ role: item.role, orderId: id(item.orderId),
|
|
inboundId: id(item.inboundId), expectedAllocatedPackageCount: Number(item.expectedAllocatedPackageCount) })),
|
|
note: '本模块只读核验计划分柜;父子订单在 PDA 实装结算后另行核验。',
|
|
}
|
|
}
|