#!/usr/bin/env node import { mkdir, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { spawnSync } from 'node:child_process' const domestic = process.env.ADMIN_TEST_GATEWAY || 'http://127.0.0.1:9000' const overseas = process.env.ADMIN_OVERSEAS_GATEWAY || 'http://127.0.0.1:9300' let customerId = process.env.OVERSEAS_TEST_CUSTOMER_ID || '' let warehouseId = process.env.OVERSEAS_TEST_WAREHOUSE_ID || '' const destinationCode = process.env.OVERSEAS_TEST_DESTINATION_CODE || 'ONT8' const partialRelabel = process.env.WAREHOUSE_TRANSFER_PARTIAL_RELABEL === 'true' const output = resolve(process.env.WAREHOUSE_TRANSFER_REMAINING_OUTPUT || 'reports/warehouse-transfer-remaining-acceptance.json') const marker = `WT-REMAIN-${new Date().toISOString().replace(/\D/g, '').slice(0, 14)}-${process.pid}` const report = { marker, startedAt: new Date().toISOString(), passed: false, steps: [] } function id(value) { return value == null ? '' : String(value) } function key(value) { return `${marker}-${value}`.slice(0, 200) } function assert(value, message) { if (!value) throw new Error(message) } function step(name, data = {}) { report.steps.push({ name, at: new Date().toISOString(), ...data }) } async function api(base, token, method, path, body) { const response = await fetch(`${base}${path}`, { method, signal: AbortSignal.timeout(10000), headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(body === undefined || body instanceof FormData ? {} : { 'Content-Type': 'application/json' }), }, body: body === undefined ? undefined : body instanceof FormData ? body : JSON.stringify(body), }) const text = await response.text() let value try { value = text ? JSON.parse(text) : null } catch { value = { raw: text } } if (!response.ok || String(value?.code) !== '200') { throw new Error(`${method} ${path}: HTTP=${response.status}, code=${value?.code}, message=${value?.message}`) } return value.data } async function rawApi(base, token, method, path) { const response = await fetch(`${base}${path}`, { method, headers: token ? { Authorization: `Bearer ${token}` } : {} }) const text = await response.text() let value try { value = text ? JSON.parse(text) : null } catch { value = null } return { http: response.status, code: String(value?.code || '') } } async function createPda(adminToken) { const roles = await api(domestic, adminToken, 'GET', '/api/platform/v1/iam/roles') const role = roles.find((item) => item.roleCode === 'OVERSEAS_WAREHOUSE_PDA_OPERATOR' && item.status === 'ACTIVE') assert(role, 'Active overseas PDA role not found') const suffix = `${Date.now()}${process.pid}`.slice(-12) const username = `pda_wt_${suffix}` const password = `PdaWt${suffix}A9` const created = await api(domestic, adminToken, 'POST', '/api/platform/v1/iam/users', { username, displayName: `Warehouse transfer acceptance ${suffix}`, phone: `18${suffix.slice(-9)}`, email: `${username}@example.com`, tenantId: '0', roleIds: [id(role.id)], overseasWarehouseId: warehouseId, password, status: 'ACTIVE', }) const membershipId = id(created.membershipId) const login = await api(domestic, '', 'POST', '/api/auth/login', { loginId: username, password }) assert(login.overseasAccessToken, 'Temporary PDA login did not return an overseas token') for (let attempt = 0; attempt < 60; attempt += 1) { const response = await rawApi(overseas, login.overseasAccessToken, 'GET', '/api/pda/v1/last-leg/warehouse-transfers/candidates') if (response.http === 200 && !['403', 'A0403'].includes(response.code)) { return { membershipId, token: login.overseasAccessToken } } await new Promise((resolveWait) => setTimeout(resolveWait, 500)) } throw new Error('Temporary PDA security projection did not converge') } async function findMerchantScope(adminToken) { const tenants = await api(domestic, adminToken, 'GET', '/api/platform/v1/iam/tenants?page=1&size=200&status=ACTIVE') const ordered = [...(tenants.records || [])].sort((left, right) => { if (customerId && id(left.id) === customerId) return -1 if (customerId && id(right.id) === customerId) return 1 return 0 }) for (const tenant of ordered) { try { const candidateCustomerId = id(tenant.id) const ticket = await api(domestic, adminToken, 'POST', `/api/platform/v1/iam/tenants/${candidateCustomerId}/impersonation`, { reason: `${marker} warehouse transfer acceptance`, }) const merchant = await api(domestic, '', 'POST', '/api/auth/impersonations/exchange', { code: ticket.code }) if (!merchant.overseasAccessToken) continue try { await api(overseas, merchant.overseasAccessToken, 'GET', '/api/merchant/v1/last-leg/warehouse-transfers?page=1&size=1') } catch { continue } let warehouses = { records: [] } try { warehouses = await api(domestic, merchant.accessToken, 'GET', '/api/merchant/v1/warehouses?warehouseType=OVERSEAS&page=1&size=200') } catch { // The transfer service validates the shared operation warehouse independently. } const selected = (warehouses.records || []).find((value) => value.status === 'ACTIVE' && (!warehouseId || id(value.id) === warehouseId)) || (warehouses.records || []).find((value) => value.status === 'ACTIVE') const selectedWarehouseId = selected?.id ? id(selected.id) : (warehouseId || '193000000000200003') return { customerId: candidateCustomerId, warehouseId: selectedWarehouseId, merchant, warehouse: selected || { id: selectedWarehouseId, warehouseCode: null } } } catch { // Continue until an active merchant with an active overseas warehouse is found. } } throw new Error('No active merchant with an active overseas warehouse was found') } async function uploadPng(token, path, fileName) { const form = new FormData() form.append('file', new Blob([ Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aWQAAAABJRU5ErkJggg==', 'base64'), ], { type: 'image/png' }), fileName) return api(overseas, token, 'POST', path, form) } async function completeDelivery(platformToken, deliveryNo) { let delivery = await api(overseas, platformToken, 'GET', `/api/platform/v1/last-leg/delivery-orders/${deliveryNo}?customerId=${customerId}`) if (delivery.order.status === 'WAIT_DELIVERY') { await api(overseas, platformToken, 'PUT', `/api/platform/v1/last-leg/delivery-orders/${deliveryNo}/status?customerId=${customerId}`, { status: 'DELIVERING', expectedVersion: Number(delivery.order.version), remark: marker, }) delivery = await api(overseas, platformToken, 'GET', `/api/platform/v1/last-leg/delivery-orders/${deliveryNo}?customerId=${customerId}`) } if (delivery.order.status !== 'DELIVERED') { assert(delivery.order.status === 'DELIVERING', `Unexpected delivery status ${delivery.order.status}`) const documentsPath = `/api/platform/v1/last-leg/delivery-orders/${deliveryNo}/documents?customerId=${customerId}` const documents = await api(overseas, platformToken, 'GET', documentsPath) const form = new FormData() form.append('file', new Blob([`${marker} receipt acceptance for ${deliveryNo}`], { type: 'text/plain' }), `${marker}-${deliveryNo}-receipt.txt`) const receipt = await api(overseas, platformToken, 'POST', `/api/platform/v1/last-leg/delivery-orders/${deliveryNo}/documents/RECEIPT/files?customerId=${customerId}`, form) const bindings = (documents.categories || []).map((category) => ({ category: category.category, fileIds: (category.files || []).map((file) => id(file.fileId)), })).filter((binding) => binding.category !== 'RECEIPT' && binding.fileIds.length > 0) await api(overseas, platformToken, 'PUT', documentsPath, { expectedVersion: Number(documents.version), idempotencyKey: key(`DELIVERY-${deliveryNo}`), bolNo: documents.bolNo || null, documents: [...bindings, { category: 'RECEIPT', fileIds: [id(receipt.fileId)] }], }) delivery = await api(overseas, platformToken, 'GET', `/api/platform/v1/last-leg/delivery-orders/${deliveryNo}?customerId=${customerId}`) } assert(delivery.order.status === 'DELIVERED', `${deliveryNo} did not reach DELIVERED`) return delivery } await mkdir(dirname(output), { recursive: true }) let admin let pda try { admin = await api(domestic, '', 'POST', '/api/auth/login', { loginId: process.env.ADMIN_TEST_USERNAME || 'platform_admin', password: process.env.ADMIN_TEST_PASSWORD || 'ChangeMe123!', }) assert(admin.accessToken && admin.overseasAccessToken, 'Platform login did not return both tokens') const scope = await findMerchantScope(admin.accessToken) customerId = scope.customerId warehouseId = scope.warehouseId const merchant = scope.merchant Object.assign(report, { customerId, warehouseId, warehouseCode: scope.warehouse.warehouseCode }) pda = await createPda(admin.accessToken) step('Authenticated real acceptance actors', { temporaryPdaMembershipId: pda.membershipId }) const priorDeliveryNo = process.env.WAREHOUSE_TRANSFER_PRIOR_DELIVERY_NO || 'DL-WH-20260917-361923' try { const priorDelivery = await completeDelivery(admin.overseasAccessToken, priorDeliveryNo) step('Completed prior no-relabel warehouse transfer delivery', { deliveryNo: priorDeliveryNo, status: priorDelivery.order.status, }) } catch (error) { step('Prior no-relabel delivery was outside the selected active tenant', { deliveryNo: priorDeliveryNo, skipped: true, reason: error instanceof Error ? error.message : String(error), }) } const cartonMarks = [`CM-${marker}-1`, `CM-${marker}-2`] const cartonRelabelBindings = partialRelabel ? cartonMarks.slice(0, 1) : cartonMarks const transfer = await api(overseas, merchant.overseasAccessToken, 'POST', '/api/merchant/v1/last-leg/warehouse-transfers', { operatingWarehouseId: warehouseId, fbaWarehouseCode: partialRelabel ? null : destinationCode, destinationType: partialRelabel ? null : 'FBA', destinationWarehouseCode: partialRelabel ? null : destinationCode, destinationWarehouseName: partialRelabel ? null : destinationCode, deliveryAddress: `Acceptance inbound address ${marker}`, packageCount: 2, volumeCbm: 0.12, deliveryMethod: 'CUSTOMER_DELIVERY', estimatedArrivalAt: new Date(Date.now() + 86400000).toISOString().slice(0, 19), estimatedShipAt: new Date(Date.now() + 172800000).toISOString().slice(0, 19), fbaShipmentNo: `SHIP-${marker}`.slice(0, 128), trackingNo: `TRACK-${marker}`.slice(0, 128), fbaBoxNos: cartonMarks.join(','), remark: marker, needRelabel: true, labelDeadline: new Date(Date.now() + 7 * 86400000).toISOString().slice(0, 19), relabelContactName: 'Acceptance operator', relabelContactPhone: '13800000006', relabelDeliveryMethod: 'TRUCK', idempotencyKey: key('CREATE'), items: [{ skuCode: `SKU-${marker}`.slice(0, 128), productNameZh: 'Acceptance cargo', packageCount: 2, shippingMark: cartonMarks.join(',') }], relabelItems: cartonRelabelBindings.map((cartonMark, index) => ({ operationType: 'CARTON_AND_SKU', customerCartonMarks: [cartonMark], originalSku: `SKU-${marker}`.slice(0, 128), newSku: `NEW-${marker}-${index + 1}`.slice(0, 128), quantity: 1, fbaCartonMark: `FBA-${marker}-${index + 1}`.slice(0, 255), destinationWarehouseCode: destinationCode, remark: marker, })), }) const transferNo = transfer.transferNo assert(transferNo && transfer.status === 'DRAFT', 'Warehouse transfer draft was not created') let transferDetail = await api(overseas, merchant.overseasAccessToken, 'POST', `/api/merchant/v1/last-leg/warehouse-transfers/${transferNo}/submit`) assert(transferDetail.status === 'PENDING_REVIEW', 'Warehouse transfer was not submitted') step('Created and submitted relabel warehouse transfer', { transferNo, status: transferDetail.status }) transferDetail = await api(overseas, admin.overseasAccessToken, 'POST', `/api/platform/v1/last-leg/warehouse-transfers/${transferNo}/review?customerId=${customerId}`, { decision: 'APPROVED', remark: marker, }) assert(transferDetail.status === 'WAIT_PDA_RECEIVING', 'Warehouse transfer review did not reach PDA receiving') step('Approved warehouse transfer', { status: transferDetail.status }) const draftDetail = await api(overseas, pda.token, 'POST', `/api/pda/v1/last-leg/warehouse-transfers/${transferNo}/pallets/drafts`, { palletId: null, idempotencyKey: key('PALLET-DRAFT'), cartonCount: 2, lengthCm: 120, widthCm: 100, heightCm: 100, volumeCbm: 1.2, grossWeightKg: 20, }) const palletDraft = [...draftDetail.pallets].sort((left, right) => Number(right.sequenceNo) - Number(left.sequenceNo))[0] assert(palletDraft?.id, 'Pallet draft was not returned') const generatedDetail = await api(overseas, pda.token, 'POST', `/api/pda/v1/last-leg/warehouse-transfers/${transferNo}/pallets/${palletDraft.id}/generate`, { idempotencyKey: key('PALLET-GENERATE'), }) const generated = generatedDetail.pallets.find((value) => id(value.id) === id(palletDraft.id)) assert(generated?.palletNo, 'Physical pallet was not generated') const boundDetail = await api(overseas, pda.token, 'POST', `/api/pda/v1/last-leg/warehouse-transfers/${transferNo}/pallets/${palletDraft.id}/bind`, { palletCode: generated.palletNo, idempotencyKey: key('PALLET-BIND'), }) assert(boundDetail.pallets.some((value) => id(value.id) === id(palletDraft.id) && value.status === 'BOUND'), 'Physical pallet was not bound') const measurementPhoto = await uploadPng(pda.token, `/api/pda/v1/last-leg/warehouse-transfers/${transferNo}/measurement-photos/files`, `${marker}-measurement.png`) transferDetail = await api(overseas, pda.token, 'POST', `/api/pda/v1/last-leg/warehouse-transfers/${transferNo}/palletize`, { idempotencyKey: key('PALLETIZE'), photoFileIds: [id(measurementPhoto.fileId)], }) assert(transferDetail.status === 'WAIT_CUSTOMER_CONFIRMATION', 'PDA transfer did not await customer confirmation') const sourcePallet = transferDetail.pallets.find((value) => value.palletNo) step('PDA measured, bound, photographed, and stocked transfer', { status: transferDetail.status, sourcePalletNo: sourcePallet?.palletNo, }) transferDetail = await api(overseas, merchant.overseasAccessToken, 'POST', `/api/merchant/v1/last-leg/warehouse-transfers/${transferNo}/confirm-measurement`, { idempotencyKey: key('CONFIRM-MEASUREMENT'), remark: marker, }) assert(transferDetail.status === 'WAIT_RELABEL' && transferDetail.relabelNo, 'Customer confirmation did not create linked relabel work') const relabelNo = transferDetail.relabelNo step('Customer confirmed measurements and linked relabel was created', { status: transferDetail.status, relabelNo, }) const reviewedRelabel = await api(overseas, admin.overseasAccessToken, 'PUT', `/api/platform/v1/last-leg/relabel-applications/${relabelNo}/status`, { status: 'PENDING_PROCESSING', remark: marker, }) assert(reviewedRelabel.application.status === 'PENDING_PROCESSING', `Linked relabel review did not approve execution: ${reviewedRelabel.application.status}`) step('Platform approved linked relabel for PDA execution', { status: reviewedRelabel.application.status }) let relabel = await api(overseas, pda.token, 'POST', `/api/pda/v1/last-leg/relabel-tasks/${relabelNo}/claim`, { idempotencyKey: key('RELABEL-CLAIM') }) assert(relabel.items.length === (partialRelabel ? 1 : 2), `Expected ${partialRelabel ? 1 : 2} linked relabel items, got ${relabel.items.length}`) for (const [index, item] of relabel.items.entries()) { relabel = await api(overseas, pda.token, 'POST', `/api/pda/v1/last-leg/relabel-tasks/${relabelNo}/cartons/scan`, { cartonMark: item.cartonMark, expectedVersion: Number(relabel.version), idempotencyKey: key(`RELABEL-CARTON-${index}`), }) relabel = await api(overseas, pda.token, 'POST', `/api/pda/v1/last-leg/relabel-tasks/${relabelNo}/units`, { itemId: id(item.itemId), oldLabel: item.cartonMark, newLabel: null, skuVerifications: (item.skuItems || []).map((sku) => ({ skuCode: sku.skuCode, newSkuCode: sku.newSkuCode, quantity: Number(sku.quantity), })), expectedVersion: Number(relabel.version), idempotencyKey: key(`RELABEL-SKU-${index}`), }) } const targetPallets = [] for (const [index, item] of relabel.items.entries()) { const target = await api(overseas, pda.token, 'POST', '/api/pda/v1/last-leg/pallet-operations/relabel-pallet-label', { relabelNo, idempotencyKey: key(`TARGET-${index}`), }) const source = await api(overseas, pda.token, 'POST', '/api/pda/v1/last-leg/pallet-operations/pallets/resolve', { code: sourcePallet.palletNo }) const resolvedTarget = await api(overseas, pda.token, 'POST', '/api/pda/v1/last-leg/pallet-operations/pallets/resolve', { code: target.palletCode }) await api(overseas, pda.token, 'POST', '/api/pda/v1/last-leg/pallet-operations/relabel-disassemble', { idempotencyKey: key(`MOVE-${index}`), relabelNo, source: { palletCode: source.palletCode, expectedVersion: Number(source.palletVersion) }, target: { palletCode: resolvedTarget.palletCode, expectedVersion: Number(resolvedTarget.palletVersion) }, cartonMarks: [item.cartonMark], reason: marker, }) targetPallets.push(target) } const relabelPhoto = await uploadPng(pda.token, `/api/pda/v1/last-leg/relabel-tasks/${relabelNo}/photos/files`, `${marker}-relabel.png`) relabel = await api(overseas, pda.token, 'POST', `/api/pda/v1/last-leg/relabel-tasks/${relabelNo}/complete`, { photoFileIds: [id(relabelPhoto.fileId)], expectedVersion: Number(relabel.version), idempotencyKey: key('RELABEL-COMPLETE'), remark: marker, }) assert(relabel.applicationStatus === 'CUSTOMER_CONFIRM_PENDING', 'Linked relabel did not await customer confirmation') const confirmedRelabel = await api(overseas, merchant.overseasAccessToken, 'POST', `/api/merchant/v1/last-leg/relabel-applications/${relabelNo}/confirm-completion`, { idempotencyKey: key('RELABEL-CONFIRM'), remark: marker, }) transferDetail = await api(overseas, merchant.overseasAccessToken, 'GET', `/api/merchant/v1/last-leg/warehouse-transfers/${transferNo}`) assert(confirmedRelabel.application.status === 'READY_FOR_OUTBOUND', 'Linked relabel did not reach outbound-ready') assert(transferDetail.status === (partialRelabel ? 'WAIT_FBA_DESTINATION' : 'READY_FOR_DISPATCH'), `Warehouse transfer reached unexpected status ${transferDetail.status}`) step('Completed linked relabel and released transfer for dispatch', { relabelStatus: confirmedRelabel.application.status, transferStatus: transferDetail.status, }) const candidates = await api(overseas, admin.overseasAccessToken, 'GET', `/api/platform/v1/last-leg/dispatches/candidates?orderNo=${encodeURIComponent(relabelNo)}&sourceStage=RECEIVED`) const targetIds = new Set(targetPallets.map((value) => id(value.palletId))) const rows = candidates.filter((value) => value.sourceStage === 'RELABEL' && targetIds.has(id(value.warehousePalletId))) assert(rows.length === (partialRelabel ? 1 : 2), `Expected ${partialRelabel ? 1 : 2} smart-dispatch pallets, got ${rows.length}`) const dispatch = await api(overseas, admin.overseasAccessToken, 'POST', '/api/platform/v1/last-leg/dispatches/pre-arrange', { planPalletIds: rows.map((value) => id(value.warehousePalletId)), route: marker, relabelNo, }) step('Created smart pre-dispatch', { dispatchNo: dispatch.dispatchNo, status: dispatch.status }) if (partialRelabel) { const outboundCandidates = await api(overseas, admin.overseasAccessToken, 'GET', `/api/platform/v1/last-leg/outbound-orders/warehouse-transfer/candidates?customerId=${encodeURIComponent(customerId)}&warehouseId=${encodeURIComponent(warehouseId)}`) const remaining = outboundCandidates.filter((value) => value.transferNo === transferNo) assert(remaining.length === 1 && Number(remaining[0].packageCount) === 1, `Expected one unlabelled carton in warehouse-transfer outbound candidates, got ${JSON.stringify(remaining)}`) step('Verified split outbound routing', { relabelDispatchPallets: rows.length, remainingOutboundCandidates: remaining.length, remainingPackageCount: remaining[0].packageCount, }) } const sourceReport = resolve('reports', `${marker}-relabel-source.json`) const fulfillmentReport = resolve('reports', `${marker}-fulfillment.json`) await writeFile(sourceReport, JSON.stringify({ marker, passed: true, scenarios: [{ name: 'SPLIT-TWO-TARGETS', relabelNo, dispatchNo: dispatch.dispatchNo, partialRelabel, }] }, null, 2), 'utf8') const child = spawnSync(process.execPath, [resolve('scripts', 'overseas-relabel-fulfillment-acceptance.mjs')], { cwd: resolve('.'), stdio: 'inherit', env: { ...process.env, ADMIN_TEST_GATEWAY: domestic, ADMIN_OVERSEAS_GATEWAY: overseas, ADMIN_OVERSEAS_ACCESS_TOKEN: admin.overseasAccessToken, MERCHANT_OVERSEAS_ACCESS_TOKEN: merchant.overseasAccessToken, PDA_OVERSEAS_ACCESS_TOKEN: pda.token, OVERSEAS_TEST_CUSTOMER_ID: customerId, OVERSEAS_ACCEPTANCE_SOURCE: sourceReport, OVERSEAS_FULFILLMENT_OUTPUT: fulfillmentReport, }, }) if (child.error) throw child.error if (child.status !== 0) throw new Error(`Fulfillment acceptance failed with exit code ${child.status}`) let finalTransfer = await api(overseas, merchant.overseasAccessToken, 'GET', `/api/merchant/v1/last-leg/warehouse-transfers/${transferNo}`) let remainingDispatchNo = null let remainingFulfillmentReport = null if (partialRelabel) { assert(finalTransfer.status === 'WAIT_FBA_DESTINATION', `Partial relabel dispatch incorrectly closed the transfer: ${finalTransfer.status}`) const beforeBind = await api(overseas, admin.overseasAccessToken, 'GET', `/api/platform/v1/last-leg/outbound-orders/warehouse-transfer/candidates?customerId=${encodeURIComponent(customerId)}&warehouseId=${encodeURIComponent(warehouseId)}`) const remainingBeforeBind = beforeBind.filter((value) => value.transferNo === transferNo) assert(remainingBeforeBind.length === 1 && Number(remainingBeforeBind[0].packageCount) === 1 && remainingBeforeBind[0].palletId && remainingBeforeBind[0].warehousePalletId, `Expected one selectable remaining pallet before destination binding, got ${JSON.stringify(remainingBeforeBind)}`) await api(overseas, admin.overseasAccessToken, 'POST', '/api/platform/v1/last-leg/warehouse-transfers/bind-destination', { transfers: [{ customerId, transferNo }], destinationType: 'FBA', destinationWarehouseCode: destinationCode, destinationWarehouseName: destinationCode, idempotencyKey: key('BIND-REMAINING-DESTINATION'), }) const afterBind = await api(overseas, admin.overseasAccessToken, 'GET', `/api/platform/v1/last-leg/outbound-orders/warehouse-transfer/candidates?customerId=${encodeURIComponent(customerId)}&warehouseId=${encodeURIComponent(warehouseId)}`) assert(!afterBind.some((value) => value.transferNo === transferNo), 'Destination-bound transfer remained in the destination-completion outbound list') const remainingCandidates = await api(overseas, admin.overseasAccessToken, 'GET', `/api/platform/v1/last-leg/dispatches/candidates?orderNo=${encodeURIComponent(transferNo)}&sourceStage=RECEIVED`) const remainingRows = remainingCandidates.filter((value) => value.sourceStage === 'RECEIVED' && Number(value.packageCount) === 1 && value.destinationWarehouseCode === destinationCode) assert(remainingRows.length === 1, `Expected one destination-bound smart-dispatch pallet, got ${JSON.stringify(remainingRows)}`) const remainingDispatch = await api(overseas, admin.overseasAccessToken, 'POST', '/api/platform/v1/last-leg/dispatches/pre-arrange', { planPalletIds: remainingRows.map((value) => id(value.planPalletId)), route: `${marker}-REMAINING`, }) remainingDispatchNo = remainingDispatch.dispatchNo const remainingSourceReport = resolve('reports', `${marker}-remaining-source.json`) remainingFulfillmentReport = resolve('reports', `${marker}-remaining-fulfillment.json`) await writeFile(remainingSourceReport, JSON.stringify({ marker, passed: true, scenarios: [{ name: 'SPLIT-TWO-TARGETS', relabelNo: '', dispatchNo: remainingDispatchNo, partialRelabel: true, }] }, null, 2), 'utf8') const remainingChild = spawnSync(process.execPath, [resolve('scripts', 'overseas-relabel-fulfillment-acceptance.mjs')], { cwd: resolve('.'), stdio: 'inherit', env: { ...process.env, ADMIN_TEST_GATEWAY: domestic, ADMIN_OVERSEAS_GATEWAY: overseas, ADMIN_OVERSEAS_ACCESS_TOKEN: admin.overseasAccessToken, MERCHANT_OVERSEAS_ACCESS_TOKEN: merchant.overseasAccessToken, PDA_OVERSEAS_ACCESS_TOKEN: pda.token, OVERSEAS_TEST_CUSTOMER_ID: customerId, OVERSEAS_ACCEPTANCE_SOURCE: remainingSourceReport, OVERSEAS_FULFILLMENT_OUTPUT: remainingFulfillmentReport, }, }) if (remainingChild.error) throw remainingChild.error if (remainingChild.status !== 0) throw new Error(`Remaining fulfillment failed with exit code ${remainingChild.status}`) finalTransfer = await api(overseas, merchant.overseasAccessToken, 'GET', `/api/merchant/v1/last-leg/warehouse-transfers/${transferNo}`) assert(finalTransfer.status === 'OUTBOUNDED', `Remaining transfer did not complete outbound: ${finalTransfer.status}`) step('Bound remaining carton destination and completed its smart dispatch', { remainingDispatchNo, remainingPackageCount: 1, transferStatus: finalTransfer.status, }) } Object.assign(report, { passed: true, transferNo, relabelNo, dispatchNo: dispatch.dispatchNo, remainingDispatchNo, transferStatus: finalTransfer.status, fulfillmentReport, remainingFulfillmentReport, finishedAt: new Date().toISOString(), }) } catch (error) { report.error = error instanceof Error ? error.message : String(error) report.finishedAt = new Date().toISOString() throw error } finally { if (admin?.accessToken && pda?.membershipId) { try { await api(domestic, admin.accessToken, 'PUT', `/api/platform/v1/iam/users/${pda.membershipId}/status`, { status: 'DISABLED', }) report.pdaCleanup = 'DISABLED' } catch (error) { report.pdaCleanup = `FAILED: ${error instanceof Error ? error.message : String(error)}` } } await writeFile(output, JSON.stringify(report, null, 2), 'utf8') } console.log(JSON.stringify(report, null, 2))