This commit is contained in:
2026-08-19 09:42:07 +08:00
parent ff1d330c2d
commit b5864bfcda
56 changed files with 497 additions and 306 deletions
@@ -28,12 +28,8 @@ export function normalizeFinanceFixtureSources(sources, orders, allocations, cus
}
export function supplierCategoryForFeeItem(item) {
const value = `${item?.feeCode ?? ''} ${item?.feeTypeName ?? ''}`.toUpperCase()
if (['TRUCK', 'TRAILER', 'DRAYAGE', '拖车', '卡车'].some((candidate) => value.includes(candidate))) return 'TRUCKING'
if (['CUSTOMS', 'CLEARANCE', 'DECLARATION', '报关', '清关'].some((candidate) => value.includes(candidate))) return 'CUSTOMS'
if (['CERTIFICATE', 'CERTIFICATION', 'CERT_', '认证', '证书'].some((candidate) => value.includes(candidate))) return 'CERTIFICATE'
if (['BOOKING', 'FREIGHT', 'OCEAN', 'SHIPPING', '订舱', '海运', '运费'].some((candidate) => value.includes(candidate))) return 'BOOKING'
return null
return ['BOOKING', 'CUSTOMS', 'TRUCKING', 'CERTIFICATE'].includes(item?.supplierCategory)
? item.supplierCategory : null
}
export function buildSupplierBody(marker, category) {
@@ -145,16 +141,16 @@ async function loadFeeItem(context, direction) {
const list = context.apiSuccess(MODULE, `查询${direction}人工费用项目`, response, '', OPTIONS)
return rows(list).find((item) => item?.status === 'ACTIVE' && (direction === 'RECEIVABLE'
? item.warehouseReceivable === true
: item.warehousePayable === true && Boolean(supplierCategoryForFeeItem(item)))) || null
: item.warehousePayable === true)) || null
}
async function createManualFee(context, resource, feeItem, direction, amount, basis, businessDate, partner = null) {
async function createManualFee(context, resource, feeItem, direction, amount, basis, businessDate, supplier = null) {
if (context.financeFixtureHooks?.createManualFee) return context.financeFixtureHooks.createManualFee(context, resource, feeItem, direction, amount, basis, businessDate)
const basePath = `/api/platform/v1/inbounds/${encodeURIComponent(resource.inboundId)}/fee-details`
const listPath = `${basePath}?accountId=${encodeURIComponent(resource.tenantId)}`
const body = { feeItemId: String(feeItem.id), direction, businessDate,
unitPrice: amount, currency: 'CNY', amount, settlementIncluded: true,
partnerId: partner?.id ? String(partner.id) : null, partnerName: partner?.name || null,
unitPrice: amount, currency: 'CNY', amount,
supplierId: supplier?.id ? String(supplier.id) : null,
remark: `${context.marker}-${basis}` }
const response = await request(context, 'POST', listPath, body)
const detail = context.apiSuccess(MODULE, `${basis}人工费用明细`, response, `inboundId=${resource.inboundId}`, OPTIONS)
@@ -214,8 +210,7 @@ async function loadPublishedAutomaticFee(context, resource) {
const details = context.apiSuccess(MODULE, '回读审核链已发布自动费用来源', listResponse,
`inboundId=${resource.inboundId}`, OPTIONS)
const published = rows(details).find((item) => item?.sourceType === 'FBA_PRICING'
&& item?.reviewStatus === 'APPROVED' && item?.publicationStatus === 'PUBLISHED'
&& item?.settlementIncluded === true)
&& item?.reviewStatus === 'APPROVED' && item?.publicationStatus === 'PUBLISHED')
if (!published) {
blocker(context, '审核链已生成可结算自动费用来源',
`inboundId=${resource.inboundId}, processStatus=${pricingSummary.processStatus || pricingInbound?.processStatus || 'missing'}, feeId=${id(latest.feeId)}, feeStatus=${latest.feeStatus}, feeDetails=${rows(details).length};确认和发送成功后应同步生成 APPROVED + PUBLISHED 的 FBA_PRICING 来源`)
@@ -241,20 +236,20 @@ async function loadPublishedAutomaticFee(context, resource) {
async function loadSupplier(context, category) {
if (context.financeFixtureHooks?.loadSupplier) return context.financeFixtureHooks.loadSupplier(context, category)
if (!category) return null
const body = buildSupplierBody(context.marker, category)
const effectiveCategory = category || 'BOOKING'
const body = buildSupplierBody(context.marker, effectiveCategory)
const response = await request(context, 'POST', '/api/platform/v1/suppliers?tenantId=0', body)
const supplierId = id(context.apiSuccess(MODULE, '创建本轮 Finance 隔离供应商', response,
`category=${category}`, OPTIONS))
`category=${effectiveCategory}`, OPTIONS))
if (!supplierId) return null
const detailResponse = await context.request('GET', `/api/platform/v1/suppliers/${encodeURIComponent(supplierId)}?tenantId=0`)
const detail = context.apiSuccess(MODULE, '回读本轮 Finance 隔离供应商', detailResponse,
`supplierId=${supplierId}`, OPTIONS)
context.assert(MODULE, 'Finance 供应商为 ACTIVE 且类别匹配费用项目', detailResponse,
id(detail?.id) === supplierId && detail?.status === 'ACTIVE' && detail?.category === category
id(detail?.id) === supplierId && detail?.status === 'ACTIVE' && (!category || detail?.category === category)
&& detail?.name === body.name,
`supplierId=${supplierId}, status=${detail?.status || 'missing'}, category=${detail?.category || 'missing'}`, OPTIONS)
return detail?.status === 'ACTIVE' && detail?.category === category ? detail : null
return detail?.status === 'ACTIVE' && (!category || detail?.category === category) ? detail : null
}
async function uploadVoucher(context, customerId) {
@@ -405,7 +400,7 @@ export async function run(context) {
const payableSupplierCategory = supplierCategoryForFeeItem(feeItems.PAYABLE)
const supplier = await loadSupplier(context, payableSupplierCategory)
if (!supplier?.id) blocker(context, '取得 Warehouse PAYABLE 供应商',
`未找到 ACTIVE ${payableSupplierCategory || 'SUPPORTED'} 供应商或 feeCode + feeTypeName 无法映射正式类别的 PAYABLE 费用项;不能伪造 supplierId/partnerId`)
`未找到 ACTIVE ${payableSupplierCategory || '任意类别'} 供应商;不能伪造 supplierId`)
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')
@@ -434,7 +429,7 @@ export async function run(context) {
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), supplier)
if (fee) fixtures.push({ ...fee, supplierId: id(supplier.id), partnerId: id(supplier.id),
if (fee) fixtures.push({ ...fee, supplierId: id(supplier.id),
supplierName: supplier.name || null, supplierCategory: supplier.category || payableSupplierCategory })
}
}
@@ -448,8 +443,7 @@ export async function run(context) {
const basePath = `/api/platform/v1/loading-plans/${encodeURIComponent(container.loadingPlanId)}/fee-details`
const path = `${basePath}?accountId=${encodeURIComponent(container.tenantId)}`
const body = { feeItemId: String(feeItems.PAYABLE.id), direction: 'PAYABLE', businessDate: dateOffset(-2),
amount: 300, currency: 'CNY', settlementIncluded: true, partnerId: id(supplier.id),
partnerName: supplier.name || null, remark: `${context.marker}-CONTAINER_SHARED` }
amount: 300, currency: 'CNY', supplierId: id(supplier.id), remark: `${context.marker}-CONTAINER_SHARED` }
const response = await request(context, 'POST', path, body)
if (response?.http === 404) {
blocker(context, '按柜费用来源接口已部署并可路由',
@@ -460,7 +454,7 @@ export async function run(context) {
if (feeId) { const approval = await request(context, 'POST', `${basePath}/${feeId}/approve?accountId=${encodeURIComponent(container.tenantId)}`); context.apiSuccess(MODULE, '审核按柜费用来源', approval, '', OPTIONS) }
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,
supplierId: supplier?.id ? id(supplier.id) : null,
supplierName: supplier?.name || null, supplierCategory: supplier?.category || payableSupplierCategory,
containerBookingId: id(container.containerBookingId), containerNo: container.containerNo || null,
loadingPlanId: id(container.loadingPlanId) }
@@ -471,7 +465,7 @@ export async function run(context) {
'当前上下文没有同 marker 的已完成柜次及真实 Firstleg 体积快照,禁止借用历史柜次或模拟 Finance 分摊成功', 'no-marker-owned-container-snapshot')
} else {
context.addCoverageGap(MODULE, 'container-shared-cost-allocation', '按柜添加费用并按订单/运单体积分摊',
'已识别同 marker 的 COMPLETED 柜次,但 PAYABLE 费用项目无法按 feeCode + feeTypeName 映射类别或缺少 ACTIVE 供应商', 'missing-payable-supplier')
'已识别同 marker 的 COMPLETED 柜次,但缺少 PAYABLE 费用项目或符合其显式可空类别约束的 ACTIVE 供应商', 'missing-payable-supplier')
}
if (containerFixture) fixtures.push(containerFixture)
@@ -521,7 +515,7 @@ export async function run(context) {
feeSources: fixtureSources,
container: containerFixture ? { containerBookingId: containerFixture.containerBookingId,
containerNo: containerFixture.containerNo } : { containerBookingId: null, containerNo: null },
allocations, supplier: supplier?.id ? { supplierId: id(supplier.id), partnerId: id(supplier.id),
allocations, supplier: supplier?.id ? { supplierId: id(supplier.id),
supplierName: supplier.name || null, category: supplier.category || payableSupplierCategory } : null,
voucherFileId, pricingEvidence,
deploymentBlocker: context.steps.slice(start).filter((step) => step.deploymentBlocker).map((step) => step.evidence),