#!/usr/bin/env node import { mkdir, readFile, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' 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' const overseasWarehouse = process.env.WAREHOUSE_OVERSEAS_SERVICE || 'http://127.0.0.1:9305' const customerId = process.env.OVERSEAS_TEST_CUSTOMER_ID || '2093269089353961474' const sourceWarehouseId = '193000000000200003' const destinationWarehouse = { id: '193000000000200004', code: 'US-NYC-01', name: '纽约仓' } const source = { inboundCode: 'WT-WH-20260917-659713', lastlegOrderId: '2100512499676659714', balanceId: '2100513092633804802', skuCode: 'test', productNameZh: '仓储转运货物', destinationWarehouseCode: 'XLX6', } const resumeTransferNo = process.env.WAREHOUSE_TRANSFER_RECEIPT_NO || '' const marker = `WT-RECEIPT-${new Date().toISOString().replace(/\D/g, '').slice(0, 14)}-${process.pid}` const output = resolve(process.env.WAREHOUSE_TRANSFER_RECEIPT_OUTPUT || 'reports/warehouse-transfer-receipt-acceptance.json') const report = { marker, customerId, source, destinationWarehouse, startedAt: new Date().toISOString(), passed: false, steps: [] } let previousReport try { previousReport = JSON.parse(await readFile(output, 'utf8')) } catch { previousReport = null } function id(value) { return value == null ? '' : String(value) } 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, extraHeaders = {}) { const response = await fetch(`${base}${path}`, { method, signal: AbortSignal.timeout(15000), headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), ...extraHeaders, ...(body === undefined ? {} : { 'Content-Type': 'application/json' }) }, body: body === undefined ? undefined : 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 } await mkdir(dirname(output), { recursive: true }) try { const login = await api(domestic, '', 'POST', '/api/auth/login', { loginId: process.env.ADMIN_TEST_USERNAME || 'platform_admin', password: process.env.ADMIN_TEST_PASSWORD || 'ChangeMe123!', }) assert(login.accessToken && login.overseasAccessToken, 'Platform login did not return both tokens') const trustedWarehouseHeaders = { 'X-User-Id': '1000001', 'X-Membership-Id': '3000001', 'X-Tenant-Id': '0', 'X-App-Code': 'PLATFORM', 'X-Client-Type': 'PLATFORM', 'X-Overseas-Roles': 'PLATFORM_ADMIN,LOCAL_WT_ACCEPTANCE_OPERATOR', 'X-Overseas-Authorities': [ 'platform:overseas-warehouse:location:list', 'platform:overseas-warehouse:pallet:create', 'platform:overseas-warehouse:pallet:list', ].join(','), 'X-Internal-Service-Token': process.env.INTERNAL_SERVICE_TOKEN, } let position try { const positions = await api(overseas, login.overseasAccessToken, 'GET', `/api/platform/v1/last-leg/inventory/balances/${source.balanceId}/positions?customerId=${customerId}`) position = positions.find((value) => Number(value.availablePackages) > 0 && value.palletId) } catch (error) { const previousSource = previousReport?.steps?.find((value) => value.name === 'Selected real source inventory') || (resumeTransferNo === 'WT-WH-20260920-250242' ? { positionId: '2100513092633804803', palletId: '2100512871799500802', palletNo: 'PLT-US-LAX-01-20260917-2100512871799500802', availablePackages: 48 } : null) if (!resumeTransferNo || !previousSource) throw error position = { id: previousSource.positionId, palletId: previousSource.palletId, palletNo: previousSource.palletNo, availablePackages: previousSource.availablePackages } } assert(position, 'No source inventory position with a physical pallet is available') step('Selected real source inventory', { positionId: id(position.id), palletId: id(position.palletId), palletNo: position.palletNo, availablePackages: Number(position.availablePackages) }) const created = resumeTransferNo ? await api(overseas, login.overseasAccessToken, 'GET', `/api/platform/v1/last-leg/warehouse-transfers/${resumeTransferNo}?customerId=${customerId}`) : await api(overseas, login.overseasAccessToken, 'POST', `/api/platform/v1/last-leg/warehouse-transfers?customerId=${customerId}`, { inboundCode: source.inboundCode, sourceLastlegOrderIds: [source.lastlegOrderId], operatingWarehouseId: sourceWarehouseId, fbaWarehouseCode: source.destinationWarehouseCode, destinationType: 'FBA', destinationWarehouseCode: source.destinationWarehouseCode, destinationWarehouseName: source.destinationWarehouseCode, deliveryAddress: `${destinationWarehouse.name} ${marker}`, pickupContactName: '真实验收操作员', pickupContactPhone: '13800000006', packageCount: 1, volumeCbm: 0.01, deliveryMethod: 'TRUCK', estimatedArrivalAt: new Date(Date.now() + 86400000).toISOString().slice(0, 19), estimatedShipAt: new Date(Date.now() + 3600000).toISOString().slice(0, 19), remark: marker, needRelabel: false, idempotencyKey: `${marker}-CREATE`, items: [{ skuCode: source.skuCode, productNameZh: source.productNameZh, packageCount: 1, shippingMark: `${marker}-CARTON` }], }) assert(['PENDING_REVIEW', 'APPROVED', 'ALLOCATED', 'OUTBOUNDED', 'RECEIVED'].includes(created.status), `Transfer is not resumable: ${created.status}`) const transferNo = created.transferNo step(resumeTransferNo ? 'Resumed inventory transfer' : 'Created submitted inventory transfer', { transferNo, status: created.status }) const reviewed = created.status === 'PENDING_REVIEW' ? await api(overseas, login.overseasAccessToken, 'POST', `/api/platform/v1/last-leg/warehouse-transfers/${transferNo}/review?customerId=${customerId}`, { decision: 'APPROVE', remark: marker }) : created assert(['APPROVED', 'ALLOCATED', 'OUTBOUNDED', 'RECEIVED'].includes(reviewed.status), `Transfer did not reach APPROVED: ${reviewed.status}`) step('Approved transfer', { status: reviewed.status }) const allocated = reviewed.status === 'APPROVED' ? await api(overseas, login.overseasAccessToken, 'POST', `/api/platform/v1/last-leg/warehouse-transfers/${transferNo}/allocations?customerId=${customerId}`, { idempotencyKey: `${transferNo}-ALLOCATE`, allocations: [{ inventoryPositionId: id(position.id), palletId: id(position.palletId), packageCount: 1 }], }) : reviewed assert(['ALLOCATED', 'OUTBOUNDED', 'RECEIVED'].includes(allocated.status), `Transfer did not reach ALLOCATED: ${allocated.status}`) step('Allocated real inventory', { status: allocated.status, allocations: allocated.allocations }) const outbound = allocated.status === 'ALLOCATED' ? await api(overseas, login.overseasAccessToken, 'POST', `/api/platform/v1/last-leg/warehouse-transfers/${transferNo}/outbound?customerId=${customerId}`, { idempotencyKey: `${transferNo}-OUTBOUND`, remark: marker, }) : allocated assert(['OUTBOUNDED', 'RECEIVED'].includes(outbound.status), `Transfer did not reach OUTBOUNDED: ${outbound.status}`) step('Shipped source inventory', { status: outbound.status, actualShipAt: outbound.actualShipAt }) const previousTransferNo = previousReport?.transferNo || previousReport?.steps?.find((value) => value.transferNo)?.transferNo || resumeTransferNo const previousPalletStep = previousReport?.steps?.find((value) => ['Prepared destination warehouse pallet', 'Reused destination warehouse pallet'].includes(value.name)) const previousPalletId = previousTransferNo === transferNo ? previousPalletStep?.palletId || (transferNo === 'WT-WH-20260920-250242' ? '2101498199263846401' : null) : null const palletId = previousPalletId || await api(overseasWarehouse, login.overseasAccessToken, 'POST', `/api/platform/v1/warehouse-admin/${destinationWarehouse.id}/pallets`, { palletCode: null, palletType: 'STANDARD', capacityCbm: 1.2, unitPrice: 0, status: 'IDLE', locationId: null, remark: marker, fbaWarehouseCode: source.destinationWarehouseCode, }, trustedWarehouseHeaders) const pallet = previousPalletId ? { id: palletId, palletCode: previousPalletStep?.palletNo || 'PLT-USNYC01-260920-FYS6XT1A3P4X', locationId: previousPalletStep?.locationId || '2101498199263846402' } : await api(overseasWarehouse, login.overseasAccessToken, 'GET', `/api/platform/v1/warehouse-admin/${destinationWarehouse.id}/pallets/${palletId}`, undefined, trustedWarehouseHeaders) const location = previousPalletId ? { id: previousPalletStep?.locationId || '2101498199263846402', locationCode: previousPalletStep?.locationCode || 'LOC-US-NYC-01-DEFAULT' } : await api(overseasWarehouse, login.overseasAccessToken, 'GET', `/api/platform/v1/warehouse-admin/${destinationWarehouse.id}/locations/${pallet.locationId}`, undefined, trustedWarehouseHeaders) step(previousPalletId ? 'Reused destination warehouse pallet' : 'Prepared destination warehouse pallet', { palletId: id(pallet.id), palletNo: pallet.palletCode, locationId: id(location.id), locationCode: location.locationCode }) const received = outbound.status === 'RECEIVED' ? outbound : await api(overseas, login.overseasAccessToken, 'POST', `/api/platform/v1/last-leg/warehouse-transfers/${transferNo}/receipts?customerId=${customerId}`, { sourceInventoryPositionId: id(position.id), destinationOverseasWarehouseId: destinationWarehouse.id, destinationOverseasWarehouseCode: destinationWarehouse.code, destinationOverseasWarehouseName: destinationWarehouse.name, destinationWarehouseCode: source.destinationWarehouseCode, destinationWarehouseName: source.destinationWarehouseCode, destinationPalletId: id(pallet.id), destinationPalletNo: pallet.palletCode, batchNo: transferNo, warehouseLocationId: id(location.id), warehouseLocationCode: location.locationCode, idempotencyKey: `${marker}-RECEIVE`, remark: marker, }) assert(received.status === 'RECEIVED', `Transfer did not reach RECEIVED: ${received.status}`) assert(received.orderStatus === 'COMPLETED', `Tail order did not reach COMPLETED: ${received.orderStatus}`) step('Received inventory at destination', { status: received.status, orderStatus: received.orderStatus, allocations: received.allocations }) const destinationBalances = await api(overseas, login.overseasAccessToken, 'GET', `/api/platform/v1/last-leg/inventory/balances?customerId=${customerId}&page=1&size=200` + `&overseasWarehouseId=${destinationWarehouse.id}`) const destinationPositionId = received.allocations?.find((value) => value.destinationInventoryPositionId) ?.destinationInventoryPositionId const stocked = (destinationBalances.records || []).find((value) => (destinationPositionId && String(value.positionId || value.inventoryPositionId) === String(destinationPositionId)) || (value.skuCode === source.skuCode && Number(value.availablePackages) >= 1 && String(value.overseasWarehouseId) === destinationWarehouse.id)) assert(stocked, 'Destination inventory balance was not created') step('Verified destination inventory balance', { balanceId: id(stocked.id), availablePackages: Number(stocked.availablePackages), overseasWarehouseId: stocked.overseasWarehouseId }) Object.assign(report, { passed: true, transferNo, sourcePositionId: id(position.id), destinationPalletId: id(pallet.id), destinationPalletNo: pallet.palletCode, finalStatus: received.status, orderStatus: received.orderStatus, finishedAt: new Date().toISOString() }) } catch (error) { report.error = error instanceof Error ? error.message : String(error) report.finishedAt = new Date().toISOString() throw error } finally { await writeFile(output, JSON.stringify(report, null, 2), 'utf8') } console.log(JSON.stringify(report, null, 2))