优化
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
|
||||
const MODULE = 'inbound-measurement'
|
||||
const SCENARIO = 'mojia-carton-measurement'
|
||||
const OPTIONS = { scenario: SCENARIO, deploymentBlocker: true }
|
||||
@@ -28,6 +30,110 @@ function validPdf(response) {
|
||||
&& response.bytes?.length > 4 && new TextDecoder().decode(response.bytes.slice(0, 4)) === '%PDF'
|
||||
}
|
||||
|
||||
export function measurementReplayDigest(facts) {
|
||||
return createHash('sha256').update(JSON.stringify(facts)).digest('hex')
|
||||
}
|
||||
|
||||
async function readMeasurementReplayFacts(context, { inboundId, tenantId, measurementId }, name) {
|
||||
const response = await context.request('GET',
|
||||
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement?${query(tenantId)}`)
|
||||
const measurement = context.apiSuccess(MODULE, `${name}测量批次明细及重量事实`, response, '', OPTIONS)
|
||||
const inboundResponse = await context.request('GET', `/api/platform/inbounds/${inboundId}?${query(tenantId)}`)
|
||||
const inbound = context.apiSuccess(MODULE, `${name}入库状态及历史`, inboundResponse, '', OPTIONS)
|
||||
const valid = asId(measurement?.inboundId) === inboundId && asId(measurement?.measurementId) === measurementId
|
||||
&& Array.isArray(measurement?.items) && measurement.items.length > 0
|
||||
&& Array.isArray(measurement?.weightResults) && measurement.weightResults.length > 0
|
||||
&& new Set(measurement.items.map(item => asId(item.id))).size === measurement.items.length
|
||||
&& new Set(measurement.weightResults.map(item => asId(item.id))).size === measurement.weightResults.length
|
||||
&& asId(inbound?.summary?.id) === inboundId && asId(inbound?.summary?.tenantId) === tenantId
|
||||
&& Array.isArray(inbound?.statusHistory)
|
||||
context.assert(MODULE, `${name}原入库原批次明细及历史可核对`, response, valid,
|
||||
`measurementId=${asId(measurement?.measurementId)}, items=${measurement?.items?.length ?? 'missing'}`
|
||||
+ `, weights=${measurement?.weightResults?.length ?? 'missing'}`, OPTIONS)
|
||||
if (!valid) throw new Error('测量重放核验缺少原批次或完整明细历史事实')
|
||||
return { response, facts: { measurement, inbound } }
|
||||
}
|
||||
|
||||
export async function verifyMeasurementSubmitReplay(context, input, payload) {
|
||||
const before = await readMeasurementReplayFacts(context, input, '重复提交前')
|
||||
const response = await context.request('POST',
|
||||
`/api/platform/v1/first-leg/inbounds/${input.inboundId}/measurement/submit?${query(input.tenantId)}`, payload)
|
||||
const rejected = response?.http === 200 && String(response.body?.code) === 'A0409'
|
||||
context.assert(MODULE, '已提交测量再次提交明确A0409', response, rejected,
|
||||
`HTTP=${response?.http}, code=${response?.body?.code ?? 'missing'}`, OPTIONS)
|
||||
const after = await readMeasurementReplayFacts(context, input, '重复提交后')
|
||||
const beforeDigest = measurementReplayDigest(before.facts)
|
||||
const afterDigest = measurementReplayDigest(after.facts)
|
||||
const unchanged = beforeDigest === afterDigest
|
||||
context.assert(MODULE, '重复测量提交拒绝后批次版本明细重量及入库历史零漂移', after.response,
|
||||
unchanged, `items=${after.facts.measurement.items.length}, weights=${after.facts.measurement.weightResults.length}`
|
||||
+ `, histories=${after.facts.inbound.statusHistory.length}`, OPTIONS)
|
||||
return { measurementId: input.measurementId, rejected, unchanged, beforeDigest, afterDigest,
|
||||
itemIds: after.facts.measurement.items.map(item => asId(item.id)),
|
||||
weightIds: after.facts.measurement.weightResults.map(item => asId(item.id)),
|
||||
measurementVersion: after.facts.measurement.version,
|
||||
inboundVersion: after.facts.measurement.inboundVersion, historyCount: after.facts.inbound.statusHistory.length }
|
||||
}
|
||||
|
||||
export async function verifyMeasurementApprovalReplay(context, input, payload, idempotencyKey) {
|
||||
async function read(name) {
|
||||
const snapshot = await readMeasurementReplayFacts(context, input, name)
|
||||
const reviews = []; let total = null; let page = 1
|
||||
while (total == null || reviews.length < total) {
|
||||
const response = await context.request('GET',
|
||||
`/api/platform/v1/inbounds/${input.inboundId}/measurement-review-history?${query(input.tenantId)}&page=${page}&size=200`)
|
||||
const data = context.apiSuccess(MODULE, `${name}审核历史第${page}页`, response, '', OPTIONS)
|
||||
const valid = Array.isArray(data?.records) && Number.isInteger(Number(data?.total)) && Number(data.total) >= 0
|
||||
&& (total == null || total === Number(data.total))
|
||||
&& (data.records.length > 0 || Number(data.total) === 0)
|
||||
context.assert(MODULE, `${name}审核历史完整分页`, response, valid,
|
||||
`page=${page}, total=${data?.total ?? 'missing'}`, OPTIONS)
|
||||
if (!valid) throw new Error('测量审核重放缺少完整审核历史')
|
||||
total = Number(data.total); reviews.push(...data.records); page += 1
|
||||
}
|
||||
const feeResponse = await context.request('GET',
|
||||
`/api/platform/v1/inbounds/${input.inboundId}/fees/latest?${query(input.tenantId)}`)
|
||||
const fee = context.apiSuccess(MODULE, `${name}原初算费用ID及版本`, feeResponse, '', OPTIONS)
|
||||
const detailsResponse = await context.request('GET',
|
||||
`/api/platform/v1/inbounds/${input.inboundId}/fee-details?accountId=${encodeURIComponent(input.tenantId)}&includeHistory=true`)
|
||||
const feeDetails = context.apiSuccess(MODULE, `${name}全部版本费用明细`, detailsResponse, '', OPTIONS)
|
||||
const approvedReviewCount = reviews.filter(item => asId(item.measurementId) === input.measurementId
|
||||
&& item.reviewResult === 'APPROVED').length
|
||||
const valid = snapshot.facts.measurement.measurementStatus === 'APPROVED'
|
||||
&& snapshot.facts.measurement.measureReviewStatus === 'APPROVED'
|
||||
&& reviews.length === total && new Set(reviews.map(item => asId(item.id))).size === total
|
||||
&& approvedReviewCount === 1
|
||||
&& Boolean(asId(fee?.feeId)) && Number.isInteger(Number(fee?.feeVersion)) && Number(fee.feeVersion) >= 1
|
||||
&& Array.isArray(feeDetails) && new Set(feeDetails.map(item => asId(item.id))).size === feeDetails.length
|
||||
context.assert(MODULE, `${name}唯一审核及已生成初算费用事实有效`, detailsResponse, valid,
|
||||
`reviews=${reviews.length}, feeId=${asId(fee?.feeId)}, feeVersion=${fee?.feeVersion ?? 'missing'}`
|
||||
+ `, feeDetails=${feeDetails?.length ?? 'missing'}, approvedReviewCount=${approvedReviewCount}`
|
||||
+ `, measurementStatus=${snapshot.facts.measurement.measurementStatus}`
|
||||
+ `, measureReviewStatus=${snapshot.facts.measurement.measureReviewStatus}`, OPTIONS)
|
||||
if (!valid) throw new Error('测量审核重放缺少唯一审核或已生成初算费用事实')
|
||||
return { response: snapshot.response, facts: { ...snapshot.facts, reviews, fee, feeDetails } }
|
||||
}
|
||||
const before = await read('审核同键重放前')
|
||||
const response = await context.request('POST',
|
||||
`/api/platform/v1/inbounds/${input.inboundId}/measurements/approve?${query(input.tenantId)}`,
|
||||
payload, context.token, { 'Idempotency-Key': idempotencyKey })
|
||||
const replay = context.apiSuccess(MODULE, '测量审核同原键原请求重放成功', response, '', OPTIONS)
|
||||
const after = await read('审核同键重放后')
|
||||
const beforeDigest = measurementReplayDigest(before.facts)
|
||||
const afterDigest = measurementReplayDigest(after.facts)
|
||||
const unchanged = beforeDigest === afterDigest && replay?.initialPricingSucceeded === true
|
||||
&& asId(replay?.initialFee?.feeId) === asId(before.facts.fee.feeId)
|
||||
&& Number(replay?.initialFee?.feeVersion) === Number(before.facts.fee.feeVersion)
|
||||
context.assert(MODULE, '测量审核重放保持批次费用ID版本全部明细审核及入库历史零新增', after.response,
|
||||
unchanged, `feeId=${asId(after.facts.fee.feeId)}, version=${after.facts.fee.feeVersion}`
|
||||
+ `, reviews=${after.facts.reviews.length}, details=${after.facts.feeDetails.length}`, OPTIONS)
|
||||
return { measurementId: input.measurementId, unchanged, beforeDigest, afterDigest,
|
||||
feeId: asId(after.facts.fee.feeId), feeVersion: after.facts.fee.feeVersion,
|
||||
feeDetailIds: after.facts.feeDetails.map(item => asId(item.id)),
|
||||
reviewIds: after.facts.reviews.map(item => asId(item.id)),
|
||||
inboundHistoryCount: after.facts.inbound.statusHistory.length }
|
||||
}
|
||||
|
||||
async function reusablePallets(context, warehouseId) {
|
||||
const cacheKey = asId(warehouseId)
|
||||
const allowAnyExistingPallet = context.context?.allowAnyExistingPallet === true
|
||||
@@ -1055,14 +1161,17 @@ export async function run(context) {
|
||||
}
|
||||
}
|
||||
|
||||
const submissionPayload = { measurementId: draft.measurementId, version: Number(draft.version) }
|
||||
const replayInput = { inboundId, tenantId, measurementId: asId(draft.measurementId) }
|
||||
const submitResponse = await context.request('POST',
|
||||
`/api/platform/v1/first-leg/inbounds/${inboundId}/measurement/submit?${query(tenantId)}`,
|
||||
{ measurementId: draft.measurementId, version: Number(draft.version) })
|
||||
submissionPayload)
|
||||
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 submissionReplay = await verifyMeasurementSubmitReplay(context, replayInput, submissionPayload)
|
||||
|
||||
const rejectedScanResponse = await multipartRequest(context, scanPath, {
|
||||
boxCode, ...replacement, machineNo: 'MJ-ADMIN-E2E-03', measuredAt: new Date().toISOString().slice(0, 19),
|
||||
@@ -1081,9 +1190,12 @@ export async function run(context) {
|
||||
`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, {
|
||||
const approvalPayload = {
|
||||
measurementId: draft.measurementId, expectedVersion: Number(pendingSummary?.version), reason: null,
|
||||
}, { 'Idempotency-Key': `${context.marker}-mojia-approve`.slice(0, 128) })
|
||||
}
|
||||
const approvalKey = `${context.marker}-mojia-approve`.slice(0, 128)
|
||||
const approvalResponse = await requestWithHeaders(context, 'POST', approvalPath, approvalPayload,
|
||||
{ 'Idempotency-Key': approvalKey })
|
||||
context.apiSuccess(MODULE, 'Admin 审核通过墨家测量', approvalResponse,
|
||||
`measurementId=${asId(draft.measurementId)}`, OPTIONS)
|
||||
|
||||
@@ -1107,6 +1219,8 @@ export async function run(context) {
|
||||
&& 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)
|
||||
|
||||
const approvalReplay = await verifyMeasurementApprovalReplay(context, replayInput, approvalPayload, approvalKey)
|
||||
|
||||
let sealedCartonMarks = []
|
||||
let sealedSourcePalletNos = []
|
||||
if (dropship && finalMeasurement?.processStatus === 'WAIT_PALLETIZE') {
|
||||
@@ -1156,6 +1270,7 @@ export async function run(context) {
|
||||
OPTIONS)
|
||||
|
||||
context.createdResources.inboundMeasurement = {
|
||||
replayEvidence: { submission: submissionReplay, approval: approvalReplay },
|
||||
sealedCartonMarks, sealedSourcePalletNos,
|
||||
marker: context.marker, tenantId, inboundId,
|
||||
orderId: asId(summary?.orderId || context.createdResources?.orders?.orderId),
|
||||
|
||||
Reference in New Issue
Block a user