This commit is contained in:
2026-09-20 09:17:47 +08:00
parent aa2953172b
commit d5c4059942
17 changed files with 682 additions and 21 deletions
+32 -6
View File
@@ -11,9 +11,15 @@ export const STAGES = Object.freeze([
'LAX_DISPATCH', 'LAX_PDA_OUTBOUND', 'DELIVERY_RECEIPT', 'CN_STATUS_RECONCILIATION',
])
// These are the two domestic warehouses that are currently in the production
// master data. The release smoke route still uses Yiwu as its fixed origin,
// but the preflight must prove that both domestic operating sites are active
// and visible to the domestic PDA account.
export const DOMESTIC_WAREHOUSE_CODES = Object.freeze(['CN-YIWU-01', 'CN-DONGGUAN-01'])
export function plan() {
return {
scope: { orders: 1, origin: 'CN-YIWU-01', receiving: 'US-LAX-01', sameOrder: true },
scope: { orders: 1, origins: [...DOMESTIC_WAREHOUSE_CODES], origin: 'CN-YIWU-01', receiving: 'US-LAX-01', sameOrder: true },
status: 'FULL_FLOW_NOT_IMPLEMENTED', fullFlowVerified: false,
stages: STAGES.map(name => ({ name, status: ['CUSTOMER_ORDER', 'ORDER_REVIEW'].includes(name)
? 'IMPLEMENTED_NOT_HTTP_VERIFIED' : 'NOT_IMPLEMENTED' })),
@@ -129,15 +135,23 @@ export async function preflight(settings, http, receiveContext = () => {}) {
assertCustomer(await http.request('cn', 'GET', `/platform/v1/iam/tenants/${settings.customer.id}`, admin), settings.customer)
assertCustomer(await http.request('cn', 'GET', '/merchant/v1/iam/tenant/profile', logins.MERCHANT.accessToken), settings.customer)
const warehouses = {}
for (const [type, code] of [['DOMESTIC', 'CN-YIWU-01'], ['OVERSEAS', 'US-LAX-01']]) {
const all = await listAll(http, `/platform/v1/warehouse-admin?page={page}&size=100&warehouseType=${type}&status=ACTIVE`, admin)
for (const code of DOMESTIC_WAREHOUSE_CODES) {
const all = await listAll(http, `/platform/v1/warehouse-admin?page={page}&size=100&warehouseType=DOMESTIC&status=ACTIVE`, admin)
const matches = all.filter(w => w.warehouseCode === code && w.status === 'ACTIVE')
if (matches.length !== 1) throw new Error(`${code} 必须唯一且启用`)
warehouses[code] = id(matches[0].id)
}
for (const code of ['US-LAX-01']) {
const all = await listAll(http, `/platform/v1/warehouse-admin?page={page}&size=100&warehouseType=OVERSEAS&status=ACTIVE`, admin)
const matches = all.filter(w => w.warehouseCode === code && w.status === 'ACTIVE')
if (matches.length !== 1) throw new Error(`${code} 必须唯一且启用`)
warehouses[code] = id(matches[0].id)
}
const cnWarehouses = await http.request('cn', 'GET', '/platform/v1/first-leg/pda-loading/warehouses', logins.CN_PDA.accessToken)
if (!Array.isArray(cnWarehouses) || !cnWarehouses.some(w => w.warehouseCode === 'CN-YIWU-01'
&& id(w.id) === warehouses['CN-YIWU-01'])) throw new Error('国内 PDA 不可访问义乌仓')
if (!Array.isArray(cnWarehouses) || DOMESTIC_WAREHOUSE_CODES.some(code =>
!cnWarehouses.some(w => w.warehouseCode === code && id(w.id) === warehouses[code]))) {
throw new Error('国内 PDA 不可访问义乌或东莞仓')
}
const overseasToken = logins.OVERSEAS_LAX_PDA.overseasAccessToken
if (!overseasToken) throw new Error('洛杉矶 PDA 登录缺少海外 Token')
const binding = await http.request('overseas', 'GET', '/pda/v1/last-leg/receiving-containers/warehouse-profile', overseasToken)
@@ -153,7 +167,7 @@ export async function preflight(settings, http, receiveContext = () => {}) {
}
receiveContext({ logins, warehouses, reusablePallets })
return { ...plan(), status: 'BASIC_PREFLIGHT_PASSED', requests: http.requests,
checks: ['客户身份及箱规状态', '四个账号登录', '义乌及洛杉矶仓', 'PDA 仓库范围', '国内既有托盘及可用库位'],
checks: ['客户身份及箱规状态', '四个账号登录', '义乌、东莞及洛杉矶仓', 'PDA 仓库范围', '国内既有托盘及可用库位'],
resourceCounts: counts,
pending: ['单票执行入口的精确线路及小票报价检查', '供应商与海外资源门禁', '测量至签收的写入执行器和检查点', '所有业务阶段真实 HTTP 验证'],
}
@@ -213,6 +227,18 @@ export async function executeOrder(settings, input, journalPath, resume, fetcher
export async function main(argv = process.argv.slice(2), env = process.env) {
const [mode = 'plan', ...rest] = argv
if (mode === 'delivery-stage') {
if (rest.length !== 3 || rest[2] !== '--confirm-production-delivery') {
throw new Error('delivery-stage 需要输入文件、独立签收检查点、--confirm-production-delivery')
}
const inputPath = resolve(rest[0]), journalPath = resolve(rest[1])
if (inputPath === journalPath) throw new Error('输入和检查点不能使用同一文件')
const { executeDelivery } = await import('./prod-release-delivery-stage.mjs')
const result = await executeDelivery(config(env), JSON.parse(await readFile(inputPath, 'utf8')), journalPath)
console.log(JSON.stringify(result, null, 2))
if (result.blockers.length) process.exitCode = 1
return
}
if (mode === 'measurement-plan') {
if (rest.length !== 1 || !/^[12]$/.test(rest[0])) throw new Error('measurement-plan 需要箱数 1 或 2')
const allowedCodes = requireValue(env, 'CHONGCHONG_ACCEPTANCE_PALLET_CODES').split(',').map(v => v.trim())