import { readFile } from 'node:fs/promises' const MODULE = 'inbound-measurement' const SCENARIO = 'mojia-carton-measurement' const OPTIONS = { scenario: SCENARIO, deploymentBlocker: true } const WEIGHING_IMAGE_COUNT = 1 const FIXTURE_PALLET_CAPACITY_CBM = 1.2 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 return Math.ceil((value - Number.EPSILON) * 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 waitForCompletedMeasurement(context, measurementPath, 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 (measurement?.processStatus === 'WAIT_SPLIT') return { response, measurement, attempts: attempt } if (attempt < attempts) await new Promise((resolve) => setTimeout(resolve, 1_000)) } return { response, measurement, attempts } } async function provisionWarehouseResources(context, { inboundId, warehouseId, palletCount }) { if (!warehouseId || !Number.isInteger(palletCount) || palletCount < 1) return null const suffix = `${String(context.marker).replace(/\D/g, '').slice(-8)}${String(inboundId).slice(-6)}` const palletResponse = await context.request('POST', '/api/platform/v1/pallets?tenantId=0', { warehouseId, palletType: 'STANDARD', capacityCbm: FIXTURE_PALLET_CAPACITY_CBM, weightKg: 8, color: '蓝色', remark: `${context.marker}-INBOUND-${inboundId}`, }) const palletId = asId(context.apiSuccess(MODULE, '创建称重托盘主数据', palletResponse, `warehouseId=${warehouseId}`, OPTIONS)) if (!palletId) return null const resources = [] for (let index = 1; index <= palletCount; index += 1) { const resourceSuffix = `${suffix}${String(index).padStart(2, '0')}` let locationId = null if (context.context?.phase3LoadingRequireSharedOverflow !== true) { const locationResponse = await context.request('POST', '/api/platform/v1/locations?tenantId=0', { warehouseId, zoneCode: `E2E-${String(context.marker).slice(-8)}`, zoneName: '自动化验收区', locationCode: `E2E-${resourceSuffix}`, locationName: `测量验收库位-${index}`, locationType: 'PALLET', maxWeightKg: 1000, status: 'AVAILABLE', remark: `${context.marker}-INBOUND-${inboundId}`, }) locationId = asId(context.apiSuccess(MODULE, `创建测量占用库位 ${index}/${palletCount}`, locationResponse, `warehouseId=${warehouseId}`, OPTIONS)) } if (context.context?.phase3LoadingRequireSharedOverflow !== true && !locationId) return null resources.push({ palletId, locationId }) } context.createdResources.inboundWarehouseResources ??= {} context.createdResources.inboundWarehouseResources[inboundId] = { marker: context.marker, tenantId: '0', warehouseId: asId(warehouseId), palletCount, palletCapacityCbm: FIXTURE_PALLET_CAPACITY_CBM, resources, } return { palletId, palletCount, resources } } async function createConfirmedWeightEvidence(context, { inboundId, tenantId, palletId }) { const image = await readFile(new URL('../../../apps/pda-uniapp/static/c7.png', import.meta.url)) const uploadResponse = await context.requestMultipart('POST', `/api/platform/v1/files/measurements/${tenantId}/${inboundId}`, { content: image, type: 'image/png', name: `weight-${inboundId}.png`, }) const upload = context.apiSuccess(MODULE, '上传称重图片并执行 OCR', 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, '登记称重图片与托盘快照', createResponse, `fileId=${fileId}, palletId=${palletId}`, OPTIONS) if (!created?.id) return null 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: 100, adjustedWeightUnit: 'KG', adjustmentReason: `${context.marker} 自动化人工确认称重`, }) const confirmed = context.apiSuccess(MODULE, '人工确认称重图片净重', 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 workspaceResponse = await context.request('GET', `/api/platform/v1/first-leg/inbounds/${inboundId}/measurement?${query(tenantId)}`) const workspace = context.apiSuccess(MODULE, '回查已确认称重图片和货物总重量', workspaceResponse, `resultId=${created.id}`, OPTIONS) const confirmedRows = (workspace?.weightResults || []) .filter((result) => result?.recognitionStatus === 'CONFIRMED') const persisted = confirmedRows.length === WEIGHING_IMAGE_COUNT && asId(confirmedRows[0]?.id) === asId(created.id) && asId(confirmedRows[0]?.palletId) === palletId && closeTo(workspace?.actualGrossWeightKg, grossWeightKg, 0.001) && closeTo(workspace?.actualNetWeightKg, netWeightKg, 0.001) context.assert(MODULE, '图片张数决定托数且净重汇总已落地', workspaceResponse, persisted, `confirmedImages=${confirmedRows.length}, expectedPallets=${WEIGHING_IMAGE_COUNT}, 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' })) const image = await readFile(new URL('../../../apps/pda-uniapp/static/c7.png', import.meta.url)) form.append('image', new Blob([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)}` // Keep the isolated sample inside the customer's largest configured inner box (60x50x50cm). const firstDimensions = { 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 = { 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) const measuredResponse = await context.request('GET', measurementPath) const measured = context.apiSuccess(MODULE, '回查覆盖后的尺寸与体积结果', measuredResponse, '', OPTIONS) const measuredItem = measured?.items?.find((item) => asId(item.id) === asId(firstScan.itemId)) // The verified customer rule maps this scan to EXTRA_LARGE 60x50x50cm and adds 2cm per edge. const weightedOuter = { length: 64, width: 54, height: 54 } 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(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 && closeTo(measuredItem.outerLengthCm, weightedOuter.length) && closeTo(measuredItem.outerWidthCm, weightedOuter.width) && closeTo(measuredItem.outerHeightCm, weightedOuter.height) && closeTo(measuredItem.unitVolumeCbm, unitVolume) && closeTo(measuredItem.totalVolumeCbm, totalVolume) && closeTo(measuredItem.volumeWeightKg, volumeWeight) && closeTo(measuredItem.girthCm, girth) && closeTo(measured.actualVolumeCbm, totalVolume) && closeTo(measured.volumeWeightKg, volumeWeight), `weightedOuter=${weightedOuter.length}x${weightedOuter.width}x${weightedOuter.height}, unitVolume=${unitVolume}, totalVolume=${totalVolume}, volumeWeight=${volumeWeight}, girth=${girth}`, 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 draftPayload = { measurementId: measured.measurementId, version: Number(measured.version), actualGoodsName: `墨家测量验收-${context.marker.slice(-12)}`, items: [{ 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, outerLengthCm: measuredItem.outerLengthCm ?? null, outerWidthCm: measuredItem.outerWidthCm ?? null, outerHeightCm: measuredItem.outerHeightCm ?? null, remark: context.marker, }], } 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=1`, 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 warehouseResourcesReady = await provisionWarehouseResources(context, { inboundId, warehouseId: asId(summary?.warehouseId), palletCount: WEIGHING_IMAGE_COUNT, }) context.assert(MODULE, '测量提交前按称重图片数准备真实库位', draftResponse, Boolean(warehouseResourcesReady), `warehouseId=${asId(summary?.warehouseId) || 'missing'}, palletCount=${WEIGHING_IMAGE_COUNT}`, OPTIONS) if (!warehouseResourcesReady) { finishScenario(context, start) return } const weightEvidence = await createConfirmedWeightEvidence(context, { inboundId, tenantId, palletId: warehouseResourcesReady.palletId, }) context.assert(MODULE, '测量提交前已通过正式接口确认称重图片', draftResponse, Boolean(weightEvidence), `confirmedImages=${weightEvidence?.count ?? 0}, expected=${WEIGHING_IMAGE_COUNT}`, 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 completed = await waitForCompletedMeasurement(context, measurementPath) const finalResponse = completed.response const finalMeasurement = context.apiSuccess(MODULE, '查询审核后的最终测量状态', finalResponse, `pollAttempts=${completed.attempts}`, OPTIONS) context.assert(MODULE, '审核后入库与测量状态闭环且结果未变化', finalResponse, finalMeasurement?.processStatus === '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) 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, } finishScenario(context, start) }