This commit is contained in:
2026-08-21 12:07:21 +08:00
parent 35b3d1b0ed
commit 762d6e4768
6 changed files with 324 additions and 41 deletions
@@ -70,5 +70,8 @@ public class ReceivablePayableAdjustFeeRequest implements Serializable {
@Schema(description = "备注")
private String remark;
@Schema(description = "变更原因")
private String changeReason;
}
}
@@ -105,4 +105,7 @@ public class ReceivablePayableCargoFee extends TenantEntity {
@Schema(description = "备注")
private String remark;
@Schema(description = "变更原因")
private String changeReason;
}
@@ -36,10 +36,12 @@ import org.springblade.core.secure.annotation.PreAuth;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.DateUtil;
import org.springblade.transport.pojo.dto.ReceivablePayableAdjustFeeRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableFeeCalculateRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableGenerateRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableTransferRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableUpdateFeeRequest;
import org.springblade.transport.pojo.vo.ReceivablePayableChangeRecordVO;
import org.springblade.transport.pojo.vo.ReceivablePayableCargoFeeVO;
import org.springblade.transport.pojo.vo.ReceivablePayableDetailVO;
import org.springblade.transport.pojo.vo.ReceivablePayableFeeDetailVO;
import org.springblade.transport.service.IReceivablePayableDetailService;
@@ -119,8 +121,16 @@ public class ReceivablePayableDetailController extends BladeController {
return R.success("保存成功");
}
@GetMapping("/transfer-candidates")
@PostMapping("/calculate-adjusted-fee")
@ApiOperationSupport(order = 7)
@Operation(summary = "调整费用试算")
public R<ReceivablePayableCargoFeeVO> calculateAdjustedFee(
@RequestBody ReceivablePayableFeeCalculateRequest request) {
return R.data(detailService.calculateAdjustedFee(request));
}
@GetMapping("/transfer-candidates")
@ApiOperationSupport(order = 8)
@Operation(summary = "转结算候选明细")
public R<IPage<Map<String, Object>>> transferCandidates(Query query,
@RequestParam(required = false) String contractName,
@@ -25,12 +25,14 @@ package org.springblade.transport.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.mp.base.BaseService;
import org.springblade.transport.pojo.dto.ReceivablePayableAdjustFeeRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableFeeCalculateRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableGenerateRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableTransferRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableUpdateFeeRequest;
import org.springblade.transport.pojo.entity.MasterOrder;
import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
import org.springblade.transport.pojo.vo.ReceivablePayableChangeRecordVO;
import org.springblade.transport.pojo.vo.ReceivablePayableCargoFeeVO;
import org.springblade.transport.pojo.vo.ReceivablePayableDetailVO;
import org.springblade.transport.pojo.vo.ReceivablePayableFeeDetailVO;
@@ -58,6 +60,8 @@ public interface IReceivablePayableDetailService extends BaseService<ReceivableP
void adjustFee(ReceivablePayableAdjustFeeRequest request);
ReceivablePayableCargoFeeVO calculateAdjustedFee(ReceivablePayableFeeCalculateRequest request);
void transferSettlement(ReceivablePayableTransferRequest request);
IPage<Map<String, Object>> transferCandidates(IPage<?> page, String contractName, String batchNo,
@@ -38,6 +38,7 @@ import org.springblade.transport.mapper.ReceivablePayableChangeRecordMapper;
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
import org.springblade.transport.mapper.MasterOrderMapper;
import org.springblade.transport.pojo.dto.ReceivablePayableAdjustFeeRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableFeeCalculateRequest;
import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableGenerateRequest;
import org.springblade.transport.pojo.dto.PreSettlementSaveRequest;
@@ -74,6 +75,7 @@ import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@@ -125,12 +127,18 @@ public class ReceivablePayableDetailServiceImpl
@Override
public IPage<ReceivablePayableDetailVO> selectPage(IPage<ReceivablePayableDetail> page, ReceivablePayableDetailVO query) {
return ReceivablePayableDetailWrapper.build().pageVO(page(page, buildQuery(query)));
IPage<ReceivablePayableDetailVO> result = ReceivablePayableDetailWrapper.build()
.pageVO(page(page, buildQuery(query)));
fillCargoNames(result.getRecords());
return result;
}
@Override
public List<ReceivablePayableDetailVO> selectList(ReceivablePayableDetailVO query) {
return ReceivablePayableDetailWrapper.build().listVO(list(buildQuery(query)));
List<ReceivablePayableDetailVO> result = ReceivablePayableDetailWrapper.build()
.listVO(list(buildQuery(query)));
fillCargoNames(result);
return result;
}
@Override
@@ -257,6 +265,9 @@ public class ReceivablePayableDetailServiceImpl
if (adjusted.getRemark() != null && adjusted.getRemark().length() > 200) {
throw new ServiceException("备注不能超过200个字");
}
if (adjusted.getChangeReason() != null && adjusted.getChangeReason().length() > 300) {
throw new ServiceException("变更原因不能超过300个字");
}
if (Boolean.TRUE.equals(adjusted.getManualFee())) {
if (Func.isEmpty(adjusted.getFeeItemName())) {
throw new ServiceException("请填写手工费用项目");
@@ -290,6 +301,7 @@ public class ReceivablePayableDetailServiceImpl
existing.setAdjustAmount(signedAmount.subtract(money(existing.getOriginalAmount())));
existing.setAfterAmount(signedAmount);
existing.setRemark(adjusted.getRemark());
existing.setChangeReason(adjusted.getChangeReason());
if (newManualRow) {
cargoFeeMapper.insert(existing);
allRows.add(existing);
@@ -316,24 +328,40 @@ public class ReceivablePayableDetailServiceImpl
feeItems.put(name, money(amount));
});
}
appendChange(changes, "计费数量", existing.getTransportQuantity(), adjusted.getTransportQuantity());
appendChange(changes, "里程", existing.getMileage(), adjusted.getMileage());
appendChange(changes, "运输费", existing.getFreightAmount(), adjusted.getFreightAmount());
BigDecimal transportQuantity = money(adjusted.getTransportQuantity());
BigDecimal mileage = money(adjusted.getMileage());
BigDecimal freightAmount = money(adjusted.getFreightAmount());
Map<String, BigDecimal> effectiveFeeItems = feeItems;
boolean ruleInputChanged = money(existing.getTransportQuantity()).compareTo(transportQuantity) != 0
|| money(existing.getMileage()).compareTo(mileage) != 0;
if (ruleInputChanged) {
AdjustedFeeCalculation calculation = calculateAdjustedFee(detail, existing, transportQuantity,
mileage, freightAmount, effectiveFeeItems);
freightAmount = calculation.freightAmount();
effectiveFeeItems = calculation.feeItems();
}
appendChange(changes, "计费数量", existing.getTransportQuantity(), transportQuantity);
appendChange(changes, "里程", existing.getMileage(), mileage);
appendChange(changes, "运输费", existing.getFreightAmount(), freightAmount);
if (!Objects.equals(existing.getRemark(), adjusted.getRemark())) {
changes.add("【备注】从[" + Objects.toString(existing.getRemark(), "") + "]调整为["
+ Objects.toString(adjusted.getRemark(), "") + "]");
}
if (!Objects.equals(existing.getChangeReason(), adjusted.getChangeReason())) {
changes.add("【变更原因】从[" + Objects.toString(existing.getChangeReason(), "") + "]调整为["
+ Objects.toString(adjusted.getChangeReason(), "") + "]");
}
Map<String, Object> oldFeeItems = parseMap(existing.getFeeItemsJson());
for (String name : allowedFeeItems) {
appendChange(changes, name, decimal(oldFeeItems.get(name)), money(feeItems.get(name)));
appendChange(changes, name, decimal(oldFeeItems.get(name)), money(effectiveFeeItems.get(name)));
}
BigDecimal freightAmount = money(adjusted.getFreightAmount());
BigDecimal afterAmount = adjustedAfterAmount(freightAmount, feeItems);
existing.setTransportQuantity(money(adjusted.getTransportQuantity()));
existing.setMileage(money(adjusted.getMileage()));
BigDecimal afterAmount = adjustedAfterAmount(freightAmount, effectiveFeeItems);
existing.setTransportQuantity(transportQuantity);
existing.setMileage(mileage);
existing.setFreightAmount(freightAmount);
existing.setFeeItemsJson(JsonUtil.toJson(feeItems));
existing.setFeeItemsJson(JsonUtil.toJson(effectiveFeeItems));
existing.setRemark(adjusted.getRemark());
existing.setChangeReason(adjusted.getChangeReason());
existing.setAdjustAmount(afterAmount.subtract(money(existing.getOriginalAmount())));
existing.setAfterAmount(afterAmount);
cargoFeeMapper.updateById(existing);
@@ -347,6 +375,43 @@ public class ReceivablePayableDetailServiceImpl
refreshAdjustedDetail(detail, allRows);
}
@Override
public ReceivablePayableCargoFeeVO calculateAdjustedFee(ReceivablePayableFeeCalculateRequest request) {
if (request == null || request.getDetailId() == null || request.getFeeId() == null) {
throw new ServiceException("费用调整试算数据不能为空");
}
ReceivablePayableDetail detail = getExisting(request.getDetailId());
if (!"pending".equals(detail.getSettlementStatus())) {
throw new ServiceException("仅待结算明细允许调整试算");
}
ReceivablePayableCargoFee fee = cargoFeeMapper.selectOne(
Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
.eq(ReceivablePayableCargoFee::getId, request.getFeeId())
.eq(ReceivablePayableCargoFee::getDetailId, detail.getId())
.eq(ReceivablePayableCargoFee::getIsDeleted, 0));
if (fee == null) {
throw new ServiceException("费用明细不存在");
}
validateNonNegative(request.getTransportQuantity(), "运输量");
validateNonNegative(request.getMileage(), "里程");
AdjustedFeeCalculation calculation = calculateAdjustedFee(detail, fee,
money(request.getTransportQuantity()), money(request.getMileage()),
money(request.getFreightAmount()), normalizeFeeItems(request.getFeeItems()));
ReceivablePayableCargoFeeVO result = Objects.requireNonNull(
BeanUtil.copyProperties(fee, ReceivablePayableCargoFeeVO.class));
result.setTransportQuantity(request.getTransportQuantity());
result.setMileage(request.getMileage());
result.setFreightAmount(calculation.freightAmount());
result.setFeeItems(new LinkedHashMap<>(calculation.feeItems()));
BigDecimal afterAmount = adjustedAfterAmount(calculation.freightAmount(), calculation.feeItems());
result.setAdjustAmount(afterAmount.subtract(money(fee.getOriginalAmount())));
result.setAfterAmount(afterAmount);
result.setOriginalAmountText(formatMoney(fee.getOriginalAmount()));
result.setAdjustAmountText(formatMoney(result.getAdjustAmount()));
result.setAfterAmountText(formatMoney(afterAmount));
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void transferSettlement(ReceivablePayableTransferRequest request) {
@@ -412,6 +477,7 @@ public class ReceivablePayableDetailServiceImpl
.or().eq(ReceivablePayableDetail::getFormalSettlementNo, ""));
IPage<ReceivablePayableDetailVO> detailPage = ReceivablePayableDetailWrapper.build()
.pageVO(page(new Page<>(page.getCurrent(), page.getSize()), wrapper));
fillCargoNames(detailPage.getRecords());
Page<Map<String, Object>> result = new Page<>(detailPage.getCurrent(), detailPage.getSize(), detailPage.getTotal());
result.setRecords(detailPage.getRecords().stream().map(this::candidateMap).toList());
return result;
@@ -431,11 +497,12 @@ public class ReceivablePayableDetailServiceImpl
validateGenerateRequest(request, true);
List<Waybill> waybills = waybillService.list(buildWaybillQuery(request));
ContractManage contract = contractManageService.getById(request.getContractId());
List<ReceivablePayableCargoFee> fees = waybills.stream()
.skip((page.getCurrent() - 1) * page.getSize()).limit(page.getSize())
List<ReceivablePayableCargoFee> allFees = waybills.stream()
.flatMap(waybill -> calculatedFees(waybill, contract, request.getBillingPlanId()).stream()).toList();
List<ReceivablePayableCargoFee> fees = allFees.stream()
.skip((page.getCurrent() - 1) * page.getSize()).limit(page.getSize()).toList();
ReceivablePayableFeeDetailVO vo = buildFeeDetail(fees);
vo.setTotal((long) waybills.size());
vo.setTotal((long) allFees.size());
return vo;
}
@@ -447,6 +514,8 @@ public class ReceivablePayableDetailServiceImpl
if (Func.isEmpty(waybills)) {
throw new ServiceException("没有可生成费用的运单");
}
Long currentUserId = AuthUtil.getUserId();
Date generateTime = new Date();
ContractManage contract = contractManageService.getById(request.getContractId());
for (Waybill waybill : waybills) {
if (existsByWaybill(waybill.getId(), settlementType(request.getSettlementType()))) {
@@ -456,6 +525,7 @@ public class ReceivablePayableDetailServiceImpl
save(detail);
calculatedFees(waybill, contract, request.getBillingPlanId()).forEach(fee -> {
fee.setDetailId(detail.getId());
fillGeneratedAuditFields(fee, currentUserId, generateTime);
cargoFeeMapper.insert(fee);
});
}
@@ -466,6 +536,8 @@ public class ReceivablePayableDetailServiceImpl
rollbackFor = Exception.class)
public void generateForCompletedWaybills(List<Long> waybillIds) {
if (Func.isEmpty(waybillIds)) return;
Long currentUserId = AuthUtil.getUserId();
Date generateTime = new Date();
for (Long waybillId : waybillIds) {
Waybill waybill = waybillService.getById(waybillId);
if (waybill == null) continue;
@@ -474,12 +546,12 @@ public class ReceivablePayableDetailServiceImpl
try {
if (receivableContract != null && "客户合同".equals(receivableContract.getContractCategory())
&& isSystemGeneration(receivableContract)) {
generateCompletedWaybillDetail(waybill, receivableContract, "receivable");
generateCompletedWaybillDetail(waybill, receivableContract, "receivable", currentUserId, generateTime);
}
if (Func.isNotEmpty(waybill.getCarrierName())) {
ContractManage payableContract = findCarrierContract(waybill);
if (payableContract != null && isSystemGeneration(payableContract)) {
generateCompletedWaybillDetail(waybill, payableContract, "payable");
generateCompletedWaybillDetail(waybill, payableContract, "payable", currentUserId, generateTime);
}
}
} catch (Exception exception) {
@@ -493,7 +565,8 @@ public class ReceivablePayableDetailServiceImpl
}
}
private void generateCompletedWaybillDetail(Waybill waybill, ContractManage contract, String settlementType) {
private void generateCompletedWaybillDetail(Waybill waybill, ContractManage contract, String settlementType,
Long currentUserId, Date generateTime) {
if (existsByWaybill(waybill.getId(), settlementType)) return;
String planId = matchedPlanId(waybill, contract);
if (Func.isEmpty(planId)) return;
@@ -507,6 +580,7 @@ public class ReceivablePayableDetailServiceImpl
ReceivablePayableCargoFee copy = BeanUtil.copyProperties(fee, ReceivablePayableCargoFee.class);
copy.setId(null);
copy.setDetailId(detail.getId());
fillGeneratedAuditFields(copy, currentUserId, generateTime);
cargoFeeMapper.insert(copy);
}
}
@@ -542,6 +616,8 @@ public class ReceivablePayableDetailServiceImpl
}
ContractManage contract = contractManageService.getById(masterOrder.getContractId());
if (contract == null || !isSystemGeneration(contract)) return;
Long currentUserId = AuthUtil.getUserId();
Date generateTime = new Date();
try {
List<Waybill> masterGoods = masterOrderGoods(masterOrder);
if (masterGoods.isEmpty()) return;
@@ -564,6 +640,7 @@ public class ReceivablePayableDetailServiceImpl
copy.setId(null);
copy.setDetailId(detail.getId());
copy.setWaybillId(null);
fillGeneratedAuditFields(copy, currentUserId, generateTime);
cargoFeeMapper.insert(copy);
}
}
@@ -728,8 +805,8 @@ public class ReceivablePayableDetailServiceImpl
}
wrapper.notInSql(Waybill::getId,
"select waybill_id from blade_receivable_payable_detail"
+ " where is_deleted = 0 and waybill_id is not null and settlement_type = '"
+ targetSettlementType + "'");
+ " where is_deleted = 0 and waybill_id is not null"
+ " and settlement_type in ('receivable', 'payable')");
if (Func.isNotEmpty(request.getBatchNo())) {
wrapper.like(Waybill::getBatchNo, request.getBatchNo());
}
@@ -923,34 +1000,87 @@ public class ReceivablePayableDetailServiceImpl
private List<ReceivablePayableCargoFee> calculatedFees(Waybill waybill, ContractManage contract, String planId, boolean matchOnly) {
List<Map<String, Object>> plans = parseList(contract == null ? null : contract.getBillingPlanJson());
Map<String, Object> plan = resolveBillingPlan(plans, planId);
if (plan == null || !(plan.get("rules") instanceof List<?>)) return matchOnly ? List.of() : List.of(buildCargoFee(null, waybill));
return calculatedFees(waybill, plan, matchOnly);
}
private List<ReceivablePayableCargoFee> calculatedFees(Waybill waybill, Map<String, Object> plan,
boolean matchOnly) {
if (plan == null || !(plan.get("rules") instanceof List<?>)) {
return matchOnly ? List.of() : buildCargoFees(waybill);
}
List<ReceivablePayableCargoFee> result = new ArrayList<>();
int line = 1;
for (Object value : (List<?>) plan.get("rules")) {
if (!(value instanceof Map<?, ?> raw)) continue;
Map<String, Object> rule = new LinkedHashMap<>();
raw.forEach((key, item) -> rule.put(String.valueOf(key), item));
if (matchOnly && !matchesRule(raw, waybill)) continue;
BigDecimal amount = calculateRule(rule, waybill);
for (Waybill feeWaybill : feeWaybills(rule, waybill)) {
if (matchOnly && !matchesRule(raw, feeWaybill)) continue;
BigDecimal amount = calculateRule(rule, feeWaybill);
if (amount == null) continue;
String feeItem = stringValue(rule, "feeItem", "费用");
Map<String, String> feeGoods = summarizeFeeGoods(rule, waybill);
Map<String, String> feeGoods = summarizeFeeGoods(rule, feeWaybill);
ReceivablePayableCargoFee fee = new ReceivablePayableCargoFee();
fee.setWaybillId(waybill.getId()); fee.setLineNo(String.format("%04d", line++));
fee.setCargoName(feeGoods.getOrDefault("cargoName", waybill.getCargoName()));
fee.setCargoType(feeGoods.getOrDefault("cargoType", waybill.getCargoType()));
fee.setSpecification(feeGoods.getOrDefault("specification", waybill.getSpecification()));
fee.setModel(feeGoods.getOrDefault("model", waybill.getModel()));
fee.setCargoName(feeGoods.getOrDefault("cargoName", feeWaybill.getCargoName()));
fee.setCargoType(feeGoods.getOrDefault("cargoType", feeWaybill.getCargoType()));
fee.setSpecification(feeGoods.getOrDefault("specification", feeWaybill.getSpecification()));
fee.setModel(feeGoods.getOrDefault("model", feeWaybill.getModel()));
fee.setBillingFactor(stringValue(rule, "billingElement", "")); fee.setBillingType(stringValue(rule, "billingType", ""));
fee.setTransportQuantity(measure(rule, waybill));
fee.setQuantityUnit(feeGoods.getOrDefault("quantityUnit", waybill.getQuantityUnit()));
fee.setPriceUnit(stringValue(rule, "billingUnit", waybill.getPriceUnit())); fee.setUnitPrice(decimal(rule.get("unitPrice")));
fee.setMileage(normalizeGeneratedMileage(waybill.getMileage())); fee.setFreightAmount(isFreightRule(rule) ? amount : BigDecimal.ZERO);
fee.setTransportQuantity(measure(rule, feeWaybill));
fee.setQuantityUnit(feeGoods.getOrDefault("quantityUnit", feeWaybill.getQuantityUnit()));
fee.setPriceUnit(stringValue(rule, "billingUnit", feeWaybill.getPriceUnit())); fee.setUnitPrice(decimal(rule.get("unitPrice")));
fee.setMileage(normalizeGeneratedMileage(feeWaybill.getMileage())); fee.setFreightAmount(isFreightRule(rule) ? amount : BigDecimal.ZERO);
fee.setFeeItemsJson(JsonUtil.toJson(Map.of(feeItem, amount)));
fee.setOriginalAmount(amount); fee.setAdjustAmount(BigDecimal.ZERO); fee.setAfterAmount(amount); fee.setRemark(stringValue(rule, "remark", waybill.getRemark()));
fee.setOriginalAmount(amount); fee.setAdjustAmount(BigDecimal.ZERO); fee.setAfterAmount(amount); fee.setRemark(stringValue(rule, "remark", feeWaybill.getRemark()));
result.add(fee);
}
return result.isEmpty() && !matchOnly ? List.of(buildCargoFee(null, waybill)) : result;
}
return result.isEmpty() && !matchOnly ? buildCargoFees(waybill) : result;
}
private List<Waybill> feeWaybills(Map<String, Object> rule, Waybill waybill) {
String element = stringValue(rule, "billingElement", "按重量");
if (!List.of("按重量", "按体积", "按吨·公里", "按数量").contains(element)) {
return List.of(waybill);
}
List<Waybill> goodsWaybills = goodsWaybills(waybill);
return goodsWaybills.isEmpty() ? List.of(waybill) : goodsWaybills;
}
private List<Waybill> goodsWaybills(Waybill waybill) {
return parseList(waybill.getGoodsJson()).stream().map(goods -> {
Waybill goodsWaybill = Objects.requireNonNull(BeanUtil.copyProperties(waybill, Waybill.class));
goodsWaybill.setCargoName(stringValue(goods, "cargoName", waybill.getCargoName()));
goodsWaybill.setCargoType(stringValue(goods, "cargoType", waybill.getCargoType()));
goodsWaybill.setSpecification(stringValue(goods, "specification", waybill.getSpecification()));
goodsWaybill.setModel(stringValue(goods, "model", waybill.getModel()));
goodsWaybill.setQuantity(decimal(goods.get("quantity")));
goodsWaybill.setQuantityUnit(stringValue(goods, "quantityUnit", waybill.getQuantityUnit()));
goodsWaybill.setUnitPrice(goods.get("unitPrice") == null
? waybill.getUnitPrice() : decimal(goods.get("unitPrice")));
goodsWaybill.setPriceUnit(stringValue(goods, "priceUnit", waybill.getPriceUnit()));
goodsWaybill.setGoodsJson(JsonUtil.toJson(List.of(goods)));
return goodsWaybill;
}).toList();
}
private List<ReceivablePayableCargoFee> buildCargoFees(Waybill waybill) {
List<Waybill> goodsWaybills = goodsWaybills(waybill);
if (goodsWaybills.isEmpty()) return List.of(buildCargoFee(null, waybill));
List<ReceivablePayableCargoFee> fees = new ArrayList<>();
for (int index = 0; index < goodsWaybills.size(); index++) {
Waybill goodsWaybill = goodsWaybills.get(index);
if (index > 0) {
goodsWaybill.setFreightJson(null);
goodsWaybill.setOtherFeeTotal(BigDecimal.ZERO);
}
ReceivablePayableCargoFee fee = buildCargoFee(null, goodsWaybill);
fee.setWaybillId(waybill.getId());
fee.setLineNo(String.format("%04d", index + 1));
fees.add(fee);
}
return fees;
}
private Map<String, Object> resolveBillingPlan(List<Map<String, Object>> plans, String planId) {
@@ -1198,6 +1328,104 @@ public class ReceivablePayableDetailServiceImpl
}
}
private Map<String, BigDecimal> normalizeFeeItems(Map<String, BigDecimal> feeItems) {
Map<String, BigDecimal> result = new LinkedHashMap<>();
if (feeItems == null) return result;
feeItems.forEach((name, amount) -> {
validateNonNegative(amount, name);
result.put(name, money(amount));
});
return result;
}
private AdjustedFeeCalculation calculateAdjustedFee(ReceivablePayableDetail detail,
ReceivablePayableCargoFee fee, BigDecimal transportQuantity,
BigDecimal mileage, BigDecimal freightAmount,
Map<String, BigDecimal> feeItems) {
if ("手工调整".equals(fee.getBillingFactor())) {
throw new ServiceException("手工费用不支持按合同计费规则试算");
}
ContractManage contract = contractManageService.getById(detail.getContractId());
if (contract == null) {
throw new ServiceException("关联合同不存在");
}
Waybill adjustedWaybill = adjustedWaybill(detail, fee, transportQuantity, mileage);
List<Map<String, Object>> rules = matchingAdjustedRules(contract, fee, adjustedWaybill);
if (rules.isEmpty()) {
throw new ServiceException("未找到费用明细对应的合同计费规则,请先更新费用");
}
Map<BigDecimal, Map<String, Object>> results = new LinkedHashMap<>();
for (Map<String, Object> rule : rules) {
BigDecimal amount = calculateRule(rule, adjustedWaybill);
if (amount != null) results.putIfAbsent(money(amount), rule);
}
if (results.size() != 1) {
throw new ServiceException("费用明细对应多个计费结果,无法唯一试算,请先更新费用");
}
Map.Entry<BigDecimal, Map<String, Object>> calculated = results.entrySet().iterator().next();
BigDecimal amount = calculated.getKey();
Map<String, Object> rule = calculated.getValue();
String feeItem = stringValue(rule, "feeItem", "费用");
Map<String, BigDecimal> calculatedFeeItems = new LinkedHashMap<>(feeItems);
calculatedFeeItems.put(feeItem, amount);
BigDecimal calculatedFreight = isFreightRule(rule) ? amount : freightAmount;
return new AdjustedFeeCalculation(calculatedFreight, calculatedFeeItems);
}
private Waybill adjustedWaybill(ReceivablePayableDetail detail, ReceivablePayableCargoFee fee,
BigDecimal transportQuantity, BigDecimal mileage) {
Waybill source = detail.getWaybillId() == null ? null : waybillService.getById(detail.getWaybillId());
Waybill waybill = source == null ? new Waybill()
: Objects.requireNonNull(BeanUtil.copyProperties(source, Waybill.class));
waybill.setQuantity(transportQuantity);
waybill.setMileage(mileage);
waybill.setCargoName(fee.getCargoName());
waybill.setCargoType(fee.getCargoType());
waybill.setSpecification(fee.getSpecification());
waybill.setModel(fee.getModel());
waybill.setQuantityUnit(fee.getQuantityUnit());
waybill.setTransportType(detail.getTransportType());
Map<String, Object> goods = new LinkedHashMap<>();
goods.put("cargoName", fee.getCargoName());
goods.put("cargoType", fee.getCargoType());
goods.put("specification", fee.getSpecification());
goods.put("model", fee.getModel());
goods.put("quantity", transportQuantity);
goods.put("quantityUnit", fee.getQuantityUnit());
if ("按体积".equals(fee.getBillingFactor())) {
goods.put("volume", transportQuantity);
}
waybill.setGoodsJson(JsonUtil.toJson(List.of(goods)));
return waybill;
}
private List<Map<String, Object>> matchingAdjustedRules(ContractManage contract,
ReceivablePayableCargoFee fee, Waybill waybill) {
Set<String> feeItemNames = parseMap(fee.getFeeItemsJson()).keySet();
List<Map<String, Object>> candidates = new ArrayList<>();
for (Map<String, Object> plan : parseList(contract.getBillingPlanJson())) {
if (!(plan.get("rules") instanceof List<?> rules)) continue;
for (Object value : rules) {
if (!(value instanceof Map<?, ?> raw)) continue;
Map<String, Object> rule = new LinkedHashMap<>();
raw.forEach((key, item) -> rule.put(String.valueOf(key), item));
if (!Objects.equals(stringValue(rule, "billingElement"), fee.getBillingFactor())
|| !Objects.equals(stringValue(rule, "billingType"), fee.getBillingType())
|| !feeItemNames.contains(stringValue(rule, "feeItem"))) continue;
if (Func.isNotEmpty(fee.getPriceUnit())
&& !Objects.equals(stringValue(rule, "billingUnit"), fee.getPriceUnit())) continue;
candidates.add(rule);
}
}
List<Map<String, Object>> unitPriceMatched = candidates.stream()
.filter(rule -> decimal(rule.get("unitPrice")).compareTo(money(fee.getUnitPrice())) == 0)
.toList();
if (!unitPriceMatched.isEmpty()) candidates = unitPriceMatched;
List<Map<String, Object>> conditionMatched = candidates.stream()
.filter(rule -> matchesRule(rule, waybill)).toList();
return conditionMatched.isEmpty() ? candidates : conditionMatched;
}
private BigDecimal adjustedAfterAmount(BigDecimal freightAmount, Map<String, BigDecimal> feeItems) {
BigDecimal feeItemTotal = feeItems.values().stream().map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
boolean containsFreight = feeItems.keySet().stream().anyMatch(this::isFreightFeeItem);
@@ -1208,6 +1436,9 @@ public class ReceivablePayableDetailServiceImpl
return name != null && (name.contains("运费") || name.contains("运输费"));
}
private record AdjustedFeeCalculation(BigDecimal freightAmount, Map<String, BigDecimal> feeItems) {
}
private void appendChange(List<String> changes, String field, BigDecimal before, BigDecimal after) {
BigDecimal oldValue = money(before);
BigDecimal newValue = money(after);
@@ -1331,6 +1562,37 @@ public class ReceivablePayableDetailServiceImpl
changeRecordMapper.insert(record);
}
private void fillGeneratedAuditFields(ReceivablePayableCargoFee fee, Long currentUserId,
Date generateTime) {
fee.setUpdateUser(currentUserId);
fee.setUpdateTime(generateTime);
}
private void fillCargoNames(List<ReceivablePayableDetailVO> details) {
if (Func.isEmpty(details)) return;
List<Long> detailIds = details.stream().map(ReceivablePayableDetailVO::getId)
.filter(Objects::nonNull).distinct().toList();
if (detailIds.isEmpty()) return;
List<ReceivablePayableCargoFee> fees = cargoFeeMapper.selectList(
Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
.select(ReceivablePayableCargoFee::getDetailId, ReceivablePayableCargoFee::getCargoName)
.in(ReceivablePayableCargoFee::getDetailId, detailIds)
.eq(ReceivablePayableCargoFee::getIsDeleted, 0)
.orderByAsc(ReceivablePayableCargoFee::getDetailId)
.orderByAsc(ReceivablePayableCargoFee::getLineNo));
Map<Long, LinkedHashSet<String>> cargoNames = new LinkedHashMap<>();
for (ReceivablePayableCargoFee fee : fees) {
if (Func.isEmpty(fee.getCargoName())) continue;
cargoNames.computeIfAbsent(fee.getDetailId(), key -> new LinkedHashSet<>()).add(fee.getCargoName());
}
details.forEach(detail -> {
Set<String> names = cargoNames.get(detail.getId());
if (Func.isNotEmpty(names)) {
detail.setCargoName(String.join(",", names));
}
});
}
private void applyManualAdjustment(ReceivablePayableDetail detail, BigDecimal amount, String feeItem, String reason) {
ReceivablePayableCargoFee fee = new ReceivablePayableCargoFee();
fee.setDetailId(detail.getId()); fee.setWaybillId(detail.getWaybillId()); fee.setLineNo("ADJ-" + System.currentTimeMillis());
@@ -81,6 +81,7 @@ CREATE TABLE IF NOT EXISTS `blade_receivable_payable_cargo_fee` (
`adjust_amount` decimal(18,2) DEFAULT NULL COMMENT '调整金额',
`after_amount` decimal(18,2) DEFAULT NULL COMMENT '调整后总金额',
`remark` varchar(200) DEFAULT NULL COMMENT '备注',
`change_reason` varchar(300) DEFAULT NULL COMMENT '变更原因',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_receivable_payable_cargo_detail` (`detail_id`) USING BTREE,
KEY `idx_receivable_payable_cargo_waybill` (`waybill_id`) USING BTREE