✨ 认证退出与操作日志、密码规则,以及运输业务多项修正
This commit is contained in:
+11
-12
@@ -39,6 +39,7 @@ import org.springblade.common.constant.DataStatusEnum;
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.springblade.common.constant.ParamConstant;
|
||||
import org.springblade.common.constant.TenantConstant;
|
||||
import org.springblade.common.utils.PasswordRuleUtil;
|
||||
import org.springblade.core.cache.utils.CacheUtil;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||
@@ -76,7 +77,6 @@ import org.springblade.system.wrapper.UserWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
@@ -106,9 +106,6 @@ import static org.springblade.core.tenant.TenantGuard.EntityType.USER;
|
||||
@Slf4j
|
||||
public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implements IUserService {
|
||||
private static final String GUEST_NAME = "guest";
|
||||
private static final String PASSWORD_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
|
||||
private static final int RANDOM_PASSWORD_LENGTH = 8;
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
private static final String IAM_DEFAULT_DICT_CODE = "iam_default";
|
||||
private static final String IAM_DEFAULT_ROLE_NAME = "默认角色";
|
||||
private static final String IAM_DEFAULT_DEPT_NAME = "默认部门";
|
||||
@@ -588,7 +585,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
List<User> userList = TenantGuard.verifyBatch(this, idList, USER);
|
||||
Map<String, String> passwordMap = new LinkedHashMap<>();
|
||||
for (User user : userList) {
|
||||
String password = randomPassword();
|
||||
String password = PasswordRuleUtil.generate();
|
||||
User updateUser = new User();
|
||||
updateUser.setPassword(DigestUtil.encrypt(password));
|
||||
updateUser.setUpdateTime(DateUtil.now());
|
||||
@@ -604,6 +601,7 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
if (!Objects.equals(password, password2)) {
|
||||
throw new ServiceException("两次输入密码不一致!");
|
||||
}
|
||||
validatePlainPassword(password);
|
||||
User user = TenantGuard.verify(this, userId, USER);
|
||||
User updateUser = new User();
|
||||
updateUser.setPassword(DigestUtil.encrypt(password));
|
||||
@@ -672,7 +670,8 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
if (user.getId() != null) {
|
||||
this.updateUser(user);
|
||||
} else {
|
||||
user.setPassword(ParamCache.getValue(DEFAULT_PARAM_PASSWORD));
|
||||
// 空密码交由 saveUser 按规则填充(初始密码不合规时自动生成)
|
||||
user.setPassword(null);
|
||||
this.submit(user);
|
||||
}
|
||||
});
|
||||
@@ -1064,8 +1063,10 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
}
|
||||
}
|
||||
if (Func.isEmpty(user.getPassword())) {
|
||||
user.setPassword(ParamCache.getValue(DEFAULT_PARAM_PASSWORD));
|
||||
String initPassword = ParamCache.getValue(DEFAULT_PARAM_PASSWORD);
|
||||
user.setPassword(PasswordRuleUtil.isValid(initPassword) ? initPassword : PasswordRuleUtil.generate());
|
||||
}
|
||||
validatePlainPassword(user.getPassword());
|
||||
user.setPassword(DigestUtil.encrypt(user.getPassword()));
|
||||
Long userCount = baseMapper.selectCount(Wrappers.<User>query().lambda().eq(User::getTenantId, tenantId).eq(User::getAccount, user.getAccount()));
|
||||
if (userCount > 0L && Func.isEmpty(user.getId())) {
|
||||
@@ -1147,12 +1148,10 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
// }
|
||||
}
|
||||
|
||||
private String randomPassword() {
|
||||
StringBuilder password = new StringBuilder(RANDOM_PASSWORD_LENGTH);
|
||||
for (int index = 0; index < RANDOM_PASSWORD_LENGTH; index++) {
|
||||
password.append(PASSWORD_CHARS.charAt(SECURE_RANDOM.nextInt(PASSWORD_CHARS.length())));
|
||||
private void validatePlainPassword(String password) {
|
||||
if (!PasswordRuleUtil.isValid(password)) {
|
||||
throw new ServiceException(PasswordRuleUtil.RULE_MESSAGE);
|
||||
}
|
||||
return password.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
@@ -37,6 +37,7 @@ import org.springblade.transport.pojo.vo.ReceivablePayableCargoFeeVO;
|
||||
import org.springblade.transport.pojo.vo.ReceivablePayableDetailVO;
|
||||
import org.springblade.transport.pojo.vo.ReceivablePayableFeeDetailVO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
@@ -82,6 +83,12 @@ public interface IReceivablePayableDetailService extends BaseService<ReceivableP
|
||||
/** 完成运单后按合同系统计费模式自动生成应收、应付明细。 */
|
||||
void generateForCompletedWaybills(List<Long> waybillIds);
|
||||
|
||||
/**
|
||||
* 运单维护里程后,同步未挂结算单的应收应付明细里程;
|
||||
* 对按里程、按吨·公里计费的费用行按新里程重新计算。
|
||||
*/
|
||||
void syncMileageFromWaybill(Long waybillId, BigDecimal mileage);
|
||||
|
||||
/**
|
||||
* 批量导入完成运单后按合同系统计费模式生成应收、应付明细。
|
||||
* <p>与导入事务共用同一事务,运单尚未提交,因此直接传入实体而非主键。</p>
|
||||
|
||||
+4
@@ -657,6 +657,10 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
if (contractManage.getInvoiceCycle() != null && contractManage.getInvoiceCycle() <= 0) {
|
||||
contractManage.setInvoiceCycle(null);
|
||||
}
|
||||
BigDecimal contractAmount = contractManage.getContractAmount();
|
||||
if (contractAmount != null && contractAmount.compareTo(BigDecimal.valueOf(-1)) == 0) {
|
||||
contractManage.setContractAmount(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDraft(ContractManage contractManage) {
|
||||
|
||||
+17
@@ -621,6 +621,23 @@ public class CustomerArchiveServiceImpl extends BaseServiceImpl<CustomerArchiveM
|
||||
if (Func.isEmpty(accountVO.getAccountName()) && Func.isEmpty(accountVO.getBankAccount())) {
|
||||
continue;
|
||||
}
|
||||
if (Func.isEmpty(accountVO.getAccountName())) {
|
||||
throw new ServiceException("收款单位名称不能为空");
|
||||
}
|
||||
if (Func.isEmpty(accountVO.getAccountHolderName())) {
|
||||
throw new ServiceException("开户人姓名不能为空");
|
||||
}
|
||||
if (Func.isEmpty(accountVO.getBankName())) {
|
||||
throw new ServiceException("开户行不能为空");
|
||||
}
|
||||
if (Func.isEmpty(accountVO.getCnapsCode())) {
|
||||
throw new ServiceException("联行号不能为空");
|
||||
}
|
||||
if (Func.isEmpty(accountVO.getBankAccount())) {
|
||||
throw new ServiceException("收款账号不能为空");
|
||||
}
|
||||
validateLength(accountVO.getCnapsCode(), 32, "联行号最多32个字符");
|
||||
validateLength(accountVO.getRemark(), 200, "收款信息备注最多200个字符");
|
||||
CustomerReceiptAccount account = Objects.requireNonNull(BeanUtil.copyProperties(accountVO, CustomerReceiptAccount.class));
|
||||
account.setId(IdWorker.getId());
|
||||
account.setCustomerId(customerId);
|
||||
|
||||
+1
-1
@@ -598,7 +598,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
if (Func.isEmpty(request.getRows())) {
|
||||
throw new ServiceException("请填写需要调整的费用行");
|
||||
}
|
||||
String changeReason = requiredText(limitRemark(request.getChangeReason(), 200), "调整原因");
|
||||
String changeReason = limitRemark(request.getChangeReason(), 200);
|
||||
Map<Long, PreSettlementDetailFee> existingMap = detailFees(detail.getId()).stream()
|
||||
.collect(Collectors.toMap(PreSettlementDetailFee::getId, Function.identity()));
|
||||
if (request.getRows().size() != existingMap.size()) {
|
||||
|
||||
+280
-6
@@ -198,6 +198,209 @@ public class ReceivablePayableDetailServiceImpl
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void syncMileageFromWaybill(Long waybillId, BigDecimal mileage) {
|
||||
if (waybillId == null || mileage == null) {
|
||||
return;
|
||||
}
|
||||
Waybill waybill = waybillService.getById(waybillId);
|
||||
if (waybill == null || Objects.equals(waybill.getIsDeleted(), 1)) {
|
||||
return;
|
||||
}
|
||||
BigDecimal normalizedMileage = normalizeGeneratedMileage(mileage);
|
||||
Set<Long> detailIds = new LinkedHashSet<>();
|
||||
list(Wrappers.<ReceivablePayableDetail>lambdaQuery()
|
||||
.eq(ReceivablePayableDetail::getIsDeleted, 0)
|
||||
.eq(ReceivablePayableDetail::getWaybillId, waybillId)
|
||||
.eq(ReceivablePayableDetail::getSettlementStatus, "pending")
|
||||
.and(wrapper -> wrapper.isNull(ReceivablePayableDetail::getPreSettlementNo)
|
||||
.or().eq(ReceivablePayableDetail::getPreSettlementNo, ""))
|
||||
.and(wrapper -> wrapper.isNull(ReceivablePayableDetail::getFormalSettlementNo)
|
||||
.or().eq(ReceivablePayableDetail::getFormalSettlementNo, "")))
|
||||
.forEach(detail -> detailIds.add(detail.getId()));
|
||||
List<ReceivablePayableCargoFee> relatedFees = cargoFeeMapper.selectList(
|
||||
Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
|
||||
.eq(ReceivablePayableCargoFee::getIsDeleted, 0)
|
||||
.eq(ReceivablePayableCargoFee::getWaybillId, waybillId));
|
||||
relatedFees.stream()
|
||||
.map(ReceivablePayableCargoFee::getDetailId)
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(detailIds::add);
|
||||
if (detailIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Long detailId : detailIds) {
|
||||
ReceivablePayableDetail detail = getById(detailId);
|
||||
if (detail == null || Objects.equals(detail.getIsDeleted(), 1)
|
||||
|| !"pending".equals(detail.getSettlementStatus())
|
||||
|| Func.isNotEmpty(detail.getPreSettlementNo())
|
||||
|| Func.isNotEmpty(detail.getFormalSettlementNo())) {
|
||||
continue;
|
||||
}
|
||||
syncDetailMileage(detail, waybill, normalizedMileage);
|
||||
}
|
||||
}
|
||||
|
||||
private void syncDetailMileage(ReceivablePayableDetail detail, Waybill waybill, BigDecimal mileage) {
|
||||
List<ReceivablePayableCargoFee> rows = activeCargoFees(detail.getId());
|
||||
if (rows.isEmpty()) {
|
||||
if (Objects.equals(detail.getWaybillId(), waybill.getId())) {
|
||||
detail.setMileage(mileage);
|
||||
updateById(detail);
|
||||
}
|
||||
return;
|
||||
}
|
||||
List<String> changes = new ArrayList<>();
|
||||
boolean feeChanged = false;
|
||||
for (ReceivablePayableCargoFee fee : rows) {
|
||||
boolean related = Objects.equals(fee.getWaybillId(), waybill.getId())
|
||||
|| (fee.getWaybillId() == null && Objects.equals(detail.getWaybillId(), waybill.getId()));
|
||||
if (!related) {
|
||||
continue;
|
||||
}
|
||||
BigDecimal oldMileage = normalizeGeneratedMileage(fee.getMileage());
|
||||
fee.setMileage(mileage);
|
||||
if (isMileageBasedElement(fee.getBillingFactor()) && !isManualFee(fee)
|
||||
&& recalculateMileageBasedFee(detail, fee, waybill, oldMileage)) {
|
||||
changes.add("【运单维护里程】按" + fee.getBillingFactor() + "重新计算费用");
|
||||
}
|
||||
cargoFeeMapper.updateById(fee);
|
||||
feeChanged = true;
|
||||
appendChange(changes, "里程", oldMileage, mileage);
|
||||
}
|
||||
if (!feeChanged && !Objects.equals(detail.getWaybillId(), waybill.getId())) {
|
||||
return;
|
||||
}
|
||||
refreshAdjustedDetail(detail, activeCargoFees(detail.getId()));
|
||||
if (Objects.equals(detail.getWaybillId(), waybill.getId())) {
|
||||
detail.setMileage(mileage);
|
||||
updateById(detail);
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
saveChangeRecord(detail, String.join(";", changes), "运单维护里程同步", "0001");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按最新运单里程重算「按里程 / 按吨·公里」费用行。
|
||||
*/
|
||||
private boolean recalculateMileageBasedFee(ReceivablePayableDetail detail, ReceivablePayableCargoFee fee,
|
||||
Waybill sourceWaybill, BigDecimal previousMileage) {
|
||||
Waybill calcWaybill = waybillForMileageRecalc(sourceWaybill, fee, previousMileage);
|
||||
List<Map<String, Object>> rules = parseList(fee.getBillingRulesJson());
|
||||
if (rules.isEmpty()) {
|
||||
ContractManage contract = contractManageService.getById(detail.getContractId());
|
||||
if (contract != null) {
|
||||
rules = matchingAdjustedRules(contract, fee, calcWaybill);
|
||||
if (!rules.isEmpty()) {
|
||||
fee.setBillingRulesJson(JsonUtil.toJson(rules));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rules.isEmpty()) {
|
||||
log.warn("运单维护里程重算跳过:未找到计费规则,detailId={}, feeId={}", detail.getId(), fee.getId());
|
||||
return false;
|
||||
}
|
||||
Map<String, BigDecimal> calculatedAmounts = new LinkedHashMap<>();
|
||||
BigDecimal calculatedFreight = BigDecimal.ZERO;
|
||||
boolean freightRuleMatched = false;
|
||||
Map<String, Object> measureRule = null;
|
||||
for (Map<String, Object> rule : rules) {
|
||||
BigDecimal amount = calculateRule(rule, calcWaybill);
|
||||
if (amount == null) {
|
||||
continue;
|
||||
}
|
||||
String feeItem = stringValue(rule, "feeItem", "费用");
|
||||
calculatedAmounts.merge(feeItem, amount, BigDecimal::add);
|
||||
if (measureRule == null || isFreightRule(rule)) {
|
||||
measureRule = rule;
|
||||
}
|
||||
if (isFreightRule(rule)) {
|
||||
calculatedFreight = calculatedFreight.add(amount);
|
||||
freightRuleMatched = true;
|
||||
}
|
||||
}
|
||||
if (calculatedAmounts.isEmpty() || measureRule == null) {
|
||||
log.warn("运单维护里程重算跳过:计费规则无法试算,detailId={}, feeId={}", detail.getId(), fee.getId());
|
||||
return false;
|
||||
}
|
||||
Map<String, BigDecimal> feeItems = new LinkedHashMap<>();
|
||||
parseMap(fee.getFeeItemsJson()).forEach((name, value) -> feeItems.put(name, decimal(value)));
|
||||
calculatedAmounts.forEach(feeItems::put);
|
||||
fee.setTransportQuantity(resolveStoredTransportQuantity(measureRule, calcWaybill));
|
||||
fee.setUnitPrice(resolveCalculatedUnitPrice(measureRule, calcWaybill));
|
||||
fee.setBillingFactor(stringValue(measureRule, "billingElement", fee.getBillingFactor()));
|
||||
fee.setBillingType(stringValue(measureRule, "billingType", fee.getBillingType()));
|
||||
fee.setPriceUnit(stringValue(measureRule, "billingUnit", fee.getPriceUnit()));
|
||||
if (freightRuleMatched) {
|
||||
fee.setFreightAmount(calculatedFreight);
|
||||
}
|
||||
fee.setFeeItemsJson(JsonUtil.toJson(feeItems));
|
||||
BigDecimal afterAmount = adjustedAfterAmount(money(fee.getFreightAmount()), feeItems);
|
||||
fee.setAdjustAmount(afterAmount.subtract(money(fee.getOriginalAmount())));
|
||||
fee.setAfterAmount(afterAmount);
|
||||
return true;
|
||||
}
|
||||
|
||||
private Waybill waybillForMileageRecalc(Waybill sourceWaybill, ReceivablePayableCargoFee fee,
|
||||
BigDecimal previousMileage) {
|
||||
Waybill calcWaybill = Objects.requireNonNull(BeanUtil.copyProperties(sourceWaybill, Waybill.class));
|
||||
calcWaybill.setMileage(sourceWaybill.getMileage());
|
||||
String element = fee.getBillingFactor();
|
||||
if (!"按吨·公里".equals(element) && !"按重量".equals(element)
|
||||
&& !"按数量".equals(element) && !"按体积".equals(element)) {
|
||||
return calcWaybill;
|
||||
}
|
||||
List<Map<String, Object>> matchedGoods = parseList(sourceWaybill.getGoodsJson()).stream()
|
||||
.filter(goods -> matchesFeeCargo(goods, fee))
|
||||
.toList();
|
||||
if (!matchedGoods.isEmpty()) {
|
||||
Map<String, Object> goods = matchedGoods.get(0);
|
||||
calcWaybill.setGoodsJson(JsonUtil.toJson(List.of(goods)));
|
||||
calcWaybill.setCargoName(stringValue(goods, "cargoName", fee.getCargoName()));
|
||||
calcWaybill.setCargoType(stringValue(goods, "cargoType", fee.getCargoType()));
|
||||
calcWaybill.setSpecification(stringValue(goods, "specification", fee.getSpecification()));
|
||||
calcWaybill.setModel(stringValue(goods, "model", fee.getModel()));
|
||||
calcWaybill.setQuantity(decimal(goods.get("quantity")));
|
||||
calcWaybill.setQuantityUnit(stringValue(goods, "quantityUnit", fee.getQuantityUnit()));
|
||||
return calcWaybill;
|
||||
}
|
||||
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("quantityUnit", fee.getQuantityUnit());
|
||||
BigDecimal quantity = money(fee.getTransportQuantity());
|
||||
if ("按吨·公里".equals(element)) {
|
||||
BigDecimal baseMileage = money(previousMileage);
|
||||
if (baseMileage.signum() > 0) {
|
||||
quantity = quantity.divide(baseMileage, 8, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
||||
goods.put("quantity", quantity);
|
||||
if ("按体积".equals(element)) {
|
||||
goods.put("volume", quantity);
|
||||
}
|
||||
calcWaybill.setGoodsJson(JsonUtil.toJson(List.of(goods)));
|
||||
calcWaybill.setCargoName(fee.getCargoName());
|
||||
calcWaybill.setCargoType(fee.getCargoType());
|
||||
calcWaybill.setSpecification(fee.getSpecification());
|
||||
calcWaybill.setModel(fee.getModel());
|
||||
calcWaybill.setQuantity(quantity);
|
||||
calcWaybill.setQuantityUnit(fee.getQuantityUnit());
|
||||
return calcWaybill;
|
||||
}
|
||||
|
||||
private boolean matchesFeeCargo(Map<String, Object> goods, ReceivablePayableCargoFee fee) {
|
||||
return Objects.equals(stringValue(goods, "cargoName", ""), Objects.toString(fee.getCargoName(), ""))
|
||||
&& Objects.equals(stringValue(goods, "cargoType", ""), Objects.toString(fee.getCargoType(), ""))
|
||||
&& Objects.equals(stringValue(goods, "specification", ""), Objects.toString(fee.getSpecification(), ""))
|
||||
&& Objects.equals(stringValue(goods, "model", ""), Objects.toString(fee.getModel(), ""))
|
||||
&& Objects.equals(stringValue(goods, "quantityUnit", ""), Objects.toString(fee.getQuantityUnit(), ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReceivablePayableFeeDetailVO feeDetail(Long id) {
|
||||
ReceivablePayableDetail detail = getExisting(id);
|
||||
@@ -890,11 +1093,68 @@ public class ReceivablePayableDetailServiceImpl
|
||||
Map<String, Object> plan = resolveDefaultBillingPlan(plans, waybill.getTransportType());
|
||||
if (plan == null) return null;
|
||||
if (!(plan.get("rules") instanceof List<?> rules)) return null;
|
||||
boolean matched = rules.stream().anyMatch(value -> value instanceof Map<?, ?> raw && matchesRule(raw, waybill));
|
||||
boolean matched = rules.stream().anyMatch(value -> {
|
||||
if (!(value instanceof Map<?, ?> raw)) return false;
|
||||
Map<String, Object> rule = toRuleMap(raw);
|
||||
return feeWaybills(rule, waybill).stream()
|
||||
.anyMatch(feeWaybill -> matchesBillingRule(raw, rule, feeWaybill));
|
||||
});
|
||||
if (!matched) return null;
|
||||
return "__matched__";
|
||||
}
|
||||
|
||||
private Map<String, Object> toRuleMap(Map<?, ?> raw) {
|
||||
Map<String, Object> rule = new LinkedHashMap<>();
|
||||
raw.forEach((key, item) -> rule.put(String.valueOf(key), item));
|
||||
return rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用生成规则命中:
|
||||
* 1. 货物需满足匹配规则(始发/目的/货类/货名)
|
||||
* 2. 普通计费要素还要求货物数量单位与计费单位一致
|
||||
* 3. 按里程、按吨·公里按运单里程(×重量)另行计算,不校验数量单位
|
||||
*/
|
||||
private boolean matchesBillingRule(Map<?, ?> raw, Map<String, Object> rule, Waybill feeWaybill) {
|
||||
if (!matchesRule(raw, feeWaybill)) {
|
||||
return false;
|
||||
}
|
||||
String element = stringValue(rule, "billingElement", "");
|
||||
if (isMileageBasedElement(element)) {
|
||||
return true;
|
||||
}
|
||||
return matchesBillingUnit(rule, feeWaybill);
|
||||
}
|
||||
|
||||
private boolean isMileageBasedElement(String element) {
|
||||
return "按里程".equals(element) || "按吨·公里".equals(element);
|
||||
}
|
||||
|
||||
private boolean matchesBillingUnit(Map<String, Object> rule, Waybill feeWaybill) {
|
||||
String billingUnit = stringValue(rule, "billingUnit", "").trim();
|
||||
if (billingUnit.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
String quantityUnit = resolveQuantityUnit(feeWaybill);
|
||||
if (quantityUnit.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return billingUnit.equals(quantityUnit);
|
||||
}
|
||||
|
||||
private String resolveQuantityUnit(Waybill feeWaybill) {
|
||||
String quantityUnit = feeWaybill.getQuantityUnit() == null ? "" : feeWaybill.getQuantityUnit().trim();
|
||||
if (!quantityUnit.isEmpty()) {
|
||||
return quantityUnit;
|
||||
}
|
||||
List<Map<String, Object>> goods = parseList(feeWaybill.getGoodsJson());
|
||||
return goods.stream()
|
||||
.map(item -> stringValue(item, "quantityUnit", "").trim())
|
||||
.filter(Func::isNotEmpty)
|
||||
.findFirst()
|
||||
.orElse("");
|
||||
}
|
||||
|
||||
private boolean matchesRule(Map<?, ?> raw, Waybill waybill) {
|
||||
Object conditionValue = raw.get("matchCondition");
|
||||
if (!(conditionValue instanceof Map<?, ?> condition) || !hasConfiguredMatchCondition(condition)) return true;
|
||||
@@ -1387,10 +1647,10 @@ public class ReceivablePayableDetailServiceImpl
|
||||
Set<List<String>> freightBillingCargoKeys = new LinkedHashSet<>();
|
||||
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));
|
||||
Map<String, Object> rule = toRuleMap(raw);
|
||||
for (Waybill feeWaybill : feeWaybills(rule, waybill)) {
|
||||
if (matchOnly && !matchesRule(raw, feeWaybill)) continue;
|
||||
// 统一生成:匹配规则内货物 + 数量单位一致;按里程/按吨·公里跳过单位匹配
|
||||
if (!matchesBillingRule(raw, rule, feeWaybill)) continue;
|
||||
BigDecimal amount = calculateRule(rule, feeWaybill);
|
||||
if (amount == null) continue;
|
||||
String feeItem = stringValue(rule, "feeItem", "费用");
|
||||
@@ -1446,13 +1706,25 @@ public class ReceivablePayableDetailServiceImpl
|
||||
|
||||
private void fillCalculatedBillingFields(ReceivablePayableCargoFee fee, Waybill feeWaybill,
|
||||
Map<String, Object> rule) {
|
||||
fee.setBillingFactor(stringValue(rule, "billingElement", ""));
|
||||
String element = stringValue(rule, "billingElement", "");
|
||||
fee.setBillingFactor(element);
|
||||
fee.setBillingType(stringValue(rule, "billingType", ""));
|
||||
fee.setTransportQuantity(measure(rule, feeWaybill));
|
||||
fee.setTransportQuantity(resolveStoredTransportQuantity(rule, feeWaybill));
|
||||
fee.setPriceUnit(stringValue(rule, "billingUnit", feeWaybill.getPriceUnit()));
|
||||
fee.setUnitPrice(resolveCalculatedUnitPrice(rule, feeWaybill));
|
||||
}
|
||||
|
||||
/**
|
||||
* 费用行运输量展示值:按车辆/固定金额的计费量仅试算用(为 1),不回写到运输量字段。
|
||||
*/
|
||||
private BigDecimal resolveStoredTransportQuantity(Map<String, Object> rule, Waybill waybill) {
|
||||
String element = stringValue(rule, "billingElement", "");
|
||||
if ("按车辆".equals(element) || "固定金额(整单一口价)".equals(element)) {
|
||||
return null;
|
||||
}
|
||||
return measure(rule, waybill);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析费用行展示用的实际命中单价。
|
||||
* 区间计费的规则默认单价仅用于兜底,费用行应展示当前计费量命中的区间单价。
|
||||
@@ -1635,7 +1907,9 @@ public class ReceivablePayableDetailServiceImpl
|
||||
return switch (element) {
|
||||
case "按体积" -> goods.stream().map(this::goodsVolume).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
case "按车辆", "固定金额(整单一口价)" -> BigDecimal.ONE;
|
||||
// 按里程:计费量=运单里程;固定单价/区间单价/阶梯等均基于该计费量
|
||||
case "按里程" -> money(waybill.getMileage());
|
||||
// 按吨·公里:计费量=货物重量数量 × 运单里程
|
||||
case "按吨·公里" -> goodsQuantity(goods).multiply(money(waybill.getMileage()));
|
||||
case "按数量" -> goodsQuantity(goods);
|
||||
default -> goodsQuantity(goods);
|
||||
|
||||
+5
-1
@@ -1045,7 +1045,11 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
|
||||
TransportBusinessSupport.validateLength(mileageRemark, 200, "里程维护备注不能超过200字");
|
||||
waybill.setMileage(mileage);
|
||||
waybill.setMileageRemark(mileageRemark);
|
||||
return updateById(waybill);
|
||||
boolean updated = updateById(waybill);
|
||||
if (updated) {
|
||||
receivablePayableDetailService.syncMileageFromWaybill(waybill.getId(), mileage);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+5
@@ -27,6 +27,7 @@ import org.springblade.core.tool.utils.BeanUtil;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.vo.ContractManageVO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -47,6 +48,10 @@ public class ContractManageWrapper extends BaseEntityWrapper<ContractManage, Con
|
||||
contractManageVO.setUpdateUserName(org.springblade.system.cache.UserCache.getUserRealName(contractManage.getUpdateUser()));
|
||||
contractManageVO.setContractStageName(stageName(contractManage.getContractStage()));
|
||||
contractManageVO.setApprovalStatusName(statusName(contractManage.getApprovalStatus()));
|
||||
if (contractManageVO.getContractAmount() != null
|
||||
&& contractManageVO.getContractAmount().compareTo(BigDecimal.valueOf(-1)) == 0) {
|
||||
contractManageVO.setContractAmount(null);
|
||||
}
|
||||
return contractManageVO;
|
||||
}
|
||||
|
||||
|
||||
+15
@@ -32,6 +32,7 @@ import org.springblade.transport.pojo.vo.WaybillVO;
|
||||
import org.springblade.transport.support.TransportBusinessSupport;
|
||||
import org.springblade.transport.support.WaybillProcessSupport;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -58,9 +59,23 @@ public class WaybillWrapper extends BaseEntityWrapper<Waybill, WaybillVO> {
|
||||
waybillVO.setBusinessStatus(displayStatus);
|
||||
waybillVO.setBusinessStatusName(businessStatusName(displayStatus));
|
||||
waybillVO.setRequireAccept(WaybillProcessSupport.requiresDriverAcceptConfirmation(waybill.getProcessJson()));
|
||||
// 未维护里程等占位值 -1 对外展示为空
|
||||
if (isSentinelMinusOne(waybillVO.getMileage())) {
|
||||
waybillVO.setMileage(null);
|
||||
}
|
||||
if (isSentinelMinusOne(waybillVO.getQuantity())) {
|
||||
waybillVO.setQuantity(null);
|
||||
}
|
||||
if (isSentinelMinusOne(waybillVO.getUnitPrice())) {
|
||||
waybillVO.setUnitPrice(null);
|
||||
}
|
||||
return waybillVO;
|
||||
}
|
||||
|
||||
private static boolean isSentinelMinusOne(BigDecimal value) {
|
||||
return value != null && value.compareTo(BigDecimal.valueOf(-1)) == 0;
|
||||
}
|
||||
|
||||
public static String businessStatusName(String status) {
|
||||
if (status == null) {
|
||||
return "未知";
|
||||
|
||||
Reference in New Issue
Block a user