84 lines
6.4 KiB
JavaScript
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()
|