Files
baiyue-firstleg/scripts/admin-test-env-modules/inbound-measurement.mjs
T
2026-09-03 16:09:22 +08:00

787 lines
48 KiB
JavaScript

const MODULE = 'inbound-measurement'
const SCENARIO = 'mojia-carton-measurement'
const OPTIONS = { scenario: SCENARIO, deploymentBlocker: true }
const REUSABLE_PALLET_CODES = Object.freeze(['TP-001', 'YW-TP-002'])
const reusablePalletsByWarehouse = new Map()
const WEIGHT_IMAGE = 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])
function asId(value) { return value == null ? '' : String(value) }
function asNumber(value) { return Number(value) }
function closeTo(actual, expected, tolerance = 0.000001) {
return Number.isFinite(asNumber(actual)) && Math.abs(asNumber(actual) - expected) <= tolerance
}
function ceiling(value, scale) {
const factor = 10 ** scale
const floatingPointNoise = Number.EPSILON * Math.max(1, Math.abs(value)) * 4
return Math.ceil((value - floatingPointNoise) * factor) / factor
}
function halfUp(value, scale) {
const factor = 10 ** scale
return Math.round((value + Number.EPSILON) * factor) / factor
}
function query(tenantId) { return `tenantId=${encodeURIComponent(tenantId)}` }
function validPdf(response) {
return response.http === 200 && response.contentType.toLowerCase().includes('pdf')
&& response.bytes?.length > 4 && new TextDecoder().decode(response.bytes.slice(0, 4)) === '%PDF'
}
async function reusablePallets(context, warehouseId) {
const cacheKey = asId(warehouseId)
const override = context.context?.measurementPalletOverride
if (asId(override?.warehouseId) === cacheKey && asId(override?.id)
&& String(override?.code || '').trim()) {
context.assert(MODULE, '使用编排层提供的本轮专用称重托盘', null, true,
`warehouseId=${warehouseId}, palletId=${asId(override.id)}`, OPTIONS)
return [{ id: asId(override.id), code: String(override.code) }]
}
const allowAnyExistingPallet = context.context?.allowAnyExistingPallet === true
const scopedCacheKey = `${cacheKey}:${allowAnyExistingPallet ? 'any' : 'fixed'}`
if (reusablePalletsByWarehouse.has(scopedCacheKey)) return reusablePalletsByWarehouse.get(scopedCacheKey)
const pallets = []
if (allowAnyExistingPallet) {
const response = await context.request('GET',
`/api/platform/v1/pallets?page=1&size=100&tenantId=0&warehouseId=${encodeURIComponent(warehouseId)}`)
const page = context.apiSuccess(MODULE, '查询当前仓库已有可复用托盘', response,
`warehouseId=${warehouseId}`, OPTIONS)
const candidates = (page?.records || []).filter((item) => asId(item?.warehouseId) === cacheKey
&& asId(item?.id) && String(item?.palletCode || '').trim())
context.assert(MODULE, '当前仓库至少存在一个可复用托盘', response, candidates.length > 0,
`warehouseId=${warehouseId}, matched=${candidates.map((item) => item.palletCode).join('|') || 'missing'}`, OPTIONS)
if (candidates.length === 0) return null
const selected = candidates.slice(0, 2).map((item) => ({ id: asId(item.id), code: String(item.palletCode) }))
reusablePalletsByWarehouse.set(scopedCacheKey, selected)
return selected
}
for (const palletCode of REUSABLE_PALLET_CODES) {
const response = await context.request('GET',
`/api/platform/v1/pallets?page=1&size=20&tenantId=0&warehouseId=${encodeURIComponent(warehouseId)}&palletCode=${encodeURIComponent(palletCode)}`)
const page = context.apiSuccess(MODULE, `查询原有可复用托盘 ${palletCode}`, response,
`warehouseId=${warehouseId}`, OPTIONS)
const exact = (page?.records || []).find((item) => asId(item?.warehouseId) === cacheKey
&& String(item?.palletCode || '') === palletCode && asId(item?.id))
context.assert(MODULE, `查询原有称重托盘 ${palletCode}`, response, true,
`warehouseId=${warehouseId}, matched=${exact?.palletCode || 'missing'}, fallback=${exact ? 'not-needed' : 'create-marker-owned'}`, OPTIONS)
if (!exact) {
// A newly selected development warehouse may not have the historical fixture pallets.
// Create one marker-owned pallet through the public platform API rather than coupling
// measurement acceptance to another warehouse's seed data.
const createResponse = await context.request('POST', '/api/platform/v1/pallets?tenantId=0', {
warehouseId, palletType: 'STANDARD', capacityCbm: 1.5, weightKg: 25,
color: 'ORANGE', remark: `${context.marker}-measurement-fixture`,
})
const createdId = asId(context.apiSuccess(MODULE, '创建本轮称重托盘', createResponse,
`warehouseId=${warehouseId}`, OPTIONS))
if (!createdId) return null
const detailResponse = await context.request('GET',
`/api/platform/v1/pallets/${encodeURIComponent(createdId)}?tenantId=0&warehouseId=${encodeURIComponent(warehouseId)}`)
const created = context.apiSuccess(MODULE, '回读本轮称重托盘', detailResponse,
`warehouseId=${warehouseId}, palletId=${createdId}`, OPTIONS)
const valid = asId(created?.id) === createdId && asId(created?.warehouseId) === cacheKey
&& String(created?.palletCode || '').trim()
context.assert(MODULE, '新建称重托盘归属当前仓库且编码完整', detailResponse, valid,
`warehouseId=${warehouseId}, palletId=${createdId}, palletCode=${created?.palletCode || 'missing'}`, OPTIONS)
if (!valid) return null
pallets.push({ id: createdId, code: String(created.palletCode) })
break
}
pallets.push({ id: asId(exact.id), code: palletCode })
}
reusablePalletsByWarehouse.set(scopedCacheKey, pallets)
return pallets
}
function randomlyReusePalletIds(pallets, palletCount, randomValue) {
const seed = Number.isInteger(Number(randomValue)) ? Number(randomValue) : Date.now()
return Array.from({ length: palletCount }, (_, index) =>
pallets[(seed >>> (index % 31)) % pallets.length].id)
}
async function waitForCompletedMeasurement(context, measurementPath, expectedStatuses = ['WAIT_SPLIT'], attempts = 30) {
let response = null
let measurement = null
for (let attempt = 1; attempt <= attempts; attempt += 1) {
response = await context.request('GET', measurementPath)
measurement = response?.http === 200 && String(response?.body?.code) === '200'
? response.body.data : null
if (expectedStatuses.includes(measurement?.processStatus)) return { response, measurement, attempts: attempt }
if (attempt < attempts) await new Promise((resolve) => setTimeout(resolve, 1_000))
}
return { response, measurement, attempts }
}
async function completeDropshipPalletization(context, { inboundId, inboundCode, warehouseId, measurement }) {
const pendingResponse = await context.request('GET',
`/api/platform/v1/first-leg/pda-dropship/tasks/pending?keyword=${encodeURIComponent(inboundCode)}`)
const pending = context.apiSuccess(MODULE, '查询一件代发待合托任务', pendingResponse,
`inboundId=${inboundId}`, OPTIONS)
const task = (pending || []).find((item) => asId(item?.inboundId) === inboundId
&& asId(item?.warehouseId) === warehouseId)
context.assert(MODULE, '测量审核后生成当前入仓单唯一合托任务', pendingResponse,
Boolean(asId(task?.taskId)) && Number(task?.totalCartons) === Number(measurement?.actualPackageCount),
`taskId=${asId(task?.taskId) || 'missing'}, totalCartons=${task?.totalCartons ?? 'missing'}`, OPTIONS)
if (!task?.taskId) return null
const taskId = asId(task.taskId)
const claimResponse = await context.request('POST',
`/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/claim`, { warehouseId })
const claimed = context.apiSuccess(MODULE, '领取一件代发合托任务', claimResponse,
`taskId=${taskId}`, OPTIONS)
context.assert(MODULE, '合托任务绑定当前操作员且进入CLAIMED', claimResponse,
claimed?.taskStatus === 'CLAIMED' && asId(claimed?.inboundId) === inboundId,
`taskStatus=${claimed?.taskStatus ?? 'missing'}`, OPTIONS)
const openResponse = await context.request('POST',
`/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/pallets/open`, {
warehouseId, operationId: `${context.marker}-dropship-open`.slice(0, 128),
})
let pallet = context.apiSuccess(MODULE, '开启一件代发工作托盘', openResponse,
`taskId=${taskId}`, OPTIONS)
const palletId = asId(pallet?.palletId)
if (!palletId) return null
const totalCartons = Number(measurement?.actualPackageCount)
let scannedCartons = 0
for (let index = 0; index < totalCartons; index += 1) {
const boxCode = `${inboundCode}#${totalCartons}-${index + 1}`
const scanResponse = await context.request('POST',
`/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/pallets/${palletId}/scan`, {
warehouseId, boxCode, cartonCount: 1,
operationId: `${context.marker}-dropship-scan-${index + 1}`.slice(0, 128),
})
const scanned = context.apiSuccess(MODULE, `合托扫描第${index + 1}箱冻结箱唛`, scanResponse,
`boxCode=${boxCode}`, OPTIONS)
context.assert(MODULE, `第${index + 1}箱进入工作托盘`, scanResponse,
Number(scanned?.addedCartons) === 1 && asId(scanned?.pallet?.palletId) === palletId,
`added=${scanned?.addedCartons ?? 'missing'}, remaining=${scanned?.remainingCartons ?? 'missing'}`, OPTIONS)
pallet = scanned?.pallet || pallet
if (Number(scanned?.addedCartons) === 1) scannedCartons += 1
}
context.assert(MODULE, '一件代发工作托盘覆盖测量冻结的全部箱数', null,
totalCartons > 0 && scannedCartons === totalCartons,
`scanned=${scannedCartons}, expected=${totalCartons || 'missing'}`, OPTIONS)
const sealResponse = await context.request('POST',
`/api/platform/v1/first-leg/pda-dropship/tasks/${taskId}/pallets/${palletId}/seal`, {
warehouseId, expectedVersion: Number(pallet?.version),
operationId: `${context.marker}-dropship-seal`.slice(0, 128),
})
const sealed = context.apiSuccess(MODULE, '封托并完成一件代发合托任务', sealResponse,
`taskId=${taskId}, palletId=${palletId}`, OPTIONS)
context.assert(MODULE, '全部箱数封托后入仓单允许进入WAIT_SPLIT', sealResponse,
sealed?.taskStatus === 'COMPLETED'
&& Number(sealed?.allocatedCartons) === Number(sealed?.totalCartons)
&& sealed?.pallets?.every((item) => item?.palletStatus === 'COMPLETED'),
`taskStatus=${sealed?.taskStatus ?? 'missing'}, allocated=${sealed?.allocatedCartons ?? 'missing'}`, OPTIONS)
return sealed
}
async function provisionWarehouseResources(context, { inboundId, tenantId, warehouseId, palletCount }) {
if (!warehouseId || !Number.isInteger(palletCount) || palletCount < 1) return null
const resources = []
const candidates = await reusablePallets(context, warehouseId)
if (!candidates) return null
const palletIds = randomlyReusePalletIds(candidates, palletCount,
context.context?.waitSplitVariant?.randomValue)
const candidateKeyword = candidates.find((item) => item.id === palletIds[0])?.code || ''
const candidateResponse = await context.request('GET',
`/api/platform/v1/first-leg/inbounds/${inboundId}/weight-pallet-options?${query(tenantId)}&page=1&size=20&keyword=${encodeURIComponent(candidateKeyword)}`)
const candidatePage = context.apiSuccess(MODULE, '按关键词分页查询当前入库仓称重托盘候选', candidateResponse,
`warehouseId=${warehouseId}, keyword=${candidateKeyword}`, OPTIONS)
const pageControlled = Array.isArray(candidatePage?.records) && candidatePage.records.length <= 20
&& Number(candidatePage?.page) === 1 && Number(candidatePage?.size) === 20
&& Number(candidatePage?.total) >= candidatePage.records.length
&& candidatePage.records.some((item) => asId(item?.palletId) === palletIds[0])
context.assert(MODULE, '托盘候选保持服务端关键词分页且不返回无界数组', candidateResponse,
pageControlled,
`records=${candidatePage?.records?.length ?? 'missing'}, page=${candidatePage?.page ?? 'missing'}, size=${candidatePage?.size ?? 'missing'}, total=${candidatePage?.total ?? 'missing'}`,
OPTIONS)
if (!pageControlled) return null
for (let index = 1; index <= palletCount; index += 1) {
const palletId = palletIds[index - 1]
context.assert(MODULE, `随机复用原有称重托盘 ${index}/${palletCount}`, null,
Boolean(candidates.find((item) => item.id === palletId)?.code),
`warehouseId=${warehouseId}, palletId=${palletId}`, OPTIONS)
// 库位由 Warehouse 在测量审核时按正式规则分配;普通位不足会复用唯一的 A0 共享溢出位。
// 验收脚本不得为了制造容量而新增普通库位。
resources.push({ palletId, locationId: null })
}
context.createdResources.inboundWarehouseResources ??= {}
context.createdResources.inboundWarehouseResources[inboundId] = {
marker: context.marker, tenantId: '0', warehouseId: asId(warehouseId), palletCount,
reusablePalletCodes: candidates.map((item) => item.code), resources,
}
return { palletId: palletIds[0], palletIds, palletCount, resources }
}
async function createConfirmedWeightEvidence(context, {
inboundId, tenantId, palletIds, grossWeightPerPalletKg,
}) {
if (!Array.isArray(palletIds) || palletIds.length < 1) return null
const confirmedResults = []
for (let index = 0; index < palletIds.length; index += 1) {
const palletId = palletIds[index]
const uploadResponse = await context.requestMultipart('POST',
`/api/platform/v1/files/measurements/${tenantId}/${inboundId}`, {
content: WEIGHT_IMAGE, type: 'image/png', name: `weight-${inboundId}-${index + 1}.png`,
})
const upload = context.apiSuccess(MODULE, `上传称重图片并执行 OCR ${index + 1}/${palletIds.length}`,
uploadResponse, `inboundId=${inboundId}`, OPTIONS)
const fileId = asId(upload?.fileId)
if (!fileId) return null
const createResponse = await context.request('POST',
`/api/platform/v1/first-leg/inbounds/${inboundId}/weight-results?${query(tenantId)}`, {
fileId, palletId, ocrProvider: 'ADMIN_ACCEPTANCE', ocrRawText: null,
ocrWeightValue: null, ocrWeightUnit: null, confidence: null,
})
const created = context.apiSuccess(MODULE, `登记称重图片与托盘快照 ${index + 1}/${palletIds.length}`,
createResponse, `fileId=${fileId}, palletId=${palletId}`, OPTIONS)
if (!created?.id) return null
context.assert(MODULE, `OCR无有效重量时转人工确认 ${index + 1}/${palletIds.length}`, createResponse,
created.recognitionStatus === 'NEEDS_MANUAL' && created.ocrWeightValue == null,
`recognitionStatus=${created.recognitionStatus || 'missing'}, ocrWeightValue=${created.ocrWeightValue ?? 'null'}`,
OPTIONS)
const grossWeight = Number(grossWeightPerPalletKg) + index * 5
const confirmResponse = await context.request('POST',
`/api/platform/v1/first-leg/inbounds/${inboundId}/weight-results/${created.id}/confirm?${query(tenantId)}`, {
version: Number(created.version), adjustedWeightValue: grossWeight,
adjustedWeightUnit: 'KG', adjustmentReason: `${context.marker} 自动化人工确认称重`,
})
const confirmed = context.apiSuccess(MODULE, `人工确认称重图片净重 ${index + 1}/${palletIds.length}`,
confirmResponse, `resultId=${created.id}, palletId=${palletId}`, OPTIONS)
const netWeightKg = Number(confirmed?.netWeightKg)
const grossWeightKg = Number(confirmed?.grossWeightKg)
if (confirmed?.recognitionStatus !== 'CONFIRMED' || !Number.isFinite(netWeightKg) || netWeightKg <= 0) {
return null
}
const staleConfirmResponse = await context.request('POST',
`/api/platform/v1/first-leg/inbounds/${inboundId}/weight-results/${created.id}/confirm?${query(tenantId)}`, {
version: Number(created.version), adjustedWeightValue: grossWeight + 1,
adjustedWeightUnit: 'KG', adjustmentReason: `${context.marker} 旧版本冲突验证`,
})
context.assert(MODULE, `称重旧版本确认被拒绝且不覆盖已确认结果 ${index + 1}/${palletIds.length}`,
staleConfirmResponse,
staleConfirmResponse.http === 200 && String(staleConfirmResponse.body?.code) === 'A0409',
`HTTP=${staleConfirmResponse.http}, code=${staleConfirmResponse.body?.code ?? 'missing'}, requestId=${staleConfirmResponse.requestId || 'missing'}`,
OPTIONS)
confirmedResults.push({ id: asId(created.id), palletId, netWeightKg, grossWeightKg })
}
const workspaceResponse = await context.request('GET',
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement?${query(tenantId)}`)
const workspace = context.apiSuccess(MODULE, '回查已确认称重图片和货物总重量', workspaceResponse,
`resultIds=${confirmedResults.map((result) => result.id).join(',')}`, OPTIONS)
const confirmedRows = (workspace?.weightResults || [])
.filter((result) => result?.recognitionStatus === 'CONFIRMED')
const grossWeightKg = confirmedResults.reduce((sum, result) => sum + result.grossWeightKg, 0)
const netWeightKg = confirmedResults.reduce((sum, result) => sum + result.netWeightKg, 0)
const persisted = confirmedRows.length === palletIds.length
&& confirmedResults.every((expected) => confirmedRows.some((row) =>
asId(row?.id) === expected.id && asId(row?.palletId) === expected.palletId
&& String(row?.palletCode || '').trim() && String(row?.palletType || '').trim()
&& Number.isFinite(Number(row?.palletTareWeightKg))))
&& closeTo(workspace?.actualGrossWeightKg, grossWeightKg, 0.001)
&& closeTo(workspace?.actualNetWeightKg, netWeightKg, 0.001)
context.assert(MODULE, '图片张数决定托数且净重汇总已落地', workspaceResponse, persisted,
`confirmedImages=${confirmedRows.length}, expectedPallets=${palletIds.length}, grossWeight=${grossWeightKg}, netWeight=${netWeightKg}, actualGross=${workspace?.actualGrossWeightKg ?? 'missing'}, actualNet=${workspace?.actualNetWeightKg ?? 'missing'}`,
OPTIONS)
return persisted ? { count: confirmedRows.length, grossWeightKg, netWeightKg } : null
}
function findIsolatedInbound(context) {
const roots = [context.createdResources, context.context]
const visited = new Set()
const candidates = []
function visit(value, inheritedMarker = '') {
if (!value || typeof value !== 'object' || visited.has(value)) return
visited.add(value)
const marker = String(value.marker || value.testMarker || inheritedMarker || '')
const inboundId = asId(value.inboundId || value.inbound?.id || value.inbound?.inboundId)
const tenantId = asId(value.tenantId || value.customerId || value.inbound?.tenantId)
if (marker === context.marker && inboundId && tenantId) {
candidates.push({ inboundId, tenantId, marker, source: value })
}
for (const [key, child] of Object.entries(value)) {
if (key === 'temporaryPassword' || key === 'token') continue
if (child && typeof child === 'object') visit(child, marker)
}
}
roots.forEach((root) => visit(root))
return candidates[0] || null
}
async function multipartRequest(context, path, data) {
const form = new FormData()
form.append('data', new Blob([JSON.stringify(data)], { type: 'application/json' }))
form.append('image', new Blob([WEIGHT_IMAGE], { type: 'image/png' }), 'carton-evidence.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 content = await response.text()
let body = null
try { body = content ? JSON.parse(content) : null } catch { /* Preserve only a bounded error string. */ }
return {
method: 'POST', path, http: response.status, body,
raw: body === null ? content.slice(0, 500) : null,
requestId: body?.requestId || response.headers.get('x-request-id'),
traceId: body?.traceId || response.headers.get('x-trace-id'),
}
} catch (error) {
return { method: 'POST', path, http: 0, body: null, raw: error instanceof Error ? error.message : String(error), requestId: null, traceId: null }
}
}
async function requestWithHeaders(context, method, path, body, extraHeaders) {
try {
const response = await fetch(`${context.options.gateway}${path}`, {
method,
headers: {
Accept: 'application/json', Authorization: `Bearer ${context.token}`,
'Content-Type': 'application/json; charset=utf-8', ...extraHeaders,
},
body: JSON.stringify(body), signal: AbortSignal.timeout(30_000),
})
const content = await response.text()
let payload = null
try { payload = content ? JSON.parse(content) : null } catch { /* Preserve only a bounded error string. */ }
return {
method, path, http: response.status, body: payload,
raw: payload === null ? content.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: error instanceof Error ? error.message : String(error), requestId: null, traceId: null }
}
}
function finishScenario(context, start) {
const steps = context.steps.slice(start)
context.scenarios.push({
name: SCENARIO, module: MODULE, success: steps.every((step) => step.passed),
deploymentBlocked: steps.some((step) => step.deploymentBlocker),
passed: steps.filter((step) => step.passed).length,
failed: steps.filter((step) => !step.passed).length,
})
}
export async function run(context) {
const start = context.steps.length
const isolated = findIsolatedInbound(context)
if (!isolated) {
context.addStep(MODULE, '取得隔离的待测量入库单', null, false,
`orders 模块未提供 marker=${context.marker} 的 inboundId/tenantId;为避免修改随机历史订单,本模块停止写入`, OPTIONS)
finishScenario(context, start)
return
}
const { inboundId, tenantId } = isolated
const detailPath = `/api/platform/inbounds/${inboundId}?${query(tenantId)}`
const inboundResponse = await context.request('GET', detailPath)
const inboundDetail = context.apiSuccess(MODULE, '查询隔离入库单详情', inboundResponse,
`inboundId=${inboundId}, tenantId=${tenantId}`, OPTIONS)
const summary = inboundDetail?.summary
const provenanceOk = asId(summary?.id) === inboundId && asId(summary?.tenantId) === tenantId
&& ['WAIT_MEASURE', 'MEASURING'].includes(summary?.processStatus)
context.assert(MODULE, '隔离入库单身份与可测量状态正确', inboundResponse, provenanceOk,
`identity=${asId(summary?.id) === inboundId && asId(summary?.tenantId) === tenantId}, status=${summary?.processStatus ?? 'missing'}`, OPTIONS)
if (!provenanceOk) {
finishScenario(context, start)
return
}
const inboundCode = String(summary.inboundCode || '')
if (!inboundCode) {
context.assert(MODULE, '箱码生成前置数据完整', inboundResponse, false,
'inboundCode=missing', OPTIONS)
finishScenario(context, start)
return
}
const labelPath = `/api/platform/inbounds/${inboundId}/carton-label-pdf?${query(tenantId)}`
const labelResponse = await context.requestBinary('POST', labelPath)
const pdfOk = validPdf(labelResponse)
context.assert(MODULE, '生成箱唛 PDF 并冻结稳定箱码', labelResponse, pdfOk,
`contentType=${labelResponse.contentType || 'missing'}, bytes=${labelResponse.bytes?.length ?? 0}`, OPTIONS)
if (!pdfOk) {
finishScenario(context, start)
return
}
const frozenInboundResponse = await context.request('GET', detailPath)
const frozenInbound = context.apiSuccess(MODULE, '首次打印后回查冻结箱数', frozenInboundResponse, '', OPTIONS)
const frozenSummary = frozenInbound?.summary
const originalPackageCount = Number(frozenSummary?.totalPackageCount)
context.assert(MODULE, '首次打印按订单货物冻结原始总箱数', frozenInboundResponse,
frozenSummary?.inboundCode === inboundCode && Number.isInteger(originalPackageCount) && originalPackageCount >= 1,
`inboundCodeStable=${frozenSummary?.inboundCode === inboundCode}, totalPackageCount=${frozenSummary?.totalPackageCount ?? 'missing'}`, OPTIONS)
if (!Number.isInteger(originalPackageCount) || originalPackageCount < 1) {
finishScenario(context, start)
return
}
const boxCode = `${inboundCode}#${originalPackageCount}-1`
const reprintResponse = await context.requestBinary('POST', labelPath)
context.assert(MODULE, '重复整单打印复用已冻结箱码映射', reprintResponse, validPdf(reprintResponse),
`boxCode=${boxCode}, contentType=${reprintResponse.contentType || 'missing'}, bytes=${reprintResponse.bytes?.length ?? 0}`, OPTIONS)
const partialPrintPath = `${labelPath}&cartonIndex=1`
const partialPrintResponse = await context.requestBinary('POST', partialPrintPath)
context.assert(MODULE, '指定第1箱补打复用已冻结箱码映射', partialPrintResponse, validPdf(partialPrintResponse),
`boxCode=${boxCode}, cartonIndex=1, contentType=${partialPrintResponse.contentType || 'missing'}, bytes=${partialPrintResponse.bytes?.length ?? 0}`, OPTIONS)
if (!validPdf(reprintResponse) || !validPdf(partialPrintResponse)) {
finishScenario(context, start)
return
}
const scanPath = `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/carton-scans?${query(tenantId)}`
const measurementProfile = context.context?.waitSplitVariant?.measurementProfile
const firstDimensions = measurementProfile?.first || { length: 59.1, width: 40.2, height: 30.3 }
const firstShipmentId = `FBA-${context.marker.slice(-12)}-01`
const firstScanResponse = await multipartRequest(context, scanPath, {
boxCode, ...firstDimensions, scannedShipmentId: firstShipmentId,
machineNo: 'MJ-ADMIN-E2E-01', measuredAt: new Date().toISOString().slice(0, 19),
})
const firstScan = context.apiSuccess(MODULE, '模拟墨家首次扫描单箱', firstScanResponse,
`boxCode=${boxCode}, machineNo=MJ-ADMIN-E2E-01`, OPTIONS)
context.assert(MODULE, '首次扫描新增一条且默认代表1箱', firstScanResponse,
firstScan?.created === true && asId(firstScan?.boxCode) === boxCode
&& Number(firstScan?.confirmedPackageCount) === 1 && asId(firstScan?.itemId),
`created=${firstScan?.created}, confirmedPackageCount=${firstScan?.confirmedPackageCount ?? 'missing'}`, OPTIONS)
context.assert(MODULE, '重复打印及指定箱补打后原箱码仍可解析', firstScanResponse,
firstScan?.created === true && firstScan?.boxCode === boxCode,
`boxCodeStable=${firstScan?.boxCode === boxCode}, resolvedInboundId=${inboundId}`, OPTIONS)
if (!firstScan?.itemId || !firstScan?.measurementId) {
finishScenario(context, start)
return
}
const measurementPath = `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement?${query(tenantId)}`
const firstQueryResponse = await context.request('GET', measurementPath)
const firstQuery = context.apiSuccess(MODULE, '查询箱级测量详情', firstQueryResponse,
`measurementId=${asId(firstScan.measurementId)}`, OPTIONS)
const firstItem = firstQuery?.items?.find((item) => asId(item.id) === asId(firstScan.itemId))
context.assert(MODULE, '箱级查询回显箱码、尺寸、图片与设备信息', firstQueryResponse,
firstItem?.boxCode === boxCode && asId(firstItem?.intelligentImageFileId)
&& firstItem?.machineNo === 'MJ-ADMIN-E2E-01'
&& firstItem?.scannedShipmentId === firstShipmentId
&& closeTo(firstItem?.lengthCm, firstDimensions.length)
&& closeTo(firstItem?.widthCm, firstDimensions.width)
&& closeTo(firstItem?.heightCm, firstDimensions.height),
`itemFound=${Boolean(firstItem)}, imageFileId=${asId(firstItem?.intelligentImageFileId) || 'missing'}`, OPTIONS)
const countResponse = await context.request('PUT',
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/items/${firstScan.itemId}/confirmed-package-count?${query(tenantId)}`,
{ measurementVersion: Number(firstScan.measurementVersion), confirmedPackageCount: originalPackageCount })
const countResult = context.apiSuccess(MODULE, '人工确认当前测量代表箱数', countResponse,
`confirmedPackageCount=${originalPackageCount}`, OPTIONS)
context.assert(MODULE, '人工箱数更新且测量版本递增', countResponse,
Number(countResult?.confirmedPackageCount) === originalPackageCount
&& Number(countResult?.measurementVersion) === Number(firstScan.measurementVersion) + 1,
`version=${countResult?.measurementVersion ?? 'missing'}`, OPTIONS)
const replacement = measurementProfile?.replacement || { length: 59.5, width: 41.2, height: 31.3 }
const replacementShipmentId = `FBA-${context.marker.slice(-12)}-02`
const rescanResponse = await multipartRequest(context, scanPath, {
boxCode, ...replacement, scannedShipmentId: replacementShipmentId,
machineNo: 'MJ-ADMIN-E2E-02', measuredAt: new Date().toISOString().slice(0, 19),
})
const rescan = context.apiSuccess(MODULE, '同箱号重新扫描覆盖当前记录', rescanResponse,
`boxCode=${boxCode}, machineNo=MJ-ADMIN-E2E-02`, OPTIONS)
context.assert(MODULE, '覆盖保留同一条记录和人工箱数', rescanResponse,
rescan?.created === false && asId(rescan?.itemId) === asId(firstScan.itemId)
&& Number(rescan?.confirmedPackageCount) === originalPackageCount
&& closeTo(rescan?.lengthCm, replacement.length),
`sameItem=${asId(rescan?.itemId) === asId(firstScan.itemId)}, confirmedPackageCount=${rescan?.confirmedPackageCount ?? 'missing'}`, OPTIONS)
let measuredResponse = await context.request('GET', measurementPath)
let measured = context.apiSuccess(MODULE, '回查覆盖后的尺寸与体积结果', measuredResponse, '', OPTIONS)
let measuredItem = measured?.items?.find((item) => asId(item.id) === asId(firstScan.itemId))
const deviceOuterByType = {
SMALL: { length: 32, width: 22, height: 22 },
MEDIUM: { length: 42, width: 32, height: 32 },
LARGE: { length: 52, width: 42, height: 42 },
EXTRA_LARGE: { length: 62, width: 52, height: 52 },
}
const deviceOuter = deviceOuterByType[measurementProfile?.code] || deviceOuterByType.EXTRA_LARGE
const deviceUnitVolume = halfUp(
deviceOuter.length * deviceOuter.width * deviceOuter.height / 1_000_000, 3)
const deviceTotalVolume = deviceUnitVolume * originalPackageCount
const deviceVolumeWeight = ceiling(deviceTotalVolume * 167, 3)
const deviceGirth = ceiling(replacement.length + 2 * (replacement.width + replacement.height), 3)
context.assert(MODULE, '覆盖尺寸、箱数和服务端体积公式正确', measuredResponse,
measuredItem && closeTo(measuredItem.lengthCm, replacement.length)
&& closeTo(measuredItem.widthCm, replacement.width) && closeTo(measuredItem.heightCm, replacement.height)
&& measuredItem.scannedShipmentId === replacementShipmentId
&& Number(measuredItem.confirmedPackageCount) === originalPackageCount
&& measuredItem.outerLengthCm == null && measuredItem.outerWidthCm == null
&& measuredItem.outerHeightCm == null
&& closeTo(measuredItem.unitVolumeCbm, deviceUnitVolume)
&& closeTo(measuredItem.totalVolumeCbm, deviceTotalVolume)
&& closeTo(measuredItem.volumeWeightKg, deviceVolumeWeight) && closeTo(measuredItem.girthCm, deviceGirth)
&& closeTo(measured.actualVolumeCbm, deviceTotalVolume)
&& closeTo(measured.volumeWeightKg, deviceVolumeWeight),
`expectedOuter=${deviceOuter.length}x${deviceOuter.width}x${deviceOuter.height}, actualOuter=${measuredItem?.outerLengthCm ?? 'missing'}x${measuredItem?.outerWidthCm ?? 'missing'}x${measuredItem?.outerHeightCm ?? 'missing'}, expectedUnitVolume=${deviceUnitVolume}, actualUnitVolume=${measuredItem?.unitVolumeCbm ?? 'missing'}, expectedTotalVolume=${deviceTotalVolume}, actualTotalVolume=${measuredItem?.totalVolumeCbm ?? 'missing'}, expectedVolumeWeight=${deviceVolumeWeight}, actualVolumeWeight=${measuredItem?.volumeWeightKg ?? 'missing'}, expectedGirth=${deviceGirth}, actualGirth=${measuredItem?.girthCm ?? 'missing'}`, OPTIONS)
const dimensionAddition = 2
const weightedOuter = {
length: replacement.length + dimensionAddition,
width: replacement.width + dimensionAddition,
height: replacement.height + dimensionAddition,
}
const unitVolume = halfUp(
weightedOuter.length * weightedOuter.width * weightedOuter.height / 1_000_000, 3)
const totalVolume = unitVolume * originalPackageCount
const volumeWeight = ceiling(totalVolume * 167, 3)
const girth = ceiling(weightedOuter.length + 2 * (weightedOuter.width + weightedOuter.height), 3)
const incrementalDetailCount = Number(context.context?.phase3LoadingIncrementalDetailCount || 1)
if (incrementalDetailCount > 1) {
const packagesPerDetail = Number(context.context?.phase3LoadingIncrementalPackageCount || 1)
const configuredPackageCounts = context.context?.waitSplitVariant?.packageCounts
const detailPackageCounts = Array.isArray(configuredPackageCounts)
&& configuredPackageCounts.length === incrementalDetailCount
? configuredPackageCounts.map(Number)
: Array.from({ length: incrementalDetailCount }, () => packagesPerDetail)
const expectedPackages = detailPackageCounts.reduce((sum, count) => sum + count, 0)
context.assert(MODULE, '动态增量多明细箱数前置与冻结总数一致', measuredResponse,
expectedPackages === originalPackageCount,
`detailCount=${incrementalDetailCount}, packageCounts=${detailPackageCounts.join('+')}, frozen=${originalPackageCount}`, OPTIONS)
if (expectedPackages === originalPackageCount) {
const firstDetailPackageCount = detailPackageCounts[0]
const resetFirstCountResponse = await context.request('PUT',
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/items/${firstScan.itemId}/confirmed-package-count?${query(tenantId)}`,
{ measurementVersion: Number(measured?.version), confirmedPackageCount: firstDetailPackageCount })
const resetFirstCount = context.apiSuccess(MODULE,
'增量 fixture 将首条货物确认箱数恢复为自身箱数', resetFirstCountResponse,
`itemId=${asId(firstScan.itemId)}, confirmedPackageCount=${firstDetailPackageCount}`, OPTIONS)
context.assert(MODULE, '增量首条货物箱数和版本更新正确', resetFirstCountResponse,
Number(resetFirstCount?.confirmedPackageCount) === firstDetailPackageCount
&& Number(resetFirstCount?.measurementVersion) === Number(measured?.version) + 1,
`version=${resetFirstCount?.measurementVersion ?? 'missing'}`, OPTIONS)
for (let detailIndex = 1; detailIndex < incrementalDetailCount; detailIndex += 1) {
const detailPackageCount = detailPackageCounts[detailIndex]
const cartonIndex = detailPackageCounts.slice(0, detailIndex).reduce((sum, count) => sum + count, 0) + 1
const detailBoxCode = `${inboundCode}#${originalPackageCount}-${cartonIndex}`
const scanResponse = await multipartRequest(context, scanPath, {
boxCode: detailBoxCode, ...replacement,
scannedShipmentId: `FBA-${context.marker.slice(-12)}-${String(detailIndex + 2).padStart(2, '0')}`,
machineNo: `MJ-ADMIN-E2E-INC-${detailIndex + 1}`,
measuredAt: new Date().toISOString().slice(0, 19),
})
const scanned = context.apiSuccess(MODULE,
`增量 fixture 扫描第${detailIndex + 1}条独立货物箱码`, scanResponse,
`boxCode=${detailBoxCode}`, OPTIONS)
context.assert(MODULE, `增量第${detailIndex + 1}条扫描解析独立货物`, scanResponse,
scanned?.created === true && asId(scanned?.itemId)
&& asId(scanned?.itemId) !== asId(firstScan.itemId),
`itemId=${asId(scanned?.itemId) || 'missing'}, inboundDetailId=${asId(scanned?.inboundDetailId) || 'pending-query'}`, OPTIONS)
if (!scanned?.itemId) continue
const countDetailResponse = await context.request('PUT',
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/items/${scanned.itemId}/confirmed-package-count?${query(tenantId)}`,
{ measurementVersion: Number(scanned.measurementVersion), confirmedPackageCount: detailPackageCount })
const countDetail = context.apiSuccess(MODULE,
`增量第${detailIndex + 1}条货物确认自身箱数`, countDetailResponse,
`confirmedPackageCount=${detailPackageCount}`, OPTIONS)
context.assert(MODULE, `增量第${detailIndex + 1}条货物箱数更新正确`, countDetailResponse,
Number(countDetail?.confirmedPackageCount) === detailPackageCount,
`measurementVersion=${countDetail?.measurementVersion ?? 'missing'}`, OPTIONS)
}
measuredResponse = await context.request('GET', measurementPath)
measured = context.apiSuccess(MODULE, '回查增量 fixture 动态测量明细', measuredResponse, '', OPTIONS)
measuredItem = measured?.items?.find((item) => asId(item.id) === asId(firstScan.itemId))
const detailIds = new Set((measured?.items || []).map((item) => asId(item?.inboundDetailId)).filter(Boolean))
context.assert(MODULE, '增量 fixture 形成动态真实独立货物测量明细', measuredResponse,
detailIds.size >= incrementalDetailCount
&& measured.items.filter((item) => detailIds.has(asId(item?.inboundDetailId))).length >= incrementalDetailCount,
`measurementItems=${measured?.items?.length ?? 'missing'}, distinctInboundDetails=${detailIds.size}`, OPTIONS)
}
}
let inboundDetailId = measuredItem?.inboundDetailId
if (!inboundDetailId) {
const orderId = asId(frozenSummary?.orderId || context.createdResources?.orders?.orderId)
const orderDetailResponse = await context.request('GET',
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}?${query(tenantId)}`)
const orderDetail = context.apiSuccess(MODULE, '测量详情缺少货物ID时回查隔离订单', orderDetailResponse,
`orderId=${orderId}`, OPTIONS)
const cargoes = Array.isArray(orderDetail?.cargoes) ? [...orderDetail.cargoes] : []
cargoes.sort((left, right) => Number(left?.itemNo ?? Number.MAX_SAFE_INTEGER)
- Number(right?.itemNo ?? Number.MAX_SAFE_INTEGER) || asId(left?.id).localeCompare(asId(right?.id)))
const firstCargo = cargoes[0]
const cargoBelongsToRun = asId(orderDetail?.order?.id) === orderId
&& String(firstCargo?.cargoRemark || '').includes(context.marker)
inboundDetailId = cargoBelongsToRun ? firstCargo?.id : null
context.assert(MODULE, '草稿绑定本轮箱码对应的隔离货物', orderDetailResponse,
cargoBelongsToRun && asId(inboundDetailId),
`cargoCount=${cargoes.length}, markerMatched=${cargoBelongsToRun}, inboundDetailId=${asId(inboundDetailId) || 'missing'}`, OPTIONS)
}
if (!inboundDetailId || !measuredItem?.intelligentImageFileId) {
context.assert(MODULE, '草稿保存前置字段完整', measuredResponse, false,
`inboundDetailId=${asId(inboundDetailId) || 'missing'}, imageFileId=${asId(measuredItem?.intelligentImageFileId) || 'missing'}`, OPTIONS)
finishScenario(context, start)
return
}
const draftItems = incrementalDetailCount > 1
? (measured?.items || []).filter((item) => asId(item?.inboundDetailId) && asId(item?.intelligentImageFileId))
.slice(0, incrementalDetailCount).map((item) => ({
inboundDetailId: item.inboundDetailId, itemNo: item.itemNo,
boxSpecification: item.boxSpecification, packageCount: Number(item.confirmedPackageCount),
lengthCm: Number(item.lengthCm), widthCm: Number(item.widthCm), heightCm: Number(item.heightCm),
intelligentImageFileId: item.intelligentImageFileId,
overLimitWarning: item.overLimitWarning ?? null, coefficientMode: 'ADD', remark: context.marker,
}))
: [{
inboundDetailId, itemNo: measuredItem.itemNo,
boxSpecification: measuredItem.boxSpecification, packageCount: originalPackageCount,
lengthCm: replacement.length, widthCm: replacement.width, heightCm: replacement.height,
intelligentImageFileId: measuredItem.intelligentImageFileId,
overLimitWarning: measuredItem.overLimitWarning ?? null, coefficientMode: 'ADD', remark: context.marker,
}]
const draftPayload = {
measurementId: measured.measurementId, version: Number(measured.version),
actualGoodsName: `墨家测量验收-${context.marker.slice(-12)}`,
items: draftItems,
}
const draftResponse = await context.request('PUT',
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/draft?${query(tenantId)}`, draftPayload)
const draft = context.apiSuccess(MODULE, '保存同一测量批次草稿', draftResponse,
`measurementId=${asId(measured.measurementId)}, items=${draftItems.length}`, OPTIONS)
context.assert(MODULE, '草稿沿用批次并递增版本', draftResponse,
asId(draft?.measurementId) === asId(measured.measurementId)
&& Number(draft?.version) === Number(measured.version) + 1 && draft?.status === 'DRAFT',
`status=${draft?.status ?? 'missing'}, version=${draft?.version ?? 'missing'}`, OPTIONS)
if (!draft?.measurementId) {
finishScenario(context, start)
return
}
const palletCount = Math.max(1, Number(context.context?.waitSplitVariant?.palletCount || 1))
const warehouseResourcesReady = await provisionWarehouseResources(context, {
inboundId, tenantId, warehouseId: asId(summary?.warehouseId), palletCount,
})
context.assert(MODULE, '测量提交前按称重图片数准备真实库位', draftResponse,
Boolean(warehouseResourcesReady),
`warehouseId=${asId(summary?.warehouseId) || 'missing'}, palletCount=${palletCount}`,
OPTIONS)
if (!warehouseResourcesReady) {
finishScenario(context, start)
return
}
const weightEvidence = await createConfirmedWeightEvidence(context, {
inboundId, tenantId, palletIds: warehouseResourcesReady.palletIds,
grossWeightPerPalletKg: Number(context.context?.waitSplitVariant?.grossWeightPerPalletKg || 100),
})
context.assert(MODULE, '测量提交前已通过正式接口确认称重图片', draftResponse,
Boolean(weightEvidence),
`confirmedImages=${weightEvidence?.count ?? 0}, expected=${palletCount}`,
OPTIONS)
if (!weightEvidence) {
finishScenario(context, start)
return
}
const submitResponse = await context.request('POST',
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/submit?${query(tenantId)}`,
{ measurementId: draft.measurementId, version: Number(draft.version) })
const submitted = context.apiSuccess(MODULE, '提交测量审核', submitResponse,
`measurementId=${asId(draft.measurementId)}`, OPTIONS)
context.assert(MODULE, '提交后进入待测量审核状态', submitResponse,
submitted?.processStatus === 'MEASURE_PENDING' && submitted?.measureReviewStatus === 'PENDING',
`processStatus=${submitted?.processStatus ?? 'missing'}, reviewStatus=${submitted?.measureReviewStatus ?? 'missing'}`, OPTIONS)
const rejectedScanResponse = await multipartRequest(context, scanPath, {
boxCode, ...replacement, machineNo: 'MJ-ADMIN-E2E-03', measuredAt: new Date().toISOString().slice(0, 19),
})
const rejected = rejectedScanResponse.http === 200 && String(rejectedScanResponse.body?.code) === 'A0409'
&& String(rejectedScanResponse.body?.message || '').includes('测量已提交')
context.assert(MODULE, '测量提交后拒绝设备覆盖并返回中文提示', rejectedScanResponse, rejected,
`HTTP=${rejectedScanResponse.http}, code=${rejectedScanResponse.body?.code ?? 'missing'}, chineseMessage=${String(rejectedScanResponse.body?.message || '').includes('测量已提交')}`, OPTIONS)
const pendingInboundResponse = await context.request('GET', detailPath)
const pendingInbound = context.apiSuccess(MODULE, '审核前读取最新入库单版本', pendingInboundResponse, '', OPTIONS)
const pendingSummary = pendingInbound?.summary
context.assert(MODULE, '审核前状态和版本有效', pendingInboundResponse,
pendingSummary?.processStatus === 'MEASURE_PENDING' && pendingSummary?.measureReviewStatus === 'PENDING'
&& Number.isInteger(Number(pendingSummary?.version)),
`status=${pendingSummary?.processStatus ?? 'missing'}, version=${pendingSummary?.version ?? 'missing'}`, OPTIONS)
const approvalPath = `/api/platform/v1/inbounds/${inboundId}/measurements/approve?${query(tenantId)}`
const approvalResponse = await requestWithHeaders(context, 'POST', approvalPath, {
measurementId: draft.measurementId, expectedVersion: Number(pendingSummary?.version), reason: null,
}, { 'Idempotency-Key': `${context.marker}-mojia-approve`.slice(0, 128) })
context.apiSuccess(MODULE, 'Admin 审核通过墨家测量', approvalResponse,
`measurementId=${asId(draft.measurementId)}`, OPTIONS)
// Measurement approval may enqueue pricing/inbound completion. Poll the
// public projection instead of treating the transient WAIT_PRICING state as
// a smart-loading candidate or manufacturing fixture state in the script.
const dropship = context.createdResources?.orders?.fulfillmentType === 'ONE_CLICK_DROPSHIP'
let completed = await waitForCompletedMeasurement(context, measurementPath,
dropship ? ['WAIT_PALLETIZE'] : ['WAIT_SPLIT'])
let finalResponse = completed.response
let finalMeasurement = context.apiSuccess(MODULE, '查询审核后的最终测量状态', finalResponse,
`pollAttempts=${completed.attempts}`, OPTIONS)
context.assert(MODULE, '审核后入库与测量状态闭环且结果未变化', finalResponse,
finalMeasurement?.processStatus === (dropship ? 'WAIT_PALLETIZE' : 'WAIT_SPLIT')
&& finalMeasurement?.measureReviewStatus === 'APPROVED'
&& finalMeasurement?.measurementStatus === 'APPROVED'
&& Number(finalMeasurement?.actualPackageCount) === originalPackageCount
&& closeTo(finalMeasurement?.actualGrossWeightKg, weightEvidence.grossWeightKg, 0.001)
&& closeTo(finalMeasurement?.actualNetWeightKg, weightEvidence.netWeightKg, 0.001)
&& closeTo(finalMeasurement?.actualVolumeCbm, totalVolume)
&& closeTo(finalMeasurement?.volumeWeightKg, volumeWeight),
`processStatus=${finalMeasurement?.processStatus ?? 'missing'}, measurementStatus=${finalMeasurement?.measurementStatus ?? 'missing'}, packages=${finalMeasurement?.actualPackageCount ?? 'missing'}, pallets=${weightEvidence.count}, grossWeight=${finalMeasurement?.actualGrossWeightKg ?? 'missing'}, netWeight=${finalMeasurement?.actualNetWeightKg ?? 'missing'}`, OPTIONS)
if (dropship && finalMeasurement?.processStatus === 'WAIT_PALLETIZE') {
await completeDropshipPalletization(context, {
inboundId, inboundCode, warehouseId: asId(summary?.warehouseId), measurement: finalMeasurement,
})
completed = await waitForCompletedMeasurement(context, measurementPath, ['WAIT_SPLIT'], 10)
finalResponse = completed.response
finalMeasurement = context.apiSuccess(MODULE, '合托后回查一件代发入仓单', finalResponse,
`pollAttempts=${completed.attempts}`, OPTIONS)
context.assert(MODULE, '一件代发合托完成后进入智能分柜门禁', finalResponse,
finalMeasurement?.processStatus === 'WAIT_SPLIT'
&& finalMeasurement?.measureReviewStatus === 'APPROVED'
&& finalMeasurement?.measurementStatus === 'APPROVED',
`processStatus=${finalMeasurement?.processStatus ?? 'missing'}`, OPTIONS)
}
const approvedScanResponse = await multipartRequest(context, scanPath, {
boxCode, ...replacement, machineNo: 'MJ-ADMIN-E2E-AFTER-APPROVAL',
measuredAt: new Date().toISOString().slice(0, 19),
})
context.assert(MODULE, '审核及合托完成后箱码尺寸与图片快照不可再覆盖', approvedScanResponse,
approvedScanResponse.http === 200 && String(approvedScanResponse.body?.code) === 'A0409',
`HTTP=${approvedScanResponse.http}, code=${approvedScanResponse.body?.code ?? 'missing'}, requestId=${approvedScanResponse.requestId || 'missing'}`,
OPTIONS)
context.createdResources.inboundMeasurement = {
marker: context.marker, tenantId, inboundId, measurementId: asId(draft.measurementId),
boxCode, finalStatus: finalMeasurement?.measurementStatus || null,
processStatus: finalMeasurement?.processStatus || null,
palletCount: weightEvidence.count, actualGrossWeightKg: weightEvidence.grossWeightKg,
destinationType: context.createdResources?.orders?.destinationType || null,
destinationWarehouseId: context.createdResources?.orders?.destinationWarehouseId || null,
destinationWarehouseCode: context.createdResources?.orders?.destinationWarehouseCode || null,
}
finishScenario(context, start)
}