优化
This commit is contained in:
@@ -11,7 +11,7 @@ const OPTIONAL_MODULES = ['phase4-overseas-return-followups', 'manual-order-spli
|
||||
'manual-pda-ready-fixture', 'manual-pda-ready-verify',
|
||||
'manual-pda-batch-fixture', 'manual-pda-batch-verify',
|
||||
'manual-loading-complete-fixture', 'manual-loading-complete-verify',
|
||||
'coload-groups', 'dropship-pallet-matrix', 'dropship-browser-fixture', 'mixed-container-receiving']
|
||||
'coload-groups', 'dropship-pallet-matrix', 'dropship-browser-fixture', 'mixed-container-receiving', 'pda-loading-link-regression']
|
||||
const AVAILABLE_MODULES = new Set([...CONFIGURED_MODULES, ...OPTIONAL_MODULES])
|
||||
const A0_LOCATION_PAGE_SIZE = 200
|
||||
const A0_MAX_LOCATION_MUTATIONS = 8
|
||||
@@ -42,6 +42,7 @@ export function planA0LocationIsolation(locations, requiredSlots = 1,
|
||||
}
|
||||
|
||||
const MODULE_DEPENDENCIES = new Map([
|
||||
['pda-loading-link-regression', ['merchant-qualification-fixture']],
|
||||
['phase4-overseas-return-followups', ['phase4-overseas-return-receipt']],
|
||||
['dropship-pallet-matrix', ['merchant-qualification-fixture']],
|
||||
['dropship-browser-fixture', ['merchant-qualification-fixture']],
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
总入口是 `../admin-test-env-acceptance.mjs`。每个业务模块使用独立的同名 `.mjs` 文件,避免多个对话同时修改总入口。
|
||||
|
||||
`pda-loading-link-regression.mjs` 通过总入口 `--modules pda-loading-link-regression` 运行,复用 `dropship-pallet-matrix.mjs` 的国内正式订单、测量、封托、订舱、证据和完成生产链,仅执行国内关联回归。覆盖单票加入后全移除换柜完成、多票共托反复换三个旧柜后完成、逐托即时提交及同键重放,以及一单多柜中仍有实际分配的未开始柜导致 `OWNER_READY/A0409`。每次重新创建带 marker 的业务数据,通过公开 HTTP 回查旧柜有效分配数、PDA 终态和入仓状态;不写业务数据库伪造前置。零分配残留由真实整托移除链观察,报告保存 `createdResources.pdaLoadingLinkRegression`。负向柜保留为 `COMPLETION_FAILED`,成功柜保留为 `COMPLETED`,不自动清理;失败报告保留且不得算通过。使用显式测试环境文件及 `--output reports/<本轮名称>.json`,须有真实 HTTP 写入授权;不启动海外收货或出库。
|
||||
|
||||
退货专项同时核验新建退货单、退货主单、签收单、批次和自动箱唛的完整业务编号:`RT/RR/RB/RC/RETU-` 加 25 位大写 Base36 UUID 编码;不再截取内部 ID 六位尾号。幂等重放仍核对同一已保存编号,手工指定箱唛不受系统生成格式约束。编号生成器另有完整 128 位编码往返、原截断碰撞和本地并行生成单测;本地生成压测不增加 HTTP 并发。
|
||||
|
||||
退货专项只运行 `--modules phase4-overseas-return-receipt`,自动准备本轮客户;覆盖非法/重复预报、退货单号和 SKU 查询、非法/未知 SKU 实收、超收及失败不落账、分批签收完成、部分签收禁止合箱、合箱非法箱规/数量/来源、幂等重放前后库存快照一致、逐箱分页无重复遗漏、客户 SKU 数量守恒及标签。通过显式测试环境文件运行总入口,JSON 路径由 `--output` 指定;会创建隔离测试数据,finally 停用临时 PDA,不删除业务记录。失败保留报告,先核对记录再决定是否新批次重跑。
|
||||
|
||||
@@ -13,6 +13,7 @@ import { runUntilPickupReady } from './phase4-carrier-nodes.mjs'
|
||||
import { resolvePickerAccess, createPicker, disablePicker, uploadPhotos } from './phase4-overseas-pda.mjs'
|
||||
import { receivingProgressMatches, outboundCandidateGuards, outboundCreationGuards, outboundTaskGuards, probePartialPick } from './dropship-outbound-edge-cases.mjs'
|
||||
import { receiveMixedContainer } from './mixed-container-receiving.mjs'
|
||||
import { exerciseOldCabinets } from './pda-loading-link-regression.mjs'
|
||||
|
||||
const MODULE = 'dropship-pallet-matrix'
|
||||
const BASE = '/api/platform/v1/first-leg/pda-dropship'
|
||||
@@ -42,7 +43,11 @@ export async function runMixedContainer(context) {
|
||||
return runWorkflow(context, false, true)
|
||||
}
|
||||
|
||||
async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = false) {
|
||||
export async function runDomesticLinkRegression(context) {
|
||||
return runWorkflow(context, false, false, true)
|
||||
}
|
||||
|
||||
async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = false, linkRegression = false) {
|
||||
const marker = context.marker
|
||||
const savedOptions = context.options.prepareDropshipManualCount
|
||||
const savedContext = { ...context.context }
|
||||
@@ -122,7 +127,7 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
record.summary = inbound.summary
|
||||
record.domesticVerified = true
|
||||
}
|
||||
async function allocate(group) {
|
||||
async function allocate(group, regression = {}) {
|
||||
const label = group.map((item) => item.label).join('+')
|
||||
context.marker = `${marker}-${group[0].label}-LOAD`
|
||||
const smart = '/api/platform/v1/first-leg/smart-loading'
|
||||
@@ -141,6 +146,7 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
check(`${record.label}初始费用已计算`, null, Boolean(fee))
|
||||
}
|
||||
const summary = group[0].summary
|
||||
if (regression.rounds) await exerciseOldCabinets({ context, group, pallets, command, check, rounds: regression.rounds })
|
||||
let cabinet = await command('创建本场景模拟柜', 'POST', '/api/platform/v1/containers', {
|
||||
channelGroupCode: channelGroup(summary.channelCode), warehouseId: String(summary.warehouseId),
|
||||
containerType: '40HQ', capacityCbm: 68, maxWeightKg: 19500,
|
||||
@@ -205,6 +211,13 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
await conserve('智能推荐后')
|
||||
}
|
||||
for (const record of group) record.loading = { cabinet, plan, allocationVerified: true }
|
||||
if (regression.activeUnstarted) {
|
||||
const pallet = pallets.at(-1)
|
||||
plan = await command('分配另一实托到未开始柜前移除', 'DELETE', `${path}/pallet-assignments/${pallet.palletId}`, {
|
||||
expectedPlanVersion: Number(plan.version), operationId: `${context.marker}-split-remove`,
|
||||
})
|
||||
await exerciseOldCabinets({ context, group, pallets: [pallet], command, check, rounds: 1, active: true })
|
||||
}
|
||||
const carrier = await ensureMatsonCarrierPartner(context, MODULE, OPTIONS)
|
||||
const agent = await resolveOceanBookingAgent(context, MODULE, 'MATSON', OPTIONS)
|
||||
check(`${label}真实船司和订舱代理齐全`, null, Boolean(carrier?.id && agent?.id))
|
||||
@@ -259,8 +272,22 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
claimed.items.length > 0 && (mixedContainer
|
||||
? claimed.items.some(item => item.unitType === 'SEALED_PALLET') && claimed.items.some(item => item.unitType !== 'SEALED_PALLET')
|
||||
: claimed.items.every((item) => item.unitType === 'SEALED_PALLET')))
|
||||
const picked = await command('整托拣货', 'POST', `${pda}/batch`, batch,
|
||||
{ 'Idempotency-Key': `${context.marker}-pick` })
|
||||
let picked = claimed
|
||||
if (linkRegression) {
|
||||
for (const palletNo of [...new Set(claimed.items.map(item => item.palletNo))]) {
|
||||
const priorVersion = Number(picked.version)
|
||||
const payload = { warehouseId: String(summary.warehouseId), palletNo,
|
||||
expectedTaskVersion: priorVersion, operationId: `${context.marker}-pick-${palletNo}` }
|
||||
picked = await command('逐托即时提交', 'POST', `${pda}/pallets/pick`, payload)
|
||||
check('即时提交返回最新任务版本', null, Number(picked.version) > priorVersion)
|
||||
const replay = await command('逐托提交同键重放', 'POST', `${pda}/pallets/pick`, payload)
|
||||
check('重放不重复累加数量或版本', null,
|
||||
replay.version === picked.version && JSON.stringify(replay.items) === JSON.stringify(picked.items))
|
||||
}
|
||||
} else {
|
||||
picked = await command('整托拣货', 'POST', `${pda}/batch`, batch,
|
||||
{ 'Idempotency-Key': `${context.marker}-pick` })
|
||||
}
|
||||
check(`${label}整托拣货等待最终确认`, null, picked.workflowStatus === 'AWAITING_REVIEW')
|
||||
for (const evidenceType of ['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL']) {
|
||||
const response = await uploadEvidence(context, taskId, String(summary.warehouseId), evidenceType, 0,
|
||||
@@ -276,6 +303,18 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
}, { 'Idempotency-Key': `${context.marker}-complete` })
|
||||
const terminal = await waitForCompletion(context, `${pda}?warehouseId=${summary.warehouseId}`, `${pda}/operations`)
|
||||
const finalTask = terminal.task
|
||||
if (regression.activeUnstarted) {
|
||||
check('有实际货物的未开始柜次阻止PDA完成', terminal.taskResponse,
|
||||
finalTask?.taskStatus === 'COMPLETION_FAILED'
|
||||
&& finalTask.completionCommand?.failureStage === 'OWNER_READY'
|
||||
&& finalTask.completionCommand?.failureCode === 'A0409')
|
||||
const inbound = await command('失败后回查入仓状态未完成', 'GET',
|
||||
`/api/platform/inbounds/${group[0].source.inboundId}?tenantId=${group[0].order.tenantId}`)
|
||||
check('负向完成未推进LOADED', null, inbound.summary?.processStatus === 'LOADING')
|
||||
context.createdResources.pdaLoadingLinkRegression.scenarios.push({ name: label, taskId,
|
||||
outcome: 'ACTIVE_UNSTARTED_BLOCKED', task: finalTask })
|
||||
return
|
||||
}
|
||||
check(`${label}任务真实完成`, terminal.taskResponse, finalTask?.taskStatus === 'COMPLETED',
|
||||
`status=${finalTask?.taskStatus}, attempts=${terminal.attempts}`)
|
||||
for (const record of group) {
|
||||
@@ -285,6 +324,8 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
record.loading = { cabinet: linked, plan, task: finalTask, completed, booking: released,
|
||||
allocationVerified: true, loadingVerified: true, containerNo }
|
||||
}
|
||||
if (linkRegression) context.createdResources.pdaLoadingLinkRegression.scenarios.push({
|
||||
name: label, taskId, outcome: 'COMPLETED', task: finalTask })
|
||||
}
|
||||
async function progressShipping(group) {
|
||||
const record = group[0]
|
||||
@@ -580,6 +621,21 @@ async function runWorkflow(context, stopBeforeOverseasPda, mixedContainer = fals
|
||||
context.createdResources.dropshipPalletMatrix.stage = 'MIXED_CONTAINER_INBOUND_COMPLETED'
|
||||
return
|
||||
}
|
||||
if (linkRegression) {
|
||||
const single = await prepare('LINK-SINGLE', 2)
|
||||
await verify(single, 2)
|
||||
await allocate([single], { rounds: 1 })
|
||||
const first = await prepare('MULTI-MERGE-LINK-A', 1, { deferSeal: true })
|
||||
const second = await prepare('MULTI-MERGE-LINK-B', 1, { targetPalletNo: first.task.pallets[0].palletNo })
|
||||
await verify(first, 1)
|
||||
await verify(second, 1)
|
||||
await allocate([first, second], { rounds: 3 })
|
||||
const negative = await prepare('LINK-NEGATIVE', 2)
|
||||
await verify(negative, 2)
|
||||
await allocate([negative], { activeUnstarted: true })
|
||||
context.createdResources.dropshipPalletMatrix.stage = 'DOMESTIC_LINK_REGRESSION_VERIFIED'
|
||||
return
|
||||
}
|
||||
if (stopBeforeOverseasPda) {
|
||||
const record = await prepare('BROWSER-MULTI', 2)
|
||||
await verify(record, 2)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { runDomesticLinkRegression } from './dropship-pallet-matrix.mjs'
|
||||
import { channelGroup } from './smart-loading-channel-group.mjs'
|
||||
|
||||
export const run = runDomesticLinkRegression
|
||||
|
||||
// All resources come from this run's HTTP producer. No direct database writes.
|
||||
export async function exerciseOldCabinets({ context, group, pallets, command, check, rounds, active = false }) {
|
||||
const summary = group[0].summary
|
||||
const evidence = context.createdResources.pdaLoadingLinkRegression ||= { cabinets: [], scenarios: [] }
|
||||
for (let round = 0; round < rounds; round++) {
|
||||
const cabinet = await command('创建回归旧柜', 'POST', '/api/platform/v1/containers', {
|
||||
channelGroupCode: channelGroup(summary.channelCode), warehouseId: String(summary.warehouseId),
|
||||
containerType: '40HQ', capacityCbm: 68, maxWeightKg: 19500,
|
||||
})
|
||||
const smart = '/api/platform/v1/first-leg/smart-loading'
|
||||
let plan = await command('读取回归旧柜计划', 'GET', `${smart}/plans/by-booking/${cabinet.id}`)
|
||||
const path = `${smart}/plans/${plan.id}`
|
||||
const record = { cabinetNo: cabinet.cabinetNo, cabinetId: cabinet.id, planId: plan.id, active, rounds: round + 1 }
|
||||
evidence.cabinets.push(record)
|
||||
for (const pallet of pallets) {
|
||||
plan = await command('旧柜加入整托', 'POST', `${path}/pallet-assignments`, {
|
||||
palletId: String(pallet.palletId), expectedVersion: Number(plan.version),
|
||||
operationId: `${context.marker}-old-add-${plan.id}-${pallet.palletId}`,
|
||||
})
|
||||
}
|
||||
check('旧柜真实加入箱数大于零', null, plan.items?.reduce((sum, i) => sum + Number(i.packageCount), 0) > 0)
|
||||
if (!active) {
|
||||
for (const pallet of pallets) {
|
||||
plan = await command('旧柜全部移除整托', 'DELETE', `${path}/pallet-assignments/${pallet.palletId}`, {
|
||||
expectedPlanVersion: Number(plan.version), operationId: `${context.marker}-old-del-${plan.id}-${pallet.palletId}`,
|
||||
})
|
||||
}
|
||||
check('旧柜移除后没有有效明细', null, plan.items?.length === 0)
|
||||
}
|
||||
record.links = []
|
||||
for (const item of group) {
|
||||
const links = await command('回查旧柜关联及有效箱数', 'GET',
|
||||
`/api/platform/v1/inbounds/${item.source.inboundId}/preload/loading-plans?tenantId=${item.order.tenantId}`)
|
||||
const current = links.filter(link => String(link.loadingPlanId) === String(plan.id))
|
||||
record.links.push(...current)
|
||||
check('旧柜关联数量与真实分配一致', null, active
|
||||
? current.some(link => Number(link.allocatedPackageCount) > 0)
|
||||
: current.every(link => Number(link.allocatedPackageCount) === 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user