import { receivingProgressMatches } from './dropship-outbound-edge-cases.mjs' export async function run(context) { const { runMixedContainer } = await import('./dropship-pallet-matrix.mjs') return runMixedContainer(context) } export function mixedReceivingSchedule(group) { const fba = group.filter(r => r.order.fulfillmentType === 'STANDARD' && r.order.destinationType === 'FBA') const overseas = group.filter(r => r.order.fulfillmentType === 'STANDARD' && r.order.destinationType === 'OVERSEAS') const dropship = group.filter(r => r.order.fulfillmentType === 'ONE_CLICK_DROPSHIP') if (group.length !== 3 || fba.length !== 1 || overseas.length !== 1 || dropship.length !== 1 || dropship[0].task?.pallets?.length !== 2) throw new Error('混柜必须包含普通FBA、普通海外仓及一件代发双托三票') const [f, o, d] = [fba[0], overseas[0], dropship[0]] const fbaPallet = f.overseas.plan.pallets?.[0]?.palletNo const overseasPallet = o.overseas.plan.pallets?.[0]?.palletNo if (!fbaPallet || !overseasPallet || f.expectedCartonMarks.length < 2) throw new Error('混柜普通订单缺少真实托盘或分批箱数') const palletStep = pallet => ({ record: d, path: 'pallet', count: pallet.cartons.filter(c => String(c.inboundId) === String(d.source.inboundId)).length, body: { sourcePalletNo: pallet.palletNo } }) return [ { record: f, path: 'bulk', count: 1, body: { inboundCode: f.source.inboundCode, palletCode: fbaPallet, packageCount: 1 } }, palletStep(d.task.pallets[0]), ...o.expectedCartonMarks.map(cartonMark => ({ record: o, path: 'carton', count: 1, body: { cartonMark, palletCode: overseasPallet } })), { record: f, path: 'bulk', count: f.expectedCartonMarks.length - 1, body: { inboundCode: f.source.inboundCode, palletCode: fbaPallet, packageCount: f.expectedCartonMarks.length } }, palletStep(d.task.pallets[1]), ] } export async function receiveMixedContainer({ group, call, check, request, marker }) { const base = '/api/pda/v1/last-leg/receiving-containers/current' const steps = mixedReceivingSchedule(group) const expected = group.map(r => ({ inboundCode: r.source.inboundCode, total: r.expectedCartonMarks.length, received: 0 })) for (const record of group.filter(r => r.order.fulfillmentType === 'ONE_CLICK_DROPSHIP' || r.order.destinationType === 'OVERSEAS')) { const before = await call('混柜错误收货模式前回查', 'GET', base) const invalid = await request('POST', `${base}/receipts/bulk`, { inboundCode: record.source.inboundCode, palletCode: record.overseas.plan.pallets[0].palletNo, packageCount: 1, idempotencyKey: `${marker}-forbid-bulk-${record.label}`, }) check('普通海外仓和一件代发均不能绕过各自规则使用FBA批量收货', invalid, invalid.body?.code === 'A0409') const after = await call('混柜错误收货模式拒绝后回查', 'GET', base) check('混柜错误收货模式拒绝后零漂移', null, JSON.stringify(before) === JSON.stringify(after)) } for (const [index, step] of steps.entries()) { const before = await call('混柜交替收货前进度', 'GET', base) check('混柜每步前逐票和柜级进度准确', null, receivingProgressMatches(before, expected)) const premature = await request('POST', `${base}/complete`, { allowDifference: false, idempotencyKey: `${marker}-early-${index}` }) check('混柜任一票未齐不得正常完成', premature, premature.body?.code === 'A0409') const afterRejected = await call('混柜提前完成拒绝后回查', 'GET', base) check('混柜提前完成拒绝后零漂移', null, JSON.stringify(afterRejected) === JSON.stringify(before)) const path = `${base}/receipts/${step.path}` const body = { ...step.body, idempotencyKey: `${marker}-mixed-receive-${index}` } const received = await call(`${step.record.label}交替收货`, 'POST', path, body) check('混柜本次只收当前操作对应箱数', null, Number(received.acceptedPackages) === step.count) const replay = await call('混柜收货同键重放', 'POST', path, body) check('混柜同键重放结果稳定', null, JSON.stringify(replay) === JSON.stringify(received)) expected.find(row => row.inboundCode === step.record.source.inboundCode).received += step.count const after = await call('混柜每步后逐票回查', 'GET', base) check('混柜交替收货不串票不重计', null, receivingProgressMatches(after, expected)) const changedBody = step.path === 'pallet' ? { ...body, sourcePalletNo: `${body.sourcePalletNo}-CHANGED` } : step.path === 'bulk' ? { ...body, packageCount: body.packageCount + 1 } : { ...body, palletCode: `${body.palletCode}-CHANGED` } const changed = await request('POST', path, changedBody) check('混柜收货同键异载荷必须冲突', changed, changed.body?.code === 'A0409') const duplicate = await request('POST', path, { ...body, idempotencyKey: `${body.idempotencyKey}-duplicate` }) check('混柜同货物新键重复操作不重复收货', duplicate, step.path === 'carton' ? duplicate.body?.code === 'A0409' : duplicate.body?.code === '200' && duplicate.body?.data?.result === 'ALREADY_RECEIVED' && Number(duplicate.body?.data?.acceptedPackages) === 0) const afterRetries = await call('混柜重复和冲突请求后回查全部票', 'GET', base) check('混柜冲突及新键重复后全柜事实不变', null, JSON.stringify(afterRetries) === JSON.stringify(after)) } const body = { allowDifference: false, idempotencyKey: `${marker}-mixed-complete` } const completed = await call('混柜全部核平后正常完成入库', 'POST', `${base}/complete`, body) check('混柜三票全收且普通完成', null, completed.status === 'COMPLETED' && receivingProgressMatches(completed, expected)) const replay = await call('混柜入库完成重放', 'POST', `${base}/complete`, body) check('混柜完成重放不重复入库', null, JSON.stringify(replay) === JSON.stringify(completed)) return completed }