优化
This commit is contained in:
@@ -5,6 +5,15 @@ import { normalizeModuleOrder } from '../admin-test-env-acceptance.mjs'
|
||||
|
||||
const source = readFileSync(new URL('./dropship-pallet-matrix.mjs', import.meta.url), 'utf8')
|
||||
|
||||
test('dedicated outbound uses exact source cartons and verifies completion and stock deduction', () => {
|
||||
assert.match(source, /await outboundOverseas\(group, picker.token\)/)
|
||||
assert.match(source, /one-click-dropship\?customerId=/)
|
||||
assert.match(source, /position.inboundCode === candidate.inboundCode/)
|
||||
assert.match(source, /cartonMarks: inputs\[index\].cartonMarks/)
|
||||
assert.match(source, /outbound.outbound\?\.status === 'OUTBOUNDED'/)
|
||||
assert.match(source, /!remaining.some/)
|
||||
})
|
||||
|
||||
test('dropship matrix has a real customer prerequisite without generic dispatch fixtures', () => {
|
||||
const modules = normalizeModuleOrder(['dropship-pallet-matrix'])
|
||||
assert.ok(modules.includes('merchant-qualification-fixture'))
|
||||
|
||||
@@ -321,7 +321,8 @@ export async function run(context) {
|
||||
async function receiveOverseas(group) {
|
||||
const record = group[0]
|
||||
const label = record.label
|
||||
context.marker = `${marker}-${label}-RECEIVE`
|
||||
// The PDA account helper keeps the marker tail; retain the run identity there.
|
||||
context.marker = `${label}-RECEIVE-${marker}`
|
||||
const base = '/api/pda/v1/last-leg/receiving-containers'
|
||||
const access = await resolvePickerAccess(context)
|
||||
check(`${label}海外PDA角色可用`, null, Boolean(access))
|
||||
@@ -376,10 +377,75 @@ export async function run(context) {
|
||||
check(`${label}海外入库完成且重放稳定`, null, completed.status === 'COMPLETED'
|
||||
&& JSON.stringify(replay) === JSON.stringify(completed))
|
||||
for (const item of group) item.overseas.receiving = { completed, current, receivingVerified: true }
|
||||
await outboundOverseas(group, picker.token)
|
||||
} finally {
|
||||
await disablePicker(context, picker, records.indexOf(record) + 1)
|
||||
}
|
||||
}
|
||||
async function outboundOverseas(group, token) {
|
||||
const label = group[0].label
|
||||
const base = '/api/platform/v1/last-leg/outbound-orders'
|
||||
const customerId = String(group[0].task.tenantId)
|
||||
const warehouseId = group[0].overseas.warehouseId
|
||||
async function call(name, method, path, body, pda = false) {
|
||||
const response = await context.requestOverseas(method, path, body, {}, pda ? token : context.overseasToken)
|
||||
const value = context.apiSuccess(MODULE, `${label}${name}`, response, '', OPTIONS)
|
||||
check(`${label}${name}成功`, response, response?.body?.code === '200' && value != null)
|
||||
return value
|
||||
}
|
||||
const candidatePath = `${base}/one-click-dropship/candidates?customerId=${customerId}&warehouseId=${warehouseId}`
|
||||
const all = await call('读取专用出库可用库存', 'GET', candidatePath)
|
||||
const selected = all.filter((candidate) => group.some((record) => record.source.inboundCode === candidate.inboundCode))
|
||||
const inputs = selected.map((candidate) => {
|
||||
const record = group.find((item) => item.source.inboundCode === candidate.inboundCode)
|
||||
const pallet = record.task.pallets.find((item) => item.palletNo === candidate.palletNo)
|
||||
const cartonMarks = (pallet?.cartons || []).filter((carton) =>
|
||||
String(carton.inboundId) === String(record.source.inboundId)).map((carton) => carton.boxCode)
|
||||
check(`${label}专用出库候选逐票逐托箱数准确`, null,
|
||||
cartonMarks.length > 0 && Number(candidate.availablePackages) === cartonMarks.length
|
||||
&& String(candidate.customerId) === customerId && String(candidate.overseasWarehouseId) === warehouseId)
|
||||
return { inventoryPositionId: candidate.inventoryPositionId, packages: cartonMarks.length,
|
||||
sku: candidate.skuCode, cartonMarks }
|
||||
})
|
||||
check(`${label}专用出库候选覆盖全部票和箱`, null, group.every((record) =>
|
||||
selected.filter((item) => item.inboundCode === record.source.inboundCode)
|
||||
.reduce((sum, item) => sum + Number(item.availablePackages), 0) === record.expectedCartonMarks.length))
|
||||
const body = { recipient: `HTTP acceptance ${context.marker}`, items: inputs,
|
||||
remark: context.marker, idempotencyKey: `${context.marker}-outbound-create` }
|
||||
const task = await call('创建一件代发专用出库', 'POST', `${base}/one-click-dropship?customerId=${customerId}`, body)
|
||||
for (const record of group) record.outbound = { task, selected, completed: false }
|
||||
const replay = await call('专用出库创建同键重放', 'POST', `${base}/one-click-dropship?customerId=${customerId}`, body)
|
||||
check(`${label}专用出库重放不重复建单`, null, JSON.stringify(replay) === JSON.stringify(task))
|
||||
const pdaPath = `/api/pda/v1/last-leg/outbound-tasks/${task.taskNo}`
|
||||
const detail = await call('PDA读取专用出库任务', 'GET', pdaPath, undefined, true)
|
||||
check(`${label}PDA出库明细完整`, null, detail.items?.length === inputs.length)
|
||||
for (const [index, candidate] of selected.entries()) {
|
||||
const item = detail.items.find((row) => row.palletNo === candidate.palletNo && row.skuCode === candidate.skuCode)
|
||||
check(`${label}PDA拣货来源仓位和入仓码一致`, null, Boolean(item)
|
||||
&& item.positions?.some((position) => String(position.inventoryPositionId) === String(candidate.inventoryPositionId)
|
||||
&& position.inboundCode === candidate.inboundCode))
|
||||
const pick = { outboundPalletItemId: item.outboundPalletItemId,
|
||||
inventoryPositionId: candidate.inventoryPositionId, packages: inputs[index].packages,
|
||||
cartonMarks: inputs[index].cartonMarks, idempotencyKey: `${context.marker}-outbound-pick-${index}` }
|
||||
const picked = await call('PDA专用出库拣货', 'POST', `${pdaPath}/pick`, pick, true)
|
||||
const repeated = await call('PDA出库拣货同键重放', 'POST', `${pdaPath}/pick`, pick, true)
|
||||
check(`${label}PDA出库拣货重放不重复扣减`, null, JSON.stringify(repeated) === JSON.stringify(picked))
|
||||
}
|
||||
const completion = { idempotencyKey: `${context.marker}-outbound-complete` }
|
||||
const completed = await call('PDA完成专用出库', 'POST', `${pdaPath}/complete`, completion, true)
|
||||
const repeated = await call('PDA专用出库完成重放', 'POST', `${pdaPath}/complete`, completion, true)
|
||||
check(`${label}专用出库任务完成且重放稳定`, null,
|
||||
completed.status === 'COMPLETED' && JSON.stringify(repeated) === JSON.stringify(completed))
|
||||
const outbound = await call('回查专用出库单终态', 'GET', `${base}/${task.outboundNo}`)
|
||||
check(`${label}专用出库单真实完成且箱数核平`, null,
|
||||
outbound.outbound?.status === 'OUTBOUNDED' && outbound.outbound?.outboundType === 'ONE_CLICK_DROPSHIP'
|
||||
&& Number(outbound.outbound.packageCount) === inputs.reduce((sum, item) => sum + item.packages, 0)
|
||||
&& outbound.pallets.every((item) => Number(item.packageCount) === Number(item.pickedPackageCount)))
|
||||
const remaining = await call('回查专用出库可用库存扣减', 'GET', candidatePath)
|
||||
check(`${label}本轮每票库存全部扣减`, null,
|
||||
!remaining.some((candidate) => group.some((record) => record.source.inboundCode === candidate.inboundCode)))
|
||||
for (const record of group) record.outbound = { task: completed, selected, detail: outbound, completed: true }
|
||||
}
|
||||
try {
|
||||
await verify(await prepare('SINGLE-MULTI', 2), 2)
|
||||
await verify(await prepare('SINGLE-SINGLE', 1), 1)
|
||||
@@ -402,7 +468,7 @@ export async function run(context) {
|
||||
await convertOverseas(group)
|
||||
await receiveOverseas(group)
|
||||
}
|
||||
context.createdResources.dropshipPalletMatrix.stage = 'OVERSEAS_RECEIVING_COMPLETED'
|
||||
context.createdResources.dropshipPalletMatrix.stage = 'DEDICATED_OUTBOUND_COMPLETED'
|
||||
} finally {
|
||||
context.marker = marker
|
||||
context.options.prepareDropshipManualCount = savedOptions
|
||||
|
||||
@@ -91,6 +91,19 @@ async function verifyTruckDeparturePayables(context, aggregate) {
|
||||
&& row.direction === 'PAYABLE' && row.reviewStatus === 'APPROVED'
|
||||
&& Math.round(Number(row.amount) * 100) === Math.round(Number(fees.get(id(row.sourceFeeId)).amount) * 100)),
|
||||
`expected=${fees.size}, imported=${imported.size}`, OPTIONS)
|
||||
const candidates = new Map()
|
||||
for (const customer of new Set([...fees.values()].map(fee => id(fee.tenantId)))) {
|
||||
const response = await request(context, 'GET', `/api/platform/finance/v1/fee-pool?customerId=${customer}&direction=PAYABLE&supplierId=${id(truck.carrierId)}`)
|
||||
const rows = context.apiSuccess(MODULE, '回查拖车费可用于应付结算', response, '', OPTIONS)
|
||||
for (const row of Array.isArray(rows) ? rows : []) {
|
||||
if (row.sourceType === 'CONTAINER_FEE' && fees.has(id(row.sourceId))) candidates.set(id(row.sourceId), row)
|
||||
}
|
||||
}
|
||||
context.assert(MODULE, '拖车费已进入应付结算候选且未付款', null,
|
||||
candidates.size === fees.size && [...candidates.values()].every(row => row.direction === 'PAYABLE'
|
||||
&& row.settlementMode === 'ON_ACCOUNT' && row.status === 'AVAILABLE' && !row.paidAt
|
||||
&& Math.round(Number(row.amount) * 100) === Math.round(Number(fees.get(id(row.sourceId)).amount) * 100)),
|
||||
`expected=${fees.size}, candidates=${candidates.size}`, OPTIONS)
|
||||
}
|
||||
|
||||
export async function run(context) {
|
||||
|
||||
Reference in New Issue
Block a user