diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeApBillSyncSupport.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeApBillSyncSupport.java index f2d12f48..db94c665 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeApBillSyncSupport.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/support/KingdeeApBillSyncSupport.java @@ -32,11 +32,9 @@ import org.springblade.core.log.exception.ServiceException; import org.springblade.core.tool.api.R; import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.Func; -import org.springblade.system.cache.DictBizCache; import org.springblade.system.cache.SysCache; import org.springblade.system.feign.ISysClient; import org.springblade.system.pojo.entity.Dept; -import org.springblade.system.pojo.entity.DictBiz; import org.springblade.system.pojo.entity.FeeItem; import org.springblade.thirdparty.bfi.pojo.dto.BfiApBillQueryDTO; import org.springblade.thirdparty.bfi.pojo.dto.BfiApBillSaveDTO; @@ -102,8 +100,10 @@ public class KingdeeApBillSyncSupport { private static final int PAY_RESULT_PAGE_SIZE = 200; /** 结算明细行(运费)计费方案匹配费用项(运输类无税率残差组标识用) */ private static final String FREIGHT_FEE_ITEM = "运输费"; - /** 业务字典:费用类型(fee_category),费用项档案分类编码 -> 名称 */ - private static final String FEE_CATEGORY_DICT = "fee_category"; + /** 费用大类编码(业务字典 fee_category 键值):运输 */ + public static final String FEE_CATEGORY_FREIGHT = "A01"; + /** 费用大类编码(业务字典 fee_category 键值):装卸 */ + public static final String FEE_CATEGORY_LOADING = "A10"; private final IBfiService bfiService; private final ContractManageMapper contractManageMapper; @@ -424,7 +424,7 @@ public class KingdeeApBillSyncSupport { ContractManage contract = requireContract(settlement.getContractId(), context); // 合同计费方案规则:费用类型/费用项 -> 税率(拆行分组用) Map taxRateByRule = taxRateByRule(contract); - // 费用项档案:费用项名称 -> 费用类型名称(费用大类判定,字典 fee_category 翻译) + // 费用项档案:费用项名称 -> 费用大类编码(费用大类判定) Map feeCategoryByName = feeCategoryByName(context); EntryResolveContext resolveContext = new EntryResolveContext(settlement, project, taxRateByRule, feeCategoryByName); // 结算明细费用行:一车多货按货物拆分的费用快照行 @@ -540,7 +540,7 @@ public class KingdeeApBillSyncSupport { String feeType = Func.toStr(summaryFee.getFeeType()).trim(); String feeItem = Func.toStr(summaryFee.getFeeItem()).trim(); String sourceDesc = "结算合计[" + feeType + "-" + feeItem + "]"; - // 非运费类(费用项档案分类/费用类型/费用项名称均不含“运费/运输费”)统一映射装卸物料 + // 非运费类(费用项档案分类编码非 A01 且名称无运费字样)统一映射装卸物料 boolean loadingFee = !isFreightCategory(feeType, feeItem, context.feeCategoryByName()); String materialNumber = resolveMaterialNumber(loadingFee, context.project(), fallbackTransportType, sourceDesc); // 结算合计费用项行无运输量,数量默认 1 @@ -667,12 +667,12 @@ public class KingdeeApBillSyncSupport { /** * 费用项按费用大类/税率拆分(纯函数): - * 费用项经费用项档案/业务字典判为装卸大类(名称含“装卸”的费用类型)、其余归运输大类; + * 费用项经费用项档案判为装卸大类(费用大类编码 A10)、其余归运输大类; * 各大类内按计费方案规则税率分组(未配置税率的费用项归入无税率组), * 费用项金额合计与结算金额的残差并入运输类无税率组,保证拆分后各行金额合计与结算金额勾稽 * * @param feeItems 费用项名称 -> 金额(费用项JSON解析结果,保持行序) - * @param feeCategoryByName 费用项档案:费用项名称 -> 费用类型名称(空 Map 时回退名称关键字判定) + * @param feeCategoryByName 费用项档案:费用项名称 -> 费用大类编码(空 Map 时回退名称关键字判定) * @param taxRateByRule 合同计费方案:费用类型/费用项 -> 税率(百分数) * @param rowAmount 货物费用行结算金额(含税),残差勾稽基准 * @return 拆分行列表(保持费用项出现顺序) @@ -720,29 +720,33 @@ public class KingdeeApBillSyncSupport { } /** - * 装卸大类判定:费用项在费用项档案(blade_fee_item)中的费用类型名称(业务字典 fee_category 翻译) - * 含“装卸”归装卸大类;档案未维护该费用项时回退费用项名称关键字判定 + * 装卸大类判定:费用项在费用项档案(blade_fee_item)中的费用大类编码为 A10 归装卸大类; + * 档案未维护该费用项时回退费用项名称关键字判定 */ static boolean isLoadingCategory(String feeItemName, Map feeCategoryByName) { - String categoryName = feeItemName == null || feeCategoryByName == null ? null : feeCategoryByName.get(feeItemName); - if (Func.isNotBlank(categoryName)) { - return categoryName.contains("装卸"); + String categoryCode = feeItemName == null || feeCategoryByName == null ? null : feeCategoryByName.get(feeItemName); + if (Func.isNotBlank(categoryCode)) { + return FEE_CATEGORY_LOADING.equals(categoryCode); } return isLoadingFeeItem(feeItemName); } - /** 运费类费用判定(关键字兜底):名称含“运费”或“运输费” */ + /** 运费类费用判定(关键字兜底):名称含“运费”或“运输”(业务字典 fee_category 运输类的字典值为“运输”) */ static boolean isFreightFeeText(String name) { - return name != null && (name.contains("运费") || name.contains("运输费")); + return name != null && (name.contains("运费") || name.contains("运输")); } /** - * 运费类判定(手动添加费用行物料映射用):费用项档案分类名称、费用类型、费用项名称任一 - * 含“运费/运输费”即归运费类映射运输物料;档案未维护时退化为名称关键字判定(与原口径一致) + * 运费类判定(手动添加费用行物料映射用):费用项档案维护的费用大类编码为 A01 归运费类映射运输物料; + * 档案未维护时回退行费用类型编码与费用项名称关键字判定 */ - static boolean isFreightCategory(String feeTypeName, String feeItemName, Map feeCategoryByName) { - String categoryName = feeItemName == null || feeCategoryByName == null ? null : feeCategoryByName.get(feeItemName); - return isFreightFeeText(categoryName) || isFreightFeeText(feeTypeName) || isFreightFeeText(feeItemName); + public static boolean isFreightCategory(String feeTypeCode, String feeItemName, Map feeCategoryByName) { + String categoryCode = feeItemName == null || feeCategoryByName == null ? null : feeCategoryByName.get(feeItemName); + if (Func.isNotBlank(categoryCode)) { + // 档案维护的费用项以档案分类编码为准 + return FEE_CATEGORY_FREIGHT.equals(categoryCode); + } + return FEE_CATEGORY_FREIGHT.equals(feeTypeCode) || isFreightFeeText(feeItemName); } /** @@ -844,8 +848,8 @@ public class KingdeeApBillSyncSupport { } /** - * 费用项档案分类:费用项名称 -> 费用类型名称(blade_fee_item.fee_category 编码经业务字典 - * {@link #FEE_CATEGORY_DICT} 翻译;批量同步时全批共享一次加载结果),费用大类判定用; + * 费用项档案分类:费用项名称 -> 费用大类编码(blade_fee_item.fee_category,业务字典 fee_category 键值, + * 运输 A01 / 装卸 A10;批量同步时全批共享一次加载结果),费用大类判定用; * 档案读取失败直接拦截 */ public Map feeCategoryByName(KingdeeSyncContext context) { @@ -853,8 +857,6 @@ public class KingdeeApBillSyncSupport { if (!cache.isEmpty()) { return cache; } - Map categoryNames = DictBizCache.getList(FEE_CATEGORY_DICT).stream() - .collect(Collectors.toMap(DictBiz::getDictKey, DictBiz::getDictValue, (first, second) -> first)); R> response = sysClient.getFeeItems(false); if (response == null || !response.isSuccess() || Func.isEmpty(response.getData())) { throw new ServiceException("推送金蝶失败:费用项基础档案读取失败,请稍后重试"); @@ -864,10 +866,9 @@ public class KingdeeApBillSyncSupport { if (feeItem == null || Func.isBlank(feeItem.getName()) || Func.isBlank(feeItem.getFeeCategory())) { continue; } - loaded.putIfAbsent(feeItem.getName().trim(), - categoryNames.getOrDefault(feeItem.getFeeCategory().trim(), feeItem.getFeeCategory().trim())); + loaded.putIfAbsent(feeItem.getName().trim(), feeItem.getFeeCategory().trim()); } - log.info("费用项档案加载:{}项(费用项名称 -> 费用类型名称)", loaded.size()); + log.info("费用项档案加载:{}项(费用项名称 -> 费用大类编码)", loaded.size()); cache.putAll(loaded); return cache; } diff --git a/blade-service/blade-transport/src/test/java/org/springblade/transport/KingdeeEntrySplitTest.java b/blade-service/blade-transport/src/test/java/org/springblade/transport/KingdeeEntrySplitTest.java index 9b0575b1..50584e52 100644 --- a/blade-service/blade-transport/src/test/java/org/springblade/transport/KingdeeEntrySplitTest.java +++ b/blade-service/blade-transport/src/test/java/org/springblade/transport/KingdeeEntrySplitTest.java @@ -86,11 +86,11 @@ class KingdeeEntrySplitTest { assertEquals(new BigDecimal("800"), splits.get(1).getAmount()); } - /** 费用项档案分类判定:名称不含“装卸”的费用项,档案费用类型名称含“装卸”即归装卸大类 */ + /** 费用项档案分类判定:名称不含“装卸”的费用项,档案费用大类编码 A10 即归装卸大类 */ @Test void loadingCategoryFromFeeItemArchive() { Map feeItems = Map.of("吊装作业", new BigDecimal("500")); - Map feeCategoryByName = Map.of("吊装作业", "装卸"); + Map feeCategoryByName = Map.of("吊装作业", "A10"); List splits = KingdeeApBillSyncSupport.splitFeeItems(feeItems, feeCategoryByName, Map.of(), new BigDecimal("500")); assertEquals(1, splits.size()); assertTrue(splits.get(0).isLoading()); @@ -166,4 +166,23 @@ class KingdeeEntrySplitTest { List splits = KingdeeApBillSyncSupport.splitFeeItems(feeItems, Map.of(), Map.of(), BigDecimal.ZERO); assertTrue(splits.isEmpty()); } + + /** 手工合计行运费类判定:费用项档案分类编码 A01 归运费类映射运输物料(SJS2026101900004 车辆维修费误映射装卸物料回归用例) */ + @Test + void freightCategoryMatchesArchiveCode() { + Map feeCategoryByName = Map.of( + "车辆维修费", "A01", + "人工装卸费用", "A10"); + assertTrue(KingdeeApBillSyncSupport.isFreightCategory("A01", "车辆维修费", feeCategoryByName)); + assertFalse(KingdeeApBillSyncSupport.isFreightCategory("A10", "人工装卸费用", feeCategoryByName)); + // 档案编码优先于名称关键字:A02 档案的“装卸搬运作业费”不归装卸大类 + Map feeItems = Map.of("装卸搬运作业费", new BigDecimal("100")); + List splits = KingdeeApBillSyncSupport.splitFeeItems(feeItems, + Map.of("装卸搬运作业费", "A02"), Map.of(), new BigDecimal("100")); + assertFalse(splits.get(0).isLoading()); + // 档案未维护:回退行费用类型编码与名称关键字判定 + assertTrue(KingdeeApBillSyncSupport.isFreightCategory("A01", "车辆维修费", Map.of())); + assertFalse(KingdeeApBillSyncSupport.isFreightCategory("A10", "人工装卸费用", Map.of())); + assertTrue(KingdeeApBillSyncSupport.isFreightCategory("", "运输费", Map.of())); + } }