优化
This commit is contained in:
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
import { PRODUCT_CATALOG, buildOrderPayload, diversifyRoutes, parseArguments, validateCount } from './create-pending-review-orders.mjs'
|
||||
import { PRODUCT_CATALOG, buildOrderPayload, diversifyRoutes, parseArguments, randomOrderOptions, validateCount } from './create-pending-review-orders.mjs'
|
||||
|
||||
const source = readFileSync(new URL('./create-pending-review-orders.mjs', import.meta.url), 'utf8')
|
||||
|
||||
@@ -50,6 +50,11 @@ test('builds an order payload owned by the fixed profile', () => {
|
||||
},
|
||||
product: PRODUCT_CATALOG[0],
|
||||
packageCount: 7,
|
||||
options: {
|
||||
tradeTerm: 'EXW', pickupMethod: 'SELF_TRUCK', taxMode: 'TAX_REFUND',
|
||||
customsInspection: true, expectedShipDays: 15,
|
||||
senderLocation: { province: '浙江省', city: '义乌市', district: '稠城街道', postalCode: '322000' },
|
||||
},
|
||||
productImageFileId: '33',
|
||||
})
|
||||
assert.equal(payload.receivingWarehouseId, '11')
|
||||
@@ -58,6 +63,11 @@ test('builds an order payload owned by the fixed profile', () => {
|
||||
assert.equal(payload.customerName, '固定测试客户')
|
||||
assert.equal(payload.cargoes.length, 1)
|
||||
assert.equal(payload.cargoes[0].productImageFileId, '33')
|
||||
assert.equal(payload.tradeTerm, 'EXW')
|
||||
assert.equal(payload.pickupMethod, 'SELF_TRUCK')
|
||||
assert.equal(payload.taxMode, 'TAX_REFUND')
|
||||
assert.equal(payload.customsInspection, true)
|
||||
assert.equal(payload.sender, null)
|
||||
assert.equal(payload.cargoes[0].cargoName, PRODUCT_CATALOG[0].cargoName)
|
||||
assert.equal(payload.cargoes[0].packageCount, 7)
|
||||
assert.equal(payload.cargoes[0].totalQuantity, PRODUCT_CATALOG[0].unitsPerPackage * 7)
|
||||
@@ -65,6 +75,17 @@ test('builds an order payload owned by the fixed profile', () => {
|
||||
assert.equal(payload.cargoes[0].declaredValue, PRODUCT_CATALOG[0].unitDeclaredValue * PRODUCT_CATALOG[0].unitsPerPackage * 7)
|
||||
})
|
||||
|
||||
test('randomizes only supported basic-order fields', () => {
|
||||
for (let index = 0; index < 100; index += 1) {
|
||||
const options = randomOrderOptions()
|
||||
assert.ok(['FOB', 'EXW', 'CIF', 'DDP'].includes(options.tradeTerm))
|
||||
assert.ok(['SELF_EXPRESS', 'PICKUP', 'SELF_TRUCK'].includes(options.pickupMethod))
|
||||
assert.ok(['BUY_DECLARATION', 'TAX_REFUND'].includes(options.taxMode))
|
||||
assert.equal(typeof options.customsInspection, 'boolean')
|
||||
assert.ok(options.expectedShipDays >= 7 && options.expectedShipDays <= 60)
|
||||
}
|
||||
})
|
||||
|
||||
test('interleaves different logistics combinations before repeating one', () => {
|
||||
const route = (receivingId, channelCode, pricingTaxMode, destinationCode) => ({
|
||||
receiving: { id: receivingId }, channel: { channelCode }, pricingTaxMode,
|
||||
|
||||
@@ -8,6 +8,15 @@ import { pathToFileURL } from 'node:url'
|
||||
const MAX_COUNT = 100
|
||||
const MAX_ROUTE_POOL_SIZE = 40
|
||||
const MAX_DESTINATIONS_PER_LOGISTICS = 3
|
||||
const TRADE_TERMS = ['FOB', 'EXW', 'CIF', 'DDP']
|
||||
const PICKUP_METHODS = ['SELF_EXPRESS', 'PICKUP', 'SELF_TRUCK']
|
||||
const TAX_MODES = ['BUY_DECLARATION', 'TAX_REFUND']
|
||||
const SENDER_LOCATIONS = [
|
||||
{ province: '广东省', city: '深圳市', district: '南山区', postalCode: '518000' },
|
||||
{ province: '广东省', city: '东莞市', district: '南城街道', postalCode: '523000' },
|
||||
{ province: '浙江省', city: '义乌市', district: '稠城街道', postalCode: '322000' },
|
||||
{ province: '福建省', city: '厦门市', district: '湖里区', postalCode: '361000' },
|
||||
]
|
||||
|
||||
export const PRODUCT_CATALOG = [
|
||||
{ cargoName: '蓝牙音箱', cargoNameEn: 'Bluetooth speaker', hsCode: '8518210000', material: '塑料和电子元件 / Plastic and electronic components', usage: '音频播放 / Audio playback', brand: 'SOUNDTEST', modelPrefix: 'SPK', lengthCm: 42, widthCm: 32, heightCm: 28, unitsPerPackage: 8, cartonGrossWeightKg: 12.8, unitDeclaredValue: 18.5, productLengthCm: 18, productWidthCm: 10, productHeightCm: 8 },
|
||||
@@ -85,6 +94,10 @@ function shuffled(values) {
|
||||
return result
|
||||
}
|
||||
|
||||
function pickRandom(values) {
|
||||
return values[randomInt(values.length)]
|
||||
}
|
||||
|
||||
function round(value, scale) {
|
||||
return Number(value.toFixed(scale))
|
||||
}
|
||||
@@ -110,6 +123,17 @@ export function diversifyRoutes(routes) {
|
||||
return result
|
||||
}
|
||||
|
||||
export function randomOrderOptions() {
|
||||
return {
|
||||
tradeTerm: pickRandom(TRADE_TERMS),
|
||||
pickupMethod: pickRandom(PICKUP_METHODS),
|
||||
taxMode: pickRandom(TAX_MODES),
|
||||
customsInspection: randomInt(2) === 1,
|
||||
expectedShipDays: randomInt(7, 61),
|
||||
senderLocation: pickRandom(SENDER_LOCATIONS),
|
||||
}
|
||||
}
|
||||
|
||||
function requireSuccess(response, action) {
|
||||
if (response.http !== 200 || String(response.body?.code) !== '200') {
|
||||
throw new Error(`${action} failed: http=${response.http}, code=${response.body?.code ?? 'missing'}, message=${response.body?.message ?? 'missing'}, requestId=${response.requestId || 'missing'}, traceId=${response.traceId || 'missing'}`)
|
||||
@@ -214,7 +238,7 @@ async function uploadProductImage(gateway, timeoutMs, token, marker) {
|
||||
return data.fileId
|
||||
}
|
||||
|
||||
export function buildOrderPayload({ marker, index, profile, master, product, packageCount, productImageFileId }) {
|
||||
export function buildOrderPayload({ marker, index, profile, master, product, packageCount, options = randomOrderOptions(), productImageFileId }) {
|
||||
const sequence = String(index).padStart(3, '0')
|
||||
const shipmentId = `SHIP-${sequence}-${marker}`.slice(0, 64)
|
||||
const referenceId = `REF-${sequence}-${marker}`.slice(0, 64)
|
||||
@@ -228,39 +252,39 @@ export function buildOrderPayload({ marker, index, profile, master, product, pac
|
||||
customerId: profile.id,
|
||||
customerName: companyName,
|
||||
transportMode: 'SEA',
|
||||
tradeTerm: 'FOB',
|
||||
tradeTerm: options.tradeTerm,
|
||||
destinationCountryCode: master.destination.countryCode || 'US',
|
||||
destinationWarehouseId: master.destination.id,
|
||||
destinationWarehouseName: master.destination.warehouseName,
|
||||
expectedShipDate: addDays(new Date(), 30),
|
||||
customerRemark: `待审核订单 ${sequence} ${marker}`,
|
||||
pickupMethod: 'PICKUP',
|
||||
expectedShipDate: addDays(new Date(), options.expectedShipDays),
|
||||
customerRemark: `基础待审核订单 ${sequence} ${marker} ${options.tradeTerm}`,
|
||||
pickupMethod: options.pickupMethod,
|
||||
receivingWarehouseId: master.receiving.id,
|
||||
receivingWarehouseName: master.receiving.warehouseName,
|
||||
channelCode: master.channel.channelCode,
|
||||
channelName: master.channel.channelName,
|
||||
carrierId: null,
|
||||
carrierName: null,
|
||||
taxMode: 'BUY_DECLARATION',
|
||||
taxMode: options.taxMode,
|
||||
pricingTaxMode: master.pricingTaxMode,
|
||||
containerMode: 'LCL',
|
||||
customsInspection: false,
|
||||
customsInspection: options.customsInspection,
|
||||
destinationType: 'FBA',
|
||||
amazonWarehouseCode: master.destination.warehouseCode,
|
||||
shipmentId,
|
||||
referenceId,
|
||||
sender: {
|
||||
sender: options.pickupMethod === 'PICKUP' ? {
|
||||
companyName,
|
||||
contactName: '订单测试联系人',
|
||||
phone: '13800138000',
|
||||
email: 'pending-order@example.com',
|
||||
countryCode: 'CN',
|
||||
province: '广东省',
|
||||
city: '深圳市',
|
||||
district: '南山区',
|
||||
province: options.senderLocation.province,
|
||||
city: options.senderLocation.city,
|
||||
district: options.senderLocation.district,
|
||||
addressLine: `${marker} 固定客户测试地址`,
|
||||
postalCode: '518000',
|
||||
},
|
||||
postalCode: options.senderLocation.postalCode,
|
||||
} : null,
|
||||
receiver: null,
|
||||
cargoes: [{
|
||||
skuNo: `SKU-${sequence}-${marker}`.slice(0, 64),
|
||||
@@ -356,7 +380,8 @@ export async function main(argv = process.argv.slice(2)) {
|
||||
const master = randomizedRoutes[(index - 1) % randomizedRoutes.length]
|
||||
const product = randomizedProducts[(index - 1) % randomizedProducts.length]
|
||||
const packageCount = randomInt(1, 31)
|
||||
const payload = buildOrderPayload({ marker, index, profile, master, product, packageCount, productImageFileId })
|
||||
const options = randomOrderOptions()
|
||||
const payload = buildOrderPayload({ marker, index, profile, master, product, packageCount, options, productImageFileId })
|
||||
const createResponse = await request(gateway, timeoutMs, 'POST',
|
||||
'/api/merchant/v1/orders/first-leg', {
|
||||
token,
|
||||
@@ -401,11 +426,16 @@ export async function main(argv = process.argv.slice(2)) {
|
||||
channelCode: master.channel.channelCode,
|
||||
channelName: master.channel.channelName,
|
||||
pricingTaxMode: master.pricingTaxMode,
|
||||
tradeTerm: options.tradeTerm,
|
||||
pickupMethod: options.pickupMethod,
|
||||
taxMode: options.taxMode,
|
||||
customsInspection: options.customsInspection,
|
||||
expectedShipDate: payload.expectedShipDate,
|
||||
cargoName: product.cargoName,
|
||||
packageCount,
|
||||
}
|
||||
orders.push(result)
|
||||
console.log(`CREATED ${index}/${count} orderId=${result.orderId} orderNo=${result.orderNo} logistics=${result.channelCode}/${result.pricingTaxMode} destination=${result.destinationWarehouseCode} product=${result.cargoName} cartons=${result.packageCount} status=PENDING_REVIEW`)
|
||||
console.log(`CREATED ${index}/${count} orderId=${result.orderId} orderNo=${result.orderNo} logistics=${result.channelCode}/${result.pricingTaxMode} destination=${result.destinationWarehouseCode} pickup=${result.pickupMethod} tax=${result.taxMode} inspection=${result.customsInspection} product=${result.cargoName} cartons=${result.packageCount} status=PENDING_REVIEW`)
|
||||
}
|
||||
|
||||
const report = {
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { mkdir, writeFile } from 'node:fs/promises'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import process from 'node:process'
|
||||
|
||||
const REQUIRED_EVIDENCE = ['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL']
|
||||
const value = (name, fallback = '') => {
|
||||
const prefix = `--${name}=`
|
||||
const inline = process.argv.find((item) => item.startsWith(prefix))
|
||||
if (inline) return inline.slice(prefix.length)
|
||||
const index = process.argv.indexOf(`--${name}`)
|
||||
return index >= 0 ? process.argv[index + 1] || fallback : fallback
|
||||
}
|
||||
const gateway = value('gateway', process.env.ADMIN_TEST_GATEWAY || 'http://8.163.30.128:9000').replace(/\/$/, '')
|
||||
const username = value('username', process.env.ADMIN_TEST_USERNAME || 'platform_admin')
|
||||
const password = value('password', process.env.ADMIN_TEST_PASSWORD)
|
||||
const taskIds = value('task-ids').split(',').map((item) => item.trim()).filter(Boolean)
|
||||
const seedEvidence = process.argv.includes('--seed-evidence')
|
||||
const stamp = new Date().toISOString().replace(/\D/g, '').slice(0, 14)
|
||||
const marker = `PDA-FINAL-DIAG-${stamp}`
|
||||
const output = resolve(value('output', `reports/pda-final-complete-diagnostic-${stamp}.json`))
|
||||
|
||||
if (!password || taskIds.length === 0) {
|
||||
throw new Error('required: ADMIN_TEST_PASSWORD (or --password) and --task-ids')
|
||||
}
|
||||
|
||||
let token = ''
|
||||
const calls = []
|
||||
async function call(method, path, body, headers = {}) {
|
||||
let response
|
||||
try {
|
||||
response = await fetch(`${gateway}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...(body === undefined ? {} : { 'Content-Type': 'application/json; charset=utf-8' }),
|
||||
...headers,
|
||||
},
|
||||
body: body === undefined ? undefined : JSON.stringify(body),
|
||||
signal: AbortSignal.timeout(60_000),
|
||||
})
|
||||
const raw = await response.text()
|
||||
let payload
|
||||
try { payload = raw ? JSON.parse(raw) : null } catch { payload = { raw: raw.slice(0, 1000) } }
|
||||
const result = {
|
||||
method, path, ...(body === undefined ? {} : { body }), http: response.status,
|
||||
code: payload?.code == null ? null : String(payload.code), message: payload?.message || null,
|
||||
requestId: payload?.requestId || response.headers.get('x-request-id'),
|
||||
traceId: payload?.traceId || response.headers.get('x-trace-id'), data: payload?.data ?? null,
|
||||
}
|
||||
calls.push(result)
|
||||
return result
|
||||
} catch (error) {
|
||||
const result = { method, path, ...(body === undefined ? {} : { body }), http: 0, code: null,
|
||||
message: String(error), requestId: null, traceId: null, data: null }
|
||||
calls.push(result)
|
||||
return result
|
||||
}
|
||||
}
|
||||
async function uploadEvidence(tenantId, inboundId, evidenceType) {
|
||||
const path = `/api/platform/v1/files/loading-evidence/${encodeURIComponent(tenantId)}/${encodeURIComponent(inboundId)}?evidenceType=${encodeURIComponent(evidenceType)}`
|
||||
const png = Uint8Array.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,6,0,0,0,31,21,196,137,0,0,0,13,73,68,65,84,8,215,99,248,207,192,240,31,0,5,0,1,255,137,153,61,29,0,0,0,0,73,69,78,68,174,66,96,130])
|
||||
const fileName = `${marker}-${inboundId}-${evidenceType.toLowerCase()}.png`
|
||||
const form = new FormData()
|
||||
form.append('file', new Blob([png], { type: 'image/png' }), fileName)
|
||||
try {
|
||||
const response = await fetch(`${gateway}${path}`, {
|
||||
method: 'POST', headers: { Accept: 'application/json', Authorization: `Bearer ${token}` },
|
||||
body: form, signal: AbortSignal.timeout(60_000),
|
||||
})
|
||||
const raw = await response.text()
|
||||
let payload
|
||||
try { payload = raw ? JSON.parse(raw) : null } catch { payload = { raw: raw.slice(0, 1000) } }
|
||||
const result = { method: 'POST', path, body: { fileName, evidenceType }, http: response.status,
|
||||
code: payload?.code == null ? null : String(payload.code), message: payload?.message || null,
|
||||
requestId: payload?.requestId || response.headers.get('x-request-id'),
|
||||
traceId: payload?.traceId || response.headers.get('x-trace-id'), data: payload?.data ?? null }
|
||||
calls.push(result)
|
||||
return result
|
||||
} catch (error) {
|
||||
const result = { method: 'POST', path, body: { fileName, evidenceType }, http: 0, code: null,
|
||||
message: String(error), requestId: null, traceId: null, data: null }
|
||||
calls.push(result)
|
||||
return result
|
||||
}
|
||||
}
|
||||
const ok = (response) => response?.http === 200 && response?.code === '200'
|
||||
const id = (input) => input == null ? '' : String(input)
|
||||
|
||||
const login = await call('POST', '/api/auth/login', { username, password })
|
||||
login.body = { username, password: '[REDACTED]' }
|
||||
if (!ok(login) || !login.data?.accessToken) {
|
||||
login.data = null
|
||||
const failedReport = { marker, startedAt: new Date().toISOString(), gateway, taskIds, stopped: true,
|
||||
fatal: `login failed: HTTP=${login.http}, code=${login.code}, message=${login.message || 'missing'}`,
|
||||
results: [], calls }
|
||||
await mkdir(dirname(output), { recursive: true })
|
||||
await writeFile(output, `${JSON.stringify(failedReport, null, 2)}\n`, 'utf8')
|
||||
console.log(JSON.stringify({ output, marker, stopped: true, fatal: failedReport.fatal }, null, 2))
|
||||
process.exit(1)
|
||||
}
|
||||
token = login.data.accessToken
|
||||
login.data = { authenticated: true }
|
||||
|
||||
const pendingResponse = await call('GET', '/api/platform/v1/first-leg/pda-loading/tasks/pending')
|
||||
const pendingRows = ok(pendingResponse) && Array.isArray(pendingResponse.data) ? pendingResponse.data : []
|
||||
const results = []
|
||||
let stopped = false
|
||||
|
||||
for (const taskId of taskIds) {
|
||||
if (stopped) break
|
||||
const pending = pendingRows.find((item) => id(item?.taskId) === taskId) || null
|
||||
const warehouseId = id(pending?.warehouseId)
|
||||
const taskPath = `/api/platform/v1/first-leg/pda-loading/tasks/${encodeURIComponent(taskId)}`
|
||||
const detailResponse = warehouseId
|
||||
? await call('GET', `${taskPath}?warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
: { http: 0, code: null, message: 'task missing from pending list; warehouseId unavailable', data: null }
|
||||
const detail = detailResponse.data
|
||||
const planId = id(detail?.loadingPlanId || pending?.loadingPlanId)
|
||||
const planPath = `/api/platform/v1/first-leg/smart-loading/plans/${encodeURIComponent(planId)}`
|
||||
const planBefore = planId ? await call('GET', planPath) : null
|
||||
const owners = [...new Map((detail?.items || []).map((item) => [
|
||||
`${id(item?.tenantId)}:${id(item?.inboundId)}`,
|
||||
{ tenantId: id(item?.tenantId), inboundId: id(item?.inboundId) },
|
||||
])).values()].filter((item) => item.tenantId && item.inboundId)
|
||||
const inboundBefore = []
|
||||
const evidenceBefore = []
|
||||
for (const owner of owners) {
|
||||
inboundBefore.push(await call('GET',
|
||||
`/api/platform/inbounds/${encodeURIComponent(owner.inboundId)}?tenantId=${encodeURIComponent(owner.tenantId)}`))
|
||||
evidenceBefore.push(await call('GET',
|
||||
`/api/platform/v1/inbounds/${encodeURIComponent(owner.inboundId)}/loading/evidence?tenantId=${encodeURIComponent(owner.tenantId)}`))
|
||||
}
|
||||
const evidenceSeed = []
|
||||
if (seedEvidence) {
|
||||
for (let ownerIndex = 0; ownerIndex < owners.length; ownerIndex += 1) {
|
||||
const owner = owners[ownerIndex]
|
||||
const activeTypes = new Set((evidenceBefore[ownerIndex]?.data?.evidences || [])
|
||||
.filter((item) => item?.status !== 'VOID').map((item) => item?.evidenceType))
|
||||
for (const evidenceType of REQUIRED_EVIDENCE.filter((type) => !activeTypes.has(type))) {
|
||||
const upload = await uploadEvidence(owner.tenantId, owner.inboundId, evidenceType)
|
||||
const fileId = id(upload.data?.fileId)
|
||||
if (!ok(upload) || !fileId) {
|
||||
evidenceSeed.push({ evidenceType, upload, bind: null })
|
||||
break
|
||||
}
|
||||
const bindPath = `/api/platform/v1/inbounds/${encodeURIComponent(owner.inboundId)}/loading/evidence?tenantId=${encodeURIComponent(owner.tenantId)}`
|
||||
const bindBody = { expectedVersion: 0, evidenceType, fileId,
|
||||
fileName: 'client-must-not-override.png',
|
||||
sealNo: evidenceType === 'SEAL' ? `SEAL-${stamp}` : null, remark: marker }
|
||||
const bind = await call('POST', bindPath, bindBody,
|
||||
{ 'Idempotency-Key': `${marker}-${owner.inboundId}-${evidenceType}`.slice(0, 128) })
|
||||
evidenceSeed.push({ evidenceType, upload, bind })
|
||||
if (!ok(bind)) break
|
||||
}
|
||||
}
|
||||
}
|
||||
const evidenceCurrent = []
|
||||
for (const owner of owners) {
|
||||
evidenceCurrent.push(await call('GET',
|
||||
`/api/platform/v1/inbounds/${encodeURIComponent(owner.inboundId)}/loading/evidence?tenantId=${encodeURIComponent(owner.tenantId)}`))
|
||||
}
|
||||
const unresolved = (detail?.items || []).filter((item) => ['PENDING', 'PICKING'].includes(item?.itemStatus))
|
||||
const evidenceReady = evidenceCurrent.length > 0 && evidenceCurrent.every((response) => {
|
||||
const activeTypes = new Set((response.data?.evidences || [])
|
||||
.filter((item) => item?.status !== 'VOID').map((item) => item?.evidenceType))
|
||||
return ok(response) && REQUIRED_EVIDENCE.every((type) => activeTypes.has(type))
|
||||
})
|
||||
const ready = ok(pendingResponse) && ok(detailResponse) && pending
|
||||
&& pending.workflowStatus === 'AWAITING_REVIEW'
|
||||
&& detail?.workflowStatus === 'AWAITING_REVIEW'
|
||||
&& Number(pending?.pendingItemCount) === 0 && unresolved.length === 0
|
||||
&& Number.isInteger(Number(detail?.version)) && evidenceReady
|
||||
const item = {
|
||||
taskId, pending, detail: detail ? {
|
||||
id: detail.id, version: detail.version, workflowStatus: detail.workflowStatus,
|
||||
taskStatus: detail.taskStatus, inboundId: detail.inboundId, loadingPlanId: detail.loadingPlanId,
|
||||
containerBookingId: detail.containerBookingId,
|
||||
items: (detail.items || []).map((row) => ({ id: row.id, tenantId: row.tenantId,
|
||||
inboundId: row.inboundId, itemStatus: row.itemStatus, plannedPackageCount: row.plannedPackageCount,
|
||||
pickedPackageCount: row.pickedPackageCount, version: row.version })),
|
||||
} : null,
|
||||
owners, evidenceReady, evidenceSeed: evidenceSeed.map((entry) => ({ evidenceType: entry.evidenceType,
|
||||
upload: entry.upload ? { http: entry.upload.http, code: entry.upload.code, requestId: entry.upload.requestId,
|
||||
traceId: entry.upload.traceId, fileId: entry.upload.data?.fileId || null } : null,
|
||||
bind: entry.bind ? { http: entry.bind.http, code: entry.bind.code, requestId: entry.bind.requestId,
|
||||
traceId: entry.bind.traceId } : null })), ready, complete: null,
|
||||
}
|
||||
if (!ready) {
|
||||
item.blocker = `pending/detail/evidence not ready; pendingWorkflow=${pending?.workflowStatus || 'missing'}, detailWorkflow=${detail?.workflowStatus || 'missing'}, pendingItemCount=${pending?.pendingItemCount ?? 'missing'}, unresolved=${unresolved.length}, evidenceReady=${evidenceReady}`
|
||||
results.push(item)
|
||||
stopped = true
|
||||
continue
|
||||
}
|
||||
const body = {
|
||||
expectedTaskVersion: Number(detail.version),
|
||||
operationId: `${marker}-${taskId}`.slice(0, 128), remark: marker,
|
||||
}
|
||||
const complete = await call('POST', `${taskPath}/complete`, body,
|
||||
{ 'Idempotency-Key': `${marker}-${taskId}`.slice(0, 128) })
|
||||
item.complete = complete
|
||||
item.taskAfter = await call('GET', `${taskPath}?warehouseId=${encodeURIComponent(warehouseId)}`)
|
||||
item.planAfter = await call('GET', planPath)
|
||||
item.inboundAfter = []
|
||||
for (const owner of owners) {
|
||||
item.inboundAfter.push(await call('GET',
|
||||
`/api/platform/inbounds/${encodeURIComponent(owner.inboundId)}?tenantId=${encodeURIComponent(owner.tenantId)}`))
|
||||
}
|
||||
results.push(item)
|
||||
if (!ok(complete)) stopped = true
|
||||
}
|
||||
|
||||
const report = { marker, startedAt: new Date().toISOString(), gateway, taskIds, seedEvidence, stopped, results,
|
||||
calls: calls.map((entry) => entry.path === '/api/auth/login' ? { ...entry, data: { authenticated: true } } : entry) }
|
||||
await mkdir(dirname(output), { recursive: true })
|
||||
await writeFile(output, `${JSON.stringify(report, null, 2)}\n`, 'utf8')
|
||||
console.log(JSON.stringify({ output, marker, stopped, results: results.map((item) => ({
|
||||
taskId: item.taskId, ready: item.ready, blocker: item.blocker || null,
|
||||
complete: item.complete ? { http: item.complete.http, code: item.complete.code,
|
||||
message: item.complete.message, requestId: item.complete.requestId, traceId: item.complete.traceId } : null,
|
||||
})) }, null, 2))
|
||||
Reference in New Issue
Block a user