2026-10-03 14:52:04 +08:00
import { randomUUID } from 'node:crypto'
import { setTimeout as delay } from 'node:timers/promises'
const MODULE = 'fleet-company-cost-acceptance'
const OPTIONS = { scenario : 'company-vehicle-cost-payment' , deploymentBlocker : true }
const FINANCE = '/api/platform/finance/v1'
const MASTER = '/api/platform/v1/last-leg/master-data'
const PNG = Buffer . from ( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aH1kAAAAASUVORK5CYII=' , 'base64' )
const id = value => value == null ? '' : String ( value )
const ok = response => response ? . http === 200 && id ( response ? . body ? . code ) === '200'
function scaled ( value , scale ) {
if ( ! new RegExp ( `^\\d+(\\.\\d{1, ${ scale } })?$` ). test ( id ( value ))) throw new Error ( '验收金额或汇率格式无效' )
const [ whole , fraction = '' ] = id ( value ). split ( '.' )
return BigInt ( whole ) * 10 n ** BigInt ( scale ) + BigInt ( fraction . padEnd ( scale , '0' ))
}
const cents = value => scaled ( value , 2 )
const amount = value => Number ( ` ${ value / 100 n } . ${ id ( value % 100 n ). padStart ( 2 , '0' ) } ` )
function canonical ( value ) {
if ( Array . isArray ( value )) return value . map ( canonical ). sort (( a , b ) => JSON . stringify ( a ). localeCompare ( JSON . stringify ( b )))
if ( value && typeof value === 'object' ) return Object . fromEntries ( Object . keys ( value ). sort (). map ( key => [ key , canonical ( value [ key ])]))
return value
}
const same = ( a , b ) => JSON . stringify ( canonical ( a )) === JSON . stringify ( canonical ( b ))
/** Internal authorized TEST module; entrypoint owns registration, credentials and execution. No direct fetch, SQL or historical fixtures. */
export async function run ( context , { pollAttempts = 60 , pollDelayMs = 1000 } = {}) {
if ( ! context . marker || ! context . createdResources ) throw new Error ( '公司车辆成本验收须有独立marker和本轮资源上下文' )
2026-10-27 17:44:41 +08:00
const resources = context . options ? . fixedCompanyCostResources
if ( ! resources || ! [ 'supplierId' , 'vehicleId' ]. every ( key => typeof resources [ key ] === 'string' && /^[1-9]\d*$/ . test ( resources [ key ])))
throw new Error ( '公司成本验收必须配置既有供应商和车辆,禁止自动创建主档' )
2026-10-03 14:52:04 +08:00
if ( context . createdResources . fleetCompanyCostAcceptance ) throw new Error ( '本轮已存在公司成本验收记录;保留原证据,不自动重新造数' )
if ( ! Number . isInteger ( pollAttempts ) || pollAttempts < 1 || pollAttempts > 120 || ! Number . isFinite ( pollDelayMs ) || pollDelayMs < 0 || pollDelayMs > 5000 )
throw new Error ( '公司成本轮询范围无效' )
const proof = { marker : context . marker , status : 'CHECKING' , cases : [], requests : [], cleanup : 'NOT_CREATED' , isolationScope : 'Only marker-owned company fees must be absent from the customer fee pool. Customer-specific cost/account records and unrelated concurrent changes are not inspected or compared.' }
context . createdResources . fleetCompanyCostAcceptance = proof
function check ( label , condition , response = null ) {
context . assert ( MODULE , label , response , Boolean ( condition ), '' , OPTIONS )
if ( ! condition ) throw new Error ( ` ${ MODULE } : ${ label } ` )
}
function evidence ( label , method , path , response ) {
proof . requests . push ({ label , method , path , http : response ? . http , code : response ? . body ? . code ,
requestId : response ? . requestId , traceId : response ? . traceId })
}
async function request ( method , path , body , label , { key , reject } = {}) {
const planned = { label , method , path , requestBody : body , idempotencyKey : key , state : 'PLANNED' }
proof . requests . push ( planned )
await context . checkpoint ? .()
const response = await context . request ( method , path , body , context . token , key ? { 'Idempotency-Key' : key } : {})
Object . assign ( planned , { state : 'RETURNED' , http : response ? . http , code : response ? . body ? . code , requestId : response ? . requestId , traceId : response ? . traceId })
await context . checkpoint ? .()
if ( reject ) {
check ( label , response ? . http === 200 && reject . includes ( id ( response ? . body ? . code )), response )
return response . body ? . data
}
const data = context . apiSuccess ( MODULE , label , response , '' , OPTIONS )
check ( label , ok ( response ) && data != null , response )
return data
}
async function snapshotCustomer ( label ) {
const pool = await request ( 'GET' , ` ${ FINANCE } /fee-pool?direction=ALL` , undefined , ` ${ label } 只读核对公司来源未进入客户费用池` )
check ( '客户费用池隔离查询形状完整' , Array . isArray ( pool ))
// Do not retain or operate other tasks' customer records. Only count this run's unique marker.
return { companyFeePoolRows : pool . filter ( row => JSON . stringify ( row ). includes ( context . marker )). length }
}
async function page ( path , label ) {
const result = await request ( 'GET' , ` ${ path } ?page=1&size=100` , undefined , label )
check ( ` ${ label } 分页完整` , Array . isArray ( result . records ) && Number ( result . page ) === 1 && Number ( result . size ) === 100
&& Number ( result . total ) === result . records . length && Number ( result . total ) <= 100 )
return result . records
}
let failure
try {
const businessDate = new Date (). toISOString (). slice ( 0 , 10 ), month = businessDate . slice ( 0 , 7 )
const exchange = await request ( 'GET' , ` ${ MASTER } /exchange-rates/ ${ month } ` , undefined , '读取本月有效USD汇率,不修改共享汇率' )
check ( 'USD汇率月份有效' , exchange . month === month && exchange . status === 'ACTIVE' && scaled ( exchange . usdToCnyAverage , 6 ) > 0 n )
proof . exchangeRate = { month , usdToCnyAverage : id ( exchange . usdToCnyAverage ), recordVersion : id ( exchange . recordVersion ) }
proof . customerBefore = await snapshotCustomer ( '操作前' )
check ( '本轮marker尚无客户费用污染,禁止覆盖旧验收' , proof . customerBefore . companyFeePoolRows === 0 )
2026-10-27 17:44:41 +08:00
proof . supplierId = id ( resources . supplierId )
const supplier = await request ( 'GET' , `/api/platform/v1/suppliers/ ${ encodeURIComponent ( proof . supplierId ) } ?tenantId=0` , undefined , '核对已配置的既有供应商' )
check ( '复用供应商为平台启用海外拖车供应商' , id ( supplier . id ) === proof . supplierId && id ( supplier . tenantId ) === '0'
&& supplier . status === 'ACTIVE' && supplier . category === 'TRUCKING' && supplier . truckingScope === 'OVERSEAS' )
const vehicle = await request ( 'GET' , ` ${ MASTER } /vehicles/ ${ encodeURIComponent ( id ( resources . vehicleId )) } ` , undefined , '核对已配置的既有公司车辆' )
check ( '复用车辆为海外自有在役车辆' , id ( vehicle . id ) === id ( resources . vehicleId ) && Boolean ( vehicle . plateNumber )
&& vehicle . region === 'OVERSEAS' && vehicle . ownership === 'OWNED' && vehicle . status === 'IN_SERVICE' )
proof . vehicleId = id ( vehicle . id ); proof . plateNumber = vehicle . plateNumber
proof . reusedMasterData = true ; proof . cleanup = 'REUSED_UNCHANGED'
2026-10-03 14:52:04 +08:00
const care = `/api/platform/v1/last-leg/fleet/vehicles/ ${ encodeURIComponent ( proof . vehicleId ) } /records`
for ( const [ recordType , currency , originalAmount ] of [[ 'REPAIR' , 'CNY' , 27.31 ], [ 'RESCUE' , 'USD' , 10 ]]) {
const scenario = { recordType , status : 'CHECKING' , commands : [] }; proof . cases . push ( scenario )
const details = { recordType , occurredAt : new Date ( Date . now () - 1000 ). toISOString (), distanceUnit : null , odometer : null ,
lastServiceOdometer : null , nextServiceOdometer : null , fuelQuantity : null , fuelUnit : null , verificationStatus : null , remark : context . marker ,
incident : { description : ` ${ context . marker } 受控验收事件` , item : ` ${ context . marker } ${ recordType } 公司费用` , place : '受控测试地点' , handler : '验收操作人' ,
processingStatus : 'COMPLETED' , exceptionType : null , reportedCost : originalAmount , currency } }
const creation = { idempotencyKey : randomUUID (), details }; scenario . creation = creation
const record = await request ( 'POST' , care , creation , `创建本轮 ${ recordType } 记录` )
check ( '仅操作当前车辆当前运行记录' , id ( record . vehicleId ) === proof . vehicleId && record . details ? . remark === context . marker && record . details ? . recordType === recordType && record . recordNo )
scenario . recordNo = record . recordNo
const recordPath = ` ${ care } / ${ encodeURIComponent ( record . recordNo ) } `
const rate = currency === 'CNY' ? 1000000 n : scaled ( exchange . usdToCnyAverage , 6 )
const total = ( cents ( originalAmount ) * rate + 500000 n ) / 1000000 n
check ( '受控金额可分为两笔付款' , total > 1 n && total <= BigInt ( Number . MAX_SAFE_INTEGER ))
let submission
async function submit () {
const command = { recordVersion : record . recordVersion , supplierId : proof . supplierId , businessDate , idempotencyKey : randomUUID () }
scenario . commands . push ({ action : 'SUBMIT' , request : command })
submission = await request ( 'POST' , ` ${ recordPath } /finance` , command , '提交真实公司费用来源' )
check ( '提交不冒充入账或付款' , submission . recordNo === record . recordNo && submission . status === 'PENDING_FINANCE' && BigInt ( submission . sourceVersion ) > 0 n )
const replay = await request ( 'POST' , ` ${ recordPath } /finance` , command , '原请求重放提交' )
check ( '重放保持同一来源版本' , replay . replayed === true && id ( replay . sourceVersion ) === id ( submission . sourceVersion ))
}
async function awaitFinance () {
for ( let attempt = 0 ; attempt < pollAttempts ; attempt ++ ) {
const rows = await request ( 'POST' , ` ${ care } /finance-status` , { recordNos : [ record . recordNo ] }, '轮询本条已提交Finance来源' )
check ( '来源状态严格按本车本记录返回' , Array . isArray ( rows ) && rows . length === 1 && rows [ 0 ]. recordNo === record . recordNo )
const state = rows [ 0 ]
check ( '投递未进入需人工处理状态' , state . status !== 'FINANCE_ATTENTION' )
if ( state . finance ) {
const cost = state . finance , source = cost . source
check ( 'Finance来源与提交版本、车辆、供应商及费用完全匹配' , source ? . sourceNo === record . recordNo && source . sourceType === recordType
&& id ( source . sourceVersion ) === id ( submission . sourceVersion ) && id ( source . vehicleId ) === proof . vehicleId
&& id ( source . supplierId ) === proof . supplierId && source . businessDate === businessDate && source . exchangeRateMonth === month
&& source . originalCurrency === currency && cents ( source . originalAmount ) === cents ( originalAmount )
&& scaled ( source . exchangeRate , 6 ) === rate && cents ( source . amount ) === total && source . remark === context . marker
&& cost . subjectCode === 'PLATFORM_DEFAULT' && cost . currency === 'CNY' && Boolean ( cost . costNo ))
check ( '首次财务接收仍未入账付款' , state . status === 'PENDING' && cost . status === 'PENDING' && cents ( cost . paidAmount ) === 0 n && cost . outstandingAmount == null )
return cost
}
check ( '未接收时明确等待,不接受假成功' , state . status === 'PENDING_FINANCE' )
if ( attempt + 1 < pollAttempts ) await delay ( pollDelayMs )
}
throw new Error ( 'Finance来源轮询超时,不能认定已接收或完成验收' )
}
await submit (); let cost = await awaitFinance (); scenario . costNo = cost . costNo
const costPath = ` ${ FINANCE } /vehicle-costs/ ${ encodeURIComponent ( cost . costNo ) } `
async function write ( action , body , label ) {
const key = randomUUID (); scenario . commands . push ({ action , key , request : body })
const result = await request ( 'POST' , ` ${ costPath } / ${ action } ` , body , label , { key })
const replay = await request ( 'POST' , ` ${ costPath } / ${ action } ` , body , ` ${ label } 原键重放` , { key })
check ( ` ${ label } 重放不重复推进` , same ( result , replay ))
return result
}
async function approve () {
cost = await write ( 'approve' , { expectedVersion : cost . recordVersion , reason : '受控验收入账' }, '确认公司成本入账' )
check ( '入账只形成未付应付' , cost . status === 'APPROVED' && cents ( cost . paidAmount ) === 0 n && cents ( cost . outstandingAmount ) === total )
}
await approve ()
if ( recordType === 'REPAIR' ) {
cost = await write ( 'void' , { expectedVersion : cost . recordVersion , reason : '受控验证未付款作废后重建' }, '未付款费用作废' )
check ( '作废保留原金额且未付款' , cost . status === 'VOIDED' && cents ( cost . paidAmount ) === 0 n && cost . outstandingAmount == null )
const previousVersion = BigInt ( submission . sourceVersion )
await submit (); cost = await awaitFinance ()
check ( '作废重建保留费用号并增加来源版本' , cost . costNo === scenario . costNo && BigInt ( submission . sourceVersion ) === previousVersion + 1 n )
await approve ()
}
const uploadPath = ` ${ costPath } /vouchers`
const uploaded = await context . requestMultipart ( 'POST' , uploadPath , { name : ` ${ recordType . toLowerCase () } -controlled-payment.png` , type : 'image/png' , content : PNG })
evidence ( '上传合成测试付款凭证' , 'POST' , uploadPath , uploaded )
const voucher = context . apiSuccess ( MODULE , '上传合成测试付款凭证' , uploaded , '' , OPTIONS )
check ( '付款凭证为公司专属真实文件' , ok ( uploaded ) && voucher ? . fileId && voucher . ownerScope === 'PLATFORM' && id ( voucher . tenantId ) === '0'
&& voucher . businessType === 'FINANCE_VEHICLE_VOUCHER' && voucher . contentType === 'image/png' && Number ( voucher . sizeBytes ) === PNG . length , uploaded )
scenario . voucherFileId = id ( voucher . fileId )
const binary = await context . requestBinary ( 'GET' , ` ${ costPath } /vouchers/ ${ encodeURIComponent ( voucher . fileId ) } /content` )
check ( '付款凭证下载字节与本轮合成图片一致' , binary ? . http === 200 && id ( binary . contentType ). startsWith ( 'image/png' ) && binary . bytes instanceof Uint8Array && Buffer . from ( binary . bytes ). equals ( PNG ), binary )
const partial = total / 2 n
const payment = { expectedVersion : cost . recordVersion , amount : amount ( partial ), paidAt : new Date ( Date . now () - 1000 ). toISOString (),
method : 'BANK_TRANSFER' , referenceNo : ` ${ context . marker } - ${ recordType } ` , voucherFileIds : [ id ( voucher . fileId )], remark : '受控验收模拟线下付款凭证,不发起银行转账' }
cost = await write ( 'payments' , payment , '登记部分付款' )
check ( '部分付款金额守恒' , cost . status === 'PARTIALLY_PAID' && cents ( cost . paidAmount ) === partial && cents ( cost . outstandingAmount ) === total - partial )
await request ( 'POST' , ` ${ costPath } /payments` , { ... payment , expectedVersion : cost . recordVersion , amount : amount ( total - partial + 1 n ) }, '拒绝超过未付余额的付款' , { key : randomUUID (), reject : [ 'FIN_409' ] })
const unchanged = await request ( 'GET' , costPath , undefined , '超付失败后回查余额及版本' )
check ( '超付拒绝没有改变财务事实' , same ( unchanged , cost ))
cost = await write ( 'payments' , { ... payment , expectedVersion : cost . recordVersion , amount : amount ( total - partial ) }, '登记剩余实际付款' )
check ( '全额付款终态金额守恒' , cost . status === 'PAID' && cents ( cost . paidAmount ) === total && cents ( cost . outstandingAmount ) === 0 n )
await request ( 'POST' , ` ${ costPath } /void` , { expectedVersion : cost . recordVersion , reason : '负向验证已付款不可作废' }, '已付款作废被拒绝' , { key : randomUUID (), reject : [ 'FIN_409' ] })
await request ( 'PUT' , recordPath , { recordVersion : record . recordVersion , reason : '负向验证已付款不可改金额' , details : { ... details , incident : { ... details . incident , reportedCost : originalAmount + 1 } } }, '已付款来源修改被拒绝' , { reject : [ 'A0409' ] })
check ( '已付款负向动作不改变费用' , same ( await request ( 'GET' , costPath , undefined , '负向动作后回查已付费用' ), cost ))
const careAfter = await request ( 'GET' , recordPath , undefined , '回查运维申报金额未被覆盖' )
check ( '负向修改不改变运维版本或金额' , id ( careAfter . recordVersion ) === id ( record . recordVersion ) && cents ( careAfter . details ? . incident ? . reportedCost ) === cents ( originalAmount ))
const paid = await page ( ` ${ costPath } /payments` , '核对实际付款流水' )
check ( '两次付款重放仅留下两条不同流水及原凭证' , paid . length === 2 && new Set ( paid . map ( row => row . paymentNo )). size === 2
&& paid . reduce (( sum , row ) => sum + cents ( row . amount ), 0 n ) === total && paid . every ( row => row . currency === 'CNY' && same ( row . voucherFileIds . map ( id ), [ id ( voucher . fileId )])))
const history = await page ( ` ${ costPath } /history` , '核对不可变财务操作历史' )
check ( '历史保留接收、入账、两次付款及作废过程' , history . filter ( row => row . action === 'PAY' ). length === 2
&& history . some ( row => row . action === 'RECEIVE' ) && history . some ( row => row . action === 'APPROVE' )
&& ( recordType !== 'REPAIR' || history . some ( row => row . action === 'VOID' )))
scenario . finance = cost ; scenario . payments = paid ; scenario . history = history ; scenario . status = 'VERIFIED'
check ( '本轮公司费用未进入客户费用池' , same ( proof . customerBefore , await snapshotCustomer ( ` ${ recordType } 完成后` )))
}
proof . status = 'VERIFIED'
} catch ( error ) { failure = error ; proof . status = 'FAILED' ; proof . error = error instanceof Error ? error . message : String ( error ) }
if ( failure ) throw failure
}