perf: 分录拆分补齐上下文缓存消除N+1,联调详细日志

N+1 治理(复用 KingdeeSyncContext 全批档案缓存,批量同步不因拆分逻辑增加重复查询):
- 合同计费方案解析按合同id缓存(BillingPlanInfo:计费单位+费用归类一次解析全批复用),
  消除批量同步逐单重复解析同一合同计费方案JSON(此前计费单位/归类各解析一次)
- 货物费用行按结算明细id上下文缓存(含空行占位,仅补查缺失明细),多结算单单据
  (收票登记分摊多结算单)跨结算单一次预查,逐结算单解析命中缓存
- 结算合计费用项按结算单id上下文缓存,多笔收票登记分摊同一结算单不再重复查询
- KingdeeSyncContext 新增 billingPlanCache/detailFeeCache/summaryFeeCache 组件

联调详细日志:
- 逐费用项归类明细(同单去重):费用项->费用大类/税率+归类来源(计费方案规则/费用项档案/
  名称特征兜底/交叉补齐);来源为名称兜底或税率未识别时升级 warn 提示补配置
- 分录生成日志增加费用项构成(运输费:100.00 + 装卸费:50.00)、货物、车号
- 货物费用行批量查询打印缓存命中/补查行数
This commit is contained in:
刘泉佳
2026-10-01 23:15:40 +08:00
parent 5d1c01e15e
commit b7beb949fb
2 changed files with 143 additions and 31 deletions
@@ -29,15 +29,24 @@ import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.CustomerReceiptAccount;
import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee;
import org.springblade.transport.pojo.entity.ProjectApply;
import org.springblade.transport.support.KingdeeApBillSyncSupport;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 金蝶同步上下文:批量同步时全批共享的档案缓存,消除逐单重复的远程/数据库查询(N+1)。
* <p>单笔同步使用 {@link #empty()},各缓存为空时自动回退为逐单查询,行为与原逻辑一致。</p>
* 金蝶同步上下文:批量同步时全批共享的档案缓存,消除逐单重复的远程/数据库查询与重复解析(N+1)。
* <p>缓存清单与加载来源:
* 计量单位档案(ISysClient.getMeasurementUnits)、费用项档案(ISysClient.getFeeItems)、
* 客商编码(客商档案按乙方/付款方查询)、合同/项目/正式结算单(selectById)、
* 结算明细(按结算单批量)、财务应付单已生成预检(A102-4 批量查询)、
* 收款账户、合同计费方案解析({@link KingdeeApBillSyncSupport.BillingPlanInfo},按合同id解析一次)、
* 货物费用行(按结算明细id查询,键全量占位避免空行重复查询)、结算合计费用项(按结算单id查询)。
* 单笔同步使用 {@link #empty()},各缓存为空时首单加载后即复用。</p>
*
* @author Chill
*/
@@ -47,11 +56,15 @@ public record KingdeeSyncContext(Map<String, String> measureUnitCodeByName, Map<
Map<Long, List<FormalSettlementDetail>> settlementDetailCache,
Map<Long, FormalSettlement> settlementCache,
Map<Long, CustomerReceiptAccount> receiptAccountCache,
Map<String, FeeItem> feeItemCacheByName) {
Map<String, FeeItem> feeItemCacheByName,
Map<Long, KingdeeApBillSyncSupport.BillingPlanInfo> billingPlanCache,
Map<Long, List<FormalSettlementDetailFee>> detailFeeCache,
Map<Long, List<FormalSettlementSummaryFee>> summaryFeeCache) {
/** 空上下文:缓存可写但初始为空,首条记录命中后全批复用 */
public static KingdeeSyncContext empty() {
return new KingdeeSyncContext(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(),
new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>());
new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>(),
new HashMap<>(), new HashMap<>(), new HashMap<>());
}
}
@@ -69,10 +69,12 @@ import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -89,6 +91,10 @@ import java.util.stream.Collectors;
* 物料编码按项目业务类型/业务模式/装卸大类映射(见 {@link #resolveMaterialNumber});
* 计量单位编码按费用类型匹配合同计费方案规则计费单位,再匹配计量单位档案编码;
* 数量运输费大类取货物运输量、其余默认 1;单价按结算金额/数量反算。</p>
* <p>缓存与 N+1:单笔/批量同步共用 {@link KingdeeSyncContext} 全批档案缓存——合同/项目/客商/正式结算单/
* 应付单预检/计量单位档案/费用项档案走既有缓存,计费方案解析({@link BillingPlanInfo})按合同id解析一次、
* 货物费用行按结算明细id、结算合计费用项按结算单id缓存复用,
* 批量同步(如收票单批量推送分摊多结算单)不因拆分逻辑增加重复查询与重复解析。</p>
*
* @author Chill
*/
@@ -111,6 +117,14 @@ public class KingdeeApBillSyncSupport {
private static final String FREIGHT_FEE_CATEGORY = "运输费";
/** 分录金额勾稽允许误差(页面金额与费用项合计的分摊尾差容忍度) */
private static final BigDecimal AMOUNT_TOLERANCE = new BigDecimal("0.01");
/** 费用归类来源:合同计费方案规则 */
private static final String PLAN_FEE_SOURCE = "合同计费方案规则";
/** 费用归类来源:费用项档案 */
private static final String ARCHIVE_FEE_SOURCE = "费用项档案";
/** 费用归类来源:名称特征兜底(计费方案与费用项档案均未维护,告警提示补配置) */
private static final String FALLBACK_FEE_SOURCE = "名称特征兜底";
/** 费用归类来源:合同计费方案税率 + 费用项档案大类(交叉补齐) */
private static final String MIXED_FEE_SOURCE = "合同计费方案税率+费用项档案大类";
private final IBfiService bfiService;
private final ContractManageMapper contractManageMapper;
@@ -436,6 +450,9 @@ public class KingdeeApBillSyncSupport {
detailsBySettlementId.computeIfAbsent(settlementDetail.getFormalSettlementId(), key -> new ArrayList<>()).add(settlementDetail);
}
List<BfiEntryGroup> entryGroups = new ArrayList<>();
// 跨结算单一次批量预查全部货物费用行入上下文缓存:逐结算单解析时命中缓存,
// 消除多结算单单据(收票登记分摊多结算单)逐结算单重复查询的 N+1
detailFeesByDetailId(settlementDetails, context);
detailsBySettlementId.forEach((settlementId, details) ->
entryGroups.addAll(this.resolveEntryGroups(requireSettlement(settlementId, context), details, context)));
return entryGroups;
@@ -467,16 +484,16 @@ public class KingdeeApBillSyncSupport {
}
ProjectApply project = requireProject(settlement.getProjectId(), context);
ContractManage contract = requireContract(settlement.getContractId(), context);
// 合同计费方案规则:费用类型/费用项 -> 计费单位
Map<String, String> billingUnitByRule = billingUnitByRule(contract);
// 合同计费方案规则:费用项 -> (费用大类, 税率),结算明细费用项拆分归类主来源
Map<String, FeeClassInfo> feeClassByRule = feeClassByRule(contract);
// 费用项档案:费用项名称 -> 档案(费用大类/税率兜底来源)
// 合同计费方案解析(上下文缓存:按合同id解析一次全批复用,消除批量同步逐单重复解析 JSON 的 N+1)
BillingPlanInfo billingPlan = billingPlanInfo(contract, context);
Map<String, String> billingUnitByRule = billingPlan.billingUnitByRule();
Map<String, FeeClassInfo> feeClassByRule = billingPlan.feeClassByRule();
// 费用项档案:费用项名称 -> 档案(费用大类/税率兜底来源,上下文缓存全批复用)
Map<String, FeeItem> feeItemArchive = feeItemArchiveByName(context);
// 计量单位档案:单位名称 -> 计量单位编码
// 计量单位档案:单位名称 -> 计量单位编码(上下文缓存全批复用)
Map<String, String> unitCodeByName = measureUnitCodeByName(context);
// 结算明细货物费用行:一车多货按货物拆分与费用项归类取数来源
Map<Long, List<FormalSettlementDetailFee>> feesByDetailId = detailFeesByDetailId(settlementDetails);
// 结算明细货物费用行:一车多货按货物拆分与费用项归类取数来源(上下文缓存:跨结算单/跨单据复用,仅补查缺失明细)
Map<Long, List<FormalSettlementDetailFee>> feesByDetailId = detailFeesByDetailId(settlementDetails, context);
List<BfiEntryGroup> entryGroups = new ArrayList<>();
int detailEntryCount = 0;
for (FormalSettlementDetail settlementDetail : settlementDetails) {
@@ -484,8 +501,8 @@ public class KingdeeApBillSyncSupport {
feesByDetailId.getOrDefault(settlementDetail.getId(), List.of()),
billingUnitByRule, feeClassByRule, unitCodeByName, feeItemArchive);
}
// 结算合计只取手工添加的费用项(manualFlag=1);系统自动汇总行是结算明细金额的再汇总,再送会与明细重复
List<FormalSettlementSummaryFee> manualSummaryFees = listSummaryFees(settlement.getId()).stream()
// 结算合计只取手工添加的费用项(manualFlag=1);系统自动汇总行是结算明细金额的再汇总,再送会与明细重复(上下文缓存按结算单id复用)
List<FormalSettlementSummaryFee> manualSummaryFees = listSummaryFees(settlement.getId(), context).stream()
.filter(summaryFee -> Integer.valueOf(1).equals(summaryFee.getManualFlag())).toList();
for (FormalSettlementSummaryFee summaryFee : manualSummaryFees) {
appendSummaryEntry(entryGroups, settlement, project, summaryFee,
@@ -520,6 +537,8 @@ public class KingdeeApBillSyncSupport {
String sourceDesc = "结算明细[" + Func.toStr(detail.getDocumentNo()) + "]";
Map<DetailSplitKey, DetailSplitGroup> groups = new LinkedHashMap<>();
boolean multiCargo = detailFees.size() > 1;
// 逐费用项归类明细日志去重:同一费用项同单只记一次(联调排查归类来源与缺配置项)
Set<String> classifiedFeeItems = new HashSet<>();
for (FormalSettlementDetailFee detailFee : detailFees) {
Map<String, BigDecimal> feeItems = parseFeeItems(detailFee.getFeeItemsJson());
// 页面合计口径:费用项JSON无运费项时,运费(freightAmount)并入运输费大类参与拆分
@@ -548,6 +567,10 @@ public class KingdeeApBillSyncSupport {
BigDecimal amount = money(item.getValue());
FeeClassInfo classInfo = classifyFeeItem(feeItem, feeClassByRule, feeItemArchive);
String feeType = feeCategoryOf(feeItem, classInfo);
// 逐费用项归类明细(每个费用项同单只记一次):来源与税率缺失告警,提示补维护计费方案/费用项档案
if (classifiedFeeItems.add(feeItem)) {
logClassify(sourceDesc, feeItem, classInfo, feeType);
}
DetailSplitKey splitKey = new DetailSplitKey(cargoKey, feeType, taxRateText(classInfo.taxRate()));
DetailSplitGroup group = groups.computeIfAbsent(splitKey, key -> new DetailSplitGroup());
if (group.firstFeeItem == null) {
@@ -555,6 +578,7 @@ public class KingdeeApBillSyncSupport {
}
group.transportGoods = firstNonBlank(detailFee.getCargoName(), group.transportGoods);
group.amountTax = group.amountTax.add(amount);
group.feeItemAmounts.merge(feeItem, amount, BigDecimal::add);
if (isFreightFeeItem(feeItem)) {
group.freightQuantity = group.freightQuantity.add(
detailFee.getTransportQuantity() == null ? BigDecimal.ZERO : money(detailFee.getTransportQuantity()));
@@ -604,13 +628,41 @@ public class KingdeeApBillSyncSupport {
group.amountNoTax == null ? null : money(group.amountNoTax),
group.transportGoods == null ? detail.getCargoName() : group.transportGoods,
detail.getVehicleNo(), settlement.getId()));
log.info("金蝶应付单分录生成:来源[{}],分录标识[{}],费用大类[{}],税率[{}],物料编码[{}],计量单位[{}],数量[{}],单价[{}],含税金额[{}],不含税金额[{}]",
log.info("金蝶应付单分录生成:来源[{}],分录标识[{}],费用大类[{}],税率[{}],费用项构成[{}],货物[{}],车号[{}],物料编码[{}],计量单位[{}],数量[{}],单价[{}],含税金额[{}],不含税金额[{}]",
sourceDesc, entryCode, splitKey.feeType(), Func.toStr(splitKey.taxRateText(), "未识别"),
feeItemDetailText(group.feeItemAmounts),
Func.toStr(group.transportGoods, detail.getCargoName()), detail.getVehicleNo(),
materialNumber, measureunitNumber, quantity, price, amount, group.amountNoTax == null ? null : money(group.amountNoTax));
}
return groups.size();
}
/**
* 费用项归类明细日志:记录费用项 -> 费用大类/税率及归类来源;归类来源为名称特征兜底
* (计费方案与费用项档案均未维护该费用项)或税率未识别时升级告警,提示补维护配置,
* 避免拆分行税率缺失被并入"未识别税率"行造成对账困难
*/
private void logClassify(String sourceDesc, String feeItem, FeeClassInfo classInfo, String feeType) {
String rateText = taxRateText(classInfo.taxRate());
if (FALLBACK_FEE_SOURCE.equals(classInfo.source())) {
log.warn("金蝶分录费用归类:{}费用项[{}]未在合同计费方案规则与费用项档案中维护,仅按名称特征归类为费用大类[{}]且税率未识别,相关费用将并入未识别税率行,请补维护计费方案或费用项档案",
sourceDesc, feeItem, feeType);
} else if (classInfo.taxRate() == null) {
log.warn("金蝶分录费用归类:{}费用项[{}]归类费用大类[{}]({})但税率未识别,相关费用将并入未识别税率行,请补维护计费方案税率",
sourceDesc, feeItem, feeType, classInfo.source());
} else {
log.info("金蝶分录费用归类:{}费用项[{}] -> 费用大类[{}],税率[{}%]({})",
sourceDesc, feeItem, feeType, rateText, classInfo.source());
}
}
/** 费用项构成文本(联调排查对账用):费用项:金额 拼接,如"运输费:100.00 + 装卸费:50.00" */
private String feeItemDetailText(Map<String, BigDecimal> feeItemAmounts) {
return feeItemAmounts.entrySet().stream()
.map(item -> item.getKey() + ":" + money(item.getValue()))
.collect(Collectors.joining(" + "));
}
/**
* 结算明细整行兜底分录(历史数据:货物费用行缺失或费用项JSON为空时使用):
* 物料按项目业务类型/业务模式/装卸大类映射,数量取运输量(兜底 1),
@@ -756,6 +808,19 @@ public class KingdeeApBillSyncSupport {
return unitCodeByName.get(billingUnit.trim());
}
/**
* 合同计费方案解析(上下文缓存):按合同id解析一次计费方案JSON,得到 计费单位映射 + 费用归类映射,
* 批量同步全批复用,消除逐单重复解析同一合同计费方案JSON的 N+1
*
* @param contract 结算合同
* @param context 金蝶同步上下文(缓存载体)
* @return 计费方案解析结果(计费单位 + 费用归类)
*/
public BillingPlanInfo billingPlanInfo(ContractManage contract, KingdeeSyncContext context) {
return context.billingPlanCache().computeIfAbsent(contract.getId(), contractId ->
new BillingPlanInfo(billingUnitByRule(contract), feeClassByRule(contract)));
}
/**
* 合同计费方案规则解析:费用类型/费用项 -> 计费单位(取首个配置了计费单位的规则,
* 计费方案 JSON 解析失败按未配置处理,由计量单位解析处统一拦截报错)
@@ -799,7 +864,7 @@ public class KingdeeApBillSyncSupport {
if (Func.isBlank(feeItem) && Func.isBlank(feeType)) {
continue;
}
FeeClassInfo classInfo = new FeeClassInfo(feeType, feeAmount(rule.get("taxRate")));
FeeClassInfo classInfo = new FeeClassInfo(feeType, feeAmount(rule.get("taxRate")), PLAN_FEE_SOURCE);
if (Func.isNotBlank(feeItem)) {
mapping.putIfAbsent(feeItem, classInfo);
}
@@ -876,21 +941,21 @@ public class KingdeeApBillSyncSupport {
* @param feeItem 费用项名称
* @param feeClassByRule 合同计费方案:费用项/费用类型 -> (费用大类, 税率)
* @param feeItemArchive 费用项档案:费用项名称 -> 档案
* @return 费用归类(费用大类 + 税率,均可为空由调用处兜底)
* @return 费用归类(费用大类 + 税率 + 归类来源,大类/税率可空由调用处兜底)
*/
private FeeClassInfo classifyFeeItem(String feeItem, Map<String, FeeClassInfo> feeClassByRule, Map<String, FeeItem> feeItemArchive) {
FeeClassInfo classInfo = feeClassByRule.get(feeItem);
FeeItem archive = feeItemArchive.get(feeItem);
if (classInfo != null) {
if (Func.isBlank(classInfo.feeType()) && archive != null) {
return new FeeClassInfo(textValue(archive.getFeeCategory()), classInfo.taxRate());
return new FeeClassInfo(textValue(archive.getFeeCategory()), classInfo.taxRate(), MIXED_FEE_SOURCE);
}
return classInfo;
}
if (archive != null) {
return new FeeClassInfo(textValue(archive.getFeeCategory()), archive.getTaxRate());
return new FeeClassInfo(textValue(archive.getFeeCategory()), archive.getTaxRate(), ARCHIVE_FEE_SOURCE);
}
return new FeeClassInfo(feeItem.contains("装卸") ? LOADING_FEE_TYPE : FREIGHT_FEE_CATEGORY, null);
return new FeeClassInfo(feeItem.contains("装卸") ? LOADING_FEE_TYPE : FREIGHT_FEE_CATEGORY, null, FALLBACK_FEE_SOURCE);
}
/** 费用大类归一:归类无大类时按费用项名称特征兜底(含“装卸”为装卸大类,其余归运输费大类) */
@@ -984,21 +1049,34 @@ public class KingdeeApBillSyncSupport {
return value == null ? null : String.valueOf(value).trim();
}
/** 结算明细id -> 货物费用行(一车多货按货物拆分与费用项归类取数来源) */
private Map<Long, List<FormalSettlementDetailFee>> detailFeesByDetailId(List<FormalSettlementDetail> settlementDetails) {
/**
* 结算明细id -> 货物费用行(一车多货按货物拆分与费用项归类取数来源)。
* <p>上下文缓存按明细id全量占位:仅补查缓存缺失的明细id,已查过的明细(含查无费用行的)不再重复查询,
* 消除批量同步、多结算单单据(如收票登记分摊多结算单)下同一明细重复查询的 N+1;
* 返回映射保持入参明细顺序。</p>
*/
private Map<Long, List<FormalSettlementDetailFee>> detailFeesByDetailId(List<FormalSettlementDetail> settlementDetails, KingdeeSyncContext context) {
List<Long> detailIds = settlementDetails.stream().map(FormalSettlementDetail::getId)
.filter(Objects::nonNull).distinct().toList();
if (Func.isEmpty(detailIds)) {
return Map.of();
}
return formalSettlementDetailFeeMapper.selectList(Wrappers.<FormalSettlementDetailFee>lambdaQuery()
.in(FormalSettlementDetailFee::getFormalSettlementDetailId, detailIds)
.eq(FormalSettlementDetailFee::getIsDeleted, 0)
.orderByAsc(FormalSettlementDetailFee::getLineNo))
.stream().collect(Collectors.groupingBy(FormalSettlementDetailFee::getFormalSettlementDetailId));
Map<Long, List<FormalSettlementDetailFee>> cache = context.detailFeeCache();
List<Long> missingIds = detailIds.stream().filter(id -> !cache.containsKey(id)).toList();
if (Func.isNotEmpty(missingIds)) {
Map<Long, List<FormalSettlementDetailFee>> loaded = formalSettlementDetailFeeMapper.selectList(Wrappers.<FormalSettlementDetailFee>lambdaQuery()
.in(FormalSettlementDetailFee::getFormalSettlementDetailId, missingIds)
.eq(FormalSettlementDetailFee::getIsDeleted, 0)
.orderByAsc(FormalSettlementDetailFee::getLineNo))
.stream().collect(Collectors.groupingBy(FormalSettlementDetailFee::getFormalSettlementDetailId));
// 查无费用行的明细也空列表占位,避免后续重复查询
missingIds.forEach(id -> cache.put(id, loaded.getOrDefault(id, List.of())));
log.info("货物费用行批量查询:明细{}行,缓存命中{}行,补查{}行", detailIds.size(), detailIds.size() - missingIds.size(), missingIds.size());
}
return detailIds.stream().collect(Collectors.toMap(id -> id, cache::get, (first, second) -> first, LinkedHashMap::new));
}
/** 正式结算合计费用项(页面结算合计,保持行序) */
/** 正式结算合计费用项(页面结算合计,保持行序);无上下文兼容入口(分录解析请用 {@link #listSummaryFees(Long, KingdeeSyncContext)}) */
public List<FormalSettlementSummaryFee> listSummaryFees(Long settlementId) {
return formalSettlementSummaryFeeMapper.selectList(Wrappers.<FormalSettlementSummaryFee>lambdaQuery()
.eq(FormalSettlementSummaryFee::getFormalSettlementId, settlementId)
@@ -1006,6 +1084,14 @@ public class KingdeeApBillSyncSupport {
.orderByAsc(FormalSettlementSummaryFee::getLineNo));
}
/**
* 正式结算合计费用项(页面结算合计,保持行序),上下文缓存按结算单id复用:
* 批量同步中多张单据(如多笔收票登记)分摊同一结算单时不再重复查询(N+1)
*/
public List<FormalSettlementSummaryFee> listSummaryFees(Long settlementId, KingdeeSyncContext context) {
return context.summaryFeeCache().computeIfAbsent(settlementId, this::listSummaryFees);
}
/**
* 计量单位档案:单位名称 -> 计量单位编码(批量同步时全批共享一次加载结果;
* 应收单同步组件复用)
@@ -1222,13 +1308,24 @@ public class KingdeeApBillSyncSupport {
}
/**
* 结算明细费用归类:费用大类(费用类型,如 物流配送/装卸搬运)+ 税率(百分比数值)。
* 合同计费方案规则解析为主来源,费用项档案兜底
* 结算明细费用归类:费用大类(费用类型,如 物流配送/装卸搬运)+ 税率(百分比数值)+ 归类来源。
* 合同计费方案规则解析为主来源,费用项档案兜底,名称特征再兜底
*
* @param feeType 费用大类,未识别为空由调用处按名称特征兜底
* @param taxRate 税率(百分比数值),未识别为空
* @param source 归类来源(合同计费方案规则/费用项档案/名称特征兜底等,联调排查用)
*/
private record FeeClassInfo(String feeType, BigDecimal taxRate) {
public record FeeClassInfo(String feeType, BigDecimal taxRate, String source) {
}
/**
* 合同计费方案解析结果(上下文缓存载体,按合同id解析一次全批复用):
* 计费单位映射 + 费用归类映射,供结算明细拆分与计量单位匹配取数
*
* @param billingUnitByRule 费用类型/费用项 -> 计费单位
* @param feeClassByRule 费用项/费用类型 -> (费用大类, 税率, 归类来源)
*/
public record BillingPlanInfo(Map<String, String> billingUnitByRule, Map<String, FeeClassInfo> feeClassByRule) {
}
/**
@@ -1249,6 +1346,8 @@ public class KingdeeApBillSyncSupport {
private String transportGoods;
/** 含税金额合计 */
private BigDecimal amountTax = BigDecimal.ZERO;
/** 组内费用项 -> 金额(联调对账明细日志用) */
private final Map<String, BigDecimal> feeItemAmounts = new LinkedHashMap<>();
/** 不含税金额分摊合计(行不含税缺失为空不送) */
private BigDecimal amountNoTax;
/** 运输量累计(运输费大类数量来源) */