Files
baiyue-firstleg/scripts/overseas-relabel-fulfillment-acceptance.mjs
T
2026-10-20 10:35:52 +08:00

84 lines
6.4 KiB
JavaScript

import { readFileSync, existsSync } from 'node:fs'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createHash } from 'node:crypto'
import { AcceptanceRun, parseArguments, validateExecutionOptions } from './admin-test-env-acceptance.mjs'
import { saveRetainedReport, validateRetainedScope, validateNativeRetainedDispatch,
installRetainedJournal, retainedApi, runRetainedNativeDispatch } from './mixed-container-outbound-test-env.mjs'
const id = value => {
if (typeof value === 'number' && !Number.isSafeInteger(value)) throw Error('Relabel ID lost precision')
return value == null ? '' : String(value)
}
const requireFact = (condition, message) => { if (!condition) throw Error(message) }
/** A dispatch number or legacy pre-arrange report cannot establish current source ownership. */
export function retainedRelabelSource(source, customerId, dispatchNo) {
const scenario = source?.scenarios?.find(value => value.name === 'SPLIT-TWO-TARGETS')
const native = scenario?.nativeDispatch
requireFact(source?.environment === 'TEST' && source.marker && native?.marker === source.marker
&& scenario.relabelNo && native.customerId === id(customerId)
&& (!dispatchNo || native.dispatchNo === dispatchNo), 'Relabel continuation requires this run native source report/customer/dispatch; old reports or a direct number cannot resume')
const inboundCodes = [...new Set((native.detail?.orderDetails || []).map(row => row.inboundCode).filter(Boolean))]
const expectedPackages = Number(native.detail?.counts?.packageCount)
requireFact(inboundCodes.length > 0 && Number.isSafeInteger(expectedPackages) && expectedPackages > 0
&& native.createBody?.cargo?.every(row => row.sourceType === 'RELABEL' && row.relabelNo === scenario.relabelNo),
'Relabel native cargo does not belong to the original changed-carton sources')
const newMarks = scenario.relabelCartonMarks, oldMarks = scenario.sourceCartonMarks
requireFact(Array.isArray(newMarks) && newMarks.length === expectedPackages && newMarks.every(mark => typeof mark === 'string' && mark.trim())
&& new Set(newMarks).size === newMarks.length && Array.isArray(oldMarks) && oldMarks.length > 0
&& oldMarks.every(mark => typeof mark === 'string' && mark.trim()) && oldMarks.every(mark => !newMarks.includes(mark)),
'Relabel source must retain original/new unique carton mark evidence; cannot infer marks from live final inventory')
const scope = { marker: native.marker, customerId: id(customerId), warehouseId: id(native.createBody.warehouseId),
inboundCodes, expectedPackages, expectedPalletItems: scenario.partialRelabel ? 1 : 2, relabelCartonMarks: newMarks, sourceCartonMarks: oldMarks }
validateNativeRetainedDispatch(native, scope)
return { scenario, native, scope }
}
export async function mainRelabel(env = process.env) {
const sourcePath = env.OVERSEAS_ACCEPTANCE_SOURCE
requireFact(sourcePath && env.OVERSEAS_TEST_CUSTOMER_ID, 'Explicit native source report and customer are required; dispatch number alone is insufficient')
const sourceBytes = readFileSync(resolve(sourcePath)), source = JSON.parse(sourceBytes)
const { scenario, native, scope } = retainedRelabelSource(source, env.OVERSEAS_TEST_CUSTOMER_ID, env.OVERSEAS_DISPATCH_NO)
if (env.OVERSEAS_RELABEL_NO) requireFact(env.OVERSEAS_RELABEL_NO === scenario.relabelNo, 'Explicit relabel number differs from original source')
scope.sourceSha256 = createHash('sha256').update(sourceBytes).digest('hex')
const output = resolve(env.OVERSEAS_FULFILLMENT_OUTPUT || 'reports/overseas-relabel-fulfillment-acceptance.json')
const result = existsSync(output) ? JSON.parse(readFileSync(output, 'utf8')) : { schema: 'NATIVE_RETAINED_OUTBOUND_V1', environment: 'TEST',
...scope, sourceReport: resolve(sourcePath), relabelNo: scenario.relabelNo, nativeDispatch: native, requests: [] }
validateRetainedScope(result, scope)
const options = parseArguments([]); options.modules = []; options.output = output; validateExecutionOptions(options)
requireFact(options.environment === 'test', 'Relabel native continuation only supports explicit TEST')
const context = installRetainedJournal(new AcceptanceRun(options), result, () => saveRetainedReport(output, result))
context.marker = scope.marker
const api = (method, path, body, token) => retainedApi(context, method, path, body, token)
try {
const platform = await api('POST', '/api/auth/login', { loginId: options.username, password: options.password }, '')
requireFact(platform.accessToken, 'Unified platform accessToken is missing')
context.token = platform.accessToken
requireFact(env.PDA_TEST_ACCESS_TOKEN || options.pdaUsername && options.pdaPassword, 'Explicit current PDA token or complete credentials are required')
const pdaToken = env.PDA_TEST_ACCESS_TOKEN || (await api('POST', '/api/auth/login', {
loginId: options.pdaUsername, password: options.pdaPassword }, '')).accessToken
requireFact(pdaToken, 'Unified PDA credentials or explicit current token are required')
await runRetainedNativeDispatch(context, result, scope, pdaToken)
result.visits = []
for (const deliveryNo of result.deliveryNos) {
const query = new URLSearchParams({ page: '1', size: '200', customerId: scope.customerId, keyword: deliveryNo })
const visits = await api('GET', `/api/platform/v1/last-leg/customer-visits?${query}`)
const visit = visits.records?.find(row => row.deliveryNo === deliveryNo)
requireFact(visit?.status === 'PENDING' && id(visit.customerId) === scope.customerId, 'Real signature did not create the original customer visit')
result.visits.push({ id: id(visit.id), deliveryNo, status: visit.status })
}
result.passed = true; result.scope = 'OVERSEAS_LASTLEG_ONLY'
} catch (error) {
result.passed = false; result.finalStatus = 'BLOCKED'; result.failedStage = 'NATIVE_RELABEL_FULFILLMENT'; process.exitCode = 1
} finally {
result.checks = context.steps
if (context.steps.some(step => step.passed === false)) { result.passed = false; result.finalStatus = 'CHECK_FAILED'; process.exitCode = 1 }
await context.checkpoint()
console.log(JSON.stringify({ marker: result.marker, dispatchNo: result.dispatchNo, outboundNo: result.outboundNo,
deliveryNos: result.deliveryNos, passed: result.passed === true, finalStatus: result.finalStatus, report: output }))
}
return result
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await mainRelabel()