test: map finance supplier category from fee type

This commit is contained in:
2026-08-15 18:03:47 +08:00
parent 2cec065582
commit f2a49d032a
2 changed files with 12 additions and 7 deletions
@@ -31,13 +31,15 @@ test('fee writes use verified Firstleg paths and do not fabricate historical IDs
assert.match(source, /chargeUnit === 'CBM'/)
})
test('PAYABLE fee selection requires the formal flag and a Finance-supported supplier category', () => {
test('PAYABLE fee selection uses the formal feeCode and feeTypeName fields', () => {
const selector = source.slice(source.indexOf('async function loadFeeItem'), source.indexOf('async function createManualFee'))
assert.match(selector, /item\.warehousePayable === true/)
assert.match(selector, /supplierCategoryForFeeItem\(item\)/)
assert.equal(supplierCategoryForFeeItem({ feeCode: 'FBA_HEAD_FREIGHT', feeName: '头程运费' }), 'BOOKING')
assert.equal(supplierCategoryForFeeItem({ code: 'CUSTOMS_CLEARANCE', name: '报关费' }), 'CUSTOMS')
assert.equal(supplierCategoryForFeeItem({ feeCode: 'UNSUPPORTED', feeName: '操作费' }), null)
assert.equal(supplierCategoryForFeeItem({ feeCode: 'FBA_HEAD_FREIGHT', feeTypeName: '头程运费' }), 'BOOKING')
assert.equal(supplierCategoryForFeeItem({ feeCode: 'FEE_TYPE_001', feeTypeName: '拖车费2' }), 'TRUCKING')
assert.equal(supplierCategoryForFeeItem({ feeCode: 'CUSTOMS_CLEARANCE', feeTypeName: '报关费' }), 'CUSTOMS')
assert.equal(supplierCategoryForFeeItem({ feeCode: 'UNSUPPORTED', feeTypeName: '操作费' }), null)
assert.equal(supplierCategoryForFeeItem({ feeCode: 'FEE_TYPE_001', feeName: '拖车费2' }), null)
})
test('Finance PAYABLE fixture creates an isolated ACTIVE supplier through the formal API', () => {
@@ -28,7 +28,7 @@ export function normalizeFinanceFixtureSources(sources, orders, allocations, cus
}
export function supplierCategoryForFeeItem(item) {
const value = `${item?.feeCode ?? item?.code ?? ''} ${item?.feeName ?? item?.name ?? ''}`.toUpperCase()
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'
@@ -406,7 +406,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'} 供应商或匹配正式类别的 PAYABLE 费用项;不能伪造 supplierId/partnerId`)
`未找到 ACTIVE ${payableSupplierCategory || 'SUPPORTED'} 供应商或 feeCode + feeTypeName 无法映射正式类别的 PAYABLE 费用项;不能伪造 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')
@@ -465,11 +465,14 @@ export async function run(context) {
supplierName: supplier?.name || null, supplierCategory: supplier?.category || payableSupplierCategory,
containerBookingId: id(container.containerBookingId), containerNo: container.containerNo || null,
loadingPlanId: id(container.loadingPlanId) }
} else {
} else if (!containerOwned) {
context.addStep(MODULE, '取得同 marker 柜次用于整柜费用分摊', null, false,
'没有同 marker 的已完成柜次和可核验 containerBookingId;不读取历史柜次、不猜测柜号', OPTIONS)
context.addCoverageGap(MODULE, 'container-shared-cost-allocation', '按柜添加费用并按订单/运单体积分摊',
'当前上下文没有同 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')
}
if (containerFixture) fixtures.push(containerFixture)