343 lines
23 KiB
JavaScript
343 lines
23 KiB
JavaScript
const MODULE = 'phase4-overseas-wallet-and-pallet-cost'
|
|
const SCENARIO = 'phase4-overseas-wallet-and-pallet-cost-http'
|
|
const OPTIONS = { scenario: SCENARIO, deploymentBlocker: true }
|
|
const PROCUREMENT = '/api/platform/v1/last-leg/overseas-procurements'
|
|
const RELABEL = '/api/platform/v1/last-leg/relabel-applications'
|
|
const FINANCE = '/api/platform/finance/v1'
|
|
const CLIENT_FINANCE = '/api/client/finance/v1'
|
|
const PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64')
|
|
|
|
const id = (value) => value == null ? '' : String(value)
|
|
const money = (value) => Number(Number(value || 0).toFixed(2))
|
|
const success = (response) => response?.http === 200 && String(response?.body?.code) === '200'
|
|
const rows = (value) => Array.isArray(value) ? value : Array.isArray(value?.records) ? value.records : []
|
|
const trace = (response) => `HTTP=${response?.http ?? 'missing'}, code=${response?.body?.code ?? 'missing'}, requestId=${response?.requestId || 'missing'}, traceId=${response?.traceId || 'missing'}`
|
|
const query = (path, params) => {
|
|
const search = new URLSearchParams()
|
|
for (const [key, value] of Object.entries(params)) if (value !== undefined && value !== null && value !== '') search.set(key, String(value))
|
|
return `${path}?${search}`
|
|
}
|
|
|
|
export function beijingDate(now = new Date()) {
|
|
const parts = Object.fromEntries(new Intl.DateTimeFormat('en-US', {
|
|
timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit',
|
|
}).formatToParts(now).map((part) => [part.type, part.value]))
|
|
return `${parts.year}${parts.month}${parts.day}`
|
|
}
|
|
|
|
export function matchesProcurementNo(orderNo, prefix, now = new Date()) {
|
|
return typeof prefix === 'string' && prefix.length > 0
|
|
&& new RegExp(`^${prefix}${beijingDate(now)}\\d{4}$`).test(String(orderNo || ''))
|
|
}
|
|
|
|
async function poll(action, predicate, attempts = 60, intervalMs = 1_000) {
|
|
let result = null
|
|
for (let index = 0; index < attempts; index += 1) {
|
|
result = await action()
|
|
if (predicate(result)) return { result, attempts: index + 1 }
|
|
if (index + 1 < attempts) await new Promise((resolve) => setTimeout(resolve, intervalMs))
|
|
}
|
|
return { result, attempts }
|
|
}
|
|
|
|
function assert(context, name, response, passed, evidence = '') {
|
|
context.assert(MODULE, name, response, passed, `${evidence}${evidence ? '; ' : ''}${trace(response)}`, OPTIONS)
|
|
}
|
|
|
|
async function merchantLogin(context, fixture) {
|
|
const response = await context.request('POST', '/api/auth/login', { loginId: fixture.username, password: fixture.password }, '')
|
|
const auth = success(response) ? response.body.data : null
|
|
assert(context, '客户账号同时取得国内和海外短时Token', response,
|
|
Boolean(auth?.accessToken) && Boolean(auth?.overseasAccessToken),
|
|
`customerId=${fixture.tenantId}, domestic=${Boolean(auth?.accessToken)}, overseas=${Boolean(auth?.overseasAccessToken)}`)
|
|
return auth
|
|
}
|
|
|
|
async function platformAccount(context, customerId) {
|
|
const response = await context.request('GET', query(`${FINANCE}/accounts`, { page: 1, pageSize: 20, customerId }))
|
|
const account = success(response) ? rows(response.body.data).find((item) => id(item.customerId) === id(customerId)) : null
|
|
return { response, account }
|
|
}
|
|
|
|
async function prepareWallet(context, customerId) {
|
|
const before = await platformAccount(context, customerId)
|
|
assert(context, '平台客户账户返回余额和授信字段', before.response,
|
|
before.account && ['actualBalance', 'frozenBalance', 'availableBalance', 'creditLimit', 'creditUsed', 'availableCredit', 'accountVersion']
|
|
.every((field) => Number.isFinite(Number(before.account[field]))), `customerId=${customerId}`)
|
|
if (!before.account) return null
|
|
|
|
const creditLimit = 2.00
|
|
const creditExpiresOn = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10)
|
|
const credit = await context.request('PUT', `${FINANCE}/accounts/${encodeURIComponent(customerId)}/credit-limit`,
|
|
{ creditLimit, creditExpiresOn, remark: context.marker,
|
|
expectedAccountVersion: Number(before.account.accountVersion) }, context.token,
|
|
{ 'Idempotency-Key': `${context.marker}-wallet-credit-limit`.slice(0, 128) })
|
|
assert(context, '授信额度可通过正式接口初始化', credit,
|
|
success(credit) && money(credit.body.data?.creditLimitAfter) === creditLimit,
|
|
`creditLimit=${credit.body?.data?.creditLimitAfter ?? 'missing'}`)
|
|
if (!success(credit)) return null
|
|
|
|
const credited = await platformAccount(context, customerId)
|
|
assert(context, '授信初始化后账户版本和可用授信已更新', credited.response,
|
|
credited.account && money(credited.account.creditLimit) === creditLimit
|
|
&& Number(credited.account.accountVersion) > Number(before.account.accountVersion),
|
|
`version=${credited.account?.accountVersion ?? 'missing'}, availableCredit=${credited.account?.availableCredit ?? 'missing'}`)
|
|
if (!credited.account) return null
|
|
|
|
const rechargeAmount = 10.00
|
|
const recharge = await context.request('POST', `${FINANCE}/accounts/${encodeURIComponent(customerId)}/adjustments`, {
|
|
type: 'RECHARGE', amount: rechargeAmount, occurredDate: new Date().toISOString().slice(0, 10),
|
|
paymentMethod: 'BANK_TRANSFER', referenceNo: `${context.marker}-AUTO`, remark: context.marker,
|
|
expectedAccountVersion: Number(credited.account.accountVersion),
|
|
}, context.token, { 'Idempotency-Key': `${context.marker}-wallet-recharge`.slice(0, 128) })
|
|
assert(context, '平台人工充值立即进入实际余额', recharge,
|
|
success(recharge) && money(recharge.body.data?.actualBalanceAfter) === money(Number(before.account.actualBalance) + rechargeAmount),
|
|
`before=${before.account.actualBalance}, after=${recharge.body?.data?.actualBalanceAfter ?? 'missing'}`)
|
|
return success(recharge) ? { before: before.account, funded: recharge.body.data, rechargeAmount, creditLimit } : null
|
|
}
|
|
|
|
async function procurementFlow(context, fixture, overseasToken) {
|
|
const merchantPath = '/api/merchant/v1/last-leg/overseas-procurements'
|
|
const payload = {
|
|
customerName: fixture.tenantName, purchaseCategory: 'OFFICE_SUPPLIES', productName: `${context.marker} 钱包采购`,
|
|
productSpecification: 'full acceptance wallet', productUrl: null, purchaseQuantity: 2,
|
|
budgetUnitPrice: 1, currency: 'USD', receivingAddress: 'Los Angeles',
|
|
contactName: '全量验收联系人', contactPhone: '13800000002', remark: context.marker,
|
|
idempotencyKey: `${context.marker}-wallet-procurement`.slice(0, 255),
|
|
}
|
|
const created = await context.requestOverseas('POST', merchantPath, payload, {}, overseasToken)
|
|
const detail = created?.body?.data
|
|
const orderNo = detail?.summary?.orderNo
|
|
assert(context, '客户创建钱包采购单', created,
|
|
success(created) && id(detail?.summary?.customerId) === id(fixture.tenantId) && detail?.summary?.status === 'PENDING_REVIEW',
|
|
`orderNo=${orderNo || 'missing'}, status=${detail?.summary?.status || 'missing'}`)
|
|
if (!success(created) || !orderNo) return null
|
|
assert(context, '采购单号按客户拼音首字母加北京时间日期和四位序号生成', created,
|
|
fixture.expectedProcurementPrefix ? matchesProcurementNo(orderNo, fixture.expectedProcurementPrefix) : /^[A-Z0-9]{1,8}\d{8}\d{4}$/.test(orderNo),
|
|
`orderNo=${orderNo}, expectedPrefix=${fixture.expectedProcurementPrefix || 'custom-customer'}`)
|
|
|
|
const customerId = id(fixture.tenantId)
|
|
const approved = await context.requestOverseas('POST', query(`${PROCUREMENT}/${encodeURIComponent(orderNo)}/review`, { customerId }), {
|
|
version: Number(detail.summary.version), decision: 'APPROVE',
|
|
idempotencyKey: `${context.marker}-wallet-procurement-review`.slice(0, 255), remark: context.marker,
|
|
})
|
|
assert(context, '平台审核通过钱包采购单', approved,
|
|
success(approved) && approved.body.data?.summary?.status === 'APPROVED',
|
|
`status=${approved.body?.data?.summary?.status || 'missing'}`)
|
|
if (!success(approved)) return null
|
|
|
|
const files = []
|
|
for (const category of ['VOUCHER', 'RECEIPT']) {
|
|
const uploaded = await context.requestOverseasMultipart('POST',
|
|
query(`${PROCUREMENT}/${encodeURIComponent(orderNo)}/files/${category}`, { customerId }),
|
|
{ content: PNG, type: 'image/png', name: `${category.toLowerCase()}-${orderNo}.png` })
|
|
assert(context, `采购单上传${category}文件`, uploaded,
|
|
success(uploaded) && id(uploaded.body.data?.fileId) !== '' && uploaded.body.data?.category === category,
|
|
`fileId=${id(uploaded.body?.data?.fileId) || 'missing'}`)
|
|
if (success(uploaded)) files.push(uploaded.body.data)
|
|
}
|
|
if (files.length !== 2) return null
|
|
|
|
const fee = await context.requestOverseas('POST', query(`${PROCUREMENT}/${encodeURIComponent(orderNo)}/fees`, { customerId }), {
|
|
feeItemCode: 'PROCUREMENT_SERVICE', feeItemName: '采购服务费', amount: 0.01,
|
|
voucherFileId: id(files.find((item) => item.category === 'VOUCHER')?.fileId), remark: context.marker,
|
|
})
|
|
const feeNo = fee?.body?.data?.feeNo
|
|
assert(context, '采购费用创建并开始钱包冻结', fee,
|
|
success(fee) && Boolean(feeNo) && ['FREEZE_PENDING', 'FROZEN'].includes(fee.body.data?.walletStatus),
|
|
`feeNo=${feeNo || 'missing'}, walletStatus=${fee.body?.data?.walletStatus || 'missing'}`)
|
|
if (!success(fee) || !feeNo) return null
|
|
|
|
const detailPath = query(`${PROCUREMENT}/${encodeURIComponent(orderNo)}`, { customerId })
|
|
const frozen = await poll(async () => context.requestOverseas('GET', detailPath),
|
|
(response) => success(response) && response.body.data?.fees?.find((item) => item.feeNo === feeNo)?.walletStatus === 'FROZEN')
|
|
const frozenFee = frozen.result?.body?.data?.fees?.find((item) => item.feeNo === feeNo)
|
|
assert(context, '采购费用跨区冻结到账', frozen.result,
|
|
frozenFee?.walletStatus === 'FROZEN', `feeNo=${feeNo}, pollRequests=${frozen.attempts}, walletStatus=${frozenFee?.walletStatus || 'missing'}`)
|
|
if (frozenFee?.walletStatus !== 'FROZEN') return null
|
|
|
|
const current = frozen.result.body.data.summary
|
|
const settledOrder = await context.requestOverseas('PATCH', detailPath, {
|
|
version: Number(current.version), inboundQuantity: Number(current.purchaseQuantity),
|
|
})
|
|
assert(context, '采购全量入库推进待出库并触发结算', settledOrder,
|
|
success(settledOrder) && settledOrder.body.data?.summary?.status === 'PENDING_OUTBOUND',
|
|
`status=${settledOrder.body?.data?.summary?.status || 'missing'}`)
|
|
const settled = await poll(async () => context.requestOverseas('GET', detailPath),
|
|
(response) => success(response) && response.body.data?.fees?.find((item) => item.feeNo === feeNo)?.walletStatus === 'SETTLED')
|
|
const settledFee = settled.result?.body?.data?.fees?.find((item) => item.feeNo === feeNo)
|
|
assert(context, '采购费用跨区结算到账', settled.result,
|
|
settledFee?.walletStatus === 'SETTLED', `feeNo=${feeNo}, pollRequests=${settled.attempts}, walletStatus=${settledFee?.walletStatus || 'missing'}`)
|
|
return { orderNo, feeNo, freezePollRequests: frozen.attempts, settlePollRequests: settled.attempts }
|
|
}
|
|
|
|
function relabelPayload(context, warehouseId, source, suffix, chargeAmount) {
|
|
const value = String(context.marker).replace(/[^A-Za-z0-9]/g, '').slice(-18).toUpperCase()
|
|
return {
|
|
customerOrderNo: `${context.marker}-${suffix}`.slice(0, 128), operationWarehouseId: id(warehouseId),
|
|
labelDeadline: new Date(Date.now() + 86_400_000).toISOString().slice(0, 19),
|
|
contactName: '全量验收联系人', contactPhone: '+1-213-555-0199', chargeAmount,
|
|
remark: context.marker, idempotencyKey: `${context.marker}-${suffix}`.slice(0, 255),
|
|
items: [{ sourceInventoryBalanceId: id(source.inventoryBalanceId),
|
|
sourceInventoryPositionId: id(source.position.positionId), sourceInboundOrderId: id(source.inboundOrderId),
|
|
skuCode: source.skuCode || `OLD-${value}-${suffix}`.slice(0, 128),
|
|
productNameZh: source.productNameZh || `${context.marker} 换标商品`,
|
|
totalPackageCount: 1, notifiedPackageCount: 1, shipmentNo: `SHIP-${value}-${suffix}`.slice(0, 128),
|
|
newSkuCode: `NEW-${value}-${suffix}`.slice(0, 128), labelRequirement: context.marker,
|
|
cartonMark: `CM-${value}-${suffix}`.slice(0, 255),
|
|
fbaCartonMark: `FBA-${value}-${suffix}`.slice(0, 255),
|
|
destinationFbaWarehouseCode: source.destinationFbaWarehouseCode }],
|
|
}
|
|
}
|
|
|
|
async function relabelFlow(context, overseasToken, warehouseId, suffix, terminalStatus) {
|
|
const candidatesResponse = await context.requestOverseas('GET', query(
|
|
'/api/merchant/v1/last-leg/relabel-applications/inventory-candidates', { operationWarehouseId: warehouseId }),
|
|
undefined, {}, overseasToken)
|
|
const candidates = success(candidatesResponse) && Array.isArray(candidatesResponse.body?.data)
|
|
? candidatesResponse.body.data : []
|
|
const source = candidates.find((candidate) => id(candidate.inboundOrderId)
|
|
&& id(candidate.inventoryBalanceId)
|
|
&& id(candidate.destinationFbaWarehouseCode)
|
|
&& candidate.positions?.some((position) => id(position.positionId) && Number(position.availablePackages) > 0))
|
|
if (source) source.position = source.positions.find((position) => id(position.positionId)
|
|
&& Number(position.availablePackages) > 0)
|
|
assert(context, `${suffix}换标读取当前客户权威库存候选`, candidatesResponse,
|
|
Boolean(source?.position), `warehouseId=${warehouseId}, candidates=${candidates.length}`)
|
|
if (!source?.position) return null
|
|
const payload = relabelPayload(context, warehouseId, source, suffix, 0.01)
|
|
const created = await context.requestOverseas('POST', '/api/merchant/v1/last-leg/relabel-applications', payload, {}, overseasToken)
|
|
const relabelNo = created?.body?.data?.application?.relabelNo
|
|
assert(context, `${suffix}换标单创建并携带收费金额`, created,
|
|
success(created) && Boolean(relabelNo) && money(created.body.data?.application?.chargeAmount) === 0.01,
|
|
`relabelNo=${relabelNo || 'missing'}, charge=${created.body?.data?.application?.chargeAmount ?? 'missing'}`)
|
|
if (!success(created) || !relabelNo) return null
|
|
|
|
const detailPath = `${RELABEL}/${encodeURIComponent(relabelNo)}`
|
|
const frozen = await poll(async () => context.requestOverseas('GET', detailPath),
|
|
(response) => success(response) && response.body.data?.application?.walletStatus === 'FROZEN')
|
|
assert(context, `${suffix}换标费用跨区冻结到账`, frozen.result,
|
|
frozen.result?.body?.data?.application?.walletStatus === 'FROZEN',
|
|
`relabelNo=${relabelNo}, pollRequests=${frozen.attempts}, walletStatus=${frozen.result?.body?.data?.application?.walletStatus || 'missing'}`)
|
|
if (frozen.result?.body?.data?.application?.walletStatus !== 'FROZEN') return null
|
|
|
|
if (terminalStatus === 'COMPLETED') {
|
|
const processing = await context.requestOverseas('PUT', `${detailPath}/status`, { status: 'PROCESSING', remark: context.marker })
|
|
assert(context, '换标单冻结后才允许进入处理中', processing,
|
|
success(processing) && processing.body.data?.application?.status === 'PROCESSING')
|
|
}
|
|
const terminal = await context.requestOverseas('PUT', `${detailPath}/status`, { status: terminalStatus, remark: context.marker })
|
|
assert(context, `${suffix}换标单进入${terminalStatus}`, terminal,
|
|
success(terminal) && terminal.body.data?.application?.status === terminalStatus)
|
|
const walletTerminal = terminalStatus === 'COMPLETED' ? 'SETTLED' : 'RELEASED'
|
|
const wallet = await poll(async () => context.requestOverseas('GET', detailPath),
|
|
(response) => success(response) && response.body.data?.application?.walletStatus === walletTerminal)
|
|
assert(context, `${suffix}换标费用跨区${walletTerminal}`, wallet.result,
|
|
wallet.result?.body?.data?.application?.walletStatus === walletTerminal,
|
|
`relabelNo=${relabelNo}, pollRequests=${wallet.attempts}, walletStatus=${wallet.result?.body?.data?.application?.walletStatus || 'missing'}`)
|
|
return { relabelNo, terminalStatus, walletStatus: walletTerminal, freezePollRequests: frozen.attempts, terminalPollRequests: wallet.attempts }
|
|
}
|
|
|
|
async function verifyClientWallet(context, token, fixture, wallet, procurement, relabels) {
|
|
const account = await context.request('GET', `${CLIENT_FINANCE}/account`, undefined, token)
|
|
const expectedActual = money(Number(wallet.before.actualBalance) + wallet.rechargeAmount - 0.02)
|
|
assert(context, '客户工作台余额与冻结结算结果一致', account,
|
|
success(account) && money(account.body.data?.actualBalance) === expectedActual
|
|
&& money(account.body.data?.frozenBalance) === money(wallet.before.frozenBalance)
|
|
&& money(account.body.data?.creditUsed) === money(wallet.before.creditUsed),
|
|
`expectedActual=${expectedActual}, actual=${account.body?.data?.actualBalance ?? 'missing'}, frozen=${account.body?.data?.frozenBalance ?? 'missing'}, creditUsed=${account.body?.data?.creditUsed ?? 'missing'}`)
|
|
const ledger = await context.request('GET', `${CLIENT_FINANCE}/account/ledger?page=1&size=100`, undefined, token)
|
|
const entries = rows(ledger?.body?.data)
|
|
const businessNos = [procurement.feeNo, ...relabels.map((item) => item.relabelNo)]
|
|
assert(context, '客户账本包含充值、冻结、结算和释放明细', ledger,
|
|
success(ledger) && entries.some((item) => item.changeType === 'RECHARGE')
|
|
&& businessNos.every((businessNo) => entries.some((item) => item.sourceNo === businessNo))
|
|
&& ['FREEZE', 'CONSUMPTION', 'UNFREEZE'].every((type) => entries.some((item) => item.changeType === type)),
|
|
`entries=${entries.length}, businessNos=${businessNos.join(',')}`)
|
|
return account?.body?.data
|
|
}
|
|
|
|
function summaryValid(value, fields) {
|
|
return fields.every((field) => Number.isInteger(Number(value?.[field])) && Number(value[field]) >= 0)
|
|
}
|
|
|
|
async function verifyPalletSummaries(context, warehouseId) {
|
|
const domestic = await context.request('GET', '/api/platform/v1/pallets/summary?tenantId=0&page=1&size=20')
|
|
const d = domestic?.body?.data
|
|
assert(context, '国内托盘汇总含闲置和占用数量', domestic,
|
|
success(domestic) && summaryValid(d, ['totalCount', 'idleCount', 'occupiedCount'])
|
|
&& Number(d.totalCount) === Number(d.idleCount) + Number(d.occupiedCount),
|
|
`total=${d?.totalCount}, idle=${d?.idleCount}, occupied=${d?.occupiedCount}`)
|
|
const overseas = await context.requestOverseas('GET', '/api/platform/v1/warehouse-admin/pallets/summary?page=1&size=20')
|
|
const o = overseas?.body?.data
|
|
assert(context, '海外托盘汇总含闲置、占用和报废数量', overseas,
|
|
success(overseas) && summaryValid(o, ['totalCount', 'idleCount', 'occupiedCount', 'scrappedCount'])
|
|
&& Number(o.totalCount) === Number(o.idleCount) + Number(o.occupiedCount) + Number(o.scrappedCount),
|
|
`total=${o?.totalCount}, idle=${o?.idleCount}, occupied=${o?.occupiedCount}, scrapped=${o?.scrappedCount}`)
|
|
const warehouse = await context.requestOverseas('GET',
|
|
`/api/platform/v1/warehouse-admin/${encodeURIComponent(warehouseId)}/pallets/summary?page=1&size=20`)
|
|
assert(context, '海外仓级托盘汇总可用', warehouse,
|
|
success(warehouse) && summaryValid(warehouse.body.data, ['totalCount', 'idleCount', 'occupiedCount', 'scrappedCount']))
|
|
}
|
|
|
|
async function verifyDestuffCosts(context, completedFacts) {
|
|
const evidence = []
|
|
for (const fact of completedFacts) {
|
|
const businessNo = `DESTUFF-${fact.containerBookingId}`
|
|
const lastleg = await poll(async () => context.requestOverseas('GET', query('/api/platform/v1/last-leg/finance-fee-sources', {
|
|
businessType: 'OVERSEAS_CONTAINER', businessNo,
|
|
})), (response) => success(response) && rows(response.body.data).length > 0, 75)
|
|
const sources = success(lastleg.result) ? rows(lastleg.result.body.data) : []
|
|
const allocated = money(sources.reduce((sum, item) => sum + Number(item.amount || 0), 0))
|
|
assert(context, '拆柜打托费用按客户体积分摊且金额守恒', lastleg.result,
|
|
sources.length > 0 && sources.every((item) => item.sourceType === 'OVERSEAS_CONTAINER_FEE'
|
|
&& item.feeItemCode === 'DESTUFF_PALLET' && Number(item.amount) > 0)
|
|
&& allocated === money(fact.expectedPalletCost),
|
|
`containerBookingId=${fact.containerBookingId}, sources=${sources.length}, allocated=${allocated}, expected=${money(fact.expectedPalletCost)}, pollRequests=${lastleg.attempts}`)
|
|
const finance = await poll(async () => context.request('GET', query(`${FINANCE}/fee-sources`, {
|
|
page: 1, pageSize: 200, keyword: businessNo,
|
|
})), (response) => success(response) && sources.length > 0
|
|
&& sources.every((source) => rows(response.body.data).some((item) => id(item.sourceFeeId) === id(source.sourceFeeId))), 60)
|
|
const delivered = success(finance.result) ? rows(finance.result.body.data)
|
|
.filter((item) => sources.some((source) => id(source.sourceFeeId) === id(item.sourceFeeId))) : []
|
|
assert(context, '拆柜打托费用跨区进入Finance且金额守恒', finance.result,
|
|
delivered.length === sources.length
|
|
&& money(delivered.reduce((sum, item) => sum + Number(item.amount || 0), 0)) === money(fact.expectedPalletCost),
|
|
`containerBookingId=${fact.containerBookingId}, delivered=${delivered.length}, expected=${sources.length}, pollRequests=${finance.attempts}`)
|
|
evidence.push({ containerBookingId: fact.containerBookingId, expectedPalletCost: money(fact.expectedPalletCost),
|
|
allocations: sources.length, lastlegPollRequests: lastleg.attempts, financePollRequests: finance.attempts })
|
|
}
|
|
return evidence
|
|
}
|
|
|
|
export async function run(context) {
|
|
const start = context.steps.length
|
|
const fixture = context.context?.merchantQualificationFixture
|
|
const completedFacts = context.createdResources?.phase4OverseasPda?.completedContainerFacts || []
|
|
const warehouseId = context.createdResources?.phase4OverseasPda?.overseasWarehouseId
|
|
|| context.context?.phase4OverseasWarehouse?.warehouseId
|
|
context.assert(MODULE, '钱包闭环必须使用本轮已审核客户和PDA实收柜', null,
|
|
Boolean(fixture?.tenantId && fixture?.username && fixture?.password && warehouseId && completedFacts.length > 0),
|
|
`fixture=${Boolean(fixture)}, warehouseId=${warehouseId || 'missing'}, completedContainers=${completedFacts.length}`, OPTIONS)
|
|
if (!fixture?.tenantId || !warehouseId || completedFacts.length === 0) return
|
|
|
|
const auth = await merchantLogin(context, fixture)
|
|
const wallet = auth ? await prepareWallet(context, id(fixture.tenantId)) : null
|
|
const procurement = wallet ? await procurementFlow(context, fixture, auth.overseasAccessToken) : null
|
|
const settledRelabel = procurement ? await relabelFlow(context, auth.overseasAccessToken, warehouseId, 'SETTLE', 'COMPLETED') : null
|
|
const releasedRelabel = settledRelabel ? await relabelFlow(context, auth.overseasAccessToken, warehouseId, 'RELEASE', 'CANCELLED') : null
|
|
const relabels = [settledRelabel, releasedRelabel].filter(Boolean)
|
|
const finalAccount = procurement && relabels.length === 2
|
|
? await verifyClientWallet(context, auth.accessToken, fixture, wallet, procurement, relabels) : null
|
|
await verifyPalletSummaries(context, warehouseId)
|
|
const destuffCosts = await verifyDestuffCosts(context, completedFacts)
|
|
context.createdResources.phase4OverseasWalletAndPalletCost = {
|
|
marker: context.marker, customerId: id(fixture.tenantId), procurement, relabels,
|
|
finalAccount, completedContainerCosts: destuffCosts,
|
|
}
|
|
const steps = context.steps.slice(start)
|
|
context.scenarios.push({ name: SCENARIO, module: MODULE, success: steps.every((step) => step.passed),
|
|
deploymentBlocked: steps.some((step) => step.deploymentBlocker && !step.passed),
|
|
passed: steps.filter((step) => step.passed).length, failed: steps.filter((step) => !step.passed).length })
|
|
}
|