✨ 完善付款申请多结算行、结算导入与计费方式回填
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
@@ -61,4 +61,6 @@ public class PaymentApplicationSaveRequest implements Serializable {
|
||||
private String remark;
|
||||
private List<PaymentApplicationInvoiceRequest> invoices;
|
||||
private List<PaymentApplicationRecordRequest> paymentRecords;
|
||||
/** 多张结算单时按行提交的付款金额 */
|
||||
private List<PaymentApplicationSettlementLineRequest> settlementLines;
|
||||
}
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/** 付款申请多结算单付款明细。 @author Chill */
|
||||
@Data
|
||||
public class PaymentApplicationSettlementLineRequest implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
private Long formalSettlementId;
|
||||
private Long preSettlementId;
|
||||
private BigDecimal paymentRatio;
|
||||
private BigDecimal appliedAmount;
|
||||
}
|
||||
+6
@@ -63,4 +63,10 @@ public class ReceivablePayableFeeCalculateRequest implements Serializable {
|
||||
@Schema(description = "动态费用项目")
|
||||
private Map<String, BigDecimal> feeItems;
|
||||
|
||||
@Schema(description = "运输单价")
|
||||
private BigDecimal unitPrice;
|
||||
|
||||
@Schema(description = "本次调整字段:transportQuantity、mileage、unitPrice 或费用项名称")
|
||||
private String changedField;
|
||||
|
||||
}
|
||||
|
||||
+5
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
@@ -99,6 +100,10 @@ public class PreSettlementDetail extends TenantEntity {
|
||||
@Schema(description = "批次号")
|
||||
private String batchNo;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "计费方式")
|
||||
private String billingType;
|
||||
|
||||
@Schema(description = "运输单价")
|
||||
private BigDecimal unitPrice;
|
||||
|
||||
|
||||
+4
@@ -73,6 +73,10 @@ public class ReceivablePayableDetailVO extends ReceivablePayableDetail {
|
||||
@Schema(description = "状态名称")
|
||||
private String settlementStatusName;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "计费方式")
|
||||
private String billingType;
|
||||
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "费用项目")
|
||||
private Map<String, Object> feeItems;
|
||||
|
||||
+6
-6
@@ -45,13 +45,13 @@ public interface UserMapper extends BaseMapper<User> {
|
||||
/**
|
||||
* 自定义分页
|
||||
*
|
||||
* @param page
|
||||
* @param user
|
||||
* @param deptIdList
|
||||
* @param tenantId
|
||||
* @return
|
||||
* @param page 分页
|
||||
* @param user 查询条件
|
||||
* @param deptId 部门 ID(含自身及子孙,由 SQL FIND_IN_SET 过滤;为空则不过滤部门)
|
||||
* @param tenantId 租户 ID
|
||||
* @return 用户列表
|
||||
*/
|
||||
List<User> selectUserPage(IPage<User> page, @Param("user") User user, @Param("deptIdList") List<Long> deptIdList, @Param("tenantId") String tenantId);
|
||||
List<User> selectUserPage(IPage<User> page, @Param("user") User user, @Param("deptId") Long deptId, @Param("tenantId") String tenantId);
|
||||
|
||||
/**
|
||||
* 获取用户
|
||||
|
||||
+12
-11
@@ -61,20 +61,21 @@
|
||||
<if test="user.status!=null and user.status>=0">
|
||||
and bu.status = #{user.status}
|
||||
</if>
|
||||
<if test="deptIdList!=null and deptIdList.size>0">
|
||||
<if test="deptId != null">
|
||||
and bu.id in (
|
||||
SELECT
|
||||
user_id
|
||||
FROM
|
||||
blade_user_dept
|
||||
WHERE
|
||||
status = 1 and is_deleted = 0 and dept_id IN
|
||||
<foreach collection="deptIdList" index="index" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
select distinct bud.user_id
|
||||
from blade_user_dept bud
|
||||
where bud.status = 1
|
||||
and bud.is_deleted = 0
|
||||
and bud.dept_id in (
|
||||
select bd.id
|
||||
from blade_dept bd
|
||||
where bd.is_deleted = 0
|
||||
and (bd.id = #{deptId} or find_in_set(#{deptId}, bd.ancestors) > 0)
|
||||
)
|
||||
)
|
||||
</if>
|
||||
ORDER BY bu.id
|
||||
ORDER BY bu.create_time DESC, bu.id DESC
|
||||
</select>
|
||||
|
||||
<select id="getUser" resultMap="userResultMap">
|
||||
|
||||
+3
-1
@@ -367,7 +367,9 @@ public class DeptServiceImpl extends ServiceImpl<DeptMapper, Dept> implements ID
|
||||
|
||||
@Override
|
||||
public List<Dept> getDeptChild(Long deptId) {
|
||||
return baseMapper.selectList(Wrappers.<Dept>query().lambda().like(Dept::getAncestors, deptId));
|
||||
// 必须用 FIND_IN_SET:LIKE '%1%' 会误匹配雪花 ID 中的数字片段,导致子部门集合爆炸
|
||||
return baseMapper.selectList(Wrappers.<Dept>query().lambda()
|
||||
.apply("FIND_IN_SET({0}, ancestors)", String.valueOf(deptId)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -407,8 +407,8 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
|
||||
@Override
|
||||
public IPage<User> selectUserPage(IPage<User> page, User user, Long deptId, String tenantId) {
|
||||
normalizeUserSearch(user);
|
||||
List<Long> deptIdList = SysCache.getDeptChildIds(deptId);
|
||||
return page.setRecords(baseMapper.selectUserPage(page, user, deptIdList, tenantId));
|
||||
// 部门过滤下推到 SQL(FIND_IN_SET),避免先展开整棵子树再拼超大 IN 列表
|
||||
return page.setRecords(baseMapper.selectUserPage(page, user, deptId, tenantId));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+16
-5
@@ -77,11 +77,14 @@ public final class SettlementImportFeeReader {
|
||||
private final List<Map<String, Object>> rows;
|
||||
private final List<String> feeItemNames;
|
||||
private final List<String> warnings;
|
||||
private final List<String> headers;
|
||||
|
||||
public ParseResult(List<Map<String, Object>> rows, List<String> feeItemNames, List<String> warnings) {
|
||||
public ParseResult(List<Map<String, Object>> rows, List<String> feeItemNames, List<String> warnings,
|
||||
List<String> headers) {
|
||||
this.rows = rows;
|
||||
this.feeItemNames = feeItemNames;
|
||||
this.warnings = warnings;
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getRows() {
|
||||
@@ -95,6 +98,10 @@ public final class SettlementImportFeeReader {
|
||||
public List<String> getWarnings() {
|
||||
return warnings;
|
||||
}
|
||||
|
||||
public List<String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
|
||||
public static ParseResult read(MultipartFile file) {
|
||||
@@ -140,7 +147,7 @@ public final class SettlementImportFeeReader {
|
||||
rows.add(mapped);
|
||||
}
|
||||
}
|
||||
return new ParseResult(rows, feeItemNames, List.of());
|
||||
return new ParseResult(rows, feeItemNames, List.of(), new ArrayList<>(headers.values()));
|
||||
} catch (ServiceException exception) {
|
||||
throw exception;
|
||||
} catch (Exception exception) {
|
||||
@@ -212,15 +219,17 @@ public final class SettlementImportFeeReader {
|
||||
}
|
||||
String field = FIXED_HEADER_MAP.get(trimmedHeader);
|
||||
if (field == null) {
|
||||
mapped.put(trimmedHeader, blankToNull(cellString(poiCell)));
|
||||
continue;
|
||||
}
|
||||
if (DATE_FIELDS.contains(field)) {
|
||||
mapped.put(field, dateCellString(poiCell));
|
||||
} else if (OPTIONAL_DECIMAL_FIELDS.contains(field)) {
|
||||
mapped.put(field, optionalDecimal(cellString(poiCell), trimmedHeader, excelRowIndex));
|
||||
} else if ("transportFee".equals(field) || "quantity".equals(field)
|
||||
|| "settlementAmount".equals(field)) {
|
||||
} else if ("transportFee".equals(field) || "quantity".equals(field)) {
|
||||
mapped.put(field, decimal(cellString(poiCell), trimmedHeader, excelRowIndex));
|
||||
} else if ("settlementAmount".equals(field)) {
|
||||
mapped.put(field, optionalDecimal(cellString(poiCell), trimmedHeader, excelRowIndex));
|
||||
} else if ("serialNumber".equals(field)) {
|
||||
mapped.put(field, intValue(cellString(poiCell)));
|
||||
} else {
|
||||
@@ -246,7 +255,9 @@ public final class SettlementImportFeeReader {
|
||||
BigDecimal feeItemTotal = feeItems.values().stream()
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal settlementAmount = transportFee.add(feeItemTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal settlementAmount = mapped.get("settlementAmount") == null
|
||||
? transportFee.add(feeItemTotal).setScale(2, RoundingMode.HALF_UP)
|
||||
: toDecimal(mapped.get("settlementAmount"));
|
||||
mapped.put("settlementAmount", settlementAmount);
|
||||
mapped.put("结算金额", settlementAmount);
|
||||
mapped.put("feeItems", feeItems);
|
||||
|
||||
+1
-1
@@ -360,7 +360,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
.in(ReceivablePayableCargoFee::getDetailId, sourceDetailIds)
|
||||
.orderByAsc(ReceivablePayableCargoFee::getLineNo))
|
||||
.stream()
|
||||
.filter(fee -> Func.isNotEmpty(fee.getBillingType()))
|
||||
.filter(fee -> Func.isNotEmpty(fee.getBillingType()) && !"-".equals(fee.getBillingType()))
|
||||
.collect(Collectors.groupingBy(ReceivablePayableCargoFee::getDetailId,
|
||||
Collectors.mapping(ReceivablePayableCargoFee::getBillingType,
|
||||
Collectors.collectingAndThen(Collectors.toCollection(LinkedHashSet::new),
|
||||
|
||||
+123
-8
@@ -72,6 +72,7 @@ import org.springblade.transport.mapper.PreSettlementMapper;
|
||||
import org.springblade.transport.mapper.ProjectApplyMapper;
|
||||
import org.springblade.transport.mapper.TemporaryCreditLimitMapper;
|
||||
import org.springblade.transport.pojo.dto.PaymentApplicationInvoiceRequest;
|
||||
import org.springblade.transport.pojo.dto.PaymentApplicationSettlementLineRequest;
|
||||
import org.springblade.transport.pojo.dto.PaymentApplicationRecordRequest;
|
||||
import org.springblade.transport.pojo.dto.PaymentApplicationSaveRequest;
|
||||
import org.springblade.transport.pojo.dto.PaymentApplicationStatusRequest;
|
||||
@@ -369,6 +370,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
matchedInvoiceAmount = matchedInvoiceAmount.add(money(item.getMatchedAmount()));
|
||||
}
|
||||
entity.setMatchedInvoiceAmount(matchedInvoiceAmount);
|
||||
// 进度预付只关联预结算单。编辑回传的 settlementId 可能是预结算单 ID,不能拿去查正式结算单。
|
||||
normalizeSettlementReference(request);
|
||||
if (TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType()) && request.getSettlementId() == null
|
||||
&& request.getSettlementIds() != null && !request.getSettlementIds().isEmpty()) {
|
||||
request.setSettlementId(request.getSettlementIds().get(0));
|
||||
@@ -385,6 +388,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
fillBillLedger(entity, request);
|
||||
validateQuota(entity);
|
||||
saveOrUpdate(entity);
|
||||
// not_null 更新策略不会把 settlementId 的 null 写回库,进度预付需显式清掉误存的正式结算单 ID
|
||||
clearDetachedFormalReference(entity);
|
||||
// 汇票付款在生成申请时即占用可用余额;编辑时按差额调整,避免重复扣减
|
||||
syncBillOccupation(entity);
|
||||
invoiceMapper.delete(Wrappers.<PaymentApplicationInvoice>lambdaQuery()
|
||||
@@ -417,8 +422,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
entity.setInvoiceStatus(matchedInvoiceAmount.compareTo(BigDecimal.ZERO) > 0 ? "matched" : "unmatched");
|
||||
entity.setPaidAmount(paidAmount);
|
||||
updateById(entity);
|
||||
saveSettlementRelations(entity, formalSettlements);
|
||||
savePreSettlementRelations(entity, preSettlements);
|
||||
saveSettlementRelations(entity, formalSettlements, request.getSettlementLines());
|
||||
savePreSettlementRelations(entity, preSettlements, request.getSettlementLines());
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@@ -1836,6 +1841,46 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 进度预付只认预结算单。尾款付款若同时带回一个并不存在的正式结算单 ID,改为按预结算单保存。
|
||||
*/
|
||||
private void normalizeSettlementReference(PaymentApplicationSaveRequest request) {
|
||||
if ("progress_advance".equals(request.getPaymentType())) {
|
||||
if (request.getPreSettlementId() == null && request.getPreSettlementIds() != null) {
|
||||
request.setPreSettlementId(request.getPreSettlementIds().stream().filter(Objects::nonNull).findFirst().orElse(null));
|
||||
}
|
||||
if (request.getPreSettlementId() == null) {
|
||||
request.setPreSettlementId(request.getSettlementId());
|
||||
}
|
||||
request.setSettlementId(null);
|
||||
request.setSettlementIds(null);
|
||||
return;
|
||||
}
|
||||
if (!TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType()) || request.getSettlementId() == null) {
|
||||
return;
|
||||
}
|
||||
boolean hasPreSettlement = request.getPreSettlementId() != null
|
||||
|| (request.getPreSettlementIds() != null && request.getPreSettlementIds().stream().anyMatch(Objects::nonNull));
|
||||
if (!hasPreSettlement) {
|
||||
return;
|
||||
}
|
||||
FormalSettlement formalSettlement = formalSettlementMapper.selectById(request.getSettlementId());
|
||||
if (formalSettlement == null || Objects.equals(formalSettlement.getIsDeleted(), 1)) {
|
||||
request.setSettlementId(null);
|
||||
request.setSettlementIds(null);
|
||||
}
|
||||
}
|
||||
|
||||
/** 预结算付款不挂正式结算单时,清掉历史误写入的正式结算单 ID,避免再次提交按正式结算单刷新。 */
|
||||
private void clearDetachedFormalReference(PaymentApplication entity) {
|
||||
if (entity.getId() == null || entity.getPreSettlementId() == null || entity.getSettlementId() != null) {
|
||||
return;
|
||||
}
|
||||
baseMapper.update(null, Wrappers.<PaymentApplication>lambdaUpdate()
|
||||
.eq(PaymentApplication::getId, entity.getId())
|
||||
.setSql("settlement_id = null, settlement_no = null"));
|
||||
}
|
||||
|
||||
private void fillReference(PaymentApplication entity, PaymentApplicationSaveRequest request) {
|
||||
if ("project_advance".equals(request.getPaymentType())) {
|
||||
ProjectApply project = projectApplyMapper.selectById(request.getProjectId());
|
||||
@@ -1898,19 +1943,33 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
entity.setPayableAmount(payableAmount);
|
||||
}
|
||||
|
||||
private void saveSettlementRelations(PaymentApplication entity, List<FormalSettlement> settlements) {
|
||||
private void saveSettlementRelations(PaymentApplication entity, List<FormalSettlement> settlements,
|
||||
List<PaymentApplicationSettlementLineRequest> lines) {
|
||||
if (settlements.isEmpty()) return;
|
||||
settlementRelationMapper.delete(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId()));
|
||||
boolean customAmounts = settlements.size() > 1 && lines != null && !lines.isEmpty();
|
||||
BigDecimal totalBase = settlements.stream()
|
||||
.map(item -> payableOfFormalSettlement(item.getId(), item.getSettlementAmount(), entity.getId()))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal remainingApplied = money(entity.getAppliedAmount());
|
||||
BigDecimal remainingPaid = money(entity.getPaidAmount());
|
||||
if (customAmounts) {
|
||||
BigDecimal lineTotal = BigDecimal.ZERO;
|
||||
for (FormalSettlement settlement : settlements) {
|
||||
BigDecimal payable = payableOfFormalSettlement(settlement.getId(), settlement.getSettlementAmount(), entity.getId());
|
||||
BigDecimal applied = this.specifiedAppliedAmount(lines, settlement.getId(), null);
|
||||
this.assertAppliedWithinPayable(settlement.getFormalSettlementNo(), applied, payable);
|
||||
lineTotal = lineTotal.add(applied);
|
||||
}
|
||||
this.assertLineTotalMatches(entity, lineTotal);
|
||||
}
|
||||
for (int index = 0; index < settlements.size(); index++) {
|
||||
FormalSettlement settlement = settlements.get(index);
|
||||
BigDecimal base = payableOfFormalSettlement(settlement.getId(), settlement.getSettlementAmount(), entity.getId());
|
||||
BigDecimal applied = index == settlements.size() - 1 ? remainingApplied
|
||||
BigDecimal applied = customAmounts
|
||||
? this.specifiedAppliedAmount(lines, settlement.getId(), null)
|
||||
: index == settlements.size() - 1 || totalBase.compareTo(BigDecimal.ZERO) == 0 ? remainingApplied
|
||||
: money(entity.getAppliedAmount()).multiply(base).divide(totalBase, 2, RoundingMode.DOWN);
|
||||
BigDecimal paid = index == settlements.size() - 1 ? remainingPaid
|
||||
: money(entity.getPaidAmount()).multiply(applied).divide(money(entity.getAppliedAmount()).max(BigDecimal.ONE), 2, RoundingMode.DOWN);
|
||||
@@ -1985,7 +2044,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
entity.setBillType("预结算单");
|
||||
}
|
||||
|
||||
private void savePreSettlementRelations(PaymentApplication entity, List<PreSettlement> settlements) {
|
||||
private void savePreSettlementRelations(PaymentApplication entity, List<PreSettlement> settlements,
|
||||
List<PaymentApplicationSettlementLineRequest> lines) {
|
||||
settlementRelationMapper.delete(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId())
|
||||
.isNotNull(PaymentApplicationSettlement::getPreSettlementId));
|
||||
@@ -2000,14 +2060,27 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
return;
|
||||
}
|
||||
// 单选/多选均落关联行,生成即占用可付款额度(删除草稿或审核不通过后释放)
|
||||
boolean customAmounts = targetSettlements.size() > 1 && lines != null && !lines.isEmpty();
|
||||
BigDecimal totalBase = targetSettlements.stream()
|
||||
.map(item -> payableOfPreSettlement(item.getId(), item.getSettlementAmount(), entity.getId()))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal remainingApplied = money(entity.getAppliedAmount());
|
||||
if (customAmounts) {
|
||||
BigDecimal lineTotal = BigDecimal.ZERO;
|
||||
for (PreSettlement settlement : targetSettlements) {
|
||||
BigDecimal payable = payableOfPreSettlement(settlement.getId(), settlement.getSettlementAmount(), entity.getId());
|
||||
BigDecimal applied = this.specifiedAppliedAmount(lines, null, settlement.getId());
|
||||
this.assertAppliedWithinPayable(settlement.getPreSettlementNo(), applied, payable);
|
||||
lineTotal = lineTotal.add(applied);
|
||||
}
|
||||
this.assertLineTotalMatches(entity, lineTotal);
|
||||
}
|
||||
for (int index = 0; index < targetSettlements.size(); index++) {
|
||||
PreSettlement settlement = targetSettlements.get(index);
|
||||
BigDecimal base = payableOfPreSettlement(settlement.getId(), settlement.getSettlementAmount(), entity.getId());
|
||||
BigDecimal applied = index == targetSettlements.size() - 1 || totalBase.compareTo(BigDecimal.ZERO) == 0
|
||||
BigDecimal applied = customAmounts
|
||||
? this.specifiedAppliedAmount(lines, null, settlement.getId())
|
||||
: index == targetSettlements.size() - 1 || totalBase.compareTo(BigDecimal.ZERO) == 0
|
||||
? remainingApplied
|
||||
: money(entity.getAppliedAmount()).multiply(base).divide(totalBase, 2, RoundingMode.DOWN);
|
||||
PaymentApplicationSettlement relation = new PaymentApplicationSettlement();
|
||||
@@ -2022,6 +2095,42 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal specifiedAppliedAmount(List<PaymentApplicationSettlementLineRequest> lines,
|
||||
Long formalSettlementId, Long preSettlementId) {
|
||||
if (lines == null) {
|
||||
throw new ServiceException("请填写每张结算单的付款金额");
|
||||
}
|
||||
for (PaymentApplicationSettlementLineRequest line : lines) {
|
||||
if (line == null) {
|
||||
continue;
|
||||
}
|
||||
boolean matched = formalSettlementId != null
|
||||
? Objects.equals(formalSettlementId, line.getFormalSettlementId())
|
||||
: Objects.equals(preSettlementId, line.getPreSettlementId());
|
||||
if (matched && line.getAppliedAmount() != null) {
|
||||
return money(line.getAppliedAmount()).setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
||||
throw new ServiceException("请填写每张结算单的付款金额");
|
||||
}
|
||||
|
||||
private void assertAppliedWithinPayable(String settlementNo, BigDecimal appliedAmount, BigDecimal payableAmount) {
|
||||
if (money(appliedAmount).compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new ServiceException("付款金额不能小于0");
|
||||
}
|
||||
if (money(appliedAmount).compareTo(money(payableAmount)) > 0) {
|
||||
String label = Func.isEmpty(settlementNo) ? "结算单" : settlementNo;
|
||||
throw new ServiceException(label + "付款金额不能超过可付款金额");
|
||||
}
|
||||
}
|
||||
|
||||
private void assertLineTotalMatches(PaymentApplication entity, BigDecimal lineTotal) {
|
||||
BigDecimal appliedAmount = money(entity.getAppliedAmount()).setScale(2, RoundingMode.HALF_UP);
|
||||
if (lineTotal.setScale(2, RoundingMode.HALF_UP).compareTo(appliedAmount) != 0) {
|
||||
throw new ServiceException("申请付款金额须等于各结算单付款金额合计");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateQuota(PaymentApplication entity) {
|
||||
if (!"project_advance".equals(entity.getPaymentType())) return;
|
||||
BigDecimal appliedAmount = money(entity.getAppliedAmount());
|
||||
@@ -2429,8 +2538,14 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
|
||||
private void change(Long id, String from, String to, String node, String reason) { PaymentApplication entity = existing(id); if (!from.equals(entity.getApprovalStatus())) throw new ServiceException("当前状态不允许此操作"); entity.setApprovalStatus(to); entity.setCurrentNode(node); entity.setCurrentProcessor(AuthUtil.getUserName()); entity.setRemark(reason == null ? entity.getRemark() : limit(reason, 200)); updateById(entity); refreshFormalSettlement(entity); }
|
||||
private void refreshFormalSettlement(PaymentApplication entity) {
|
||||
if (entity.getSettlementId() != null) formalSettlementService.refreshPaymentSummary(entity.getSettlementId());
|
||||
else if (entity.getPreSettlementId() != null) {
|
||||
if (entity.getSettlementId() != null) {
|
||||
FormalSettlement formalSettlement = formalSettlementMapper.selectById(entity.getSettlementId());
|
||||
if (formalSettlement != null && !Objects.equals(formalSettlement.getIsDeleted(), 1)) {
|
||||
formalSettlementService.refreshPaymentSummary(entity.getSettlementId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (entity.getPreSettlementId() != null) {
|
||||
formalSettlementService.refreshPaymentSummariesForPreSettlement(entity.getPreSettlementId());
|
||||
}
|
||||
}
|
||||
|
||||
+30
-1
@@ -186,6 +186,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
PreSettlementVO vo = PreSettlementWrapper.build().entityVO(settlement);
|
||||
fillPaymentApplicationAmounts(List.of(vo));
|
||||
List<PreSettlementDetail> details = listDetails(id);
|
||||
fillDetailBillingTypes(details);
|
||||
vo.setDetails(details);
|
||||
List<Long> detailIds = details.stream().map(PreSettlementDetail::getId).toList();
|
||||
Map<Long, List<PreSettlementDetailFee>> feeMap = Func.isEmpty(detailIds) ? new HashMap<>()
|
||||
@@ -788,7 +789,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
.in(ReceivablePayableCargoFee::getDetailId, detailIds)
|
||||
.orderByAsc(ReceivablePayableCargoFee::getLineNo))
|
||||
.stream()
|
||||
.filter(fee -> Func.isNotEmpty(fee.getBillingType()))
|
||||
.filter(fee -> Func.isNotEmpty(fee.getBillingType()) && !"-".equals(fee.getBillingType()))
|
||||
.collect(Collectors.groupingBy(ReceivablePayableCargoFee::getDetailId,
|
||||
Collectors.mapping(ReceivablePayableCargoFee::getBillingType,
|
||||
Collectors.collectingAndThen(Collectors.toCollection(LinkedHashSet::new),
|
||||
@@ -1492,6 +1493,34 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
.eq(PreSettlementChangeRecord::getPreSettlementId, settlementId));
|
||||
}
|
||||
|
||||
/** 预结算明细展示计费方式:按来源应收应付货物费用的计费类型汇总。 */
|
||||
private void fillDetailBillingTypes(List<PreSettlementDetail> details) {
|
||||
if (details == null || details.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<Long> sourceDetailIds = details.stream()
|
||||
.map(PreSettlementDetail::getSourceDetailId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (sourceDetailIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, String> billingTypeMap = sourceFeeMapper.selectList(
|
||||
Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
|
||||
.in(ReceivablePayableCargoFee::getDetailId, sourceDetailIds)
|
||||
.orderByAsc(ReceivablePayableCargoFee::getLineNo))
|
||||
.stream()
|
||||
.filter(fee -> Func.isNotEmpty(fee.getBillingType()) && !"-".equals(fee.getBillingType()))
|
||||
.collect(Collectors.groupingBy(ReceivablePayableCargoFee::getDetailId,
|
||||
Collectors.mapping(ReceivablePayableCargoFee::getBillingType,
|
||||
Collectors.collectingAndThen(Collectors.toCollection(LinkedHashSet::new),
|
||||
values -> String.join("、", values)))));
|
||||
for (PreSettlementDetail detail : details) {
|
||||
detail.setBillingType(billingTypeMap.getOrDefault(detail.getSourceDetailId(), ""));
|
||||
}
|
||||
}
|
||||
|
||||
private List<PreSettlementDetail> listDetails(Long settlementId) {
|
||||
return detailMapper.selectList(Wrappers.<PreSettlementDetail>lambdaQuery()
|
||||
.eq(PreSettlementDetail::getPreSettlementId, settlementId)
|
||||
|
||||
+121
-24
@@ -607,14 +607,22 @@ public class ReceivablePayableDetailServiceImpl
|
||||
BigDecimal transportQuantity = money(adjusted.getTransportQuantity());
|
||||
BigDecimal mileage = money(adjusted.getMileage());
|
||||
BigDecimal freightAmount = money(adjusted.getFreightAmount());
|
||||
BigDecimal unitPrice = adjusted.getUnitPrice() == null ? null : money(adjusted.getUnitPrice());
|
||||
Map<String, BigDecimal> effectiveFeeItems = feeItems;
|
||||
boolean billingBasisChanged = money(existing.getTransportQuantity()).compareTo(transportQuantity) != 0
|
||||
|| money(existing.getMileage()).compareTo(mileage) != 0;
|
||||
if (billingBasisChanged) {
|
||||
boolean quantityChanged = money(existing.getTransportQuantity()).compareTo(transportQuantity) != 0;
|
||||
boolean mileageChanged = money(existing.getMileage()).compareTo(mileage) != 0;
|
||||
boolean unitPriceChanged = money(existing.getUnitPrice()).compareTo(money(unitPrice)) != 0;
|
||||
if (quantityChanged || mileageChanged || unitPriceChanged) {
|
||||
String changedField = quantityChanged && !mileageChanged ? "transportQuantity"
|
||||
: mileageChanged && !quantityChanged ? "mileage"
|
||||
: unitPriceChanged && !quantityChanged && !mileageChanged ? "unitPrice" : null;
|
||||
AdjustedFeeCalculation calculation = calculateAdjustedFee(detail, existing,
|
||||
transportQuantity, mileage, freightAmount, feeItems);
|
||||
transportQuantity, mileage, freightAmount, feeItems, unitPrice, changedField);
|
||||
freightAmount = calculation.freightAmount();
|
||||
effectiveFeeItems = calculation.feeItems();
|
||||
if (calculation.unitPrice() != null) {
|
||||
adjusted.setUnitPrice(calculation.unitPrice().setScale(2, RoundingMode.HALF_UP));
|
||||
}
|
||||
}
|
||||
appendChange(changes, "规格", existing.getSpecification(), adjusted.getSpecification());
|
||||
appendChange(changes, "型号", existing.getModel(), adjusted.getModel());
|
||||
@@ -678,15 +686,21 @@ public class ReceivablePayableDetailServiceImpl
|
||||
}
|
||||
validateNonNegative(request.getTransportQuantity(), "运输量");
|
||||
validateNonNegative(request.getMileage(), "里程");
|
||||
validateNonNegative(request.getUnitPrice(), "运输单价");
|
||||
AdjustedFeeCalculation calculation = calculateAdjustedFee(detail, fee,
|
||||
money(request.getTransportQuantity()), money(request.getMileage()),
|
||||
money(request.getFreightAmount()), normalizeFeeItems(request.getFeeItems()));
|
||||
money(request.getFreightAmount()), normalizeFeeItems(request.getFeeItems()),
|
||||
request.getUnitPrice() == null ? null : money(request.getUnitPrice()),
|
||||
request.getChangedField());
|
||||
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()));
|
||||
if (calculation.unitPrice() != null) {
|
||||
result.setUnitPrice(calculation.unitPrice().setScale(2, RoundingMode.HALF_UP));
|
||||
}
|
||||
BigDecimal afterAmount = adjustedAfterAmount(calculation.freightAmount(), calculation.feeItems());
|
||||
result.setAdjustAmount(afterAmount.subtract(money(fee.getOriginalAmount())));
|
||||
result.setAfterAmount(afterAmount);
|
||||
@@ -2531,10 +2545,16 @@ public class ReceivablePayableDetailServiceImpl
|
||||
private AdjustedFeeCalculation calculateAdjustedFee(ReceivablePayableDetail detail,
|
||||
ReceivablePayableCargoFee fee, BigDecimal transportQuantity,
|
||||
BigDecimal mileage, BigDecimal freightAmount,
|
||||
Map<String, BigDecimal> feeItems) {
|
||||
Map<String, BigDecimal> feeItems, BigDecimal unitPrice,
|
||||
String changedField) {
|
||||
if (isManualFee(fee)) {
|
||||
throw new ServiceException("手工费用不支持按合同计费规则试算");
|
||||
}
|
||||
boolean transportChanged = Func.isEmpty(changedField)
|
||||
|| List.of("transportQuantity", "mileage", "unitPrice").contains(changedField);
|
||||
if (!transportChanged) {
|
||||
return new AdjustedFeeCalculation(freightAmount, new LinkedHashMap<>(feeItems), null);
|
||||
}
|
||||
Waybill adjustedWaybill = adjustedWaybill(detail, fee, transportQuantity, mileage);
|
||||
List<Map<String, Object>> rules = parseList(fee.getBillingRulesJson());
|
||||
if (rules.isEmpty()) {
|
||||
@@ -2550,26 +2570,94 @@ public class ReceivablePayableDetailServiceImpl
|
||||
if (rules.isEmpty()) {
|
||||
throw new ServiceException("未找到费用明细对应的合同计费规则,请先更新费用");
|
||||
}
|
||||
Map<String, BigDecimal> calculatedAmounts = new LinkedHashMap<>();
|
||||
BigDecimal calculatedFreight = BigDecimal.ZERO;
|
||||
boolean freightRuleMatched = false;
|
||||
Map<String, BigDecimal> calculatedFeeItems = new LinkedHashMap<>(feeItems);
|
||||
BigDecimal calculatedFreight = null;
|
||||
BigDecimal resolvedUnitPrice = null;
|
||||
boolean calculated = false;
|
||||
for (Map<String, Object> rule : rules) {
|
||||
BigDecimal amount = calculateRule(rule, adjustedWaybill);
|
||||
if (amount == null) continue;
|
||||
if (!shouldRecalculateRule(rule, changedField)) {
|
||||
continue;
|
||||
}
|
||||
String type = stringValue(rule, "billingType", "");
|
||||
boolean freightRule = isFreightRule(rule);
|
||||
Map<String, Object> effectiveRule = rule;
|
||||
if (freightRule && unitPriceAdjustable(type) && unitPrice != null) {
|
||||
effectiveRule = new LinkedHashMap<>(rule);
|
||||
effectiveRule.put("unitPrice", unitPrice);
|
||||
resolvedUnitPrice = unitPrice;
|
||||
}
|
||||
BigDecimal amount = calculateRule(effectiveRule, adjustedWaybill);
|
||||
if (amount == null) {
|
||||
continue;
|
||||
}
|
||||
calculated = true;
|
||||
String feeItem = stringValue(rule, "feeItem", "费用");
|
||||
calculatedAmounts.merge(feeItem, amount, BigDecimal::add);
|
||||
if (isFreightRule(rule)) {
|
||||
calculatedFreight = calculatedFreight.add(amount);
|
||||
freightRuleMatched = true;
|
||||
calculatedFeeItems.put(feeItem, amount);
|
||||
if (freightRule) {
|
||||
calculatedFreight = (calculatedFreight == null ? BigDecimal.ZERO : calculatedFreight).add(amount);
|
||||
if (intervalBilling(type)) {
|
||||
resolvedUnitPrice = matchedIntervalUnitPrice(rule, adjustedWaybill);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (calculatedAmounts.isEmpty()) {
|
||||
if (!calculated && Func.isEmpty(changedField)) {
|
||||
throw new ServiceException("费用明细对应的合同计费规则无法试算,请先更新费用");
|
||||
}
|
||||
Map<String, BigDecimal> calculatedFeeItems = new LinkedHashMap<>(feeItems);
|
||||
calculatedAmounts.forEach(calculatedFeeItems::put);
|
||||
return new AdjustedFeeCalculation(freightRuleMatched ? calculatedFreight : freightAmount,
|
||||
calculatedFeeItems);
|
||||
return new AdjustedFeeCalculation(calculatedFreight == null ? freightAmount : calculatedFreight,
|
||||
calculatedFeeItems, resolvedUnitPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* 运费在运输总量、里程、可调整单价变化后按方案重算。
|
||||
* 其他费用仅在计费要素使用了本次变化的运输总量或里程时重算,便于保留手工修改的费用总额。
|
||||
*/
|
||||
private boolean shouldRecalculateRule(Map<String, Object> rule, String changedField) {
|
||||
String element = stringValue(rule, "billingElement", "");
|
||||
String type = stringValue(rule, "billingType", "");
|
||||
if (isFreightRule(rule)) {
|
||||
return !"unitPrice".equals(changedField) || unitPriceAdjustable(type);
|
||||
}
|
||||
if ("unitPrice".equals(changedField) || Func.isEmpty(changedField)) {
|
||||
return Func.isEmpty(changedField) && (usesTransportQuantity(element) || usesMileage(element));
|
||||
}
|
||||
if ("transportQuantity".equals(changedField)) {
|
||||
return usesTransportQuantity(element);
|
||||
}
|
||||
if ("mileage".equals(changedField)) {
|
||||
return usesMileage(element);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean usesTransportQuantity(String element) {
|
||||
return List.of("按重量", "按体积", "按数量", "按吨·公里").contains(element);
|
||||
}
|
||||
|
||||
private boolean usesMileage(String element) {
|
||||
return List.of("按里程", "按吨·公里").contains(element);
|
||||
}
|
||||
|
||||
private boolean unitPriceAdjustable(String billingType) {
|
||||
return !tierUnitPrice(billingType) && !intervalBilling(billingType);
|
||||
}
|
||||
|
||||
private boolean tierUnitPrice(String billingType) {
|
||||
return "阶梯单价".equals(billingType);
|
||||
}
|
||||
|
||||
private boolean intervalBilling(String billingType) {
|
||||
String type = billingType == null ? "" : billingType;
|
||||
return "区间单价".equals(type) || (type.contains("区间") && type.contains("一口价"));
|
||||
}
|
||||
|
||||
private BigDecimal matchedIntervalUnitPrice(Map<String, Object> rule, Waybill waybill) {
|
||||
BigDecimal fallback = decimal(rule.get("unitPrice"));
|
||||
return range(ranges(rule), measure(rule, waybill))
|
||||
.map(matched -> {
|
||||
BigDecimal matchedPrice = decimal(matched.get("unitPrice"));
|
||||
return matchedPrice.signum() == 0 ? fallback : matchedPrice;
|
||||
})
|
||||
.orElse(fallback);
|
||||
}
|
||||
|
||||
private Waybill adjustedWaybill(ReceivablePayableDetail detail, ReceivablePayableCargoFee fee,
|
||||
@@ -2642,7 +2730,8 @@ public class ReceivablePayableDetailServiceImpl
|
||||
return name != null && (name.contains("运费") || name.contains("运输费"));
|
||||
}
|
||||
|
||||
private record AdjustedFeeCalculation(BigDecimal freightAmount, Map<String, BigDecimal> feeItems) {
|
||||
private record AdjustedFeeCalculation(BigDecimal freightAmount, Map<String, BigDecimal> feeItems,
|
||||
BigDecimal unitPrice) {
|
||||
}
|
||||
|
||||
private void appendChange(List<String> changes, String field, BigDecimal before, BigDecimal after) {
|
||||
@@ -2981,21 +3070,29 @@ public class ReceivablePayableDetailServiceImpl
|
||||
if (detailIds.isEmpty()) return;
|
||||
List<ReceivablePayableCargoFee> fees = cargoFeeMapper.selectList(
|
||||
Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
|
||||
.select(ReceivablePayableCargoFee::getDetailId, ReceivablePayableCargoFee::getCargoName)
|
||||
.select(ReceivablePayableCargoFee::getDetailId, ReceivablePayableCargoFee::getCargoName,
|
||||
ReceivablePayableCargoFee::getBillingType)
|
||||
.in(ReceivablePayableCargoFee::getDetailId, detailIds)
|
||||
.eq(ReceivablePayableCargoFee::getIsDeleted, 0)
|
||||
.orderByAsc(ReceivablePayableCargoFee::getDetailId)
|
||||
.orderByAsc(ReceivablePayableCargoFee::getLineNo));
|
||||
Map<Long, LinkedHashSet<String>> cargoNames = new LinkedHashMap<>();
|
||||
Map<Long, LinkedHashSet<String>> billingTypes = new LinkedHashMap<>();
|
||||
for (ReceivablePayableCargoFee fee : fees) {
|
||||
if (Func.isEmpty(fee.getCargoName())) continue;
|
||||
cargoNames.computeIfAbsent(fee.getDetailId(), key -> new LinkedHashSet<>()).add(fee.getCargoName());
|
||||
if (Func.isNotEmpty(fee.getCargoName())) {
|
||||
cargoNames.computeIfAbsent(fee.getDetailId(), key -> new LinkedHashSet<>()).add(fee.getCargoName());
|
||||
}
|
||||
if (Func.isNotEmpty(fee.getBillingType()) && !"-".equals(fee.getBillingType())) {
|
||||
billingTypes.computeIfAbsent(fee.getDetailId(), key -> new LinkedHashSet<>()).add(fee.getBillingType());
|
||||
}
|
||||
}
|
||||
details.forEach(detail -> {
|
||||
Set<String> names = cargoNames.get(detail.getId());
|
||||
if (Func.isNotEmpty(names)) {
|
||||
detail.setCargoName(String.join(",", names));
|
||||
}
|
||||
Set<String> types = billingTypes.get(detail.getId());
|
||||
detail.setBillingType(Func.isEmpty(types) ? "" : String.join("、", types));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+48
-2
@@ -115,6 +115,13 @@ public class SettlementImportBatchServiceImpl
|
||||
private static final String TYPE_PAYABLE = "payable";
|
||||
private static final String BILL_TYPE_FORMAL = "formal";
|
||||
private static final String BILL_TYPE_PRE = "pre";
|
||||
private static final String[] VEHICLE_ALIAS_KEYS = {
|
||||
"*车牌号/航班号/船号/班列号",
|
||||
"车牌号/航班号/船号/班列号",
|
||||
"*车牌号/船班号/船次号/班次号",
|
||||
"车牌号/船班号/船次号/班次号",
|
||||
"车号"
|
||||
};
|
||||
|
||||
private final IProjectApplyService projectApplyService;
|
||||
private final IContractManageService contractManageService;
|
||||
@@ -231,6 +238,7 @@ public class SettlementImportBatchServiceImpl
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("rows", result.getRows());
|
||||
data.put("feeItemNames", result.getFeeItemNames());
|
||||
data.put("headers", result.getHeaders());
|
||||
data.put("warnings", result.getWarnings());
|
||||
return data;
|
||||
}
|
||||
@@ -263,6 +271,7 @@ public class SettlementImportBatchServiceImpl
|
||||
}
|
||||
List<SettlementImportTemplateExcel> failureList = new ArrayList<>();
|
||||
for (Map<String, Object> row : request.getRows()) {
|
||||
keepBlankVehicleNo(row);
|
||||
SettlementImportTemplateExcel excel = mapToExcel(row);
|
||||
excel.setErrorMessage(stringValue(row, "errorMessage", "导入失败原因", "失败原因"));
|
||||
failureList.add(excel);
|
||||
@@ -737,6 +746,7 @@ public class SettlementImportBatchServiceImpl
|
||||
if (row == null) {
|
||||
continue;
|
||||
}
|
||||
keepBlankVehicleNo(row);
|
||||
BigDecimal transportFee = decimalValue(row, "transportFee", "*运输费", "运输费", "freight", "运费");
|
||||
if (transportFee == null) {
|
||||
transportFee = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
|
||||
@@ -744,7 +754,12 @@ public class SettlementImportBatchServiceImpl
|
||||
Map<String, BigDecimal> feeItems = extractFeeItems(row, feeItemNames);
|
||||
BigDecimal feeItemTotal = feeItems.values().stream()
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal settlementAmount = transportFee.add(feeItemTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal settlementAmount = decimalValue(row, "settlementAmount", "结算金额");
|
||||
if (settlementAmount == null) {
|
||||
settlementAmount = transportFee.add(feeItemTotal).setScale(2, RoundingMode.HALF_UP);
|
||||
} else {
|
||||
settlementAmount = settlementAmount.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
row.put("transportFee", transportFee);
|
||||
row.put("feeItems", feeItems);
|
||||
row.put("settlementAmount", settlementAmount);
|
||||
@@ -1114,7 +1129,7 @@ public class SettlementImportBatchServiceImpl
|
||||
excel.setSerialNumber(intValue(row.get("serialNumber")));
|
||||
excel.setOriginalNo(stringValue(row, "originalNo", "原始单号"));
|
||||
excel.setLoadingIdentifier(stringValue(row, "loadingIdentifier", "配载标识号"));
|
||||
excel.setVehicleNo(stringValue(row, "vehicleNo", "*车牌号/船班号/船次号/班次号"));
|
||||
excel.setVehicleNo(vehicleNoValue(row));
|
||||
excel.setTransportType(stringValue(row, "transportType", "*运输方式", "运输方式"));
|
||||
excel.setDriverName(stringValue(row, "driverName", "司机/船长姓名"));
|
||||
excel.setDriverPhone(stringValue(row, "driverPhone", "司机/船长手机号"));
|
||||
@@ -1147,6 +1162,30 @@ public class SettlementImportBatchServiceImpl
|
||||
return excel;
|
||||
}
|
||||
|
||||
/**
|
||||
* 车号以 vehicleNo 为准。单元格没填时不再回退到表头别名,避免失败明细和落库被自动补上车号。
|
||||
*/
|
||||
private void keepBlankVehicleNo(Map<String, Object> row) {
|
||||
if (row == null || !row.containsKey("vehicleNo")) {
|
||||
return;
|
||||
}
|
||||
String vehicleNo = stringValue(row, "vehicleNo");
|
||||
row.put("vehicleNo", vehicleNo);
|
||||
if (Func.isNotEmpty(vehicleNo)) {
|
||||
return;
|
||||
}
|
||||
for (String alias : VEHICLE_ALIAS_KEYS) {
|
||||
row.put(alias, null);
|
||||
}
|
||||
}
|
||||
|
||||
private String vehicleNoValue(Map<String, Object> row) {
|
||||
if (row != null && row.containsKey("vehicleNo")) {
|
||||
return stringValue(row, "vehicleNo");
|
||||
}
|
||||
return stringValue(row, "vehicleNo", VEHICLE_ALIAS_KEYS);
|
||||
}
|
||||
|
||||
private Map<String, Object> option(Object id, String nameKey, Object nameValue) {
|
||||
return option(id, nameKey, nameValue, null, null);
|
||||
}
|
||||
@@ -1351,8 +1390,15 @@ public class SettlementImportBatchServiceImpl
|
||||
row.put("vehicleNo", waybill.getVehicleNo());
|
||||
row.put("transportType", waybill.getTransportType());
|
||||
row.put("driverName", waybill.getDriverName());
|
||||
row.put("driverPhone", waybill.getDriverPhone());
|
||||
row.put("departureAddress", waybill.getDepartureAddress());
|
||||
row.put("departureContact", waybill.getDepartureContact());
|
||||
row.put("departurePhone", waybill.getDeparturePhone());
|
||||
row.put("arrivalAddress", waybill.getArrivalAddress());
|
||||
row.put("arrivalContact", waybill.getArrivalContact());
|
||||
row.put("arrivalPhone", waybill.getArrivalPhone());
|
||||
row.put("originalNo", waybill.getOriginalNo());
|
||||
row.put("loadingIdentifier", waybill.getLoadingIdentifier());
|
||||
row.put("cargoName", waybill.getCargoName());
|
||||
row.put("cargoType", waybill.getCargoType());
|
||||
row.put("quantity", waybill.getQuantity());
|
||||
|
||||
+15
-7
@@ -465,7 +465,7 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
|
||||
Waybill waybill = new Waybill();
|
||||
waybill.setOriginalNo(stringValue(row, "originalNo"));
|
||||
waybill.setLoadingIdentifier(stringValue(row, "loadingIdentifier", "配载标识号"));
|
||||
waybill.setVehicleNo(stringValue(row, "vehicleNo", "车牌号/船班号/船次号/班次号", "车牌号/航班号/船号/班列号"));
|
||||
waybill.setVehicleNo(vehicleNoFromRow(row));
|
||||
waybill.setDriverId(longValue(row, "driverId", "司机ID"));
|
||||
waybill.setDriverName(stringValue(row, "driverName", "司机/船长姓名", "司机/船长"));
|
||||
waybill.setDriverPhone(stringValue(row, "driverPhone", "司机/船长手机号"));
|
||||
@@ -542,6 +542,14 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 车号列没填时保持为空,不从其它表头别名回填。 */
|
||||
private String vehicleNoFromRow(Map<String, Object> row) {
|
||||
if (row != null && row.containsKey("vehicleNo")) {
|
||||
return stringValue(row, "vehicleNo");
|
||||
}
|
||||
return stringValue(row, "vehicleNo", "车牌号/船班号/船次号/班次号", "车牌号/航班号/船号/班列号", "车号");
|
||||
}
|
||||
|
||||
private Long longValue(Map<String, Object> row, String field, String fieldName) {
|
||||
String value = stringValue(row, field);
|
||||
if (Func.isEmpty(value)) return null;
|
||||
@@ -792,12 +800,12 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
|
||||
}
|
||||
|
||||
// 检查同一配载标识号下车牌号是否一致
|
||||
String currentVehicleNo = stringValue(row, "vehicleNo", "车牌号/船班号/船次号/班次号", "车牌号/航班号/船号/班列号");
|
||||
String currentVehicleNo = vehicleNoFromRow(row);
|
||||
for (Integer otherRowIndex : sameIdentifierRows) {
|
||||
if (otherRowIndex.equals(rowIndex)) {
|
||||
continue;
|
||||
}
|
||||
String otherVehicleNo = stringValue(allRows.get(otherRowIndex), "vehicleNo", "车牌号/船班号/船次号/班次号", "车牌号/航班号/船号/班列号");
|
||||
String otherVehicleNo = vehicleNoFromRow(allRows.get(otherRowIndex));
|
||||
if (Func.isNotEmpty(currentVehicleNo) && Func.isNotEmpty(otherVehicleNo)
|
||||
&& !currentVehicleNo.equals(otherVehicleNo)) {
|
||||
errors.add("同一配载标识号下,车牌号不一致");
|
||||
@@ -808,7 +816,7 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
|
||||
}
|
||||
|
||||
private void validateVehicleNo(Map<String, Object> row, List<String> errors) {
|
||||
String vehicleNo = stringValue(row, "vehicleNo", "车牌号/船班号/船次号/班次号", "车牌号/航班号/船号/班列号");
|
||||
String vehicleNo = vehicleNoFromRow(row);
|
||||
String transportType = stringValue(row, "transportType", "运输类型", "*运输类型", "运输方式", "*运输方式");
|
||||
|
||||
if (Func.isEmpty(transportType)) {
|
||||
@@ -1055,12 +1063,12 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
|
||||
}
|
||||
|
||||
// 检查同一运单标识号下车牌号是否一致
|
||||
String currentVehicleNo = stringValue(row, "vehicleNo", "车牌号/船班号/船次号/班次号", "车牌号/航班号/船号/班列号");
|
||||
String currentVehicleNo = vehicleNoFromRow(row);
|
||||
for (Integer otherRowIndex : sameIdentifierRows) {
|
||||
if (otherRowIndex.equals(rowIndex)) {
|
||||
continue;
|
||||
}
|
||||
String otherVehicleNo = stringValue(allRows.get(otherRowIndex), "vehicleNo", "车牌号/船班号/船次号/班次号", "车牌号/航班号/船号/班列号");
|
||||
String otherVehicleNo = vehicleNoFromRow(allRows.get(otherRowIndex));
|
||||
if (Func.isNotEmpty(currentVehicleNo) && Func.isNotEmpty(otherVehicleNo)
|
||||
&& !currentVehicleNo.equals(otherVehicleNo)) {
|
||||
errors.add("同一运单标识号下,车牌号必须一致");
|
||||
@@ -1219,7 +1227,7 @@ public class WaybillImportBatchServiceImpl extends BaseServiceImpl<WaybillImport
|
||||
WaybillImportBatchExcel excel = new WaybillImportBatchExcel();
|
||||
excel.setOriginalNo(stringValue(row, "originalNo"));
|
||||
excel.setLoadingIdentifier(stringValue(row, "loadingIdentifier", "配载标识号"));
|
||||
excel.setVehicleNo(stringValue(row, "vehicleNo", "车牌号/船班号/船次号/班次号", "车牌号/航班号/船号/班列号"));
|
||||
excel.setVehicleNo(vehicleNoFromRow(row));
|
||||
excel.setTransportType(stringValue(row, "transportType", "运输类型", "*运输类型", "运输方式", "*运输方式"));
|
||||
excel.setDriverName(stringValue(row, "driverName", "司机/船长姓名", "司机/船长"));
|
||||
excel.setDriverPhone(stringValue(row, "driverPhone", "司机/船长手机号"));
|
||||
|
||||
@@ -1324,7 +1324,9 @@ CREATE TABLE `blade_user_dept` (
|
||||
`oa_id` varchar(64) NULL DEFAULT NULL COMMENT 'OA人员ID',
|
||||
`oa_login_id` varchar(128) NULL DEFAULT NULL COMMENT 'OA登录账号',
|
||||
`sync_time` datetime NULL DEFAULT NULL COMMENT '同步时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_blade_user_dept_user` (`user_id`, `is_deleted`, `status`) USING BTREE,
|
||||
INDEX `idx_blade_user_dept_dept` (`dept_id`, `is_deleted`, `status`) USING BTREE
|
||||
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户部门表';
|
||||
|
||||
-- ----------------------------
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 用户部门关联表索引:加速人员列表按组织树过滤
|
||||
-- 部署时在业务库执行本脚本
|
||||
|
||||
ALTER TABLE `blade_user_dept`
|
||||
ADD INDEX `idx_blade_user_dept_user` (`user_id`, `is_deleted`, `status`) USING BTREE,
|
||||
ADD INDEX `idx_blade_user_dept_dept` (`dept_id`, `is_deleted`, `status`) USING BTREE;
|
||||
@@ -5487,7 +5487,9 @@ CREATE TABLE `blade_user_dept` (
|
||||
`oa_id` varchar(64) DEFAULT NULL COMMENT 'OA人员ID',
|
||||
`oa_login_id` varchar(128) DEFAULT NULL COMMENT 'OA登录账号',
|
||||
`sync_time` datetime DEFAULT NULL COMMENT '同步时间',
|
||||
PRIMARY KEY (`id`) USING BTREE
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
INDEX `idx_blade_user_dept_user` (`user_id`, `is_deleted`, `status`) USING BTREE,
|
||||
INDEX `idx_blade_user_dept_dept` (`dept_id`, `is_deleted`, `status`) USING BTREE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户部门表';
|
||||
|
||||
-- ----------------------------
|
||||
|
||||
Reference in New Issue
Block a user