优化
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readFileSync } from 'node:fs'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
import {
|
||||||
|
confirmedNetSummary,
|
||||||
|
normalizedGrossKg,
|
||||||
|
selectWeightPallet,
|
||||||
|
workspaceWeightMatchesBaseline,
|
||||||
|
} from './inbound-weight-ocr.mjs'
|
||||||
|
|
||||||
|
test('selects a stable complete pallet snapshot without status or availability', () => {
|
||||||
|
const selected = selectWeightPallet([
|
||||||
|
{ palletId: '20', palletCode: 'P-20', palletType: 'STANDARD', capacityCbm: 2, tareWeightKg: 18.5 },
|
||||||
|
{ palletId: '3', palletCode: 'P-03', palletType: 'STANDARD', capacityCbm: 1.5, tareWeightKg: 12 },
|
||||||
|
])
|
||||||
|
assert.equal(selected.palletId, '3')
|
||||||
|
assert.equal(selectWeightPallet([]), null)
|
||||||
|
assert.equal(selectWeightPallet([
|
||||||
|
{ palletId: '3', palletCode: 'P-03', palletType: 'STANDARD', capacityCbm: 1.5 },
|
||||||
|
]), null)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('normalizes gross weight and summarizes confirmed net weight only', () => {
|
||||||
|
assert.equal(normalizedGrossKg(50.125, 'LB'), 22.736)
|
||||||
|
assert.equal(normalizedGrossKg(30, 'KG'), 30)
|
||||||
|
assert.equal(normalizedGrossKg(30, 'UNKNOWN'), null)
|
||||||
|
assert.deepEqual(confirmedNetSummary({ weightResults: [
|
||||||
|
{ recognitionStatus: 'CONFIRMED', netWeightKg: 11.5 },
|
||||||
|
{ recognitionStatus: 'PENDING_CONFIRM', netWeightKg: 99 },
|
||||||
|
{ recognitionStatus: 'CONFIRMED', netWeightKg: 2.125 },
|
||||||
|
] }), { count: 2, complete: true, total: 13.625 })
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cleanup restores a non-empty carton fallback when there were no confirmed images', () => {
|
||||||
|
const initial = { actualGrossWeightKg: 54.4, weightResults: [] }
|
||||||
|
assert.deepEqual(confirmedNetSummary(initial), { count: 0, complete: true, total: 0 })
|
||||||
|
assert.equal(workspaceWeightMatchesBaseline({ actualGrossWeightKg: 54.4 }, 54.4), true)
|
||||||
|
assert.equal(workspaceWeightMatchesBaseline({ actualGrossWeightKg: null }, 54.4), false)
|
||||||
|
assert.equal(workspaceWeightMatchesBaseline({ actualGrossWeightKg: null }, null), true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('HTTP scenario uses pallet option and net weight contracts without legacy final weight', () => {
|
||||||
|
const source = readFileSync(new URL('inbound-weight-ocr.mjs', import.meta.url), 'utf8')
|
||||||
|
assert.match(source, /\/weight-pallet-options\?tenantId=/)
|
||||||
|
assert.ok(source.indexOf('const palletOptionsResponse') < source.lastIndexOf('const uploadPath'))
|
||||||
|
assert.match(source, /form\.append\('file'/)
|
||||||
|
assert.doesNotMatch(source, /form\.append\('palletId'/)
|
||||||
|
assert.match(source, /palletId:\s*id\(pallet\.palletId\)/)
|
||||||
|
assert.match(source, /grossWeightKg/)
|
||||||
|
assert.match(source, /netWeightKg/)
|
||||||
|
assert.match(source, /palletTareWeightKg/)
|
||||||
|
assert.doesNotMatch(source, /finalWeightKg/)
|
||||||
|
assert.doesNotMatch(source, /pallet\.(?:status|available)/)
|
||||||
|
assert.doesNotMatch(source, /platform:pallet:list/)
|
||||||
|
assert.match(source, /verifyDeleted\(context, target, original, baselineWorkspaceWeight\)/)
|
||||||
|
assert.match(source, /workspaceWeightMatchesBaseline\(cleanup\.workspace, baselineWorkspaceWeight\)/)
|
||||||
|
assert.doesNotMatch(source, /verifyDeleted\([^\n]+baselineConfirmedNet/)
|
||||||
|
})
|
||||||
@@ -22,6 +22,47 @@ function closeNumber(actual, expected, tolerance = 0.0005) {
|
|||||||
return Number.isFinite(Number(actual)) && Math.abs(Number(actual) - Number(expected)) < tolerance
|
return Number.isFinite(Number(actual)) && Math.abs(Number(actual) - Number(expected)) < tolerance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function palletSnapshotComplete(pallet) {
|
||||||
|
return Boolean(id(pallet?.palletId))
|
||||||
|
&& typeof pallet?.palletCode === 'string' && pallet.palletCode.length > 0
|
||||||
|
&& typeof pallet?.palletType === 'string' && pallet.palletType.length > 0
|
||||||
|
&& Number.isFinite(Number(pallet?.capacityCbm)) && Number(pallet.capacityCbm) > 0
|
||||||
|
&& Number.isFinite(Number(pallet?.tareWeightKg)) && Number(pallet.tareWeightKg) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectWeightPallet(options) {
|
||||||
|
if (!Array.isArray(options)) return null
|
||||||
|
return [...options]
|
||||||
|
.filter(palletSnapshotComplete)
|
||||||
|
.sort((left, right) => id(left.palletId).localeCompare(id(right.palletId), 'en', { numeric: true }))[0] || null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizedGrossKg(value, unit) {
|
||||||
|
const numeric = Number(value)
|
||||||
|
if (!Number.isFinite(numeric)) return null
|
||||||
|
const normalizedUnit = String(unit || '').trim().toUpperCase()
|
||||||
|
if (!['KG', 'KGS', 'LB', 'LBS'].includes(normalizedUnit)) return null
|
||||||
|
const kg = ['LB', 'LBS'].includes(normalizedUnit) ? numeric * 0.45359237 : numeric
|
||||||
|
return Math.round(kg * 1000) / 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
export function confirmedNetSummary(workspace) {
|
||||||
|
const confirmed = (workspace?.weightResults || [])
|
||||||
|
.filter((row) => row?.recognitionStatus === 'CONFIRMED')
|
||||||
|
const values = confirmed.map((row) => Number(row?.netWeightKg))
|
||||||
|
return {
|
||||||
|
count: confirmed.length,
|
||||||
|
complete: values.every((value) => Number.isFinite(value) && value > 0),
|
||||||
|
total: Math.round(values.reduce((sum, value) => sum + value, 0) * 1000) / 1000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function workspaceWeightMatchesBaseline(workspace, baselineWorkspaceWeight) {
|
||||||
|
return baselineWorkspaceWeight == null
|
||||||
|
? workspace?.actualGrossWeightKg == null
|
||||||
|
: closeNumber(workspace?.actualGrossWeightKg, baselineWorkspaceWeight, 0.0011)
|
||||||
|
}
|
||||||
|
|
||||||
function expectedConflict(response) {
|
function expectedConflict(response) {
|
||||||
return response?.http === 200 && ['A0409', '409'].includes(String(response?.body?.code))
|
return response?.http === 200 && ['A0409', '409'].includes(String(response?.body?.code))
|
||||||
}
|
}
|
||||||
@@ -161,7 +202,11 @@ function deletePath(tenantId, inboundId, resultId, version) {
|
|||||||
return `${resultPath(inboundId, resultId)}?tenantId=${encodeURIComponent(tenantId)}&version=${version}`
|
return `${resultPath(inboundId, resultId)}?tenantId=${encodeURIComponent(tenantId)}&version=${version}`
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createUploadedResult(context, target, image, overrides = {}) {
|
function palletOptionsPath(tenantId, inboundId) {
|
||||||
|
return `/api/platform/v1/first-leg/inbounds/${encodeURIComponent(inboundId)}/weight-pallet-options?tenantId=${encodeURIComponent(tenantId)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createUploadedResult(context, target, pallet, image, overrides = {}) {
|
||||||
const imagePath = `/api/platform/v1/files/measurements/${encodeURIComponent(target.tenantId)}/${encodeURIComponent(target.inboundId)}`
|
const imagePath = `/api/platform/v1/files/measurements/${encodeURIComponent(target.tenantId)}/${encodeURIComponent(target.inboundId)}`
|
||||||
const uploadResponse = await multipart(context, imagePath, image)
|
const uploadResponse = await multipart(context, imagePath, image)
|
||||||
const upload = context.apiSuccess(MODULE, '上传称重图片并执行 OCR', uploadResponse,
|
const upload = context.apiSuccess(MODULE, '上传称重图片并执行 OCR', uploadResponse,
|
||||||
@@ -195,6 +240,7 @@ async function createUploadedResult(context, target, image, overrides = {}) {
|
|||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
fileId,
|
fileId,
|
||||||
|
palletId: id(pallet.palletId),
|
||||||
ocrProvider: `ADMIN_ACCEPTANCE_${context.marker.replace(/[^A-Za-z0-9]/g, '').slice(-24)}`,
|
ocrProvider: `ADMIN_ACCEPTANCE_${context.marker.replace(/[^A-Za-z0-9]/g, '').slice(-24)}`,
|
||||||
ocrRawText: overrides.manual ? null : (recognized.ocrRawText || null),
|
ocrRawText: overrides.manual ? null : (recognized.ocrRawText || null),
|
||||||
ocrWeightValue: overrides.manual ? null : recognized.weightValue,
|
ocrWeightValue: overrides.manual ? null : recognized.weightValue,
|
||||||
@@ -215,8 +261,9 @@ async function createUploadedResult(context, target, image, overrides = {}) {
|
|||||||
const resultId = id(result?.id)
|
const resultId = id(result?.id)
|
||||||
context.assert(MODULE, '重量结果 ID、文件 ID 和初始版本契约正确', createResponse,
|
context.assert(MODULE, '重量结果 ID、文件 ID 和初始版本契约正确', createResponse,
|
||||||
Boolean(resultId) && typeof result?.id === 'string' && typeof result?.fileId === 'string'
|
Boolean(resultId) && typeof result?.id === 'string' && typeof result?.fileId === 'string'
|
||||||
&& id(result?.fileId) === fileId && Number(result?.version) === 0,
|
&& id(result?.fileId) === fileId && id(result?.palletId) === id(pallet.palletId)
|
||||||
`resultIdType=${typeof result?.id}, fileIdType=${typeof result?.fileId}, version=${result?.version ?? 'missing'}`,
|
&& Number(result?.version) === 0,
|
||||||
|
`resultIdType=${typeof result?.id}, fileIdType=${typeof result?.fileId}, palletId=${id(result?.palletId) || 'missing'}, version=${result?.version ?? 'missing'}`,
|
||||||
SCENARIO_OPTIONS)
|
SCENARIO_OPTIONS)
|
||||||
if (!resultId) throw new Error(`文件 ${fileId} 已上传但重量结果登记失败,需要人工清理该测试文件`)
|
if (!resultId) throw new Error(`文件 ${fileId} 已上传但重量结果登记失败,需要人工清理该测试文件`)
|
||||||
return { upload: recognized, fileId, result, resultId, version: Number(result.version), deleted: false }
|
return { upload: recognized, fileId, result, resultId, version: Number(result.version), deleted: false }
|
||||||
@@ -267,14 +314,12 @@ async function deleteResult(context, target, item, name, options = NEW_CONTRACT_
|
|||||||
return { response, passed }
|
return { response, passed }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function verifyDeleted(context, target, item, baselineWeight) {
|
async function verifyDeleted(context, target, item, baselineWorkspaceWeight) {
|
||||||
const check = await loadWorkspace(context, target, '删除后回查重量工作区')
|
const check = await loadWorkspace(context, target, '删除后回查重量工作区')
|
||||||
const missing = !check.workspace?.weightResults?.some((row) => id(row.id) === item.resultId)
|
const missing = !check.workspace?.weightResults?.some((row) => id(row.id) === item.resultId)
|
||||||
context.assert(MODULE, '删除后结果消失且总毛重恢复基线', check.response,
|
context.assert(MODULE, '删除后结果消失且工作区重量恢复运行前基线', check.response,
|
||||||
missing && (baselineWeight == null
|
missing && workspaceWeightMatchesBaseline(check.workspace, baselineWorkspaceWeight),
|
||||||
? check.workspace?.actualGrossWeightKg == null
|
`resultMissing=${missing}, baselineWorkspaceWeight=${baselineWorkspaceWeight ?? 'null'}, actual=${check.workspace?.actualGrossWeightKg ?? 'null'}`,
|
||||||
: closeNumber(check.workspace?.actualGrossWeightKg, baselineWeight)),
|
|
||||||
`resultMissing=${missing}, baseline=${baselineWeight ?? 'null'}, actual=${check.workspace?.actualGrossWeightKg ?? 'null'}`,
|
|
||||||
NEW_CONTRACT_OPTIONS)
|
NEW_CONTRACT_OPTIONS)
|
||||||
|
|
||||||
const imagePath = `/api/platform/v1/files/measurements/${encodeURIComponent(target.tenantId)}/${encodeURIComponent(target.inboundId)}/${encodeURIComponent(item.fileId)}`
|
const imagePath = `/api/platform/v1/files/measurements/${encodeURIComponent(target.tenantId)}/${encodeURIComponent(target.inboundId)}/${encodeURIComponent(item.fileId)}`
|
||||||
@@ -313,12 +358,50 @@ export async function run(context) {
|
|||||||
const image = { bytes: imageBytes, name: basename(imagePath), contentType: 'image/jpeg' }
|
const image = { bytes: imageBytes, name: basename(imagePath), contentType: 'image/jpeg' }
|
||||||
const initial = await loadWorkspace(context, target, '读取 OCR 测试前重量工作区')
|
const initial = await loadWorkspace(context, target, '读取 OCR 测试前重量工作区')
|
||||||
if (!initial.workspace) throw new Error('无法读取测试入库单重量工作区')
|
if (!initial.workspace) throw new Error('无法读取测试入库单重量工作区')
|
||||||
const baselineWeight = initial.workspace.actualGrossWeightKg
|
const baselineWorkspaceWeight = initial.workspace.actualGrossWeightKg
|
||||||
|
const baselineImageSummary = confirmedNetSummary(initial.workspace)
|
||||||
const baselineIds = new Set((initial.workspace.weightResults || []).map((row) => id(row.id)))
|
const baselineIds = new Set((initial.workspace.weightResults || []).map((row) => id(row.id)))
|
||||||
context.assert(MODULE, '测试入库单身份与基线可追踪', initial.response,
|
context.assert(MODULE, '测试入库单身份与基线可追踪', initial.response,
|
||||||
id(initial.workspace.inboundId) === target.inboundId
|
id(initial.workspace.inboundId) === target.inboundId
|
||||||
&& initial.workspace.processStatus === target.detail.summary.processStatus,
|
&& initial.workspace.processStatus === target.detail.summary.processStatus
|
||||||
`existingResults=${baselineIds.size}, baselineWeight=${baselineWeight ?? 'null'}`, SCENARIO_OPTIONS)
|
&& baselineImageSummary.complete,
|
||||||
|
`existingResults=${baselineIds.size}, confirmedImageCount=${baselineImageSummary.count}, baselineImageNetTotal=${baselineImageSummary.total}, baselineWorkspaceWeight=${baselineWorkspaceWeight ?? 'null'}`,
|
||||||
|
NEW_CONTRACT_OPTIONS)
|
||||||
|
if (!baselineImageSummary.complete) {
|
||||||
|
const steps = context.steps.slice(scenarioStart)
|
||||||
|
context.createdResources.inboundWeightOcr.cleaned = true
|
||||||
|
context.scenarios.push({
|
||||||
|
name: SCENARIO, module: MODULE, success: false,
|
||||||
|
deploymentBlocked: steps.some((step) => step.deploymentBlocker),
|
||||||
|
passed: steps.filter((step) => step.passed).length,
|
||||||
|
failed: steps.filter((step) => !step.passed).length,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const palletOptionsResponse = await context.request(
|
||||||
|
'GET', palletOptionsPath(target.tenantId, target.inboundId),
|
||||||
|
)
|
||||||
|
const palletOptions = context.apiSuccess(MODULE, '读取同仓称重托盘候选', palletOptionsResponse,
|
||||||
|
`inboundId=${target.inboundId}`, NEW_CONTRACT_OPTIONS)
|
||||||
|
const palletOptionsComplete = Array.isArray(palletOptions) && palletOptions.length > 0
|
||||||
|
&& palletOptions.every(palletSnapshotComplete)
|
||||||
|
context.assert(MODULE, '托盘候选包含稳定快照字段且无需状态或占用资格', palletOptionsResponse,
|
||||||
|
palletOptionsComplete,
|
||||||
|
`count=${Array.isArray(palletOptions) ? palletOptions.length : 'invalid'}, fields=palletId/palletCode/palletType/capacityCbm/tareWeightKg`,
|
||||||
|
NEW_CONTRACT_OPTIONS)
|
||||||
|
const pallet = palletOptionsComplete ? selectWeightPallet(palletOptions) : null
|
||||||
|
if (!pallet) {
|
||||||
|
const steps = context.steps.slice(scenarioStart)
|
||||||
|
context.createdResources.inboundWeightOcr.cleaned = true
|
||||||
|
context.scenarios.push({
|
||||||
|
name: SCENARIO, module: MODULE, success: false,
|
||||||
|
deploymentBlocked: steps.some((step) => step.deploymentBlocker),
|
||||||
|
passed: steps.filter((step) => step.passed).length,
|
||||||
|
failed: steps.filter((step) => !step.passed).length,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const uploadPath = `/api/platform/v1/files/measurements/${encodeURIComponent(target.tenantId)}/${encodeURIComponent(target.inboundId)}`
|
const uploadPath = `/api/platform/v1/files/measurements/${encodeURIComponent(target.tenantId)}/${encodeURIComponent(target.inboundId)}`
|
||||||
const noAuth = await multipart(context, uploadPath, image, '')
|
const noAuth = await multipart(context, uploadPath, image, '')
|
||||||
@@ -337,7 +420,7 @@ export async function run(context) {
|
|||||||
expectedRejection(oversized), `HTTP=${oversized.http}, code=${oversized.body?.code ?? 'missing'}`, SCENARIO_OPTIONS)
|
expectedRejection(oversized), `HTTP=${oversized.http}, code=${oversized.body?.code ?? 'missing'}`, SCENARIO_OPTIONS)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const original = await createUploadedResult(context, target, image, { retry: true })
|
const original = await createUploadedResult(context, target, pallet, image, { retry: true })
|
||||||
created.push(original)
|
created.push(original)
|
||||||
context.createdResources.inboundWeightOcr.resultIds.push(original.resultId)
|
context.createdResources.inboundWeightOcr.resultIds.push(original.resultId)
|
||||||
await verifyImages(context, target, original, image)
|
await verifyImages(context, target, original, image)
|
||||||
@@ -359,29 +442,41 @@ export async function run(context) {
|
|||||||
|
|
||||||
const recognizedValue = Number(original.result.ocrWeightValue)
|
const recognizedValue = Number(original.result.ocrWeightValue)
|
||||||
const recognizedUnit = original.result.ocrWeightUnit
|
const recognizedUnit = original.result.ocrWeightUnit
|
||||||
const canConfirmOriginal = Number.isFinite(recognizedValue) && recognizedValue > 0
|
const recognizedGrossKg = normalizedGrossKg(recognizedValue, recognizedUnit)
|
||||||
&& ['KG', 'KGS', 'LB', 'LBS'].includes(String(recognizedUnit || '').trim().toUpperCase())
|
const canConfirmOriginal = recognizedGrossKg != null
|
||||||
|
&& recognizedGrossKg > Number(pallet.tareWeightKg)
|
||||||
const confirmPayload = canConfirmOriginal
|
const confirmPayload = canConfirmOriginal
|
||||||
? { version: original.version }
|
? { version: original.version }
|
||||||
: {
|
: {
|
||||||
version: original.version, adjustedWeightValue: 1.234, adjustedWeightUnit: 'KG',
|
version: original.version,
|
||||||
|
adjustedWeightValue: Math.round((Number(pallet.tareWeightKg) + 1.234) * 1000) / 1000,
|
||||||
|
adjustedWeightUnit: 'KG',
|
||||||
adjustmentReason: `${context.marker} OCR未返回有效重量,人工验收`,
|
adjustmentReason: `${context.marker} OCR未返回有效重量,人工验收`,
|
||||||
}
|
}
|
||||||
const confirmResponse = await context.request('POST',
|
const confirmResponse = await context.request('POST',
|
||||||
confirmPath(target.tenantId, target.inboundId, original.resultId), confirmPayload)
|
confirmPath(target.tenantId, target.inboundId, original.resultId), confirmPayload)
|
||||||
const confirmed = context.apiSuccess(MODULE, '确认 OCR 重量并计入实际总毛重', confirmResponse,
|
const confirmed = context.apiSuccess(MODULE, '确认 OCR 重量并计入货物总重量', confirmResponse,
|
||||||
`usedOriginal=${canConfirmOriginal}`, NEW_CONTRACT_OPTIONS)
|
`usedOriginal=${canConfirmOriginal}`, NEW_CONTRACT_OPTIONS)
|
||||||
context.assert(MODULE, '确认结果状态、版本和最终KG正确', confirmResponse,
|
const confirmedGross = Number(confirmed?.grossWeightKg)
|
||||||
|
const confirmedTare = Number(confirmed?.palletTareWeightKg)
|
||||||
|
const confirmedNet = Number(confirmed?.netWeightKg)
|
||||||
|
context.assert(MODULE, '确认结果返回托盘快照、毛重和去皮净重', confirmResponse,
|
||||||
confirmed?.recognitionStatus === 'CONFIRMED' && Number(confirmed?.version) === original.version + 1
|
confirmed?.recognitionStatus === 'CONFIRMED' && Number(confirmed?.version) === original.version + 1
|
||||||
&& Number(confirmed?.finalWeightKg) > 0,
|
&& id(confirmed?.palletId) === id(pallet.palletId)
|
||||||
`status=${confirmed?.recognitionStatus ?? 'missing'}, version=${confirmed?.version ?? 'missing'}, finalKg=${confirmed?.finalWeightKg ?? 'missing'}`,
|
&& confirmed?.palletCode === pallet.palletCode && confirmed?.palletType === pallet.palletType
|
||||||
|
&& closeNumber(confirmed?.palletCapacityCbm, pallet.capacityCbm)
|
||||||
|
&& closeNumber(confirmed?.palletTareWeightKg, pallet.tareWeightKg)
|
||||||
|
&& confirmedGross > confirmedTare && confirmedNet > 0
|
||||||
|
&& closeNumber(confirmedNet, confirmedGross - confirmedTare, 0.0011),
|
||||||
|
`status=${confirmed?.recognitionStatus ?? 'missing'}, palletId=${id(confirmed?.palletId) || 'missing'}, grossKg=${confirmed?.grossWeightKg ?? 'missing'}, tareKg=${confirmed?.palletTareWeightKg ?? 'missing'}, netKg=${confirmed?.netWeightKg ?? 'missing'}`,
|
||||||
NEW_CONTRACT_OPTIONS)
|
NEW_CONTRACT_OPTIONS)
|
||||||
original.version = Number(confirmed?.version ?? original.version)
|
original.version = Number(confirmed?.version ?? original.version)
|
||||||
const afterConfirm = await loadWorkspace(context, target, '确认后回查实际总毛重')
|
const afterConfirm = await loadWorkspace(context, target, '确认后回查货物总重量')
|
||||||
const expectedTotal = Number(baselineWeight || 0) + Number(confirmed?.finalWeightKg || 0)
|
const confirmedSummary = confirmedNetSummary(afterConfirm.workspace)
|
||||||
context.assert(MODULE, '确认后实际总毛重按服务端结果重算', afterConfirm.response,
|
context.assert(MODULE, '货物总重量按全部已确认净重由服务端汇总', afterConfirm.response,
|
||||||
closeNumber(afterConfirm.workspace?.actualGrossWeightKg, expectedTotal),
|
confirmedSummary.complete && confirmedSummary.count === baselineImageSummary.count + 1
|
||||||
`baseline=${baselineWeight ?? 'null'}, added=${confirmed?.finalWeightKg ?? 'missing'}, actual=${afterConfirm.workspace?.actualGrossWeightKg ?? 'missing'}`,
|
&& closeNumber(afterConfirm.workspace?.actualGrossWeightKg, confirmedSummary.total, 0.0011),
|
||||||
|
`confirmedCount=${confirmedSummary.count}, confirmedNetTotal=${confirmedSummary.total}, actual=${afterConfirm.workspace?.actualGrossWeightKg ?? 'missing'}`,
|
||||||
NEW_CONTRACT_OPTIONS)
|
NEW_CONTRACT_OPTIONS)
|
||||||
|
|
||||||
const staleDelete = await context.request('DELETE',
|
const staleDelete = await context.request('DELETE',
|
||||||
@@ -389,9 +484,9 @@ export async function run(context) {
|
|||||||
context.assert(MODULE, '旧版本删除返回并发冲突', staleDelete, expectedConflict(staleDelete),
|
context.assert(MODULE, '旧版本删除返回并发冲突', staleDelete, expectedConflict(staleDelete),
|
||||||
`HTTP=${staleDelete.http}, code=${staleDelete.body?.code ?? 'missing'}`, NEW_CONTRACT_OPTIONS)
|
`HTTP=${staleDelete.http}, code=${staleDelete.body?.code ?? 'missing'}`, NEW_CONTRACT_OPTIONS)
|
||||||
const deletedOriginal = await deleteResult(context, target, original, '删除本轮已确认图片及重量结果')
|
const deletedOriginal = await deleteResult(context, target, original, '删除本轮已确认图片及重量结果')
|
||||||
if (deletedOriginal.passed) await verifyDeleted(context, target, original, baselineWeight)
|
if (deletedOriginal.passed) await verifyDeleted(context, target, original, baselineWorkspaceWeight)
|
||||||
|
|
||||||
const manual = await createUploadedResult(context, target, image, { manual: true })
|
const manual = await createUploadedResult(context, target, pallet, image, { manual: true })
|
||||||
created.push(manual)
|
created.push(manual)
|
||||||
context.createdResources.inboundWeightOcr.resultIds.push(manual.resultId)
|
context.createdResources.inboundWeightOcr.resultIds.push(manual.resultId)
|
||||||
await rejectWithoutMutation(context, target, manual, '零重量确认被拒绝', {
|
await rejectWithoutMutation(context, target, manual, '零重量确认被拒绝', {
|
||||||
@@ -408,7 +503,7 @@ export async function run(context) {
|
|||||||
adjustedWeightUnit: 'KG', adjustmentReason: context.marker,
|
adjustedWeightUnit: 'KG', adjustmentReason: context.marker,
|
||||||
})
|
})
|
||||||
|
|
||||||
const adjustedValue = 10.125
|
const adjustedValue = Math.ceil(((Number(pallet.tareWeightKg) + 5) / 0.45359237) * 1000) / 1000
|
||||||
const adjustedConfirmResponse = await context.request('POST',
|
const adjustedConfirmResponse = await context.request('POST',
|
||||||
confirmPath(target.tenantId, target.inboundId, manual.resultId), {
|
confirmPath(target.tenantId, target.inboundId, manual.resultId), {
|
||||||
version: manual.version, adjustedWeightValue: adjustedValue, adjustedWeightUnit: 'LB',
|
version: manual.version, adjustedWeightValue: adjustedValue, adjustedWeightUnit: 'LB',
|
||||||
@@ -416,16 +511,19 @@ export async function run(context) {
|
|||||||
})
|
})
|
||||||
const adjusted = context.apiSuccess(MODULE, '人工改重与确认原子完成', adjustedConfirmResponse,
|
const adjusted = context.apiSuccess(MODULE, '人工改重与确认原子完成', adjustedConfirmResponse,
|
||||||
`adjusted=${adjustedValue} LB`, NEW_CONTRACT_OPTIONS)
|
`adjusted=${adjustedValue} LB`, NEW_CONTRACT_OPTIONS)
|
||||||
const expectedKg = Math.round(adjustedValue * 0.45359237 * 1000) / 1000
|
const expectedGrossKg = Math.round(adjustedValue * 0.45359237 * 1000) / 1000
|
||||||
context.assert(MODULE, 'LB 人工改重保留输入并正确换算最终KG', adjustedConfirmResponse,
|
const expectedNetKg = Math.round((expectedGrossKg - Number(pallet.tareWeightKg)) * 1000) / 1000
|
||||||
|
context.assert(MODULE, 'LB 人工改重保留毛重输入并正确换算去皮净重', adjustedConfirmResponse,
|
||||||
adjusted?.recognitionStatus === 'CONFIRMED' && Number(adjusted?.version) === manual.version + 2
|
adjusted?.recognitionStatus === 'CONFIRMED' && Number(adjusted?.version) === manual.version + 2
|
||||||
&& closeNumber(adjusted?.adjustedWeightValue, adjustedValue)
|
&& closeNumber(adjusted?.adjustedWeightValue, adjustedValue)
|
||||||
&& adjusted?.adjustedWeightUnit === 'LB' && closeNumber(adjusted?.finalWeightKg, expectedKg),
|
&& adjusted?.adjustedWeightUnit === 'LB'
|
||||||
`adjusted=${adjusted?.adjustedWeightValue ?? 'missing'} ${adjusted?.adjustedWeightUnit ?? ''}, finalKg=${adjusted?.finalWeightKg ?? 'missing'}, expectedKg=${expectedKg}`,
|
&& closeNumber(adjusted?.grossWeightKg, expectedGrossKg, 0.0011)
|
||||||
|
&& closeNumber(adjusted?.netWeightKg, expectedNetKg, 0.0011),
|
||||||
|
`adjusted=${adjusted?.adjustedWeightValue ?? 'missing'} ${adjusted?.adjustedWeightUnit ?? ''}, grossKg=${adjusted?.grossWeightKg ?? 'missing'}, netKg=${adjusted?.netWeightKg ?? 'missing'}, expectedNetKg=${expectedNetKg}`,
|
||||||
NEW_CONTRACT_OPTIONS)
|
NEW_CONTRACT_OPTIONS)
|
||||||
manual.version = Number(adjusted?.version ?? manual.version)
|
manual.version = Number(adjusted?.version ?? manual.version)
|
||||||
const deletedManual = await deleteResult(context, target, manual, '删除本轮人工改重图片及结果')
|
const deletedManual = await deleteResult(context, target, manual, '删除本轮人工改重图片及结果')
|
||||||
if (deletedManual.passed) await verifyDeleted(context, target, manual, baselineWeight)
|
if (deletedManual.passed) await verifyDeleted(context, target, manual, baselineWorkspaceWeight)
|
||||||
} finally {
|
} finally {
|
||||||
for (const item of created.filter((value) => !value.deleted)) {
|
for (const item of created.filter((value) => !value.deleted)) {
|
||||||
await deleteResult(context, target, item, '异常收尾清理本轮重量结果', NEW_CONTRACT_OPTIONS)
|
await deleteResult(context, target, item, '异常收尾清理本轮重量结果', NEW_CONTRACT_OPTIONS)
|
||||||
@@ -433,11 +531,10 @@ export async function run(context) {
|
|||||||
const cleanup = await loadWorkspace(context, target, '最终清理后回查重量工作区')
|
const cleanup = await loadWorkspace(context, target, '最终清理后回查重量工作区')
|
||||||
const createdIds = new Set(created.map((item) => item.resultId))
|
const createdIds = new Set(created.map((item) => item.resultId))
|
||||||
const leftovers = (cleanup.workspace?.weightResults || []).filter((row) => createdIds.has(id(row.id)))
|
const leftovers = (cleanup.workspace?.weightResults || []).filter((row) => createdIds.has(id(row.id)))
|
||||||
const restored = leftovers.length === 0 && (baselineWeight == null
|
const restored = leftovers.length === 0
|
||||||
? cleanup.workspace?.actualGrossWeightKg == null
|
&& workspaceWeightMatchesBaseline(cleanup.workspace, baselineWorkspaceWeight)
|
||||||
: closeNumber(cleanup.workspace?.actualGrossWeightKg, baselineWeight))
|
context.assert(MODULE, '仅清理本轮结果且工作区重量恢复运行前基线', cleanup.response,
|
||||||
context.assert(MODULE, '仅清理本轮结果且订单总毛重恢复运行前基线', cleanup.response,
|
restored, `leftovers=${leftovers.length}, baselineWorkspaceWeight=${baselineWorkspaceWeight ?? 'null'}, actual=${cleanup.workspace?.actualGrossWeightKg ?? 'null'}`,
|
||||||
restored, `leftovers=${leftovers.length}, baseline=${baselineWeight ?? 'null'}, actual=${cleanup.workspace?.actualGrossWeightKg ?? 'null'}`,
|
|
||||||
NEW_CONTRACT_OPTIONS)
|
NEW_CONTRACT_OPTIONS)
|
||||||
context.createdResources.inboundWeightOcr.cleaned = restored
|
context.createdResources.inboundWeightOcr.cleaned = restored
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user