2026-08-12 10:59:39 +08:00
const MODULE = 'finance-full-fixtures'
const OPTIONS = { scenario : 'finance-full-fixture-production' , deploymentBlocker : true }
function id ( value ) { return value == null ? '' : String ( value ) }
function key ( context , operation ) { return ` ${ context . marker } - ${ operation } ` . slice ( 0 , 128 ) }
function request ( context , method , path , body , token = context . token , extra = {}) {
return context . request ( method , path , body , token , { 'Idempotency-Key' : key ( context , path . replaceAll ( '/' , '_' )), ... extra })
}
function rows ( value ) { return Array . isArray ( value ) ? value : Array . isArray ( value ? . records ) ? value . records : [] }
function dateOffset ( days ) { const value = new Date (); value . setUTCDate ( value . getUTCDate () + days ); return value . toISOString (). slice ( 0 , 10 ) }
function blocker ( context , name , evidence ) { context . addStep ( MODULE , name , null , false , evidence , OPTIONS ) }
2026-08-12 12:35:29 +08:00
async function createOrder ( context , suffix , pricingProfile = null ) {
if ( context . financeFixtureHooks ? . createOrder ) return context . financeFixtureHooks . createOrder ( context , suffix , pricingProfile )
2026-08-12 10:59:39 +08:00
const originalMarker = context . marker
const original = context . createdResources . orders
2026-08-12 12:35:29 +08:00
const originalFetch = globalThis . fetch
2026-08-12 10:59:39 +08:00
context . marker = ` ${ originalMarker } - ${ suffix } `
2026-08-12 12:35:29 +08:00
if ( pricingProfile && typeof originalFetch === 'function' ) {
// The orders module discovers candidates independently. Reorder only those
// real responses so its payload uses one exact active price-book row.
globalThis . fetch = async ( input , init = {}) => {
const response = await originalFetch ( input , init )
const url = String ( input )
if (( init . method || 'GET' ) === 'GET' && ( url . includes ( '/api/merchant/v1/warehouses?' )
|| url . includes ( '/api/merchant/v1/first-leg/pricing/channels?' ))) {
const body = await response . clone (). json (). catch (() => null )
const data = body ? . code === '200' ? body . data : null
if ( url . includes ( 'warehouseType=FBA' )) {
const records = Array . isArray ( data ? . records ) ? data . records : []
const match = records . find (( item ) => id ( item . warehouseCode || item . code ) === id ( pricingProfile . destinationCode ))
if ( match ) body . data = { ... data , records : [ match , ... records . filter (( item ) => item !== match )] }
} else if ( url . includes ( 'warehouseType=DOMESTIC' )) {
const records = Array . isArray ( data ? . records ) ? data . records : []
const match = records . find (( item ) => id ( item . id ) === id ( pricingProfile . originWarehouseId ))
if ( match ) body . data = { ... data , records : [ match , ... records . filter (( item ) => item !== match )] }
} else {
const channels = Array . isArray ( data ) ? data : []
const match = channels . find (( item ) => id ( item . channelCode ) === id ( pricingProfile . channelCode ))
if ( match ) body . data = [{ ... match , pricingTaxModes : [ pricingProfile . pricingTaxMode ,
...( match . pricingTaxModes || []). filter (( mode ) => mode !== pricingProfile . pricingTaxMode )] },
... channels . filter (( item ) => item !== match )]
}
return new Response ( JSON . stringify ( body ), { status : response . status , statusText : response . statusText , headers : response . headers })
}
return response
}
}
2026-08-12 10:59:39 +08:00
try {
const orders = await import ( new URL ( './orders.mjs' , import . meta . url ))
await orders . run ( context )
const created = context . createdResources . orders
2026-08-12 12:35:29 +08:00
return created ? . marker === context . marker && created ? . orderId && created ? . inboundId
? { ... created , pricingProfile } : null
2026-08-12 10:59:39 +08:00
} finally {
2026-08-12 12:35:29 +08:00
globalThis . fetch = originalFetch
2026-08-12 10:59:39 +08:00
context . marker = originalMarker
if ( original ) context . createdResources . orders = original
else delete context . createdResources . orders
}
}
async function measureOrder ( context , resource ) {
if ( context . financeFixtureHooks ? . measureOrder ) return context . financeFixtureHooks . measureOrder ( context , resource )
const originalMarker = context . marker
const createdKeys = [ 'orders' , 'inboundMeasurement' , 'ocrInbound' , 'testInbound' ]
const contextKeys = [ 'inboundWeightOcr' , 'ocrInbound' , 'testInbound' , 'createdInbound' ]
const createdSnapshot = new Map ( createdKeys . map (( key ) => [ key , { present : Object . hasOwn ( context . createdResources , key ), value : context . createdResources [ key ] }]))
const contextSnapshot = new Map ( contextKeys . map (( key ) => [ key , { present : Object . hasOwn ( context . context , key ), value : context . context [ key ] }]))
context . marker = resource . marker
context . createdResources . orders = resource
context . createdResources . ocrInbound = resource
context . createdResources . testInbound = resource
context . context . inboundWeightOcr = resource
context . context . ocrInbound = resource
context . context . testInbound = resource
context . context . createdInbound = resource
try {
const measurement = await import ( new URL ( './inbound-measurement.mjs' , import . meta . url ))
await measurement . run ( context )
const result = context . createdResources . inboundMeasurement
const detailResponse = await context . request ( 'GET' , `/api/platform/inbounds/ ${ encodeURIComponent ( resource . inboundId ) } ?tenantId= ${ encodeURIComponent ( resource . tenantId ) } ` )
const detail = context . apiSuccess ( MODULE , '回读审核测量实重与总体积' , detailResponse , `inboundId= ${ resource . inboundId } ` , OPTIONS )
const summary = detail ? . summary || {}
const actualWeightKg = Number ( summary . actualGrossWeightKg ?? summary . actualWeightKg )
const totalVolumeCbm = Number ( summary . actualVolumeCbm ?? summary . totalVolumeCbm )
context . assert ( MODULE , '审核测量快照实重和总体积均大于零' , detailResponse ,
Number . isFinite ( actualWeightKg ) && actualWeightKg > 0 && Number . isFinite ( totalVolumeCbm ) && totalVolumeCbm > 0 ,
`actualWeightKg= ${ actualWeightKg || 'missing' } , totalVolumeCbm= ${ totalVolumeCbm || 'missing' } ` , OPTIONS )
return result ? . marker === resource . marker ? { ... resource , ... result , actualWeightKg , totalVolumeCbm } : null
} finally {
context . marker = originalMarker
for ( const [ key , snapshot ] of createdSnapshot ) {
if ( snapshot . present ) context . createdResources [ key ] = snapshot . value
else delete context . createdResources [ key ]
}
for ( const [ key , snapshot ] of contextSnapshot ) {
if ( snapshot . present ) context . context [ key ] = snapshot . value
else delete context . context [ key ]
}
}
}
async function loadFeeItem ( context , direction ) {
if ( context . financeFixtureHooks ? . loadFeeItem ) return context . financeFixtureHooks . loadFeeItem ( context , direction )
const response = await context . request ( 'GET' , '/api/platform/v1/fee-items?page=1&pageSize=200' )
const list = context . apiSuccess ( MODULE , `查询 ${ direction } 人工费用项目` , response , '' , OPTIONS )
return rows ( list ). find (( item ) => item ? . status === 'ACTIVE' && ( direction === 'RECEIVABLE'
? item . warehouseReceivable === true : item . warehousePayable === true )) || null
}
async function createManualFee ( context , resource , feeItem , direction , amount , basis , businessDate ) {
if ( context . financeFixtureHooks ? . createManualFee ) return context . financeFixtureHooks . createManualFee ( context , resource , feeItem , direction , amount , basis , businessDate )
2026-08-12 12:18:59 +08:00
const basePath = `/api/platform/v1/inbounds/ ${ encodeURIComponent ( resource . inboundId ) } /fee-details`
const listPath = ` ${ basePath } ?accountId= ${ encodeURIComponent ( resource . tenantId ) } `
2026-08-12 10:59:39 +08:00
const body = { feeItemId : Number ( feeItem . id ), direction , businessDate ,
unitPrice : amount , currency : 'CNY' , amount , settlementIncluded : true , remark : ` ${ context . marker } - ${ basis } ` }
2026-08-12 12:18:59 +08:00
const response = await request ( context , 'POST' , listPath , body )
2026-08-12 10:59:39 +08:00
const detail = context . apiSuccess ( MODULE , ` ${ basis } 人工费用明细` , response , `inboundId= ${ resource . inboundId } ` , OPTIONS )
const feeId = id ( detail ? . id )
context . assert ( MODULE , ` ${ basis } 费用来源身份和金额` , response ,
Boolean ( feeId ) && id ( detail ? . inboundId ) === id ( resource . inboundId ) && Number ( detail ? . amount ) === Number ( amount ),
`feeId= ${ feeId || 'missing' } , amount= ${ detail ? . amount ?? 'missing' } ` , OPTIONS )
if ( ! feeId ) return null
2026-08-12 12:18:59 +08:00
const approve = await request ( context , 'POST' , ` ${ basePath } / ${ feeId } /approve?accountId= ${ encodeURIComponent ( resource . tenantId ) } ` )
2026-08-12 10:59:39 +08:00
context . apiSuccess ( MODULE , ` ${ basis } 费用审核` , approve , '' , OPTIONS )
return { feeSourceId : feeId , sourceType : detail ? . sourceType || 'MANUAL' , direction , amount : Number ( amount ), businessDate ,
currency : 'CNY' , billingBasis : basis , inboundId : id ( resource . inboundId ), orderId : id ( resource . orderId ),
chargeableWeightKg : detail ? . chargeableWeightKg ?? null , chargeableVolumeCbm : detail ? . chargeableVolumeCbm ?? null }
}
async function calculateAutomaticFee ( context , resource ) {
if ( context . financeFixtureHooks ? . calculateAutomaticFee ) return context . financeFixtureHooks . calculateAutomaticFee ( context , resource )
const pricingInboundResponse = await context . request ( 'GET' ,
`/api/platform/inbounds/ ${ encodeURIComponent ( resource . inboundId ) } ?tenantId= ${ encodeURIComponent ( resource . tenantId ) } ` )
const pricingInbound = context . apiSuccess ( MODULE , '计费前回读入库主表计费快照' , pricingInboundResponse ,
`inboundId= ${ resource . inboundId } ` , OPTIONS )
const pricingSummary = pricingInbound ? . summary || {}
const actualWeightKg = Number ( pricingSummary . actualGrossWeightKg ?? pricingSummary . actualWeightKg )
const actualVolumeCbm = Number ( pricingSummary . actualVolumeCbm ?? pricingSummary . totalVolumeCbm )
if ( ! ( actualWeightKg > 0 ) || ! ( actualVolumeCbm > 0 )) {
blocker ( context , '自动计费前审核测量快照完整' ,
`服务端计费实际读取 fl_inbound_order.actual_gross_weight_kg/actual_volume_cbm; inboundId= ${ resource . inboundId } , actualWeightKg= ${ actualWeightKg || 'missing' } , actualVolumeCbm= ${ actualVolumeCbm || 'missing' } ;测量表有值但主表未同步` )
return null
}
const base = `/api/platform/v1/inbounds/ ${ encodeURIComponent ( resource . inboundId ) } /fees`
const query = `tenantId= ${ encodeURIComponent ( resource . tenantId ) } `
const calculateResponse = await request ( context , 'POST' , ` ${ base } /calculate? ${ query } ` , {})
const calculated = context . apiSuccess ( MODULE , '按审核测量快照执行真实自动计费' , calculateResponse ,
`orderId= ${ resource . orderId } , inboundId= ${ resource . inboundId } ` , OPTIONS )
const feeSourceId = id ( calculated ? . feeId )
const billingBasis = calculated ? . chargeUnit === 'KG' ? 'WEIGHT_KG'
: calculated ? . chargeUnit === 'CBM' ? 'VOLUME_CBM' : null
const responseWeightKg = Number ( calculated ? . actualWeightKg )
const responseVolumeCbm = Number ( calculated ? . actualVolumeCbm )
context . assert ( MODULE , '自动费用返回来源、金额和计费依据' , calculateResponse ,
Boolean ( feeSourceId ) && Boolean ( billingBasis ) && Number ( calculated ? . totalAmount ) >= 0
&& responseWeightKg > 0 && responseVolumeCbm > 0 ,
`feeSourceId= ${ feeSourceId || 'missing' } , chargeUnit= ${ calculated ? . chargeUnit || 'missing' } , amount= ${ calculated ? . totalAmount ?? 'missing' } , actualWeightKg= ${ responseWeightKg || 'missing' } , actualVolumeCbm= ${ responseVolumeCbm || 'missing' } ` , OPTIONS )
if ( ! feeSourceId || ! billingBasis ) return null
const confirmResponse = await request ( context , 'POST' , ` ${ base } /confirm? ${ query } ` )
const confirmed = context . apiSuccess ( MODULE , '确认真实自动费用快照' , confirmResponse , '' , OPTIONS )
const sendResponse = await request ( context , 'POST' , ` ${ base } /send? ${ query } ` )
const sent = context . apiSuccess ( MODULE , '发布真实自动费用来源' , sendResponse , '' , OPTIONS )
return { feeSourceId , orderId : id ( resource . orderId ), inboundId : id ( resource . inboundId ), sourceType : 'FBA_PRICING' ,
direction : 'RECEIVABLE' , currency : sent ? . currency || confirmed ? . currency || calculated . currency ,
amount : Number ( sent ? . totalAmount ?? confirmed ? . totalAmount ?? calculated . totalAmount ),
businessDate : String ( sent ? . sentAt || confirmed ? . confirmedAt || calculated . calculatedAt || '' ). slice ( 0 , 10 ),
billingBasis , chargeableQuantity : sent ? . chargeableQuantity ?? calculated . chargeableQuantity ,
chargeUnit : sent ? . chargeUnit || calculated . chargeUnit ,
actualWeightKg : responseWeightKg , actualVolumeCbm : responseVolumeCbm }
}
async function loadSupplier ( context ) {
if ( context . financeFixtureHooks ? . loadSupplier ) return context . financeFixtureHooks . loadSupplier ( context )
const response = await context . request ( 'GET' , '/api/platform/v1/partners?page=1&size=200&status=ACTIVE' )
const page = context . apiSuccess ( MODULE , '查询真实供应商往来单位' , response , '' , OPTIONS )
return rows ( page ). find (( item ) => item ? . status === 'ACTIVE' ) || null
}
async function uploadVoucher ( context , customerId ) {
if ( context . financeFixtureHooks ? . uploadVoucher ) return context . financeFixtureHooks . uploadVoucher ( context , customerId )
const path = `/api/platform/finance/v1/vouchers?customerId= ${ encodeURIComponent ( customerId ) } `
const form = new FormData ()
const png = Buffer . from ( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=' , 'base64' )
form . append ( 'file' , new Blob ([ png ], { type : 'image/png' }), `finance- ${ context . marker } .png` )
try {
const response = await fetch ( ` ${ context . options . gateway }${ path } ` , { method : 'POST' ,
headers : { Accept : 'application/json' , Authorization : `Bearer ${ context . token } ` }, body : form ,
signal : AbortSignal . timeout ( 30_000 ) })
const body = await response . json (). catch (() => null )
const evidence = { method : 'POST' , path , http : response . status , body , raw : null ,
requestId : body ? . requestId || response . headers . get ( 'x-request-id' ), traceId : body ? . traceId || response . headers . get ( 'x-trace-id' ) }
const data = context . apiSuccess ( MODULE , '上传本轮 Finance 凭证' , evidence , '' , OPTIONS )
return id ( data ? . fileId ) || null
} catch ( error ) {
blocker ( context , '上传本轮 Finance 凭证' , `multipart request failed: ${ String ( error ) } ` )
return null
}
}
async function loadPricingEvidence ( context ) {
if ( context . financeFixtureHooks ? . loadPricingEvidence ) return context . financeFixtureHooks . loadPricingEvidence ( context )
const response = await context . request ( 'GET' , '/api/platform/v1/fba-pricing/carrier-rates/price-books?status=ACTIVE' )
const versions = context . apiSuccess ( MODULE , '读取真实生效价格配置' , response , '' , OPTIONS )
2026-08-12 12:35:29 +08:00
const active = rows ( versions ). filter (( item ) => item ? . status === 'ACTIVE' )
const profiles = []
for ( const version of active ) {
const detailResponse = await context . request ( 'GET' , `/api/platform/v1/fba-pricing/carrier-rates/price-books/ ${ encodeURIComponent ( version . id ) } ` )
const detail = context . apiSuccess ( MODULE , '读取 ACTIVE 价格簿真实费率行' , detailResponse , `versionId= ${ version . id } ` , OPTIONS )
const rates = Array . isArray ( detail ? . rates ) ? detail . rates : []
for ( const rate of rates ) {
if ( rate ? . destinationType !== 'FBA' || ! rate ? . originWarehouseId || ! rate ? . channelCode || ! rate ? . pricingTaxMode || ! rate ? . destinationCode ) continue
if ( ! [ 'KG' , 'CBM' ]. includes ( rate . chargeUnit ) || profiles . some (( item ) => item . chargeUnit === rate . chargeUnit )) continue
const matches = rates . filter (( item ) => id ( item . originWarehouseId ) === id ( rate . originWarehouseId )
&& item . channelCode === rate . channelCode && item . destinationType === rate . destinationType
&& String ( item . destinationCode ). toUpperCase () === String ( rate . destinationCode ). toUpperCase ()
&& item . pricingTaxMode === rate . pricingTaxMode )
if ( matches . length !== 1 ) continue
profiles . push ({ versionId : id ( version . id ), rateId : id ( rate . id ), chargeUnit : rate . chargeUnit ,
originWarehouseId : id ( rate . originWarehouseId ), channelCode : rate . channelCode ,
pricingTaxMode : rate . pricingTaxMode , destinationCode : rate . destinationCode })
}
}
context . assert ( MODULE , 'ACTIVE 价格簿包含 KG 与 CBM 可用真实组合' , response ,
profiles . some (( item ) => item . chargeUnit === 'KG' ) && profiles . some (( item ) => item . chargeUnit === 'CBM' ),
`units= ${ profiles . map (( item ) => item . chargeUnit ). join ( ',' ) || 'none' } ` , OPTIONS )
return { activeVersionIds : active . map (( item ) => id ( item . id )). filter ( Boolean ), profiles }
}
async function createMarkerContainer ( context , resource ) {
if ( context . financeFixtureHooks ? . createMarkerContainer ) return context . financeFixtureHooks . createMarkerContainer ( context , resource )
if ( context . financeFixtureHooks ) return null
const original = context . createdResources . phase3ContainerLoading
const originalMarker = context . marker
const snapshots = [ 'orders' , 'inboundMeasurement' ]. map (( key ) => [ key ,
{ present : Object . hasOwn ( context . createdResources , key ), value : context . createdResources [ key ] }])
context . createdResources . orders = resource
context . createdResources . inboundMeasurement = resource
context . marker = resource . marker || originalMarker
try {
const module = await import ( new URL ( './phase3-container-loading.mjs' , import . meta . url ))
await module . run ( context )
const created = context . createdResources . phase3ContainerLoading
return created ? . marker === context . marker && created ? . containerBookingId && created ? . loadingPlanId ? { ... created } : null
} finally {
context . marker = originalMarker
for ( const [ key , snapshot ] of snapshots ) {
if ( snapshot . present ) context . createdResources [ key ] = snapshot . value
else delete context . createdResources [ key ]
}
if ( original ) context . createdResources . phase3ContainerLoading = original
else delete context . createdResources . phase3ContainerLoading
}
2026-08-12 10:59:39 +08:00
}
2026-08-12 13:24:17 +08:00
async function createContainerCost ( context , customerId , container , voucherFileId , snapshotInput ) {
if ( context . financeFixtureHooks ? . createContainerCost ) return context . financeFixtureHooks . createContainerCost ( context , customerId , container , voucherFileId , snapshotInput )
2026-08-12 10:59:39 +08:00
const body = { customerId : Number ( customerId ), costType : 'CONTAINER_SHARED' , orderId : null ,
2026-08-12 13:24:17 +08:00
// Finance names this field containerId, but its Firstleg cost snapshot contract
// resolves it as the loading plan ID, not the physical container booking ID.
2026-08-12 13:37:20 +08:00
containerId : String ( container . loadingPlanId ), expenseName : ` ${ context . marker } -整柜成本` , amount : 300 ,
2026-08-12 10:59:39 +08:00
currency : 'CNY' , voucherFileIds : [ voucherFileId ], remark : context . marker }
const response = await request ( context , 'POST' , '/api/platform/finance/v1/cost-entries' , body )
2026-08-12 13:24:17 +08:00
if ( response ? . body ? . code === 'FIN_400' ) {
const first = snapshotInput ? . items ? .[ 0 ]
blocker ( context , 'Finance 整柜成本请求关联真实装柜计划' ,
`message= ${ response . body . message || 'missing' } , loadingPlanId= ${ container . loadingPlanId || 'missing' } , containerBookingId= ${ container . containerBookingId || 'missing' } , voucherFileId= ${ voucherFileId || 'missing' } , publicSnapshotValid= ${ snapshotInput ? . valid === true } , validItems= ${ snapshotInput ? . items ? . length || 0 } , accountId= ${ first ? . accountId ?? 'missing' } , orderId= ${ first ? . orderId ?? 'missing' } , volumeCbm= ${ first ? . volumeCbm ?? 'missing' } , requestId= ${ response . requestId || 'missing' } , traceId= ${ response . traceId || 'missing' } ;若 publicSnapshotValid=true,说明 fixture ID 与公开装柜明细有效,阻断位于 Finance -> Firstleg 内部快照服务部署/契约` )
}
2026-08-12 10:59:39 +08:00
const cost = context . apiSuccess ( MODULE , '创建整柜成本并触发真实体积分摊' , response , '' , OPTIONS )
2026-08-12 14:40:05 +08:00
if ( ! cost ? . id ) return null
const detailResponse = await context . request ( 'GET' ,
`/api/platform/finance/v1/cost-entries/ ${ encodeURIComponent ( cost . id ) } ?customerId= ${ encodeURIComponent ( customerId ) } ` )
const detail = context . apiSuccess ( MODULE , '回读整柜成本分摊结果' , detailResponse ,
`costId= ${ cost . id } ` , OPTIONS )
return { costId : id ( cost . id ), allocations : Array . isArray ( detail ? . allocations ) ? detail . allocations : null }
2026-08-12 10:59:39 +08:00
}
2026-08-12 13:24:17 +08:00
async function verifyContainerCostSnapshotInput ( context , customerId , container ) {
if ( context . financeFixtureHooks ? . verifyContainerCostSnapshotInput ) {
return context . financeFixtureHooks . verifyContainerCostSnapshotInput ( context , customerId , container )
}
const path = `/api/platform/v1/first-leg/smart-loading/plans/ ${ encodeURIComponent ( container . loadingPlanId ) } `
const response = await context . request ( 'GET' , path )
const plan = context . apiSuccess ( MODULE , '回读 Finance 柜次体积快照输入' , response ,
`loadingPlanId= ${ container . loadingPlanId } ` , OPTIONS )
const items = Array . isArray ( plan ? . items ) ? plan . items : []
const valid = items . filter (( item ) => id ( item ? . accountId ) === id ( customerId )
&& Number ( item ? . orderId ) > 0 && Number ( item ? . volumeCbm ) > 0 )
context . assert ( MODULE , '装柜明细满足 Finance 内部快照筛选条件' , response ,
plan ? . status === 'COMPLETED' && valid . length > 0 ,
`status= ${ plan ? . status || 'missing' } , items= ${ items . length } , validItems= ${ valid . length } , accountId= ${ items [ 0 ] ? . accountId ?? 'missing' } , orderId= ${ items [ 0 ] ? . orderId ?? 'missing' } , volumeCbm= ${ items [ 0 ] ? . volumeCbm ?? 'missing' } ` , OPTIONS )
return { valid : plan ? . status === 'COMPLETED' && valid . length > 0 , items : valid }
}
2026-08-12 10:59:39 +08:00
export async function run ( context ) {
const start = context . steps . length
const pricingEvidence = await loadPricingEvidence ( context )
const resources = []
2026-08-12 12:35:29 +08:00
const profiles = pricingEvidence . profiles || []
for ( const [ index , suffix ] of [ 'AUTO1' , 'AUTO2' , 'AUTO3' , 'AUTO4' , 'MANUAL' ]. entries ()) {
const resource = await createOrder ( context , suffix , profiles [ index % profiles . length ] || null )
2026-08-12 10:59:39 +08:00
context . assert ( MODULE , `创建 ${ suffix } marker 隔离订单与入库单` , null , Boolean ( resource ),
resource ? `orderId= ${ resource . orderId } , inboundId= ${ resource . inboundId } ` : `marker= ${ context . marker } - ${ suffix } 未返回独立入库单` , OPTIONS )
if ( resource ) resources . push ( resource )
}
const measured = []
for ( const resource of resources ) {
const result = await measureOrder ( context , resource )
if ( result ) measured . push ({ ... resource , ... result })
}
const feeItems = {
RECEIVABLE : await loadFeeItem ( context , 'RECEIVABLE' ),
PAYABLE : await loadFeeItem ( context , 'PAYABLE' ),
}
const supplier = await loadSupplier ( context )
if ( ! supplier ? . id ) blocker ( context , '取得真实 PAYABLE 往来单位' , '未找到 ACTIVE 往来单位;不能伪造 supplierId/partnerId' )
for ( const direction of [ 'RECEIVABLE' , 'PAYABLE' ]) {
if ( ! feeItems [ direction ]) context . addCoverageGap ( MODULE , `missing- ${ direction . toLowerCase () } -fee-item` , ` ${ direction } 人工费用 fixture` ,
`没有找到带 warehouse ${ direction === 'RECEIVABLE' ? 'Receivable' : 'Payable' } =true 的 ACTIVE 费用项目,禁止猜测费用项目 ID` , 'missing-deployed-fee-item' )
}
const fixtures = []
for ( const resource of measured . slice ( 0 , 4 )) {
const fee = await calculateAutomaticFee ( context , resource )
if ( fee ) fixtures . push ( fee )
}
for ( const basis of [ 'WEIGHT_KG' , 'VOLUME_CBM' ]) {
if ( ! fixtures . some (( fee ) => fee . billingBasis === basis )) {
blocker ( context , ` ${ basis } 自动计费 fixture` ,
`activePriceVersions= ${ pricingEvidence . activeVersionIds ? . join ( ',' ) || 'none' } ;本轮隔离订单未产生 ${ basis } 来源,价格配置/订单条件不支持,不能伪装成功` )
}
}
2026-08-12 12:18:59 +08:00
const receivableResources = measured . slice ( 0 , 4 )
if ( feeItems . RECEIVABLE ) {
for ( const [ index , resource ] of receivableResources . entries ()) {
const fee = await createManualFee ( context , resource , feeItems . RECEIVABLE , 'RECEIVABLE' , 100 + index * 10 ,
'MANUAL_RECEIVABLE' , dateOffset ( - 1 - ( index % 2 )))
if ( fee ) fixtures . push ( fee )
}
}
const manualResources = [ measured . at ( - 1 )]. filter ( Boolean )
if ( feeItems . PAYABLE && supplier ? . id ) {
for ( const [ index , manualResource ] of manualResources . entries ()) {
const fee = await createManualFee ( context , manualResource , feeItems . PAYABLE , 'PAYABLE' , 80 + index * 10 ,
'MANUAL_ORDER_FEE' , dateOffset ( - 3 - index ))
if ( fee ) fixtures . push ({ ... fee , supplierId : id ( supplier . id ), partnerId : id ( supplier . id ), supplierName : supplier . partnerName || null })
}
2026-08-12 10:59:39 +08:00
}
2026-08-12 12:35:29 +08:00
const container = await createMarkerContainer ( context , measured [ 0 ])
|| context . createdResources ? . phase3ContainerLoading
const containerOwned = ( container ? . marker === context . marker || String ( container ? . marker || '' ). startsWith ( ` ${ context . marker } -` ))
2026-08-12 10:59:39 +08:00
&& container ? . loadingPlanId && container ? . containerBookingId
let containerFixture = null
if ( containerOwned && feeItems . PAYABLE && supplier ? . id ) {
2026-08-12 12:18:59 +08:00
const basePath = `/api/platform/v1/loading-plans/ ${ encodeURIComponent ( container . loadingPlanId ) } /fee-details`
const path = ` ${ basePath } ?accountId= ${ encodeURIComponent ( container . tenantId ) } `
2026-08-12 10:59:39 +08:00
const body = { feeItemId : Number ( feeItems . PAYABLE . id ), direction : 'PAYABLE' , businessDate : dateOffset ( - 2 ), amount : 300 , currency : 'CNY' , settlementIncluded : true , remark : ` ${ context . marker } -CONTAINER_SHARED` }
const response = await request ( context , 'POST' , path , body )
2026-08-12 12:47:06 +08:00
if ( response ? . http === 404 ) {
blocker ( context , '按柜费用来源接口已部署并可路由' ,
`POST ${ path } returned HTTP 404 before Controller handling; Firstleg Controller contract is /api/platform/v1/loading-plans/{loadingPlanId}/fee-details, but current Gateway route does not expose /api/platform/v1/loading-plans/**; requestId= ${ response . requestId || 'missing' } , traceId= ${ response . traceId || 'missing' } ` )
}
2026-08-12 10:59:39 +08:00
const detail = context . apiSuccess ( MODULE , '创建按柜费用来源' , response , '' , OPTIONS )
const feeId = id ( detail ? . id )
2026-08-12 12:18:59 +08:00
if ( feeId ) { const approval = await request ( context , 'POST' , ` ${ basePath } / ${ feeId } /approve?accountId= ${ encodeURIComponent ( container . tenantId ) } ` ); context . apiSuccess ( MODULE , '审核按柜费用来源' , approval , '' , OPTIONS ) }
2026-08-12 10:59:39 +08:00
containerFixture = { feeSourceId : feeId || null , sourceType : 'MANUAL' , direction : 'PAYABLE' , amount : 300 ,
businessDate : body . businessDate , currency : 'CNY' , billingBasis : 'CONTAINER_SHARED' ,
supplierId : supplier ? . id ? id ( supplier . id ) : null , partnerId : supplier ? . id ? id ( supplier . id ) : null ,
containerBookingId : id ( container . containerBookingId ), containerNo : container . containerNo || null ,
loadingPlanId : id ( container . loadingPlanId ) }
} else {
context . addStep ( MODULE , '取得同 marker 柜次用于整柜费用分摊' , null , false ,
'没有同 marker 的已完成柜次和可核验 containerBookingId;不读取历史柜次、不猜测柜号' , OPTIONS )
context . addCoverageGap ( MODULE , 'container-shared-cost-allocation' , '按柜添加费用并按订单/运单体积分摊' ,
'当前上下文没有同 marker 的已完成柜次及真实 Firstleg 体积快照,禁止借用历史柜次或模拟 Finance 分摊成功' , 'no-marker-owned-container-snapshot' )
}
if ( containerFixture ) fixtures . push ( containerFixture )
const customerId = id ( measured [ 0 ] ? . tenantId ) || null
const voucherFileId = customerId ? await uploadVoucher ( context , customerId ) : null
if ( ! voucherFileId ) blocker ( context , '取得本轮 Finance voucherFileId' ,
'未通过 /api/platform/finance/v1/vouchers multipart 上传取得真实 fileId;整柜成本不得模拟成功' )
let allocations = []
if ( containerFixture ? . feeSourceId && voucherFileId && supplier ? . id ) {
2026-08-12 13:24:17 +08:00
const snapshotInput = await verifyContainerCostSnapshotInput ( context , customerId , container )
const cost = snapshotInput . valid ? await createContainerCost ( context , customerId , container , voucherFileId , snapshotInput ) : null
2026-08-12 10:59:39 +08:00
if ( Array . isArray ( cost ? . allocations )) allocations = cost . allocations
else blocker ( context , '回读整柜成本分摊结果' ,
2026-08-12 14:40:05 +08:00
`costId= ${ cost ? . costId || 'missing' } ;Finance 成本详情未返回真实 fin_cost_allocation,禁止从金额和体积自行伪造持久化结果` )
2026-08-12 10:59:39 +08:00
}
if ( allocations . length === 0 ) blocker ( context , '输出真实订单/运单分摊' ,
'缺少已完成柜次、凭证、供应商或 Finance 分摊响应;不伪造 allocation ID/金额' )
const distinctBusinessDates = new Set ( fixtures . map (( item ) => item . businessDate ). filter ( Boolean ))
if ( distinctBusinessDates . size < 2 ) blocker ( context , '费用来源覆盖不同业务日期' ,
`businessDates= ${ [... distinctBusinessDates ]. join ( ',' ) || 'none' } ;至少需要两个真实日期以验收按日期排除` )
if ( measured . length > 0 && measured . every (( item ) => ! item . waybillId )) blocker ( context , '取得订单真实 waybillId' ,
'当前订单/入库接口未返回独立 waybillId;输出 null,不使用 orderId、orderNo 或 inboundId 冒充运单 ID' )
context . createdResources . financeFullFixtures = {
marker : context . marker , customerId ,
orders : measured . map (( item ) => ({ orderId : id ( item . orderId ), orderNo : item . orderNo || null ,
waybillId : item . waybillId ? id ( item . waybillId ) : null , inboundId : id ( item . inboundId ),
actualWeightKg : item . actualWeightKg , totalVolumeCbm : item . totalVolumeCbm ,
businessDate : fixtures . find (( fee ) => fee . orderId === id ( item . orderId )) ? . businessDate || null })),
feeSources : fixtures ,
container : containerFixture ? { containerBookingId : containerFixture . containerBookingId ,
containerNo : containerFixture . containerNo } : { containerBookingId : null , containerNo : null },
allocations , supplier : supplier ? . id ? { supplierId : id ( supplier . id ), partnerId : id ( supplier . id ), supplierName : supplier . partnerName || null } : null ,
voucherFileId , pricingEvidence ,
deploymentBlocker : context . steps . slice ( start ). filter (( step ) => step . deploymentBlocker ). map (( step ) => step . evidence ),
}
const steps = context . steps . slice ( start )
context . scenarios . push ({ name : OPTIONS . scenario , module : MODULE , success : steps . every (( step ) => step . passed ), deploymentBlocked : steps . some (( step ) => step . deploymentBlocker ), passed : steps . filter (( step ) => step . passed ). length , failed : steps . filter (( step ) => ! step . passed ). length })
}