// Internal probes invoked only by the registered dropship matrix and its fresh fixtures. // A failed expectation remains a deployment blocker. Continue other probes only when // authoritative task/inventory snapshots prove the failed request changed no state. const MODULE = 'dropship-pallet-matrix' const OPTIONS = { scenario: 'dropship-outbound-edge-cases', deploymentBlocker: true } const BASE = '/api/platform/v1/last-leg/outbound-orders' // Expected progress comes from submitted fixture operations, never the server response. export function receivingProgressMatches(snapshot, expected) { const count = value => (typeof value === 'number' || typeof value === 'string' && /^\d+$/.test(value)) && Number.isSafeInteger(Number(value)) && Number(value) >= 0 ? Number(value) : NaN if (!Array.isArray(snapshot?.inbounds) || !Array.isArray(expected) || !expected.length) return false if (new Set(expected.map(row => row.inboundCode)).size !== expected.length || snapshot.inbounds.length !== expected.length || new Set(snapshot.inbounds.map(row => row.inboundCode)).size !== expected.length) return false let total = 0 let received = 0 for (const row of expected) { if (!Number.isInteger(row.total) || !Number.isInteger(row.received) || row.total <= 0 || row.received < 0 || row.received > row.total) return false const actual = snapshot.inbounds.find(item => item.inboundCode === row.inboundCode) if (!actual || count(actual.totalPackages) !== row.total || count(actual.receivedPackages) !== row.received || count(actual.pendingPackages) !== row.total - row.received) return false total += row.total received += row.received } return count(snapshot.totalPackages) === total && count(snapshot.receivedPackages) === received && count(snapshot.pendingPackages) === total - received } function record(edge, name, response, passed, evidence = '') { edge.context.assert(MODULE, `${edge.label}${name}`, response, passed, evidence, OPTIONS) } async function request(edge, method, path, body, pda = false) { return edge.context.requestOverseas(method, path, body, {}, pda ? edge.token : edge.context.overseasToken) } async function candidates(edge) { const rows = await edge.call('边界测试回查本轮库存候选', 'GET', edge.candidatePath) return rows.filter(row => edge.group.some(x => x.source.inboundCode === row.inboundCode)) .map(row => ({ position: row.inventoryPositionId, sku: row.skuCode, inbound: row.inboundCode, packages: row.availablePackages })) .sort((a, b) => `${a.position}:${a.sku}`.localeCompare(`${b.position}:${b.sku}`)) } async function taskSnapshot(edge) { return edge.call('边界测试回查出库冻结事实', 'GET', `${BASE}/${edge.task.outboundNo}`) } export async function outboundCandidateGuards(edge) { const warehouse = encodeURIComponent(edge.warehouseId) const customer = encodeURIComponent(edge.customerId) const paths = [ ['平台首次查询不传客户', `${BASE}/one-click-dropship/candidates?warehouseId=${warehouse}`], ['平台指定客户查询', `${BASE}/one-click-dropship/candidates?customerId=${customer}&warehouseId=${warehouse}`], ] const snapshots = [] for (const [name, path] of paths) { const rows = await edge.call(name, 'GET', path) edge.check(`${edge.label}${name}返回候选数组`, null, Array.isArray(rows)) edge.check(`${edge.label}${name}仓库隔离`, null, rows.every(row => String(row.overseasWarehouseId) === String(edge.warehouseId))) if (name === '平台指定客户查询') { edge.check(`${edge.label}指定客户不返回其他客户库存`, null, rows.every(row => String(row.customerId) === String(edge.customerId))) } const own = rows.filter(row => edge.group.some(x => x.source.inboundCode === row.inboundCode)) for (const record of edge.group) { const selected = own.filter(row => row.inboundCode === record.source.inboundCode) const expectedPallets = [...new Set(record.task.pallets.map(p => p.palletNo))].sort() const actualPallets = [...new Set(selected.map(row => row.palletNo))].sort() edge.check(`${record.label}${name}全部托盘与箱数完整`, null, JSON.stringify(expectedPallets) === JSON.stringify(actualPallets) && selected.reduce((sum, row) => sum + Number(row.availablePackages), 0) === record.expectedCartonMarks.length && selected.every(row => String(row.customerId) === String(edge.customerId))) } snapshots.push(own.map(row => ({ inbound: row.inboundCode, pallet: row.palletNo, position: String(row.inventoryPositionId), sku: row.skuCode, packages: Number(row.availablePackages) })) .sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)))) } edge.check(`${edge.label}客户筛选前后本轮库存事实一致`, null, JSON.stringify(snapshots[0]) === JSON.stringify(snapshots[1])) } export async function outboundCreationGuards(edge) { const before = await candidates(edge) const first = edge.body.items[0] const cases = [ ['超量创建', { ...first, packages: first.packages + 100, cartonMarks: [] }], ['错误SKU创建', { ...first, sku: `WRONG-${edge.context.marker}` }], ['不存在箱唛创建', { ...first, packages: 1, cartonMarks: [`MISSING-${edge.context.marker}`] }], ['重复箱唛创建', { ...first, packages: 2, cartonMarks: [first.cartonMarks[0], first.cartonMarks[0]] }], ['箱数与箱唛不符创建', { ...first, packages: first.packages + 1 }], ] for (const [index, [name, input]] of cases.entries()) { const response = await request(edge, 'POST', `${BASE}/one-click-dropship?customerId=${edge.customerId}`, { ...edge.body, items: [input], idempotencyKey: `${edge.body.idempotencyKey}-invalid-${index}` }) record(edge, `${name}返回A0409`, response, response.body?.code === 'A0409') const after = await candidates(edge) edge.check(`${edge.label}${name}后库存候选零漂移`, null, JSON.stringify(before) === JSON.stringify(after)) } // Two real concurrent requests, same fixture and same key; no parallel fixture workers. const body = { ...edge.body, idempotencyKey: `${edge.body.idempotencyKey}-race-cancel` } const create = () => request(edge, 'POST', `${BASE}/one-click-dropship?customerId=${edge.customerId}`, body) const responses = await Promise.all([create(), create()]) const tasks = responses.filter(r => r.body?.code === '200').map(r => r.body.data) record(edge, '并发同键创建均成功且只生成一个任务', responses[1], tasks.length === 2 && tasks[0].taskNo === tasks[1].taskNo && tasks[0].outboundNo === tasks[1].outboundNo) edge.check(`${edge.label}并发创建至少一个可回查结果`, null, tasks.length > 0) for (const outboundNo of new Set(tasks.map(t => t.outboundNo))) { for (let replay = 0; replay < 2; replay++) { const cancelled = await request(edge, 'POST', `${BASE}/${outboundNo}/cancel`, { reason: '本轮未拣取消及重放验收' }) edge.check(`${edge.label}未拣取消及重复取消成功`, cancelled, cancelled.body?.code === '200') } const detail = await edge.call('回查取消任务状态', 'GET', `${BASE}/${outboundNo}`) edge.check(`${edge.label}取消后明细全部取消`, null, detail.outbound?.status === 'CANCELLED' && detail.pallets.every(p => p.status === 'CANCELLED' && Number(p.pickedPackageCount) === 0)) } edge.check(`${edge.label}取消后逐票SKU可用箱数恢复`, null, JSON.stringify(before) === JSON.stringify(await candidates(edge))) } export async function outboundTaskGuards(edge) { const before = await taskSnapshot(edge) const first = edge.body.items[0] const changedBodies = [ ['收件人', { ...edge.body, recipient: `${edge.body.recipient}-changed` }], ['备注', { ...edge.body, remark: `${edge.body.remark || ''}-changed` }], ['明细', { ...edge.body, items: [{ ...first, packages: first.packages + 1 }, ...edge.body.items.slice(1)] }], ['箱唛', { ...edge.body, items: [{ ...first, cartonMarks: [`CHANGED-${edge.context.marker}`, ...first.cartonMarks.slice(1)] }, ...edge.body.items.slice(1)] }], ] for (const [field, body] of changedBodies) { const changed = await request(edge, 'POST', `${BASE}/one-click-dropship?customerId=${edge.customerId}`, body) record(edge, `出库创建同键异载荷返回A0409:${field}`, changed, changed.body?.code === 'A0409') edge.check(`${edge.label}同键修改${field}后出库事实零漂移`, null, JSON.stringify(before) === JSON.stringify(await taskSnapshot(edge))) } const path = `/api/pda/v1/last-leg/outbound-tasks/${edge.task.taskNo}` const incomplete = await request(edge, 'POST', `${path}/complete`, { idempotencyKey: `${edge.body.idempotencyKey}-incomplete` }, true) record(edge, '未拣齐禁止完成出库', incomplete, incomplete.body?.code === 'A0409') edge.check(`${edge.label}未拣齐完成失败后零漂移`, null, JSON.stringify(before) === JSON.stringify(await taskSnapshot(edge))) } export async function probePartialPick(edge) { const before = await taskSnapshot(edge) const path = `/api/pda/v1/last-leg/outbound-tasks/${edge.task.taskNo}/pick` const partial = { ...edge.pick, packages: 1, cartonMarks: edge.pick.cartonMarks.slice(0, 1), idempotencyKey: `${edge.pick.idempotencyKey}-partial` } const result = await request(edge, 'POST', path, partial, true) record(edge, '四箱任务先拣一箱成功', result, result.body?.code === '200') const after = await taskSnapshot(edge) if (result.body?.code !== '200') { edge.check(`${edge.label}分批拣货失败后无库存明细变化`, null, JSON.stringify(before) === JSON.stringify(after)) // Keep the failed assertion; whole-item picking below only continues later coverage. return 0 } edge.check(`${edge.label}分批首箱只累计一箱`, null, after.pallets.reduce((sum, p) => sum + Number(p.pickedPackageCount), 0) === 1) const replay = await request(edge, 'POST', path, partial, true) record(edge, '分批首箱同键重放成功', replay, replay.body?.code === '200') edge.check(`${edge.label}分批重放不重复扣减`, null, JSON.stringify(after) === JSON.stringify(await taskSnapshot(edge))) const cancel = await request(edge, 'POST', `${BASE}/${edge.task.outboundNo}/cancel`, { reason: '已拣取消拦截验收' }) record(edge, '部分已拣禁止取消', cancel, cancel.body?.code === 'A0409') edge.check(`${edge.label}已拣取消失败后零漂移`, null, JSON.stringify(after) === JSON.stringify(await taskSnapshot(edge))) return 1 }