// 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} 不守恒`) } }