Files
baiyue-firstleg/scripts/prod-release-measurement-plan.mjs

75 lines
4.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Shared declaration / measurement arithmetic. Pure functions; no HTTP or master-data writes.
export const CARTON_PROFILE = Object.freeze({ lengthCm: 40, widthCm: 30, heightCm: 30, weightKg: 7.5 })
export function cartonTotals(packageCount) {
if (!Number.isInteger(packageCount) || packageCount < 1 || packageCount > 2) throw new Error('单票冒烟仅允许 1~2 箱')
return { packageCount, lengthCm: 40, widthCm: 30, heightCm: 30,
grossWeightKg: packageCount * 7.5, volumeCbm: packageCount * 36 / 1000 }
}
export function normalizeOrderInput(input) {
if (!Array.isArray(input?.payload?.cargoes) || input.payload.cargoes.length !== 1) throw new Error('单票冒烟仅支持一个 SKU')
const cargo = input.payload.cargoes[0]
const totals = cartonTotals(cargo.packageCount)
if (cargo.netWeightKg != null && (!Number.isFinite(Number(cargo.netWeightKg))
|| Number(cargo.netWeightKg) <= 0 || Number(cargo.netWeightKg) > totals.grossWeightKg)) {
throw new Error('申报商品净重无效或超过含箱货重;不能据此推算商品净重')
}
return { ...input, payload: { ...input.payload, cargoes: [{ ...cargo, ...totals }] } }
}
function scaled(value, places, label, allowZero = false) {
const text = String(value)
if (!/^\d+(?:\.\d+)?$/.test(text)) throw new Error(`${label} 无效`)
const [whole, fraction = ''] = text.split('.')
if (fraction.slice(places).replace(/0/g, '')) throw new Error(`${label} 超出允许精度`)
const result = BigInt(whole) * 10n ** BigInt(places) + BigInt(fraction.slice(0, places).padEnd(places, '0'))
if ((!allowZero && result === 0n) || result > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error(`${label} 超出安全范围`)
return result
}
export function measurementPlan({ packageCount, warehouseId, pallets, allowedCodes }) {
const totals = cartonTotals(packageCount)
if (!Array.isArray(allowedCodes) || !allowedCodes.length || new Set(allowedCodes).size !== allowedCodes.length
|| allowedCodes.some(code => typeof code !== 'string' || !code.trim())) throw new Error('需要明确允许复用的既有托盘编码')
if (typeof warehouseId !== 'string' || !/^[1-9]\d*$/.test(warehouseId)) throw new Error('仓库 ID 无效')
const candidates = pallets.filter(p => p.warehouseId === warehouseId && allowedCodes.includes(p.palletCode))
// Resolve every allowed code exactly; do not silently choose another warehouse or create a replacement.
for (const code of allowedCodes) {
if (candidates.filter(p => p.palletCode === code).length !== 1) throw new Error('允许的托盘缺失或编码重复')
}
const pallet = candidates.find(p => p.palletCode === allowedCodes[0])
if (typeof pallet.id !== 'string' || !/^[1-9]\d*$/.test(pallet.id)) throw new Error('托盘 ID 无效')
const capacity = scaled(pallet.capacityCbm, 6, '托容量')
const tare = scaled(pallet.weightKg, 3, '托盘皮重', true)
const cartonVolume = 36000n // cubic centimetres, i.e. 0.036 m³
const totalVolume = cartonVolume * BigInt(packageCount)
const count = Number((totalVolume + capacity - 1n) / capacity)
const cartonsPerPallet = Number(capacity / cartonVolume)
if (!cartonsPerPallet || count > packageCount || cartonsPerPallet * count < packageCount) {
throw new Error('按体积取整无法容纳完整箱,需人工确认托盘容量或装托规则')
}
let remaining = packageCount
const weights = Array.from({ length: count }, (_, index) => {
const cartons = Math.min(cartonsPerPallet, remaining - (count - index - 1))
remaining -= cartons
const netGrams = BigInt(cartons) * 7500n
if (netGrams + tare > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error('连托毛重超出安全范围')
return { sequence: index + 1, palletId: pallet.id, palletCode: pallet.palletCode,
packageCount: cartons, volumeCbm: cartons * 36 / 1000,
palletTareWeightKg: Number(tare) / 1000, netWeightKg: Number(netGrams) / 1000,
grossWeightKg: Number(netGrams + tare) / 1000 }
})
return { status: 'MEASUREMENT_PLAN_ONLY', fullFlowVerified: false, ...totals,
cargoWeightKg: totals.grossWeightKg, palletCount: count, capacityCbm: Number(capacity) / 1_000_000,
weights, weightGrossTotalKg: weights.reduce((sum, w) => sum + Math.round(w.grossWeightKg * 1000), 0) / 1000,
locationPolicy: 'SERVER_ALLOCATES_EXISTING_OR_A0', measurementWritten: false }
}
export function assertConfirmedWeight(actual, expected) {
if (actual?.recognitionStatus !== 'CONFIRMED' || actual.palletId !== expected.palletId) throw new Error('称重确认身份或状态不符')
for (const key of ['grossWeightKg', 'netWeightKg', 'palletTareWeightKg']) {
if (scaled(actual[key], 3, key, true) !== scaled(expected[key], 3, key, true)) throw new Error(`称重回查 ${key} 不守恒`)
}
}