diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java
index 46748a99..e61d785d 100644
--- a/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java
+++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/config/BfiProperties.java
@@ -230,6 +230,11 @@ public class BfiProperties {
* 装卸大类、手动添加非运费杂费物料编码(装卸)
*/
private String loading = "XDWL11002004";
+
+ /**
+ * 仓储大类、手动添加仓储费用物料编码(仓储)
+ */
+ private String warehouse = "XDWL11002001";
}
/**
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 653180b7..12a1060f 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
@@ -104,6 +104,33 @@ public class KingdeeApBillSyncSupport {
public static final String FEE_CATEGORY_FREIGHT = "A01";
/** 费用大类编码(业务字典 fee_category 键值):装卸 */
public static final String FEE_CATEGORY_LOADING = "A10";
+ /** 费用大类编码(业务字典 fee_category 键值):仓储 */
+ public static final String FEE_CATEGORY_WAREHOUSE = "A05";
+
+ /**
+ * 分录费用大类(三分类):label 用于分组键与分录标识拼接
+ */
+ public enum FeeCategory {
+
+ /** 运输 */
+ FREIGHT("运输费"),
+
+ /** 仓储 */
+ WAREHOUSE("仓储"),
+
+ /** 装卸 */
+ LOADING("装卸");
+
+ private final String label;
+
+ FeeCategory(String label) {
+ this.label = label;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+ }
private final IBfiService bfiService;
private final ContractManageMapper contractManageMapper;
@@ -403,10 +430,10 @@ public class KingdeeApBillSyncSupport {
* 解析单张正式结算单分录:页面结算明细每行(一个运单)按 货物 → 费用大类 → 税率 拆分多行
* + 手动添加的结算合计费用项每行一条(系统自动汇总行是明细金额的再汇总不送)。
*
取数口径:
- * 结算明细拆行——一车多货(存在货物费用行)先按货物拆;费用项名称含“装卸”归装卸大类、其余归运输大类,
- * 各大类内按合同计费方案规则税率分组、不同税率分行,运输类每行数量取运单运输量、装卸类每行数量默认 1,
+ * 结算明细拆行——一车多货(存在货物费用行)先按货物拆;费用项按档案分类判定 装卸(A10)/仓储(A05)/运输 三大类,
+ * 各大类内按合同计费方案规则税率分组、不同税率分行,运输类每行数量取运单运输量、装卸/仓储类每行数量默认 1,
* 费用项金额合计与结算金额的残差并入运输类无税率组保证勾稽;
- * 物料编码按装卸大类/项目业务模式/运单运输方式映射(thirdparty.bfi.material.* 配置);
+ * 物料编码按费用大类/项目业务模式/运单运输方式映射(thirdparty.bfi.material.* 配置);
* 单价按结算金额/数量反算;
* 结算金额:结算明细行取结算金额(含税)、结算合计费用项行取合计行结算金额;
* 运输货物/车号仅结算明细拆分行传(货物名称/车号);分录标识:结算明细拆分行取单据号-货物序号-大类-税率、
@@ -479,8 +506,8 @@ public class KingdeeApBillSyncSupport {
/**
* 单个货物费用行按费用大类/税率拆分生成分录:
- * 费用项名称含“装卸”归装卸大类、其余归运输大类,各大类内按合同计费方案税率分组、不同税率分行;
- * 运输类每行数量取运单运输量、装卸类每行数量默认 1;
+ * 费用项按档案分类判定 装卸(A10)/仓储(A05)/运输 三大类,各大类内按合同计费方案税率分组、不同税率分行;
+ * 运输类每行数量取运单运输量、装卸/仓储类每行数量默认 1;
* 运输类首行携带行不含税金额(拆分行无分类不含税金额,仅保证不含税合计勾稽);
* 运输货物取货物名称、车号取结算明细车号(同一车牌号就是一个车)
*/
@@ -504,21 +531,20 @@ public class KingdeeApBillSyncSupport {
String entryCodePrefix = documentNo + "-" + cargoSeq;
boolean amountNoTaxSent = false;
for (FeeLineSplit split : splits) {
- String category = split.isLoading() ? "装卸" : "运输费";
- // 物料:装卸大类映射装卸物料;运输类按项目业务模式/运单运输方式映射
- String materialNumber = resolveMaterialNumber(split.isLoading(), context.project(), detail.getTransportType(), sourceDesc);
- // 数量:运输类取运输量,装卸类默认 1
- BigDecimal quantity = split.isLoading() ? BigDecimal.ONE : freightQuantity;
+ // 物料:装卸大类映射装卸物料、仓储大类映射仓储物料;运输类按项目业务模式/运单运输方式映射
+ String materialNumber = resolveMaterialNumber(split.getCategory(), context.project(), detail.getTransportType(), sourceDesc);
+ // 数量:运输类取运输量,装卸/仓储类默认 1
+ BigDecimal quantity = split.getCategory() == FeeCategory.FREIGHT ? freightQuantity : BigDecimal.ONE;
BigDecimal amount = money(split.getAmount());
BigDecimal price = entryPrice(amount, quantity);
// 不含税金额:仅运输类首行携带,保证不含税合计与结算明细勾稽
BigDecimal amountNoTax = null;
- if (!split.isLoading() && !amountNoTaxSent && rowAmountNoTax != null) {
+ if (split.getCategory() == FeeCategory.FREIGHT && !amountNoTaxSent && rowAmountNoTax != null) {
amountNoTax = money(rowAmountNoTax);
amountNoTaxSent = true;
}
// 分录唯一标识:单据号-货物序号-费用大类-税率(税率未配置不拼)
- String entryCode = entryCodePrefix + "-" + category + split.taxRateSuffix();
+ String entryCode = entryCodePrefix + "-" + split.getCategory().getLabel() + split.taxRateSuffix();
entryGroups.add(new BfiEntryGroup(materialNumber, entryCode, quantity, price, amount,
amountNoTax, cargoName, vehicleNo, context.settlement().getId()));
log.info("金蝶应付单分录生成:来源[{}],分录标识[{}],物料编码[{}],数量[{}],单价[{}],含税金额[{}],不含税金额[{}]",
@@ -532,8 +558,8 @@ public class KingdeeApBillSyncSupport {
}
/**
- * 单条手动添加的结算合计费用项生成一条分录:非运费类(装卸/仓储等杂费)映射装卸物料,
- * 运费类按项目业务模式/运输方式映射(运输方式取结算明细首行);
+ * 单条手动添加的结算合计费用项生成一条分录:运费类按项目业务模式/运输方式映射(运输方式取结算明细首行),
+ * 仓储大类(档案编码 A05)映射仓储物料,其余非运费类映射装卸物料;
* 非运费类数量默认 1,金额取合计行结算金额,运输货物/车号不传,分录标识取结算单号-费用类型-费用项拼接
*/
private void appendSummaryEntry(List entryGroups, EntryResolveContext context,
@@ -542,9 +568,10 @@ 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);
+ // 物料:运费类按项目业务模式/运输方式映射,仓储大类(档案编码 A05)映射仓储物料,其余非运费类映射装卸物料
+ FeeCategory feeCategory = isFreightCategory(feeType, feeItem, context.feeCategoryByName()) ? FeeCategory.FREIGHT
+ : isWarehouseCategory(feeItem, context.feeCategoryByName()) ? FeeCategory.WAREHOUSE : FeeCategory.LOADING;
+ String materialNumber = resolveMaterialNumber(feeCategory, context.project(), fallbackTransportType, sourceDesc);
// 结算合计费用项行无运输量,数量默认 1
BigDecimal quantity = BigDecimal.ONE;
BigDecimal amount = money(summaryFee.getSettlementAmount());
@@ -560,20 +587,22 @@ public class KingdeeApBillSyncSupport {
/**
* 物料编码映射(Nacos 配置 thirdparty.bfi.material.*):
- * 装卸大类(及手动添加的非运费杂费)映射装卸物料 loading;业务模式/项目业务类型含“国际”(国际运输、
+ * 装卸大类映射装卸物料 loading;仓储大类映射仓储物料 warehouse;业务模式/项目业务类型含“国际”(国际运输、
* 国际货物运输代理服务)映射国际货代服务 international;国内运输按运单运输方式查 domestic 映射
* (公路运输/铁路运输/水路运输/航空运输),运输方式未配置映射时直接拦截报错
*
- * @param loadingFee 该行是否属于装卸大类(或非运费类杂费)
+ * @param feeCategory 该行费用大类(运输/仓储/装卸)
* @param project 结算单关联项目(业务类型/业务模式取数来源)
* @param transportType 运单运输方式(结算合计费用项行取结算明细首行运输方式)
* @param sourceDesc 来源行描述,用于异常提示
* @return 金蝶物料编码
*/
- private String resolveMaterialNumber(boolean loadingFee, ProjectApply project, String transportType, String sourceDesc) {
+ private String resolveMaterialNumber(FeeCategory feeCategory, ProjectApply project, String transportType, String sourceDesc) {
BfiProperties.Material material = bfiProperties.getMaterial();
String materialNumber;
- if (loadingFee) {
+ if (feeCategory == FeeCategory.WAREHOUSE) {
+ materialNumber = material.getWarehouse();
+ } else if (feeCategory == FeeCategory.LOADING) {
materialNumber = material.getLoading();
} else if (isInternationalBusiness(project)) {
materialNumber = material.getInternational();
@@ -590,9 +619,9 @@ public class KingdeeApBillSyncSupport {
if (Func.isBlank(materialNumber)) {
throw new ServiceException("推送金蝶失败:" + sourceDesc + "未在配置 thirdparty.bfi.material 中维护对应物料编码,请补充后重试");
}
- // 物料映射判定依据(联调排查:装卸大类/业务模式/项目业务类型/运输方式 -> 物料)
- log.info("金蝶物料映射:来源[{}],装卸大类[{}],业务模式[{}],项目业务类型[{}],运单运输方式[{}] -> 物料编码[{}]",
- sourceDesc, loadingFee ? "是" : "否",
+ // 物料映射判定依据(联调排查: 费用大类/业务模式/项目业务类型/运输方式 -> 物料)
+ log.info("金蝶物料映射: 来源[{}], 费用大类[{}], 业务模式[{}], 项目业务类型[{}], 运单运输方式[{}] -> 物料编码[{}]",
+ sourceDesc, feeCategory.getLabel(),
project == null ? null : project.getBusinessMode(), project == null ? null : project.getBusinessType(),
transportType, materialNumber);
return materialNumber;
@@ -669,7 +698,7 @@ public class KingdeeApBillSyncSupport {
/**
* 费用项按费用大类/税率拆分(纯函数):
- * 费用项经费用项档案判为装卸大类(费用大类编码 A10)、其余归运输大类;
+ * 费用项经费用项档案判为 装卸(A10)/仓储(A05)/运输 三大类(仓储不做名称关键字兜底);
* 各大类内按计费方案规则税率分组(未配置税率的费用项归入无税率组),
* 费用项金额合计与结算金额的残差并入运输类无税率组,保证拆分后各行金额合计与结算金额勾稽
*
@@ -691,11 +720,11 @@ public class KingdeeApBillSyncSupport {
if (amount.signum() == 0) {
continue;
}
- boolean loading = isLoadingCategory(item.getKey(), feeCategoryByName);
+ FeeCategory category = resolveCategory(item.getKey(), feeCategoryByName);
BigDecimal taxRate = taxRateByRule == null ? null : taxRateByRule.get(item.getKey());
- FeeLineSplit split = grouped.get(splitGroupKey(loading, taxRate));
+ FeeLineSplit split = grouped.get(splitGroupKey(category, taxRate));
if (split == null) {
- grouped.put(splitGroupKey(loading, taxRate), new FeeLineSplit(loading, taxRate, amount, item.getKey()));
+ grouped.put(splitGroupKey(category, taxRate), new FeeLineSplit(category, taxRate, amount, item.getKey()));
} else {
split.addAmount(amount);
}
@@ -704,16 +733,37 @@ public class KingdeeApBillSyncSupport {
BigDecimal residual = (rowAmount == null ? BigDecimal.ZERO : rowAmount).subtract(grouped.values().stream()
.map(FeeLineSplit::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
if (residual.signum() != 0) {
- FeeLineSplit freightDefault = grouped.computeIfAbsent(splitGroupKey(false, null),
- key -> new FeeLineSplit(false, null, BigDecimal.ZERO, FREIGHT_FEE_ITEM));
+ FeeLineSplit freightDefault = grouped.computeIfAbsent(splitGroupKey(FeeCategory.FREIGHT, null),
+ key -> new FeeLineSplit(FeeCategory.FREIGHT, null, BigDecimal.ZERO, FREIGHT_FEE_ITEM));
freightDefault.addAmount(residual);
}
return new ArrayList<>(grouped.values());
}
- /** 费用大类/税率分组键(装卸大类与运输大类、税率去重分组) */
- private static String splitGroupKey(boolean loading, BigDecimal taxRate) {
- return (loading ? "装卸" : "运输费") + "@" + (taxRate == null ? "" : taxRate.stripTrailingZeros().toPlainString());
+ /** 费用大类/税率分组键(运输/仓储/装卸大类、税率去重分组) */
+ private static String splitGroupKey(FeeCategory category, BigDecimal taxRate) {
+ return category.getLabel() + "@" + (taxRate == null ? "" : taxRate.stripTrailingZeros().toPlainString());
+ }
+
+ /**
+ * 分录费用大类判定(三分类):费用项档案分类编码 A10 归装卸大类(保留名称关键字兜底现状)、
+ * A05 归仓储大类(仓储仅认档案编码,不做名称关键字兜底,分类以档案维护为准),其余归运输大类
+ */
+ static FeeCategory resolveCategory(String feeItemName, Map feeCategoryByName) {
+ String categoryCode = feeItemName == null || feeCategoryByName == null ? null : feeCategoryByName.get(feeItemName);
+ if (FEE_CATEGORY_WAREHOUSE.equals(categoryCode)) {
+ return FeeCategory.WAREHOUSE;
+ }
+ return isLoadingCategory(feeItemName, feeCategoryByName) ? FeeCategory.LOADING : FeeCategory.FREIGHT;
+ }
+
+ /**
+ * 仓储大类判定(手动添加结算合计费用行物料映射用):费用项档案维护的费用大类编码为 A05 归仓储大类,
+ * 无名称关键字兜底,分类以档案维护为准
+ */
+ static boolean isWarehouseCategory(String feeItemName, Map feeCategoryByName) {
+ String categoryCode = feeItemName == null || feeCategoryByName == null ? null : feeCategoryByName.get(feeItemName);
+ return FEE_CATEGORY_WAREHOUSE.equals(categoryCode);
}
/** 装卸大类费用项判定(关键字兜底):费用项名称含“装卸” */
@@ -781,17 +831,17 @@ public class KingdeeApBillSyncSupport {
}
/**
- * 费用大类/税率拆分行:装卸大类 loading(true=装卸)、税率 taxRate(百分数,未配置为 null)、
+ * 费用大类/税率拆分行:费用大类 category(运输/仓储/装卸)、税率 taxRate(百分数,未配置为 null)、
* 组内金额合计 amount、组内首个费用项 firstFeeItem
*/
public static final class FeeLineSplit {
- private final boolean loading;
+ private final FeeCategory category;
private final BigDecimal taxRate;
private final String firstFeeItem;
private BigDecimal amount;
- FeeLineSplit(boolean loading, BigDecimal taxRate, BigDecimal amount, String firstFeeItem) {
- this.loading = loading;
+ FeeLineSplit(FeeCategory category, BigDecimal taxRate, BigDecimal amount, String firstFeeItem) {
+ this.category = category;
this.taxRate = taxRate;
this.amount = amount;
this.firstFeeItem = firstFeeItem;
@@ -801,8 +851,18 @@ public class KingdeeApBillSyncSupport {
amount = amount.add(value);
}
+ /** 装卸大类判定(兼容保留) */
public boolean isLoading() {
- return loading;
+ return category == FeeCategory.LOADING;
+ }
+
+ /** 仓储大类判定 */
+ public boolean isWarehouse() {
+ return category == FeeCategory.WAREHOUSE;
+ }
+
+ public FeeCategory getCategory() {
+ return category;
}
public BigDecimal getTaxRate() {
@@ -851,7 +911,7 @@ public class KingdeeApBillSyncSupport {
/**
* 费用项档案分类:费用项名称 -> 费用大类编码(blade_fee_item.fee_category,业务字典 fee_category 键值,
- * 运输 A01 / 装卸 A10;批量同步时全批共享一次加载结果),费用大类判定用;
+ * 运输 A01 / 装卸 A10 / 仓储 A05;批量同步时全批共享一次加载结果),费用大类判定用;
* 档案读取失败直接拦截
*/
public Map feeCategoryByName(KingdeeSyncContext context) {
@@ -1133,7 +1193,7 @@ public class KingdeeApBillSyncSupport {
/**
* 财务应付单/付款申请分录:结算明细按 货物→费用大类→税率 拆分行 + 结算合计费用项(手动添加的每行一条)
*
- * @param materialNumber 物料编码(按装卸大类/项目业务模式/运单运输方式映射,thirdparty.bfi.material.* 配置)
+ * @param materialNumber 物料编码(按费用大类/项目业务模式/运单运输方式映射,thirdparty.bfi.material.* 配置)
* @param entryCode 分录唯一标识(awrp_entry_code):结算明细拆分行取单据号-货物序号-大类-税率,结算合计费用项行取结算单号-费用类型-费用项
* @param quantity 该行分录数量(运输类取运输量,装卸类/其它默认 1)
* @param price 该行分录单价(结算金额/数量反算)