调整预结算、正式结算

This commit is contained in:
2026-08-24 00:58:54 +08:00
parent 79f4c35235
commit 8c5704caba
15 changed files with 530 additions and 40 deletions
@@ -28,4 +28,16 @@ public class FormalSettlementSaveRequest implements Serializable {
private BigDecimal exchangeRate; private BigDecimal exchangeRate;
private String attachmentsJson; private String attachmentsJson;
private String remark; private String remark;
private List<SummaryFee> summaryFees;
@Data
public static class SummaryFee implements Serializable {
@Serial private static final long serialVersionUID = 1L;
private Long id;
private String feeType;
private String feeItem;
private BigDecimal adjustAmount;
private String remark;
private Integer manualFlag;
}
} }
@@ -57,6 +57,15 @@ public class CommonRoute extends TenantEntity {
@Schema(description = "发货地") @Schema(description = "发货地")
private String departureName; private String departureName;
@Schema(description = "发货省ID")
private String departureProvinceId;
@Schema(description = "发货市ID")
private String departureCityId;
@Schema(description = "发货区ID")
private String departureDistrictId;
@Schema(description = "发货地址") @Schema(description = "发货地址")
private String departureAddress; private String departureAddress;
@@ -78,6 +87,15 @@ public class CommonRoute extends TenantEntity {
@Schema(description = "收货地") @Schema(description = "收货地")
private String arrivalName; private String arrivalName;
@Schema(description = "收货省ID")
private String arrivalProvinceId;
@Schema(description = "收货市ID")
private String arrivalCityId;
@Schema(description = "收货区ID")
private String arrivalDistrictId;
@Schema(description = "收货地址") @Schema(description = "收货地址")
private String arrivalAddress; private String arrivalAddress;
@@ -0,0 +1,40 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import java.io.Serial;
import java.math.BigDecimal;
/**
* 正式结算合计费用实体类
*
* @author Chill
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("blade_formal_settlement_summary_fee")
@Schema(description = "正式结算合计费用")
public class FormalSettlementSummaryFee extends TenantEntity {
@Serial private static final long serialVersionUID = 1L;
private Long formalSettlementId;
private Integer lineNo;
private String feeType;
private String feeItem;
private BigDecimal originalAmount;
private BigDecimal adjustAmount;
private BigDecimal settlementAmount;
private String remark;
private Integer manualFlag;
}
@@ -15,6 +15,7 @@ import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.FormalSettlementDetail; import org.springblade.transport.pojo.entity.FormalSettlementDetail;
import org.springblade.transport.pojo.entity.FormalSettlementSource; import org.springblade.transport.pojo.entity.FormalSettlementSource;
import org.springblade.transport.pojo.entity.FormalSettlementPayment; import org.springblade.transport.pojo.entity.FormalSettlementPayment;
import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serial; import java.io.Serial;
@@ -39,5 +40,6 @@ public class FormalSettlementVO extends FormalSettlement {
@TableField(exist = false) private String preSettlementNo; @TableField(exist = false) private String preSettlementNo;
@TableField(exist = false) private List<FormalSettlementSource> sources; @TableField(exist = false) private List<FormalSettlementSource> sources;
@TableField(exist = false) private List<FormalSettlementDetail> details; @TableField(exist = false) private List<FormalSettlementDetail> details;
@TableField(exist = false) private List<FormalSettlementSummaryFee> summaryFees;
@TableField(exist = false) private List<FormalSettlementPayment> payments; @TableField(exist = false) private List<FormalSettlementPayment> payments;
} }
@@ -73,64 +73,79 @@ public class FormalSettlementController extends BladeController {
@GetMapping("/contract-options") @GetMapping("/contract-options")
@ApiOperationSupport(order = 4) @ApiOperationSupport(order = 4)
@Operation(summary = "可选合同") @Operation(summary = "可选合同")
public R<List<Map<String, Object>>> contractOptions(@RequestParam(required = false) String keyword) { public R<List<Map<String, Object>>> contractOptions(@RequestParam(required = false) String keyword,
return R.data(preSettlementService.contractOptions(keyword)); @RequestParam(required = false) Long projectId) {
return R.data(preSettlementService.contractOptions(keyword, projectId));
}
@GetMapping("/next-no")
@ApiOperationSupport(order = 5)
@Operation(summary = "获取最新正式结算单号")
public R<String> nextNo() {
return R.data(formalSettlementService.nextNo());
}
@GetMapping("/fee-options")
@ApiOperationSupport(order = 6)
@Operation(summary = "可选费用类型及费用项")
public R<List<Map<String, Object>>> feeOptions() {
return R.data(preSettlementService.feeOptions());
} }
@GetMapping("/candidate-details") @GetMapping("/candidate-details")
@ApiOperationSupport(order = 5) @ApiOperationSupport(order = 7)
@Operation(summary = "可选应收应付明细") @Operation(summary = "可选应收应付明细")
public R<IPage<Map<String, Object>>> candidateDetails(Query query, @RequestParam Long contractId, public R<IPage<Map<String, Object>>> candidateDetails(Query query, @RequestParam Long contractId,
@RequestParam String settlementType, @RequestParam(required = false) String batchNo, @RequestParam String settlementType, @RequestParam(required = false) String batchNo,
@RequestParam(required = false) String feeStartDate, @RequestParam(required = false) String feeEndDate) { @RequestParam(required = false) String createStartDate, @RequestParam(required = false) String createEndDate) {
return R.data(preSettlementService.candidateDetails(Condition.getPage(query), contractId, return R.data(preSettlementService.candidateDetailsByCreateTime(Condition.getPage(query), contractId,
settlementType, batchNo, feeStartDate, feeEndDate)); settlementType, batchNo, createStartDate, createEndDate));
} }
@PostMapping("/save") @PostMapping("/save")
@ApiOperationSupport(order = 6) @ApiOperationSupport(order = 8)
@Operation(summary = "保存正式结算草稿") @Operation(summary = "保存正式结算草稿")
public R<Long> save(@RequestBody FormalSettlementSaveRequest request) { return R.data(formalSettlementService.saveDraft(request)); } public R<Long> save(@RequestBody FormalSettlementSaveRequest request) { return R.data(formalSettlementService.saveDraft(request)); }
@PostMapping("/remove") @PostMapping("/remove")
@ApiOperationSupport(order = 7) @ApiOperationSupport(order = 9)
@Operation(summary = "删除正式结算草稿") @Operation(summary = "删除正式结算草稿")
public R remove(@RequestParam Long id) { formalSettlementService.removeDraft(id); return R.success("删除成功"); } public R remove(@RequestParam Long id) { formalSettlementService.removeDraft(id); return R.success("删除成功"); }
@PostMapping("/submit") @PostMapping("/submit")
@ApiOperationSupport(order = 8) @ApiOperationSupport(order = 10)
@Operation(summary = "提交审批") @Operation(summary = "提交审批")
public R submit(@RequestBody FormalSettlementStatusRequest request) { formalSettlementService.submit(request); return R.success("提交成功"); } public R submit(@RequestBody FormalSettlementStatusRequest request) { formalSettlementService.submit(request); return R.success("提交成功"); }
@PostMapping("/approve") @PostMapping("/approve")
@ApiOperationSupport(order = 9) @ApiOperationSupport(order = 11)
@Operation(summary = "审批通过") @Operation(summary = "审批通过")
public R approve(@RequestBody FormalSettlementStatusRequest request) { formalSettlementService.approve(request); return R.success("审批通过"); } public R approve(@RequestBody FormalSettlementStatusRequest request) { formalSettlementService.approve(request); return R.success("审批通过"); }
@PostMapping("/return") @PostMapping("/return")
@ApiOperationSupport(order = 10) @ApiOperationSupport(order = 12)
@Operation(summary = "审批驳回") @Operation(summary = "审批驳回")
public R returnBill(@RequestBody FormalSettlementStatusRequest request) { formalSettlementService.returnBill(request); return R.success("已驳回"); } public R returnBill(@RequestBody FormalSettlementStatusRequest request) { formalSettlementService.returnBill(request); return R.success("已驳回"); }
@PostMapping("/void") @PostMapping("/void")
@ApiOperationSupport(order = 11) @ApiOperationSupport(order = 13)
@Operation(summary = "作废正式结算单") @Operation(summary = "作废正式结算单")
public R voidBill(@RequestBody FormalSettlementStatusRequest request) { formalSettlementService.voidBill(request); return R.success("作废成功"); } public R voidBill(@RequestBody FormalSettlementStatusRequest request) { formalSettlementService.voidBill(request); return R.success("作废成功"); }
@PostMapping("/sync-kingdee") @PostMapping("/sync-kingdee")
@ApiOperationSupport(order = 12) @ApiOperationSupport(order = 14)
@Operation(summary = "推送金蝶应付单") @Operation(summary = "推送金蝶应付单")
public R<String> syncKingdee(@RequestParam Long id) { return R.data(formalSettlementService.syncKingdee(id)); } public R<String> syncKingdee(@RequestParam Long id) { return R.data(formalSettlementService.syncKingdee(id)); }
@GetMapping("/detail-fees") @GetMapping("/detail-fees")
@ApiOperationSupport(order = 13) @ApiOperationSupport(order = 15)
@Operation(summary = "正式结算货物费用快照") @Operation(summary = "正式结算货物费用快照")
public R<List<FormalSettlementDetailFee>> detailFees(@RequestParam Long detailId) { public R<List<FormalSettlementDetailFee>> detailFees(@RequestParam Long detailId) {
return R.data(formalSettlementService.detailFees(detailId)); return R.data(formalSettlementService.detailFees(detailId));
} }
@PostMapping("/adjust-detail") @PostMapping("/adjust-detail")
@ApiOperationSupport(order = 14) @ApiOperationSupport(order = 16)
@Operation(summary = "调整草稿结算明细") @Operation(summary = "调整草稿结算明细")
public R adjustDetail(@RequestBody PreSettlementDetailAdjustRequest request) { public R adjustDetail(@RequestBody PreSettlementDetailAdjustRequest request) {
formalSettlementService.adjustDetail(request); formalSettlementService.adjustDetail(request);
@@ -138,7 +153,7 @@ public class FormalSettlementController extends BladeController {
} }
@PostMapping("/apply-payment") @PostMapping("/apply-payment")
@ApiOperationSupport(order = 15) @ApiOperationSupport(order = 17)
@Operation(summary = "发起尾款付款申请") @Operation(summary = "发起尾款付款申请")
public R<String> applyPayment(@RequestBody FormalSettlementPaymentRequest request) { public R<String> applyPayment(@RequestBody FormalSettlementPaymentRequest request) {
return R.data(formalSettlementService.applyPayment(request)); return R.data(formalSettlementService.applyPayment(request));
@@ -0,0 +1,20 @@
/**
* 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>
* Author: Chill Zhuang (bladejava@qq.com)
*/
package org.springblade.transport.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee;
/**
* 正式结算合计费用 Mapper
*
* @author Chill
*/
public interface FormalSettlementSummaryFeeMapper extends BaseMapper<FormalSettlementSummaryFee> {
}
@@ -29,6 +29,7 @@ import org.springblade.transport.pojo.dto.FormalSettlementPaymentRequest;
public interface IFormalSettlementService extends BaseService<FormalSettlement> { public interface IFormalSettlementService extends BaseService<FormalSettlement> {
IPage<FormalSettlementVO> selectPage(IPage<FormalSettlement> page, FormalSettlementVO query); IPage<FormalSettlementVO> selectPage(IPage<FormalSettlement> page, FormalSettlementVO query);
IPage<PreSettlementVO> candidatePreSettlements(IPage<PreSettlement> page, PreSettlementVO query); IPage<PreSettlementVO> candidatePreSettlements(IPage<PreSettlement> page, PreSettlementVO query);
String nextNo();
FormalSettlementVO detail(Long id); FormalSettlementVO detail(Long id);
Long saveDraft(FormalSettlementSaveRequest request); Long saveDraft(FormalSettlementSaveRequest request);
void removeDraft(Long id); void removeDraft(Long id);
@@ -33,13 +33,20 @@ public interface IPreSettlementService extends BaseService<PreSettlement> {
PreSettlementVO detail(Long id); PreSettlementVO detail(Long id);
List<Map<String, Object>> contractOptions(String keyword); default List<Map<String, Object>> contractOptions(String keyword) {
return contractOptions(keyword, null);
}
List<Map<String, Object>> contractOptions(String keyword, Long projectId);
List<Map<String, Object>> feeOptions(); List<Map<String, Object>> feeOptions();
IPage<Map<String, Object>> candidateDetails(IPage<?> page, Long contractId, String settlementType, IPage<Map<String, Object>> candidateDetails(IPage<?> page, Long contractId, String settlementType,
String batchNo, String feeStartDate, String feeEndDate); String batchNo, String feeStartDate, String feeEndDate);
IPage<Map<String, Object>> candidateDetailsByCreateTime(IPage<?> page, Long contractId,
String settlementType, String batchNo, String createStartDate, String createEndDate);
Long saveDraft(PreSettlementSaveRequest request); Long saveDraft(PreSettlementSaveRequest request);
void removeDraft(Long id); void removeDraft(Long id);
@@ -194,10 +194,16 @@ public class CommonRouteServiceImpl extends BaseServiceImpl<CommonRouteMapper, C
commonRoute.setRouteCode(TransportBusinessSupport.trimToNull(commonRoute.getRouteCode())); commonRoute.setRouteCode(TransportBusinessSupport.trimToNull(commonRoute.getRouteCode()));
commonRoute.setRouteName(TransportBusinessSupport.trimToNull(commonRoute.getRouteName())); commonRoute.setRouteName(TransportBusinessSupport.trimToNull(commonRoute.getRouteName()));
commonRoute.setDepartureName(TransportBusinessSupport.trimToNull(commonRoute.getDepartureName())); commonRoute.setDepartureName(TransportBusinessSupport.trimToNull(commonRoute.getDepartureName()));
commonRoute.setDepartureProvinceId(TransportBusinessSupport.trimToNull(commonRoute.getDepartureProvinceId()));
commonRoute.setDepartureCityId(TransportBusinessSupport.trimToNull(commonRoute.getDepartureCityId()));
commonRoute.setDepartureDistrictId(TransportBusinessSupport.trimToNull(commonRoute.getDepartureDistrictId()));
commonRoute.setDepartureAddress(TransportBusinessSupport.trimToNull(commonRoute.getDepartureAddress())); commonRoute.setDepartureAddress(TransportBusinessSupport.trimToNull(commonRoute.getDepartureAddress()));
commonRoute.setDepartureContact(TransportBusinessSupport.trimToNull(commonRoute.getDepartureContact())); commonRoute.setDepartureContact(TransportBusinessSupport.trimToNull(commonRoute.getDepartureContact()));
commonRoute.setDeparturePhone(TransportBusinessSupport.trimToNull(commonRoute.getDeparturePhone())); commonRoute.setDeparturePhone(TransportBusinessSupport.trimToNull(commonRoute.getDeparturePhone()));
commonRoute.setArrivalName(TransportBusinessSupport.trimToNull(commonRoute.getArrivalName())); commonRoute.setArrivalName(TransportBusinessSupport.trimToNull(commonRoute.getArrivalName()));
commonRoute.setArrivalProvinceId(TransportBusinessSupport.trimToNull(commonRoute.getArrivalProvinceId()));
commonRoute.setArrivalCityId(TransportBusinessSupport.trimToNull(commonRoute.getArrivalCityId()));
commonRoute.setArrivalDistrictId(TransportBusinessSupport.trimToNull(commonRoute.getArrivalDistrictId()));
commonRoute.setArrivalAddress(TransportBusinessSupport.trimToNull(commonRoute.getArrivalAddress())); commonRoute.setArrivalAddress(TransportBusinessSupport.trimToNull(commonRoute.getArrivalAddress()));
commonRoute.setArrivalContact(TransportBusinessSupport.trimToNull(commonRoute.getArrivalContact())); commonRoute.setArrivalContact(TransportBusinessSupport.trimToNull(commonRoute.getArrivalContact()));
commonRoute.setArrivalPhone(TransportBusinessSupport.trimToNull(commonRoute.getArrivalPhone())); commonRoute.setArrivalPhone(TransportBusinessSupport.trimToNull(commonRoute.getArrivalPhone()));
@@ -217,15 +223,33 @@ public class CommonRouteServiceImpl extends BaseServiceImpl<CommonRouteMapper, C
TransportBusinessSupport.validateRequired(commonRoute.getDepartureAddress(), "发货地址不能为空"); TransportBusinessSupport.validateRequired(commonRoute.getDepartureAddress(), "发货地址不能为空");
TransportBusinessSupport.validateRequired(commonRoute.getArrivalName(), "收货地不能为空"); TransportBusinessSupport.validateRequired(commonRoute.getArrivalName(), "收货地不能为空");
TransportBusinessSupport.validateRequired(commonRoute.getArrivalAddress(), "收货地址不能为空"); TransportBusinessSupport.validateRequired(commonRoute.getArrivalAddress(), "收货地址不能为空");
validateRegionIds(
commonRoute.getDepartureProvinceId(),
commonRoute.getDepartureCityId(),
commonRoute.getDepartureDistrictId(),
"发货地址"
);
validateRegionIds(
commonRoute.getArrivalProvinceId(),
commonRoute.getArrivalCityId(),
commonRoute.getArrivalDistrictId(),
"收货地址"
);
validateRouteNameUnique(commonRoute); validateRouteNameUnique(commonRoute);
validateAddressCombinationUnique(commonRoute); validateAddressCombinationUnique(commonRoute);
TransportBusinessSupport.validateLength(commonRoute.getRouteCode(), 255, "线路编号不能超过255字"); TransportBusinessSupport.validateLength(commonRoute.getRouteCode(), 255, "线路编号不能超过255字");
TransportBusinessSupport.validateLength(commonRoute.getRouteName(), 255, "线路名称不能超过255字"); TransportBusinessSupport.validateLength(commonRoute.getRouteName(), 255, "线路名称不能超过255字");
TransportBusinessSupport.validateLength(commonRoute.getDepartureName(), 255, "发货地不能超过255字"); TransportBusinessSupport.validateLength(commonRoute.getDepartureName(), 255, "发货地不能超过255字");
TransportBusinessSupport.validateLength(commonRoute.getDepartureProvinceId(), 32, "发货省ID不能超过32字");
TransportBusinessSupport.validateLength(commonRoute.getDepartureCityId(), 32, "发货市ID不能超过32字");
TransportBusinessSupport.validateLength(commonRoute.getDepartureDistrictId(), 32, "发货区ID不能超过32字");
TransportBusinessSupport.validateLength(commonRoute.getDepartureAddress(), 255, "发货地址不能超过255字"); TransportBusinessSupport.validateLength(commonRoute.getDepartureAddress(), 255, "发货地址不能超过255字");
TransportBusinessSupport.validateLength(commonRoute.getDepartureContact(), 255, "发货联系人不能超过255字"); TransportBusinessSupport.validateLength(commonRoute.getDepartureContact(), 255, "发货联系人不能超过255字");
TransportBusinessSupport.validateLength(commonRoute.getDeparturePhone(), 255, "发货联系方式不能超过255字"); TransportBusinessSupport.validateLength(commonRoute.getDeparturePhone(), 255, "发货联系方式不能超过255字");
TransportBusinessSupport.validateLength(commonRoute.getArrivalName(), 255, "收货地不能超过255字"); TransportBusinessSupport.validateLength(commonRoute.getArrivalName(), 255, "收货地不能超过255字");
TransportBusinessSupport.validateLength(commonRoute.getArrivalProvinceId(), 32, "收货省ID不能超过32字");
TransportBusinessSupport.validateLength(commonRoute.getArrivalCityId(), 32, "收货市ID不能超过32字");
TransportBusinessSupport.validateLength(commonRoute.getArrivalDistrictId(), 32, "收货区ID不能超过32字");
TransportBusinessSupport.validateLength(commonRoute.getArrivalAddress(), 255, "收货地址不能超过255字"); TransportBusinessSupport.validateLength(commonRoute.getArrivalAddress(), 255, "收货地址不能超过255字");
TransportBusinessSupport.validateLength(commonRoute.getArrivalContact(), 255, "收货联系人不能超过255字"); TransportBusinessSupport.validateLength(commonRoute.getArrivalContact(), 255, "收货联系人不能超过255字");
TransportBusinessSupport.validateLength(commonRoute.getArrivalPhone(), 255, "收货联系方式不能超过255字"); TransportBusinessSupport.validateLength(commonRoute.getArrivalPhone(), 255, "收货联系方式不能超过255字");
@@ -239,6 +263,14 @@ public class CommonRouteServiceImpl extends BaseServiceImpl<CommonRouteMapper, C
TransportBusinessSupport.validateCoordinate(commonRoute.getArrivalLatitude(), false); TransportBusinessSupport.validateCoordinate(commonRoute.getArrivalLatitude(), false);
} }
private void validateRegionIds(String provinceId, String cityId, String districtId, String addressLabel) {
boolean allEmpty = Func.isEmpty(provinceId) && Func.isEmpty(cityId) && Func.isEmpty(districtId);
boolean allPresent = Func.isNotEmpty(provinceId) && Func.isNotEmpty(cityId) && Func.isNotEmpty(districtId);
if (!allEmpty && !allPresent) {
throw new ServiceException(addressLabel + "的省、市、区ID必须完整");
}
}
private void validateRouteNameUnique(CommonRoute commonRoute) { private void validateRouteNameUnique(CommonRoute commonRoute) {
LambdaQueryWrapper<CommonRoute> queryWrapper = Wrappers.<CommonRoute>lambdaQuery() LambdaQueryWrapper<CommonRoute> queryWrapper = Wrappers.<CommonRoute>lambdaQuery()
.eq(CommonRoute::getIsDeleted, 0) .eq(CommonRoute::getIsDeleted, 0)
@@ -21,10 +21,12 @@ import org.springblade.transport.mapper.FormalSettlementDetailMapper;
import org.springblade.transport.mapper.FormalSettlementDetailFeeMapper; import org.springblade.transport.mapper.FormalSettlementDetailFeeMapper;
import org.springblade.transport.mapper.FormalSettlementMapper; import org.springblade.transport.mapper.FormalSettlementMapper;
import org.springblade.transport.mapper.FormalSettlementSourceMapper; import org.springblade.transport.mapper.FormalSettlementSourceMapper;
import org.springblade.transport.mapper.FormalSettlementSummaryFeeMapper;
import org.springblade.transport.mapper.FormalSettlementPaymentMapper; import org.springblade.transport.mapper.FormalSettlementPaymentMapper;
import org.springblade.transport.mapper.PreSettlementDetailMapper; import org.springblade.transport.mapper.PreSettlementDetailMapper;
import org.springblade.transport.mapper.PreSettlementDetailFeeMapper; import org.springblade.transport.mapper.PreSettlementDetailFeeMapper;
import org.springblade.transport.mapper.PreSettlementMapper; import org.springblade.transport.mapper.PreSettlementMapper;
import org.springblade.transport.mapper.PreSettlementSummaryFeeMapper;
import org.springblade.transport.mapper.ReceivablePayableDetailMapper; import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper; import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest; import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest;
@@ -35,11 +37,13 @@ import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.FormalSettlementDetail; import org.springblade.transport.pojo.entity.FormalSettlementDetail;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee; import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
import org.springblade.transport.pojo.entity.FormalSettlementSource; import org.springblade.transport.pojo.entity.FormalSettlementSource;
import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee;
import org.springblade.transport.pojo.entity.FormalSettlementPayment; import org.springblade.transport.pojo.entity.FormalSettlementPayment;
import org.springblade.transport.pojo.entity.ContractManage; import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.PreSettlement; import org.springblade.transport.pojo.entity.PreSettlement;
import org.springblade.transport.pojo.entity.PreSettlementDetail; import org.springblade.transport.pojo.entity.PreSettlementDetail;
import org.springblade.transport.pojo.entity.PreSettlementDetailFee; import org.springblade.transport.pojo.entity.PreSettlementDetailFee;
import org.springblade.transport.pojo.entity.PreSettlementSummaryFee;
import org.springblade.transport.pojo.entity.ReceivablePayableDetail; import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee; import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee;
import org.springblade.transport.pojo.entity.Waybill; import org.springblade.transport.pojo.entity.Waybill;
@@ -47,6 +51,7 @@ import org.springblade.transport.pojo.vo.FormalSettlementVO;
import org.springblade.transport.pojo.vo.PreSettlementVO; import org.springblade.transport.pojo.vo.PreSettlementVO;
import org.springblade.transport.service.IFormalSettlementService; import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.service.IContractManageService; import org.springblade.transport.service.IContractManageService;
import org.springblade.transport.service.IPreSettlementService;
import org.springblade.transport.service.IWaybillService; import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.wrapper.PreSettlementWrapper; import org.springblade.transport.wrapper.PreSettlementWrapper;
import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.jackson.JsonUtil;
@@ -55,12 +60,17 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -79,15 +89,18 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
private static final String RETURNED = "returned"; private static final String RETURNED = "returned";
private static final String VOIDED = "voided"; private static final String VOIDED = "voided";
private final FormalSettlementSourceMapper sourceMapper; private final FormalSettlementSourceMapper sourceMapper;
private final FormalSettlementSummaryFeeMapper summaryFeeMapper;
private final FormalSettlementPaymentMapper paymentMapper; private final FormalSettlementPaymentMapper paymentMapper;
private final FormalSettlementDetailMapper detailMapper; private final FormalSettlementDetailMapper detailMapper;
private final FormalSettlementDetailFeeMapper detailFeeMapper; private final FormalSettlementDetailFeeMapper detailFeeMapper;
private final PreSettlementMapper preSettlementMapper; private final PreSettlementMapper preSettlementMapper;
private final PreSettlementSummaryFeeMapper preSummaryFeeMapper;
private final PreSettlementDetailMapper preDetailMapper; private final PreSettlementDetailMapper preDetailMapper;
private final PreSettlementDetailFeeMapper preDetailFeeMapper; private final PreSettlementDetailFeeMapper preDetailFeeMapper;
private final ReceivablePayableDetailMapper receivablePayableMapper; private final ReceivablePayableDetailMapper receivablePayableMapper;
private final ReceivablePayableCargoFeeMapper receivablePayableCargoFeeMapper; private final ReceivablePayableCargoFeeMapper receivablePayableCargoFeeMapper;
private final IContractManageService contractManageService; private final IContractManageService contractManageService;
private final IPreSettlementService preSettlementService;
private final IWaybillService waybillService; private final IWaybillService waybillService;
@Override @Override
@@ -130,6 +143,26 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
return PreSettlementWrapper.build().pageVO(result); return PreSettlementWrapper.build().pageVO(result);
} }
@Override
public synchronized String nextNo() {
String prefix = "JS" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
List<String> numbers = list(Wrappers.<FormalSettlement>lambdaQuery()
.select(FormalSettlement::getFormalSettlementNo)
.likeRight(FormalSettlement::getFormalSettlementNo, prefix))
.stream().map(FormalSettlement::getFormalSettlementNo).toList();
int sequence = 1;
if (!numbers.isEmpty()) {
String latest = numbers.stream().max(String::compareTo).orElse(prefix);
try {
sequence = Integer.parseInt(latest.substring(prefix.length())) + 1;
} catch (RuntimeException ignored) {
sequence = Math.toIntExact(count(Wrappers.<FormalSettlement>lambdaQuery()
.likeRight(FormalSettlement::getFormalSettlementNo, prefix))) + 1;
}
}
return prefix + String.format("%04d", sequence);
}
@Override @Override
public FormalSettlementVO detail(Long id) { public FormalSettlementVO detail(Long id) {
FormalSettlement settlement = existing(id); FormalSettlement settlement = existing(id);
@@ -138,6 +171,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
.eq(FormalSettlementSource::getFormalSettlementId, id).orderByAsc(FormalSettlementSource::getCreateTime))); .eq(FormalSettlementSource::getFormalSettlementId, id).orderByAsc(FormalSettlementSource::getCreateTime)));
vo.setDetails(detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery() vo.setDetails(detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
.eq(FormalSettlementDetail::getFormalSettlementId, id).orderByAsc(FormalSettlementDetail::getLineNo))); .eq(FormalSettlementDetail::getFormalSettlementId, id).orderByAsc(FormalSettlementDetail::getLineNo)));
vo.setSummaryFees(listSummaryFees(id));
vo.setPayments(paymentMapper.selectList(Wrappers.<FormalSettlementPayment>lambdaQuery() vo.setPayments(paymentMapper.selectList(Wrappers.<FormalSettlementPayment>lambdaQuery()
.eq(FormalSettlementPayment::getFormalSettlementId, id).orderByDesc(FormalSettlementPayment::getCreateTime))); .eq(FormalSettlementPayment::getFormalSettlementId, id).orderByDesc(FormalSettlementPayment::getCreateTime)));
return vo; return vo;
@@ -192,6 +226,9 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
settlement.setRemark(limit(request.getRemark(), 200)); settlement.setRemark(limit(request.getRemark(), 200));
saveOrUpdate(settlement); saveOrUpdate(settlement);
rebuildSnapshots(settlement, sources, directDetails); rebuildSnapshots(settlement, sources, directDetails);
rebuildSummaryFees(settlement.getId());
applySummaryRequest(settlement.getId(), request.getSummaryFees());
refreshSettlementAmount(settlement);
return settlement.getId(); return settlement.getId();
} }
@@ -205,6 +242,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
if (!detailIds.isEmpty()) detailFeeMapper.delete(Wrappers.<FormalSettlementDetailFee>lambdaQuery() if (!detailIds.isEmpty()) detailFeeMapper.delete(Wrappers.<FormalSettlementDetailFee>lambdaQuery()
.in(FormalSettlementDetailFee::getFormalSettlementDetailId, detailIds)); .in(FormalSettlementDetailFee::getFormalSettlementDetailId, detailIds));
detailMapper.delete(Wrappers.<FormalSettlementDetail>lambdaQuery().eq(FormalSettlementDetail::getFormalSettlementId, id)); detailMapper.delete(Wrappers.<FormalSettlementDetail>lambdaQuery().eq(FormalSettlementDetail::getFormalSettlementId, id));
summaryFeeMapper.delete(Wrappers.<FormalSettlementSummaryFee>lambdaQuery()
.eq(FormalSettlementSummaryFee::getFormalSettlementId, id));
removeById(id); removeById(id);
} }
@@ -312,14 +351,232 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
detail.setAdjustAmount(detail.getSettlementAmountTax().subtract(detail.getOriginalAmount())); detail.setAdjustAmount(detail.getSettlementAmountTax().subtract(detail.getOriginalAmount()));
detail.setRemark(reason); detail.setRemark(reason);
detailMapper.updateById(detail); detailMapper.updateById(detail);
BigDecimal amount = detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery() rebuildSummaryFees(settlement.getId());
.eq(FormalSettlementDetail::getFormalSettlementId, settlement.getId())).stream() refreshSettlementAmount(settlement);
.map(FormalSettlementDetail::getSettlementAmountTax).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add); }
private void rebuildSummaryFees(Long settlementId) {
List<FormalSettlementSummaryFee> existingRows = listSummaryFees(settlementId);
Map<String, FormalSettlementSummaryFee> existingGenerated = existingRows.stream()
.filter(row -> !Integer.valueOf(1).equals(row.getManualFlag()))
.collect(Collectors.toMap(this::summaryKey, Function.identity(), (first, second) -> first));
List<FormalSettlementSummaryFee> existingManualRows = existingRows.stream()
.filter(row -> Integer.valueOf(1).equals(row.getManualFlag())).toList();
Map<String, BigDecimal> aggregates = new LinkedHashMap<>();
for (FormalSettlementSource source : sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.eq(FormalSettlementSource::getFormalSettlementId, settlementId))) {
List<PreSettlementSummaryFee> sourceSummary = preSummaryFeeMapper.selectList(
Wrappers.<PreSettlementSummaryFee>lambdaQuery()
.eq(PreSettlementSummaryFee::getPreSettlementId, source.getPreSettlementId())
.eq(PreSettlementSummaryFee::getIsDeleted, 0));
sourceSummary.forEach(row -> {
BigDecimal additionalAmount = Integer.valueOf(1).equals(row.getManualFlag())
? money(row.getSettlementAmount()) : money(row.getAdjustAmount());
aggregates.merge(summaryKey(row.getFeeType(), row.getFeeItem()), additionalAmount, BigDecimal::add);
});
}
Map<String, String> feeTypeMap = feeTypeMap();
for (FormalSettlementDetail detail : detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
.eq(FormalSettlementDetail::getFormalSettlementId, settlementId))) {
List<FormalSettlementDetailFee> detailFees = detailFeeMapper.selectList(
Wrappers.<FormalSettlementDetailFee>lambdaQuery()
.eq(FormalSettlementDetailFee::getFormalSettlementDetailId, detail.getId())
.eq(FormalSettlementDetailFee::getIsDeleted, 0));
appendFeeAggregates(aggregates, feeTypeMap, detailFees);
}
summaryFeeMapper.delete(Wrappers.<FormalSettlementSummaryFee>lambdaQuery()
.eq(FormalSettlementSummaryFee::getFormalSettlementId, settlementId)
.eq(FormalSettlementSummaryFee::getManualFlag, 0));
int lineNo = 1;
for (Map.Entry<String, BigDecimal> entry : aggregates.entrySet()) {
String[] keyParts = entry.getKey().split("@@@", 2);
FormalSettlementSummaryFee old = existingGenerated.get(entry.getKey());
FormalSettlementSummaryFee row = new FormalSettlementSummaryFee();
row.setFormalSettlementId(settlementId);
row.setFeeType(keyParts[0]);
row.setFeeItem(keyParts.length > 1 ? keyParts[1] : "");
row.setOriginalAmount(money(entry.getValue()));
row.setAdjustAmount(old == null ? BigDecimal.ZERO.setScale(2) : money(old.getAdjustAmount()));
row.setSettlementAmount(row.getOriginalAmount().add(row.getAdjustAmount()));
row.setRemark(old == null ? "" : old.getRemark());
row.setManualFlag(0);
row.setLineNo(lineNo++);
summaryFeeMapper.insert(row);
}
for (FormalSettlementSummaryFee manualRow : existingManualRows) {
manualRow.setLineNo(lineNo++);
summaryFeeMapper.updateById(manualRow);
}
renumberSummaryFees(settlementId);
}
private void appendFeeAggregates(Map<String, BigDecimal> aggregates, Map<String, String> feeTypeMap,
List<FormalSettlementDetailFee> detailFees) {
for (FormalSettlementDetailFee fee : detailFees) {
Map<String, BigDecimal> feeItems = parseFeeItems(fee.getFeeItemsJson());
boolean containsFreight = feeItems.keySet().stream().anyMatch(this::isFreightFeeItem);
BigDecimal knownAmount = feeItems.values().stream().map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (!containsFreight) {
BigDecimal freightAmount = money(fee.getFreightAmount());
aggregates.merge(summaryKey("物流配送", "运输费"), freightAmount, BigDecimal::add);
knownAmount = knownAmount.add(freightAmount);
}
feeItems.forEach((feeItem, amount) -> aggregates.merge(
summaryKey(feeTypeMap.getOrDefault(feeItem,
isFreightFeeItem(feeItem) ? "物流配送" : "其他费用"), feeItem),
money(amount), BigDecimal::add));
BigDecimal residualAmount = money(fee.getSettlementAmountTax()).subtract(knownAmount);
if (residualAmount.signum() != 0) {
aggregates.merge(summaryKey("其他费用", "其他费用"), residualAmount, BigDecimal::add);
}
}
}
private void applySummaryRequest(Long settlementId, List<FormalSettlementSaveRequest.SummaryFee> requestRows) {
if (requestRows == null) return;
Map<String, Set<String>> allowedManualFees = new LinkedHashMap<>();
for (Map<String, Object> option : preSettlementService.feeOptions()) {
Set<String> feeItems = new LinkedHashSet<>();
if (option.get("feeItems") instanceof List<?> values) {
values.forEach(value -> feeItems.add(String.valueOf(value)));
}
allowedManualFees.put(String.valueOf(option.get("feeType")), feeItems);
}
Map<Long, FormalSettlementSummaryFee> existingMap = listSummaryFees(settlementId).stream()
.collect(Collectors.toMap(FormalSettlementSummaryFee::getId, Function.identity()));
List<FormalSettlementSummaryFee> existingManualRows = existingMap.values().stream()
.filter(row -> Integer.valueOf(1).equals(row.getManualFlag())).toList();
Set<Long> retainedManualIds = new LinkedHashSet<>();
int nextLineNo = existingMap.size() + 1;
for (FormalSettlementSaveRequest.SummaryFee requestRow : requestRows) {
if (Integer.valueOf(1).equals(requestRow.getManualFlag())) {
String feeType = requiredText(requestRow.getFeeType(), "费用类型");
String feeItem = requiredText(requestRow.getFeeItem(), "费用项");
if (!allowedManualFees.getOrDefault(feeType, Set.of()).contains(feeItem)) {
throw new ServiceException("费用类型与费用项不匹配或费用项已停用");
}
FormalSettlementSummaryFee row = requestRow.getId() == null ? null : existingMap.get(requestRow.getId());
if (row == null && requestRow.getId() == null) row = new FormalSettlementSummaryFee();
if (row == null || row.getId() != null && !Integer.valueOf(1).equals(row.getManualFlag())) {
throw new ServiceException("存在无效的手工费用行");
}
row.setFormalSettlementId(settlementId);
row.setFeeType(feeType);
row.setFeeItem(feeItem);
row.setOriginalAmount(BigDecimal.ZERO.setScale(2));
row.setAdjustAmount(money(requestRow.getAdjustAmount()));
row.setSettlementAmount(row.getAdjustAmount());
row.setRemark(limit(requestRow.getRemark(), 50));
row.setManualFlag(1);
if (row.getId() == null) {
row.setLineNo(nextLineNo++);
summaryFeeMapper.insert(row);
} else {
summaryFeeMapper.updateById(row);
}
retainedManualIds.add(row.getId());
continue;
}
FormalSettlementSummaryFee row = existingMap.get(requestRow.getId());
if (row == null) {
row = existingMap.values().stream()
.filter(item -> !Integer.valueOf(1).equals(item.getManualFlag()))
.filter(item -> Objects.equals(summaryKey(item),
summaryKey(requestRow.getFeeType(), requestRow.getFeeItem())))
.findFirst().orElse(null);
}
if (row == null || Integer.valueOf(1).equals(row.getManualFlag())) {
throw new ServiceException("存在无效的结算合计行");
}
row.setAdjustAmount(money(requestRow.getAdjustAmount()));
row.setSettlementAmount(money(row.getOriginalAmount()).add(row.getAdjustAmount()));
row.setRemark(limit(requestRow.getRemark(), 50));
summaryFeeMapper.updateById(row);
}
for (FormalSettlementSummaryFee manualRow : existingManualRows) {
if (!retainedManualIds.contains(manualRow.getId())) summaryFeeMapper.deleteById(manualRow.getId());
}
renumberSummaryFees(settlementId);
}
private void refreshSettlementAmount(FormalSettlement settlement) {
BigDecimal amount = listSummaryFees(settlement.getId()).stream()
.map(FormalSettlementSummaryFee::getSettlementAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
settlement.setSettlementAmount(amount); settlement.setSettlementAmount(amount);
settlement.setLocalSettlementAmount(amount.multiply(settlement.getExchangeRate() == null ? BigDecimal.ONE : settlement.getExchangeRate())); settlement.setLocalSettlementAmount(amount.multiply(
settlement.getExchangeRate() == null ? BigDecimal.ONE : settlement.getExchangeRate()));
updateById(settlement); updateById(settlement);
} }
private List<FormalSettlementSummaryFee> listSummaryFees(Long settlementId) {
return summaryFeeMapper.selectList(Wrappers.<FormalSettlementSummaryFee>lambdaQuery()
.eq(FormalSettlementSummaryFee::getFormalSettlementId, settlementId)
.eq(FormalSettlementSummaryFee::getIsDeleted, 0)
.orderByAsc(FormalSettlementSummaryFee::getLineNo));
}
private void renumberSummaryFees(Long settlementId) {
List<FormalSettlementSummaryFee> rows = listSummaryFees(settlementId);
for (int index = 0; index < rows.size(); index++) {
FormalSettlementSummaryFee row = rows.get(index);
row.setLineNo(index + 1);
summaryFeeMapper.updateById(row);
}
}
private Map<String, String> feeTypeMap() {
Map<String, String> result = new LinkedHashMap<>();
for (Map<String, Object> option : preSettlementService.feeOptions()) {
if (option.get("feeItems") instanceof List<?> values) {
values.forEach(value -> result.putIfAbsent(String.valueOf(value),
String.valueOf(option.get("feeType"))));
}
}
return result;
}
private Map<String, BigDecimal> parseFeeItems(String json) {
Map<String, BigDecimal> result = new LinkedHashMap<>();
if (Func.isEmpty(json)) return result;
try {
Object parsed = JsonUtil.parse(json, Map.class);
if (parsed instanceof Map<?, ?> map) {
map.forEach((key, value) -> result.put(String.valueOf(key), decimal(value)));
}
} catch (RuntimeException ignored) {
// 兼容历史费用JSON,不影响正式结算保存。
}
return result;
}
private BigDecimal decimal(Object value) {
if (value == null || String.valueOf(value).isBlank()) return BigDecimal.ZERO;
try {
return new BigDecimal(String.valueOf(value));
} catch (NumberFormatException ignored) {
return BigDecimal.ZERO;
}
}
private boolean isFreightFeeItem(String name) {
return name != null && (name.contains("运费") || name.contains("运输费"));
}
private String summaryKey(FormalSettlementSummaryFee row) {
return summaryKey(row.getFeeType(), row.getFeeItem());
}
private String summaryKey(String feeType, String feeItem) {
return String.valueOf(feeType) + "@@@" + String.valueOf(feeItem);
}
private String requiredText(String value, String field) {
if (Func.isEmpty(value)) throw new ServiceException(field + "不能为空");
return value.trim();
}
private void rebuildSnapshots(FormalSettlement settlement, List<PreSettlement> sources, List<ReceivablePayableDetail> directDetails) { private void rebuildSnapshots(FormalSettlement settlement, List<PreSettlement> sources, List<ReceivablePayableDetail> directDetails) {
List<Long> existingDetailIds = detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery() List<Long> existingDetailIds = detailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
.eq(FormalSettlementDetail::getFormalSettlementId, settlement.getId())).stream().map(FormalSettlementDetail::getId).toList(); .eq(FormalSettlementDetail::getFormalSettlementId, settlement.getId())).stream().map(FormalSettlementDetail::getId).toList();
@@ -490,12 +747,6 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
target.setCurrency(Func.isEmpty(source.getCurrency()) ? "RMB" : source.getCurrency()); target.setLocalCurrency("RMB"); target.setCurrency(Func.isEmpty(source.getCurrency()) ? "RMB" : source.getCurrency()); target.setLocalCurrency("RMB");
} }
private synchronized String nextNo() {
String prefix = "JS" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
long count = count(Wrappers.<FormalSettlement>lambdaQuery().likeRight(FormalSettlement::getFormalSettlementNo, prefix));
return prefix + String.format("%04d", count + 1);
}
private synchronized String nextPaymentNo() { private synchronized String nextPaymentNo() {
String prefix = "FK" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); String prefix = "FK" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
long count = paymentMapper.selectCount(Wrappers.<FormalSettlementPayment>lambdaQuery() long count = paymentMapper.selectCount(Wrappers.<FormalSettlementPayment>lambdaQuery()
@@ -503,7 +754,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
return prefix + String.format("%04d", count + 1); return prefix + String.format("%04d", count + 1);
} }
private BigDecimal money(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } private BigDecimal money(BigDecimal value) { return (value == null ? BigDecimal.ZERO : value).setScale(2, RoundingMode.HALF_UP); }
private BigDecimal nonNegative(BigDecimal value, String field) { if (value == null || value.signum() < 0) throw new ServiceException(field + "不能小于0"); return value; } private BigDecimal nonNegative(BigDecimal value, String field) { if (value == null || value.signum() < 0) throw new ServiceException(field + "不能小于0"); return value; }
private BigDecimal positive(BigDecimal value, String field) { if (value == null || value.signum() <= 0) throw new ServiceException(field + "必须大于0"); return value; } private BigDecimal positive(BigDecimal value, String field) { if (value == null || value.signum() <= 0) throw new ServiceException(field + "必须大于0"); return value; }
private String required(String value, String field) { if (Func.isEmpty(value)) throw new ServiceException("请填写" + field); return value; } private String required(String value, String field) { if (Func.isEmpty(value)) throw new ServiceException("请填写" + field); return value; }
@@ -174,11 +174,12 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
} }
@Override @Override
public List<Map<String, Object>> contractOptions(String keyword) { public List<Map<String, Object>> contractOptions(String keyword, Long projectId) {
LambdaQueryWrapper<ContractManage> wrapper = Wrappers.<ContractManage>lambdaQuery() LambdaQueryWrapper<ContractManage> wrapper = Wrappers.<ContractManage>lambdaQuery()
.eq(ContractManage::getIsDeleted, 0) .eq(ContractManage::getIsDeleted, 0)
.in(ContractManage::getApprovalStatus, "approved", "change_approved") .in(ContractManage::getApprovalStatus, "approved", "change_approved")
.ne(ContractManage::getContractStage, "terminated") .ne(ContractManage::getContractStage, "terminated")
.eq(projectId != null, ContractManage::getProjectId, projectId)
.and(Func.isNotEmpty(keyword), query -> query.like(ContractManage::getContractName, keyword) .and(Func.isNotEmpty(keyword), query -> query.like(ContractManage::getContractName, keyword)
.or().like(ContractManage::getContractNo, keyword)) .or().like(ContractManage::getContractNo, keyword))
.orderByDesc(ContractManage::getCreateTime); .orderByDesc(ContractManage::getCreateTime);
@@ -201,12 +202,28 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
@Override @Override
public IPage<Map<String, Object>> candidateDetails(IPage<?> page, Long contractId, String settlementType, public IPage<Map<String, Object>> candidateDetails(IPage<?> page, Long contractId, String settlementType,
String batchNo, String feeStartDate, String feeEndDate) { String batchNo, String feeStartDate, String feeEndDate) {
return candidateDetails(page, contractId, settlementType, batchNo, feeStartDate, feeEndDate,
false, true);
}
@Override
public IPage<Map<String, Object>> candidateDetailsByCreateTime(IPage<?> page, Long contractId,
String settlementType, String batchNo, String createStartDate, String createEndDate) {
return candidateDetails(page, contractId, settlementType, batchNo, createStartDate, createEndDate,
true, false);
}
private IPage<Map<String, Object>> candidateDetails(IPage<?> page, Long contractId, String settlementType,
String batchNo, String startDate, String endDate, boolean byCreateTime,
boolean validateContractType) {
if (contractId == null) { if (contractId == null) {
throw new ServiceException("请先选择合同"); throw new ServiceException("请先选择合同");
} }
validateSettlementType(settlementType); validateSettlementType(settlementType);
ContractManage contract = loadAvailableContract(contractId); ContractManage contract = loadAvailableContract(contractId);
if (!Objects.equals(contractSettlementType(contract), settlementType)) { // 正式结算需兼容历史单据及预结算转正式结算时保留的结算类型,
// 候选数据仍由合同、项目、组织、结算类型及未结算状态共同约束。
if (validateContractType && !Objects.equals(contractSettlementType(contract), settlementType)) {
throw new ServiceException("结算类型与合同类别不一致"); throw new ServiceException("结算类型与合同类别不一致");
} }
LambdaQueryWrapper<ReceivablePayableDetail> wrapper = Wrappers.<ReceivablePayableDetail>lambdaQuery() LambdaQueryWrapper<ReceivablePayableDetail> wrapper = Wrappers.<ReceivablePayableDetail>lambdaQuery()
@@ -223,8 +240,14 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
.and(query -> query.eq(ReceivablePayableDetail::getCustomerName, contract.getPartyA()) .and(query -> query.eq(ReceivablePayableDetail::getCustomerName, contract.getPartyA())
.or().eq(ReceivablePayableDetail::getCustomerName, contract.getPartyB())) .or().eq(ReceivablePayableDetail::getCustomerName, contract.getPartyB()))
.like(Func.isNotEmpty(batchNo), ReceivablePayableDetail::getBatchNo, batchNo) .like(Func.isNotEmpty(batchNo), ReceivablePayableDetail::getBatchNo, batchNo)
.ge(Func.isNotEmpty(feeStartDate), ReceivablePayableDetail::getFeeDate, parseDate(feeStartDate)) .ge(!byCreateTime && Func.isNotEmpty(startDate), ReceivablePayableDetail::getFeeDate,
.le(Func.isNotEmpty(feeEndDate), ReceivablePayableDetail::getFeeDate, parseDate(feeEndDate)) parseDate(startDate))
.le(!byCreateTime && Func.isNotEmpty(endDate), ReceivablePayableDetail::getFeeDate,
parseDate(endDate))
.ge(byCreateTime && Func.isNotEmpty(startDate), ReceivablePayableDetail::getCreateTime,
byCreateTime && Func.isNotEmpty(startDate) ? parseDate(startDate).atStartOfDay() : null)
.lt(byCreateTime && Func.isNotEmpty(endDate), ReceivablePayableDetail::getCreateTime,
byCreateTime && Func.isNotEmpty(endDate) ? parseDate(endDate).plusDays(1).atStartOfDay() : null)
.orderByDesc(ReceivablePayableDetail::getCreateTime); .orderByDesc(ReceivablePayableDetail::getCreateTime);
IPage<ReceivablePayableDetail> sourcePage = sourceDetailMapper.selectPage( IPage<ReceivablePayableDetail> sourcePage = sourceDetailMapper.selectPage(
new Page<>(page.getCurrent(), page.getSize()), wrapper); new Page<>(page.getCurrent(), page.getSize()), wrapper);
@@ -613,30 +636,46 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
private Map<String, Object> candidateMap(ReceivablePayableDetail source) { private Map<String, Object> candidateMap(ReceivablePayableDetail source) {
Map<String, Object> result = new LinkedHashMap<>(); Map<String, Object> result = new LinkedHashMap<>();
Waybill waybill = source.getWaybillId() == null ? null : waybillService.getById(source.getWaybillId());
result.put("id", source.getId()); result.put("id", source.getId());
result.put("documentNo", source.getDocumentNo()); result.put("documentNo", source.getDocumentNo());
result.put("createTime", source.getCreateTime());
result.put("projectName", source.getProjectName());
result.put("deptName", source.getDeptName());
result.put("feeDate", source.getFeeDate()); result.put("feeDate", source.getFeeDate());
result.put("customerName", source.getCustomerName()); result.put("customerName", source.getCustomerName());
result.put("departureAddress", waybillValue(source.getWaybillId(), Waybill::getDepartureAddress)); result.put("contractNo", source.getContractNo());
result.put("arrivalAddress", waybillValue(source.getWaybillId(), Waybill::getArrivalAddress));
result.put("contractName", source.getContractName()); result.put("contractName", source.getContractName());
result.put("sourceType", source.getSourceType());
result.put("preSettlementNo", source.getPreSettlementNo());
result.put("formalSettlementNo", source.getFormalSettlementNo());
result.put("waybillNo", source.getWaybillNo()); result.put("waybillNo", source.getWaybillNo());
result.put("vehicleNo", source.getVehicleNo()); result.put("vehicleNo", source.getVehicleNo());
result.put("departureAddress", waybill == null ? "" :
firstNotEmpty(waybill.getDepartureAddress(), waybill.getDepartureName()));
result.put("arrivalAddress", waybill == null ? "" :
firstNotEmpty(waybill.getArrivalAddress(), waybill.getArrivalName()));
result.put("actualDepartureTime", waybill == null || waybill.getStartDate() == null ? null :
waybill.getStartDate().atStartOfDay());
result.put("actualCompletionTime", waybill == null || waybill.getEndDate() == null ? null :
waybill.getEndDate().atStartOfDay());
result.put("transportType", source.getTransportType()); result.put("transportType", source.getTransportType());
result.put("cargoName", source.getCargoName()); result.put("cargoName", source.getCargoName());
result.put("cargoType", source.getCargoType()); result.put("cargoType", source.getCargoType());
result.put("transportQuantity", source.getTransportQuantity());
result.put("quantityUnit", source.getQuantityUnit());
result.put("mileage", source.getMileage());
result.put("batchNo", source.getBatchNo()); result.put("batchNo", source.getBatchNo());
result.put("unitPrice", source.getUnitPrice());
result.put("freightAmount", money(source.getFreightAmount()));
result.put("feeItemsJson", source.getFeeItemsJson());
result.put("totalAmount", money(source.getTotalAmount())); result.put("totalAmount", money(source.getTotalAmount()));
result.put("currency", source.getCurrency()); result.put("currency", source.getCurrency());
result.put("settlementStatusName", "待结算");
result.put("remark", source.getRemark());
return result; return result;
} }
private String waybillValue(Long waybillId, Function<Waybill, String> getter) {
if (waybillId == null) return "";
Waybill waybill = waybillService.getById(waybillId);
return waybill == null ? "" : getter.apply(waybill);
}
private void synchronizeDetails(PreSettlement settlement, List<Long> requestedIds, boolean allowSourceMismatch) { private void synchronizeDetails(PreSettlement settlement, List<Long> requestedIds, boolean allowSourceMismatch) {
List<Long> distinctIds = requestedIds.stream().filter(Objects::nonNull).distinct().toList(); List<Long> distinctIds = requestedIds.stream().filter(Objects::nonNull).distinct().toList();
List<PreSettlementDetail> existingDetails = listDetails(settlement.getId()); List<PreSettlementDetail> existingDetails = listDetails(settlement.getId());
@@ -0,0 +1,7 @@
ALTER TABLE `blade_common_route`
ADD COLUMN `departure_province_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '发货省ID' AFTER `departure_name`,
ADD COLUMN `departure_city_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '发货市ID' AFTER `departure_province_id`,
ADD COLUMN `departure_district_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '发货区ID' AFTER `departure_city_id`,
ADD COLUMN `arrival_province_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '收货省ID' AFTER `arrival_name`,
ADD COLUMN `arrival_city_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '收货市ID' AFTER `arrival_province_id`,
ADD COLUMN `arrival_district_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '收货区ID' AFTER `arrival_city_id`;
@@ -62,6 +62,22 @@ CREATE TABLE IF NOT EXISTS `blade_formal_settlement_detail_fee` (
KEY `idx_formal_detail_fee_detail` (`formal_settlement_detail_id`), KEY `idx_formal_detail_fee_source` (`source_fee_id`) KEY `idx_formal_detail_fee_detail` (`formal_settlement_detail_id`), KEY `idx_formal_detail_fee_source` (`source_fee_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='正式结算货物费用快照'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='正式结算货物费用快照';
CREATE TABLE IF NOT EXISTS `blade_formal_settlement_summary_fee` (
`id` bigint(20) NOT NULL COMMENT '主键', `tenant_id` varchar(12) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '000000' COMMENT '租户ID',
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人', `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
`create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间', `status` int(11) DEFAULT '1' COMMENT '状态',
`is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除', `formal_settlement_id` bigint(20) NOT NULL COMMENT '正式结算单ID',
`line_no` int(11) NOT NULL COMMENT '行号', `fee_type` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '费用类型',
`fee_item` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '费用项',
`original_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '原金额',
`adjust_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '调整金额',
`settlement_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '结算金额',
`remark` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
`manual_flag` int(11) NOT NULL DEFAULT '0' COMMENT '是否手工添加', PRIMARY KEY (`id`) USING BTREE,
KEY `idx_formal_settlement_summary_bill` (`formal_settlement_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='正式结算合计费用';
CREATE TABLE IF NOT EXISTS `blade_formal_settlement_payment` ( CREATE TABLE IF NOT EXISTS `blade_formal_settlement_payment` (
`id` bigint(20) NOT NULL, `tenant_id` varchar(12) NOT NULL DEFAULT '000000', `create_user` bigint(20) DEFAULT NULL, `id` bigint(20) NOT NULL, `tenant_id` varchar(12) NOT NULL DEFAULT '000000', `create_user` bigint(20) DEFAULT NULL,
`create_dept` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_user` bigint(20) DEFAULT NULL, `create_dept` bigint(20) DEFAULT NULL, `create_time` datetime DEFAULT NULL, `update_user` bigint(20) DEFAULT NULL,
@@ -0,0 +1,24 @@
-- 正式结算新增结算合计费用表
CREATE TABLE IF NOT EXISTS `blade_formal_settlement_summary_fee` (
`id` bigint(20) NOT NULL COMMENT '主键',
`tenant_id` varchar(12) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '000000' COMMENT '租户ID',
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_user` bigint(20) DEFAULT NULL COMMENT '修改人',
`update_time` datetime DEFAULT NULL COMMENT '修改时间',
`status` int(11) DEFAULT '1' COMMENT '状态',
`is_deleted` int(11) DEFAULT '0' COMMENT '是否已删除',
`formal_settlement_id` bigint(20) NOT NULL COMMENT '正式结算单ID',
`line_no` int(11) NOT NULL COMMENT '行号',
`fee_type` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '费用类型',
`fee_item` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '费用项',
`original_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '原金额',
`adjust_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '调整金额',
`settlement_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '结算金额',
`remark` varchar(50) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '备注',
`manual_flag` int(11) NOT NULL DEFAULT '0' COMMENT '是否手工添加',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_formal_settlement_summary_bill` (`formal_settlement_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='正式结算合计费用';
+6
View File
@@ -18,6 +18,9 @@ CREATE TABLE `blade_common_route` (
`route_name` varchar(100) DEFAULT NULL COMMENT '线路名称', `route_name` varchar(100) DEFAULT NULL COMMENT '线路名称',
`departure_address_id` bigint(20) DEFAULT NULL COMMENT '发货地址ID', `departure_address_id` bigint(20) DEFAULT NULL COMMENT '发货地址ID',
`departure_name` varchar(100) DEFAULT NULL COMMENT '发货地', `departure_name` varchar(100) DEFAULT NULL COMMENT '发货地',
`departure_province_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '发货省ID',
`departure_city_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '发货市ID',
`departure_district_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '发货区ID',
`departure_address` varchar(255) DEFAULT NULL COMMENT '发货地址', `departure_address` varchar(255) DEFAULT NULL COMMENT '发货地址',
`departure_longitude` decimal(18,6) DEFAULT NULL COMMENT '发货经度', `departure_longitude` decimal(18,6) DEFAULT NULL COMMENT '发货经度',
`departure_latitude` decimal(18,6) DEFAULT NULL COMMENT '发货纬度', `departure_latitude` decimal(18,6) DEFAULT NULL COMMENT '发货纬度',
@@ -25,6 +28,9 @@ CREATE TABLE `blade_common_route` (
`departure_phone` varchar(100) DEFAULT NULL COMMENT '发货联系方式', `departure_phone` varchar(100) DEFAULT NULL COMMENT '发货联系方式',
`arrival_address_id` bigint(20) DEFAULT NULL COMMENT '收货地址ID', `arrival_address_id` bigint(20) DEFAULT NULL COMMENT '收货地址ID',
`arrival_name` varchar(100) DEFAULT NULL COMMENT '收货地', `arrival_name` varchar(100) DEFAULT NULL COMMENT '收货地',
`arrival_province_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '收货省ID',
`arrival_city_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '收货市ID',
`arrival_district_id` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '收货区ID',
`arrival_address` varchar(255) DEFAULT NULL COMMENT '收货地址', `arrival_address` varchar(255) DEFAULT NULL COMMENT '收货地址',
`arrival_longitude` decimal(18,6) DEFAULT NULL COMMENT '收货经度', `arrival_longitude` decimal(18,6) DEFAULT NULL COMMENT '收货经度',
`arrival_latitude` decimal(18,6) DEFAULT NULL COMMENT '收货纬度', `arrival_latitude` decimal(18,6) DEFAULT NULL COMMENT '收货纬度',