1、新增结算调整单
2、新增运输对账
This commit is contained in:
+4
@@ -75,6 +75,10 @@ public class ApiScopePermissionHandler implements IPermissionHandler {
|
|||||||
if (request == null || user == null) {
|
if (request == null || user == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// 超级管理员在菜单授权树中默认拥有全部菜单权限,与框架默认处理器保持一致。
|
||||||
|
if (AuthUtil.isAdministrator()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
List<String> codes = permissionMenu(permission, user.getRoleId());
|
List<String> codes = permissionMenu(permission, user.getRoleId());
|
||||||
return codes != null && !codes.isEmpty();
|
return codes != null && !codes.isEmpty();
|
||||||
}
|
}
|
||||||
|
|||||||
+6
@@ -52,6 +52,9 @@ public class PreSettlementSaveRequest implements Serializable {
|
|||||||
@Schema(description = "合同ID")
|
@Schema(description = "合同ID")
|
||||||
private Long contractId;
|
private Long contractId;
|
||||||
|
|
||||||
|
@Schema(description = "结算类型:receivable/payable")
|
||||||
|
private String settlementType;
|
||||||
|
|
||||||
@Schema(description = "汇率日期")
|
@Schema(description = "汇率日期")
|
||||||
private LocalDate exchangeRateDate;
|
private LocalDate exchangeRateDate;
|
||||||
|
|
||||||
@@ -67,6 +70,9 @@ public class PreSettlementSaveRequest implements Serializable {
|
|||||||
@Schema(description = "应收应付明细ID")
|
@Schema(description = "应收应付明细ID")
|
||||||
private List<Long> sourceDetailIds;
|
private List<Long> sourceDetailIds;
|
||||||
|
|
||||||
|
@Schema(description = "是否允许批量转结算兼容历史来源明细的项目、所属组织或客商差异")
|
||||||
|
private Boolean allowSourceMismatch;
|
||||||
|
|
||||||
@Schema(description = "结算合计费用")
|
@Schema(description = "结算合计费用")
|
||||||
private List<SummaryFee> summaryFees;
|
private List<SummaryFee> summaryFees;
|
||||||
|
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
package org.springblade.transport.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SettlementAdjustmentSaveRequest implements Serializable {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private Long id;
|
||||||
|
private Long formalSettlementId;
|
||||||
|
private String remark;
|
||||||
|
private List<Detail> details;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Detail implements Serializable {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private Long formalSettlementDetailId;
|
||||||
|
private Long formalSettlementDetailFeeId;
|
||||||
|
private String feeType;
|
||||||
|
private String feeItem;
|
||||||
|
private BigDecimal adjustmentAmountTax;
|
||||||
|
private BigDecimal adjustmentAmountNoTax;
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package org.springblade.transport.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SettlementAdjustmentStatusRequest implements Serializable {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private Long id;
|
||||||
|
private String reason;
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
/** BladeX Commercial License Agreement. Copyright (c) 2018-2099, https://bladex.cn. Author: Chill Zhuang. */
|
||||||
|
package org.springblade.transport.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/** 运输对账人工匹配请求。 @author Chill */
|
||||||
|
@Data
|
||||||
|
public class TransportReconciliationManualMatchRequest implements Serializable {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private Long reconciliationId;
|
||||||
|
private Long internalId;
|
||||||
|
private Long externalId;
|
||||||
|
}
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
/** BladeX Commercial License Agreement. Copyright (c) 2018-2099, https://bladex.cn. Author: Chill Zhuang. */
|
||||||
|
package org.springblade.transport.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
/** 运输对账单保存请求。 @author Chill */
|
||||||
|
@Data
|
||||||
|
public class TransportReconciliationSaveRequest implements Serializable {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private Long id;
|
||||||
|
private Long formalSettlementId;
|
||||||
|
private String reconciliationMode;
|
||||||
|
private LocalDate reconciliationDate;
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package org.springblade.transport.pojo.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.core.tenant.mp.TenantEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("blade_settlement_adjustment")
|
||||||
|
public class SettlementAdjustment extends TenantEntity {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private String adjustmentNo;
|
||||||
|
private Long formalSettlementId;
|
||||||
|
private String formalSettlementNo;
|
||||||
|
private String settlementType;
|
||||||
|
private String projectName;
|
||||||
|
private String deptName;
|
||||||
|
private String customerName;
|
||||||
|
private String contractNo;
|
||||||
|
private String contractName;
|
||||||
|
private BigDecimal adjustmentAmount;
|
||||||
|
private BigDecimal originalSettlementAmount;
|
||||||
|
private BigDecimal adjustedSettlementAmount;
|
||||||
|
private String approvalStatus;
|
||||||
|
private String currentNode;
|
||||||
|
private String currentProcessor;
|
||||||
|
private String kingdeeSyncStatus;
|
||||||
|
private String remark;
|
||||||
|
private LocalDateTime approvedTime;
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package org.springblade.transport.pojo.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.core.tenant.mp.TenantEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("blade_settlement_adjustment_detail")
|
||||||
|
public class SettlementAdjustmentDetail extends TenantEntity {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private Long adjustmentId;
|
||||||
|
private Long formalSettlementDetailId;
|
||||||
|
private Long formalSettlementDetailFeeId;
|
||||||
|
private String feeType;
|
||||||
|
private String feeItem;
|
||||||
|
private BigDecimal originalAmountTax;
|
||||||
|
private BigDecimal adjustmentAmountTax;
|
||||||
|
private BigDecimal adjustmentAmountNoTax;
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <p>Author: Chill Zhuang (bladejava@qq.com)</p>
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运输对账单实体类
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("blade_transport_reconciliation")
|
||||||
|
@Schema(description = "运输对账单")
|
||||||
|
public class TransportReconciliation extends TenantEntity {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private String reconciliationNo;
|
||||||
|
private Long formalSettlementId;
|
||||||
|
private String formalSettlementNo;
|
||||||
|
private String preSettlementNos;
|
||||||
|
private String settlementType;
|
||||||
|
private String reconciliationMode;
|
||||||
|
private Long projectId;
|
||||||
|
private String projectName;
|
||||||
|
private Long deptId;
|
||||||
|
private String deptName;
|
||||||
|
private Long contractId;
|
||||||
|
private String contractNo;
|
||||||
|
private String contractName;
|
||||||
|
private String payerName;
|
||||||
|
private String payeeName;
|
||||||
|
private String currency;
|
||||||
|
private BigDecimal settlementAmount;
|
||||||
|
private BigDecimal paidAmount;
|
||||||
|
private Long reconcilerId;
|
||||||
|
private String reconcilerName;
|
||||||
|
private LocalDate reconciliationDate;
|
||||||
|
private String reconciliationStatus;
|
||||||
|
private String matchStatus;
|
||||||
|
private Integer internalBillCount;
|
||||||
|
private Integer externalBillCount;
|
||||||
|
private Integer differenceCount;
|
||||||
|
private BigDecimal internalQuantity;
|
||||||
|
private BigDecimal externalQuantity;
|
||||||
|
private BigDecimal differenceQuantity;
|
||||||
|
private BigDecimal internalAmount;
|
||||||
|
private BigDecimal externalAmount;
|
||||||
|
private BigDecimal differenceAmount;
|
||||||
|
private Integer matchedCount;
|
||||||
|
private Integer unmatchedCount;
|
||||||
|
private Boolean billUpdated;
|
||||||
|
private LocalDateTime completedTime;
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <p>Author: Chill Zhuang (bladejava@qq.com)</p>
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.pojo.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.core.tenant.mp.TenantEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/** 运输对账账单变更记录。 @author Chill */
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("blade_transport_reconciliation_change_record")
|
||||||
|
public class TransportReconciliationChangeRecord extends TenantEntity {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private Long reconciliationId;
|
||||||
|
private Long internalDetailId;
|
||||||
|
private Long formalSettlementId;
|
||||||
|
private Long formalSettlementDetailId;
|
||||||
|
private Long sourceDetailId;
|
||||||
|
private String documentNo;
|
||||||
|
private String cargoName;
|
||||||
|
private BigDecimal beforeAmount;
|
||||||
|
private BigDecimal afterAmount;
|
||||||
|
private String beforeDataJson;
|
||||||
|
private String afterDataJson;
|
||||||
|
private Long operatorId;
|
||||||
|
private String operatorName;
|
||||||
|
private LocalDateTime changeTime;
|
||||||
|
private String changeReason;
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <p>Author: Chill Zhuang (bladejava@qq.com)</p>
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.pojo.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.core.tenant.mp.TenantEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/** 运输对账外部账单行。 @author Chill */
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("blade_transport_reconciliation_external")
|
||||||
|
public class TransportReconciliationExternal extends TenantEntity {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private Long reconciliationId;
|
||||||
|
private Integer externalLineNo;
|
||||||
|
private String vehicleNo;
|
||||||
|
private String departureAddress;
|
||||||
|
private String arrivalAddress;
|
||||||
|
private LocalDateTime actualDepartureTime;
|
||||||
|
private LocalDateTime actualCompletionTime;
|
||||||
|
private String transportType;
|
||||||
|
private String cargoName;
|
||||||
|
private String cargoType;
|
||||||
|
private String specification;
|
||||||
|
private String model;
|
||||||
|
private BigDecimal transportQuantity;
|
||||||
|
private String quantityUnit;
|
||||||
|
private BigDecimal mileage;
|
||||||
|
private String batchNo;
|
||||||
|
private BigDecimal unitPrice;
|
||||||
|
private BigDecimal freightAmount;
|
||||||
|
private String feeItemsJson;
|
||||||
|
private BigDecimal settlementAmount;
|
||||||
|
private Boolean suspectedDuplicate;
|
||||||
|
private String matchStatus;
|
||||||
|
private Long matchedInternalId;
|
||||||
|
private String errorMessage;
|
||||||
|
private String rawDataJson;
|
||||||
|
}
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <p>Author: Chill Zhuang (bladejava@qq.com)</p>
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.pojo.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.core.tenant.mp.TenantEntity;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/** 运输对账内部账单快照。 @author Chill */
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("blade_transport_reconciliation_internal")
|
||||||
|
public class TransportReconciliationInternal extends TenantEntity {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
private Long reconciliationId;
|
||||||
|
private Long formalSettlementDetailId;
|
||||||
|
private Long formalSettlementDetailFeeId;
|
||||||
|
private Long sourceDetailId;
|
||||||
|
private Long sourceCargoFeeId;
|
||||||
|
private Integer lineNo;
|
||||||
|
private String documentNo;
|
||||||
|
private String waybillNo;
|
||||||
|
private String vehicleNo;
|
||||||
|
private String departureAddress;
|
||||||
|
private String arrivalAddress;
|
||||||
|
private LocalDateTime actualDepartureTime;
|
||||||
|
private LocalDateTime actualCompletionTime;
|
||||||
|
private String transportType;
|
||||||
|
private String cargoName;
|
||||||
|
private String cargoType;
|
||||||
|
private String specification;
|
||||||
|
private String model;
|
||||||
|
private BigDecimal transportQuantity;
|
||||||
|
private String quantityUnit;
|
||||||
|
private BigDecimal mileage;
|
||||||
|
private String batchNo;
|
||||||
|
private BigDecimal unitPrice;
|
||||||
|
private BigDecimal freightAmount;
|
||||||
|
private String feeItemsJson;
|
||||||
|
private BigDecimal settlementAmount;
|
||||||
|
private Long matchedExternalId;
|
||||||
|
private Integer matchedExternalLineNo;
|
||||||
|
private String matchResult;
|
||||||
|
private String updateResult;
|
||||||
|
private String updateMessage;
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package org.springblade.transport.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.transport.pojo.entity.SettlementAdjustment;
|
||||||
|
import org.springblade.transport.pojo.entity.SettlementAdjustmentDetail;
|
||||||
|
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class SettlementAdjustmentVO extends SettlementAdjustment {
|
||||||
|
private LocalDate createStartDate;
|
||||||
|
private LocalDate createEndDate;
|
||||||
|
private String approvalStatusName;
|
||||||
|
private String settlementTypeName;
|
||||||
|
private String createUserName;
|
||||||
|
private List<SettlementAdjustmentDetail> details;
|
||||||
|
private List<FormalSettlementDetail> formalDetails;
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
/** BladeX Commercial License Agreement. Copyright (c) 2018-2099, https://bladex.cn. Author: Chill Zhuang. */
|
||||||
|
package org.springblade.transport.pojo.vo;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliation;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationChangeRecord;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationExternal;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationInternal;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** 运输对账单视图实体类。 @author Chill */
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class TransportReconciliationVO extends TransportReconciliation {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
@TableField(exist = false) private String createUserName;
|
||||||
|
@TableField(exist = false) private String updateUserName;
|
||||||
|
@TableField(exist = false) private String reconciliationModeName;
|
||||||
|
@TableField(exist = false) private String reconciliationStatusName;
|
||||||
|
@TableField(exist = false) private String matchStatusName;
|
||||||
|
@TableField(exist = false) private List<TransportReconciliationInternal> internalDetails;
|
||||||
|
@TableField(exist = false) private List<TransportReconciliationExternal> externalDetails;
|
||||||
|
@TableField(exist = false) private List<TransportReconciliationChangeRecord> changeRecords;
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package org.springblade.transport.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import org.springblade.transport.pojo.dto.SettlementAdjustmentSaveRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.SettlementAdjustmentStatusRequest;
|
||||||
|
import org.springblade.transport.pojo.entity.SettlementAdjustment;
|
||||||
|
import org.springblade.transport.pojo.vo.SettlementAdjustmentVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public interface ISettlementAdjustmentService {
|
||||||
|
IPage<SettlementAdjustmentVO> selectPage(IPage<SettlementAdjustment> page, SettlementAdjustmentVO query);
|
||||||
|
SettlementAdjustmentVO detail(Long id);
|
||||||
|
List<Map<String, Object>> candidateFormalSettlements(String keyword);
|
||||||
|
List<Map<String, Object>> formalDetails(Long formalSettlementId);
|
||||||
|
Long saveDraft(SettlementAdjustmentSaveRequest request);
|
||||||
|
void removeDraft(Long id);
|
||||||
|
void submit(SettlementAdjustmentStatusRequest request);
|
||||||
|
void approve(SettlementAdjustmentStatusRequest request);
|
||||||
|
void returnBill(SettlementAdjustmentStatusRequest request);
|
||||||
|
String repush(Long adjustmentId);
|
||||||
|
}
|
||||||
+5
-4
@@ -118,11 +118,12 @@ public class ReceivablePayableDetailController extends BladeController {
|
|||||||
public R<IPage<Map<String, Object>>> transferCandidates(Query query,
|
public R<IPage<Map<String, Object>>> transferCandidates(Query query,
|
||||||
@RequestParam(required = false) String contractName,
|
@RequestParam(required = false) String contractName,
|
||||||
@RequestParam(required = false) String batchNo,
|
@RequestParam(required = false) String batchNo,
|
||||||
@RequestParam(required = false) String generateStartDate,
|
@RequestParam(required = false) String generateStartDate,
|
||||||
@RequestParam(required = false) String generateEndDate,
|
@RequestParam(required = false) String generateEndDate,
|
||||||
@RequestParam(required = false) String settlementBillType) {
|
@RequestParam(required = false) String settlementBillType,
|
||||||
|
@RequestParam(required = false) String settlementType) {
|
||||||
return R.data(detailService.transferCandidates(Condition.getPage(query), contractName, batchNo,
|
return R.data(detailService.transferCandidates(Condition.getPage(query), contractName, batchNo,
|
||||||
generateStartDate, generateEndDate, settlementBillType));
|
generateStartDate, generateEndDate, settlementBillType, settlementType));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/transfer-settlement")
|
@PostMapping("/transfer-settlement")
|
||||||
|
|||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package org.springblade.transport.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
|
import org.springblade.core.mp.support.Condition;
|
||||||
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.secure.annotation.PreAuth;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.transport.pojo.dto.SettlementAdjustmentSaveRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.SettlementAdjustmentStatusRequest;
|
||||||
|
import org.springblade.transport.pojo.entity.SettlementAdjustment;
|
||||||
|
import org.springblade.transport.pojo.vo.SettlementAdjustmentVO;
|
||||||
|
import org.springblade.transport.service.ISettlementAdjustmentService;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@PreAuth(menu = "settlement_adjustment")
|
||||||
|
@RequestMapping("/settlement-adjustment")
|
||||||
|
public class SettlementAdjustmentController extends BladeController {
|
||||||
|
private final ISettlementAdjustmentService service;
|
||||||
|
@GetMapping("/list") public R<IPage<SettlementAdjustmentVO>> list(SettlementAdjustmentVO query, Query page) { return R.data(service.selectPage(Condition.getPage(page), query)); }
|
||||||
|
@GetMapping("/detail") public R<SettlementAdjustmentVO> detail(@RequestParam Long id) { return R.data(service.detail(id)); }
|
||||||
|
@GetMapping("/candidate-formal-settlements") public R<List<Map<String, Object>>> candidates(@RequestParam(required = false) String keyword) { return R.data(service.candidateFormalSettlements(keyword)); }
|
||||||
|
@GetMapping("/formal-details") public R<List<Map<String, Object>>> formalDetails(@RequestParam Long formalSettlementId) { return R.data(service.formalDetails(formalSettlementId)); }
|
||||||
|
@PostMapping("/save") public R<Long> save(@RequestBody SettlementAdjustmentSaveRequest request) { return R.data(service.saveDraft(request)); }
|
||||||
|
@PostMapping("/remove") public R remove(@RequestParam Long id) { service.removeDraft(id); return R.success("删除成功"); }
|
||||||
|
@PostMapping("/submit") public R submit(@RequestBody SettlementAdjustmentStatusRequest request) { service.submit(request); return R.success("提交成功"); }
|
||||||
|
@PostMapping("/approve") public R approve(@RequestBody SettlementAdjustmentStatusRequest request) { service.approve(request); return R.success("审批通过"); }
|
||||||
|
@PostMapping("/return") public R returnBill(@RequestBody SettlementAdjustmentStatusRequest request) { service.returnBill(request); return R.success("已驳回"); }
|
||||||
|
@PostMapping("/repush") public R<String> repush(@RequestParam Long id) { return R.data(service.repush(id)); }
|
||||||
|
}
|
||||||
+144
@@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <p>Author: Chill Zhuang (bladejava@qq.com)</p>
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springblade.common.excel.ImportFailureExcelUtil;
|
||||||
|
import org.springblade.core.boot.ctrl.BladeController;
|
||||||
|
import org.springblade.core.mp.support.Condition;
|
||||||
|
import org.springblade.core.mp.support.Query;
|
||||||
|
import org.springblade.core.secure.annotation.PreAuth;
|
||||||
|
import org.springblade.core.tool.api.R;
|
||||||
|
import org.springblade.core.tool.utils.DateUtil;
|
||||||
|
import org.springblade.core.excel.util.ExcelUtil;
|
||||||
|
import org.springblade.transport.excel.CargoReconciliationExcel;
|
||||||
|
import org.springblade.transport.excel.CargoReconciliationFailureExcel;
|
||||||
|
import org.springblade.transport.excel.VehicleReconciliationExcel;
|
||||||
|
import org.springblade.transport.excel.VehicleReconciliationFailureExcel;
|
||||||
|
import org.springblade.transport.pojo.dto.TransportReconciliationManualMatchRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.TransportReconciliationSaveRequest;
|
||||||
|
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationInternal;
|
||||||
|
import org.springblade.transport.pojo.vo.TransportReconciliationVO;
|
||||||
|
import org.springblade.transport.service.ITransportReconciliationService;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** 运输对账单控制器。 @author Chill */
|
||||||
|
@RestController
|
||||||
|
@AllArgsConstructor
|
||||||
|
@PreAuth(menu = "transport_reconciliation")
|
||||||
|
@RequestMapping("/transport-reconciliation")
|
||||||
|
@Tag(name = "运输对账", description = "运输对账管理")
|
||||||
|
public class TransportReconciliationController extends BladeController {
|
||||||
|
private final ITransportReconciliationService reconciliationService;
|
||||||
|
|
||||||
|
@GetMapping("/list")
|
||||||
|
@ApiOperationSupport(order = 1)
|
||||||
|
@Operation(summary = "运输对账分页")
|
||||||
|
public R<IPage<TransportReconciliationVO>> list(TransportReconciliationVO query, Query pageQuery) {
|
||||||
|
return R.data(reconciliationService.selectPage(Condition.getPage(pageQuery), query));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/detail")
|
||||||
|
@ApiOperationSupport(order = 2)
|
||||||
|
@Operation(summary = "运输对账详情")
|
||||||
|
public R<TransportReconciliationVO> detail(@RequestParam Long id) { return R.data(reconciliationService.detail(id)); }
|
||||||
|
|
||||||
|
@GetMapping("/formal-options")
|
||||||
|
@ApiOperationSupport(order = 3)
|
||||||
|
@Operation(summary = "可选正式结算单")
|
||||||
|
public R<IPage<FormalSettlement>> formalOptions(Query pageQuery, @RequestParam(required = false) String settlementType,
|
||||||
|
@RequestParam(required = false) String keyword) {
|
||||||
|
return R.data(reconciliationService.formalOptions(Condition.getPage(pageQuery), settlementType, keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/save")
|
||||||
|
@ApiOperationSupport(order = 4)
|
||||||
|
@Operation(summary = "保存运输对账草稿")
|
||||||
|
public R<Long> save(@RequestBody TransportReconciliationSaveRequest request) { return R.data(reconciliationService.saveDraft(request)); }
|
||||||
|
|
||||||
|
@PostMapping("/remove")
|
||||||
|
@ApiOperationSupport(order = 5)
|
||||||
|
@Operation(summary = "删除运输对账草稿")
|
||||||
|
public R remove(@RequestParam Long id) { reconciliationService.removeDraft(id); return R.success("删除成功"); }
|
||||||
|
|
||||||
|
@PostMapping("/import-vehicle")
|
||||||
|
@ApiOperationSupport(order = 6)
|
||||||
|
@Operation(summary = "导入整车总额外部账单")
|
||||||
|
public R importVehicle(@RequestParam Long id, MultipartFile file, HttpServletResponse response) {
|
||||||
|
List<VehicleReconciliationFailureExcel> failures = reconciliationService.importVehicles(id, ExcelUtil.read(file, VehicleReconciliationExcel.class));
|
||||||
|
if (!failures.isEmpty()) {
|
||||||
|
ImportFailureExcelUtil.export(response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", failures, VehicleReconciliationFailureExcel.class);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return R.success("导入数据成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/import-cargo")
|
||||||
|
@ApiOperationSupport(order = 7)
|
||||||
|
@Operation(summary = "导入货物明细外部账单")
|
||||||
|
public R importCargo(@RequestParam Long id, MultipartFile file, HttpServletResponse response) {
|
||||||
|
List<CargoReconciliationFailureExcel> failures = reconciliationService.importCargoes(id, ExcelUtil.read(file, CargoReconciliationExcel.class));
|
||||||
|
if (!failures.isEmpty()) {
|
||||||
|
ImportFailureExcelUtil.export(response, "运输对账导入失败明细" + DateUtil.time(), "导入失败明细", failures, CargoReconciliationFailureExcel.class);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return R.success("导入数据成功");
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/template")
|
||||||
|
@ApiOperationSupport(order = 8)
|
||||||
|
@Operation(summary = "下载运输对账模板")
|
||||||
|
public void template(@RequestParam String mode, HttpServletResponse response) {
|
||||||
|
if ("cargo".equals(mode)) ExcelUtil.export(response, "货物明细对账模板", "货物明细对账模板", new ArrayList<CargoReconciliationExcel>(), CargoReconciliationExcel.class);
|
||||||
|
else ExcelUtil.export(response, "整车总额对账模板", "整车总额对账模板", new ArrayList<VehicleReconciliationExcel>(), VehicleReconciliationExcel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/match")
|
||||||
|
@ApiOperationSupport(order = 9)
|
||||||
|
@Operation(summary = "自动匹配内部账单")
|
||||||
|
public R match(@RequestParam Long id) { reconciliationService.autoMatch(id); return R.success("匹配完成"); }
|
||||||
|
|
||||||
|
@PostMapping("/manual-match")
|
||||||
|
@ApiOperationSupport(order = 10)
|
||||||
|
@Operation(summary = "人工匹配账单明细")
|
||||||
|
public R manualMatch(@RequestBody TransportReconciliationManualMatchRequest request) { reconciliationService.manualMatch(request); return R.success("人工匹配成功"); }
|
||||||
|
|
||||||
|
@PostMapping("/unmatch")
|
||||||
|
@ApiOperationSupport(order = 11)
|
||||||
|
@Operation(summary = "取消明细匹配")
|
||||||
|
public R unmatch(@RequestParam Long internalId) { reconciliationService.unmatch(internalId); return R.success("已取消匹配"); }
|
||||||
|
|
||||||
|
@PostMapping("/adjust")
|
||||||
|
@ApiOperationSupport(order = 12)
|
||||||
|
@Operation(summary = "调整内部账单明细")
|
||||||
|
public R adjust(@RequestBody TransportReconciliationInternal row) { reconciliationService.adjustInternal(row); return R.success("调整成功"); }
|
||||||
|
|
||||||
|
@PostMapping("/update-by-match")
|
||||||
|
@ApiOperationSupport(order = 13)
|
||||||
|
@Operation(summary = "按匹配结果更新账单")
|
||||||
|
public R updateByMatch(@RequestParam Long id) { reconciliationService.updateByMatch(id); return R.success("账单更新完成"); }
|
||||||
|
|
||||||
|
@PostMapping("/complete")
|
||||||
|
@ApiOperationSupport(order = 14)
|
||||||
|
@Operation(summary = "完成运输对账")
|
||||||
|
public R complete(@RequestParam Long id) { reconciliationService.complete(id); return R.success("对账单确认完成"); }
|
||||||
|
}
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <p>Author: Chill Zhuang (bladejava@qq.com)</p>
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.excel;
|
||||||
|
|
||||||
|
import cn.idev.excel.annotation.ExcelIgnore;
|
||||||
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
|
import cn.idev.excel.annotation.format.NumberFormat;
|
||||||
|
import cn.idev.excel.annotation.write.style.ColumnWidth;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/** 货物明细对账导入模型。 @author Chill */
|
||||||
|
@Data
|
||||||
|
@ColumnWidth(22)
|
||||||
|
public class CargoReconciliationExcel implements Serializable {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
@ExcelProperty("车牌号") private String vehicleNo;
|
||||||
|
@ExcelProperty("发货地址") private String departureAddress;
|
||||||
|
@ExcelProperty("到货地址") private String arrivalAddress;
|
||||||
|
@ExcelProperty("实际发货时间") private String actualDepartureTime;
|
||||||
|
@ExcelProperty("实际完成时间") private String actualCompletionTime;
|
||||||
|
@ExcelProperty("货物名称") private String cargoName;
|
||||||
|
@ExcelProperty("货物类型") private String cargoType;
|
||||||
|
@ExcelProperty("规格") private String specification;
|
||||||
|
@ExcelProperty("型号") private String model;
|
||||||
|
@ExcelProperty("运输量") @NumberFormat("0.000000") private BigDecimal transportQuantity;
|
||||||
|
@ExcelProperty("运输单价") @NumberFormat("0.00") private BigDecimal unitPrice;
|
||||||
|
@ExcelProperty("里程(KM)") @NumberFormat("0.00") private BigDecimal mileage;
|
||||||
|
@ExcelProperty("运输费") @NumberFormat("0.00") private BigDecimal freightAmount;
|
||||||
|
@ExcelProperty("费用项目名称1") @NumberFormat("0.00") private BigDecimal feeItemOne;
|
||||||
|
@ExcelProperty("费用项目名称2") @NumberFormat("0.00") private BigDecimal feeItemTwo;
|
||||||
|
@ExcelProperty("结算金额") @NumberFormat("0.00") private BigDecimal settlementAmount;
|
||||||
|
@ExcelIgnore private String errorMessage;
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
/** BladeX Commercial License Agreement. Copyright (c) 2018-2099, https://bladex.cn. Author: Chill Zhuang. */
|
||||||
|
package org.springblade.transport.excel;
|
||||||
|
|
||||||
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/** 货物明细对账导入失败模型。 @author Chill */
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class CargoReconciliationFailureExcel extends CargoReconciliationExcel {
|
||||||
|
@ExcelProperty("导入失败原因") private String errorMessage;
|
||||||
|
}
|
||||||
+40
@@ -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>
|
||||||
|
* <p>Author: Chill Zhuang (bladejava@qq.com)</p>
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.excel;
|
||||||
|
|
||||||
|
import cn.idev.excel.annotation.ExcelIgnore;
|
||||||
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
|
import cn.idev.excel.annotation.format.NumberFormat;
|
||||||
|
import cn.idev.excel.annotation.write.style.ColumnWidth;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/** 整车总额对账导入模型。 @author Chill */
|
||||||
|
@Data
|
||||||
|
@ColumnWidth(22)
|
||||||
|
public class VehicleReconciliationExcel implements Serializable {
|
||||||
|
@Serial private static final long serialVersionUID = 1L;
|
||||||
|
@ExcelProperty("车牌号") private String vehicleNo;
|
||||||
|
@ExcelProperty("发货地址") private String departureAddress;
|
||||||
|
@ExcelProperty("到货地址") private String arrivalAddress;
|
||||||
|
@ExcelProperty("实际发货时间") private String actualDepartureTime;
|
||||||
|
@ExcelProperty("实际完成时间") private String actualCompletionTime;
|
||||||
|
@ExcelProperty("运输类型") private String transportType;
|
||||||
|
@ExcelProperty("货物名称") private String cargoName;
|
||||||
|
@ExcelProperty("货物类型") private String cargoType;
|
||||||
|
@ExcelProperty("运输总量") @NumberFormat("0.000000") private BigDecimal transportQuantity;
|
||||||
|
@ExcelProperty("里程(KM)") @NumberFormat("0.00") private BigDecimal mileage;
|
||||||
|
@ExcelProperty("批次号") private String batchNo;
|
||||||
|
@ExcelProperty("运输单价") @NumberFormat("0.00") private BigDecimal unitPrice;
|
||||||
|
@ExcelProperty("运费") @NumberFormat("0.00") private BigDecimal freightAmount;
|
||||||
|
@ExcelProperty("费用项目1") @NumberFormat("0.00") private BigDecimal feeItemOne;
|
||||||
|
@ExcelProperty("结算费用合计") @NumberFormat("0.00") private BigDecimal settlementAmount;
|
||||||
|
@ExcelIgnore private String errorMessage;
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
/** BladeX Commercial License Agreement. Copyright (c) 2018-2099, https://bladex.cn. Author: Chill Zhuang. */
|
||||||
|
package org.springblade.transport.excel;
|
||||||
|
|
||||||
|
import cn.idev.excel.annotation.ExcelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/** 整车对账导入失败模型。 @author Chill */
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public class VehicleReconciliationFailureExcel extends VehicleReconciliationExcel {
|
||||||
|
@ExcelProperty("导入失败原因") private String errorMessage;
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package org.springblade.transport.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springblade.transport.pojo.entity.SettlementAdjustmentDetail;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface SettlementAdjustmentDetailMapper extends BaseMapper<SettlementAdjustmentDetail> {}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package org.springblade.transport.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springblade.transport.pojo.entity.SettlementAdjustment;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface SettlementAdjustmentMapper extends BaseMapper<SettlementAdjustment> {}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
/** BladeX Commercial License Agreement. Copyright (c) 2018-2099, https://bladex.cn. Author: Chill Zhuang. */
|
||||||
|
package org.springblade.transport.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationChangeRecord;
|
||||||
|
|
||||||
|
/** 运输对账变更记录 Mapper。 @author Chill */
|
||||||
|
@Mapper
|
||||||
|
public interface TransportReconciliationChangeRecordMapper extends BaseMapper<TransportReconciliationChangeRecord> {
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
/** BladeX Commercial License Agreement. Copyright (c) 2018-2099, https://bladex.cn. Author: Chill Zhuang. */
|
||||||
|
package org.springblade.transport.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationExternal;
|
||||||
|
|
||||||
|
/** 运输对账外部账单 Mapper。 @author Chill */
|
||||||
|
@Mapper
|
||||||
|
public interface TransportReconciliationExternalMapper extends BaseMapper<TransportReconciliationExternal> {
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
/** BladeX Commercial License Agreement. Copyright (c) 2018-2099, https://bladex.cn. Author: Chill Zhuang. */
|
||||||
|
package org.springblade.transport.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationInternal;
|
||||||
|
|
||||||
|
/** 运输对账内部账单 Mapper。 @author Chill */
|
||||||
|
@Mapper
|
||||||
|
public interface TransportReconciliationInternalMapper extends BaseMapper<TransportReconciliationInternal> {
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
/** BladeX Commercial License Agreement. Copyright (c) 2018-2099, https://bladex.cn. Author: Chill Zhuang. */
|
||||||
|
package org.springblade.transport.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliation;
|
||||||
|
|
||||||
|
/** 运输对账单 Mapper。 @author Chill */
|
||||||
|
@Mapper
|
||||||
|
public interface TransportReconciliationMapper extends BaseMapper<TransportReconciliation> {
|
||||||
|
}
|
||||||
+2
-1
@@ -58,7 +58,8 @@ public interface IReceivablePayableDetailService extends BaseService<ReceivableP
|
|||||||
void transferSettlement(ReceivablePayableTransferRequest request);
|
void transferSettlement(ReceivablePayableTransferRequest request);
|
||||||
|
|
||||||
IPage<Map<String, Object>> transferCandidates(IPage<?> page, String contractName, String batchNo,
|
IPage<Map<String, Object>> transferCandidates(IPage<?> page, String contractName, String batchNo,
|
||||||
String generateStartDate, String generateEndDate, String settlementBillType);
|
String generateStartDate, String generateEndDate, String settlementBillType,
|
||||||
|
String settlementType);
|
||||||
|
|
||||||
IPage<Map<String, Object>> generateWaybills(IPage<?> page, ReceivablePayableGenerateRequest request);
|
IPage<Map<String, Object>> generateWaybills(IPage<?> page, ReceivablePayableGenerateRequest request);
|
||||||
|
|
||||||
|
|||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <p>Author: Chill Zhuang (bladejava@qq.com)</p>
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import org.springblade.core.mp.base.BaseService;
|
||||||
|
import org.springblade.transport.excel.CargoReconciliationExcel;
|
||||||
|
import org.springblade.transport.excel.CargoReconciliationFailureExcel;
|
||||||
|
import org.springblade.transport.excel.VehicleReconciliationExcel;
|
||||||
|
import org.springblade.transport.excel.VehicleReconciliationFailureExcel;
|
||||||
|
import org.springblade.transport.pojo.dto.TransportReconciliationManualMatchRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.TransportReconciliationSaveRequest;
|
||||||
|
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliation;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationInternal;
|
||||||
|
import org.springblade.transport.pojo.vo.TransportReconciliationVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** 运输对账单服务。 @author Chill */
|
||||||
|
public interface ITransportReconciliationService extends BaseService<TransportReconciliation> {
|
||||||
|
IPage<TransportReconciliationVO> selectPage(IPage<TransportReconciliation> page, TransportReconciliationVO query);
|
||||||
|
IPage<FormalSettlement> formalOptions(IPage<FormalSettlement> page, String settlementType, String keyword);
|
||||||
|
TransportReconciliationVO detail(Long id);
|
||||||
|
Long saveDraft(TransportReconciliationSaveRequest request);
|
||||||
|
void removeDraft(Long id);
|
||||||
|
List<VehicleReconciliationFailureExcel> importVehicles(Long id, List<VehicleReconciliationExcel> rows);
|
||||||
|
List<CargoReconciliationFailureExcel> importCargoes(Long id, List<CargoReconciliationExcel> rows);
|
||||||
|
void autoMatch(Long id);
|
||||||
|
void manualMatch(TransportReconciliationManualMatchRequest request);
|
||||||
|
void unmatch(Long internalId);
|
||||||
|
void adjustInternal(TransportReconciliationInternal row);
|
||||||
|
void updateByMatch(Long id);
|
||||||
|
void complete(Long id);
|
||||||
|
}
|
||||||
+13
-6
@@ -272,7 +272,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
|||||||
settlement.setRemark(request.getRemark());
|
settlement.setRemark(request.getRemark());
|
||||||
saveOrUpdate(settlement);
|
saveOrUpdate(settlement);
|
||||||
if (request.getSourceDetailIds() != null) {
|
if (request.getSourceDetailIds() != null) {
|
||||||
synchronizeDetails(settlement, request.getSourceDetailIds());
|
synchronizeDetails(settlement, request.getSourceDetailIds(), Boolean.TRUE.equals(request.getAllowSourceMismatch()));
|
||||||
}
|
}
|
||||||
settlement.setExchangeRate(normalizeRate(request.getExchangeRate(), settlement.getCurrency()));
|
settlement.setExchangeRate(normalizeRate(request.getExchangeRate(), settlement.getCurrency()));
|
||||||
rebuildSummaryFees(settlement.getId(), true);
|
rebuildSummaryFees(settlement.getId(), true);
|
||||||
@@ -637,7 +637,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
|||||||
return waybill == null ? "" : getter.apply(waybill);
|
return waybill == null ? "" : getter.apply(waybill);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void synchronizeDetails(PreSettlement settlement, List<Long> requestedIds) {
|
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());
|
||||||
Set<Long> requestedSet = new LinkedHashSet<>(distinctIds);
|
Set<Long> requestedSet = new LinkedHashSet<>(distinctIds);
|
||||||
@@ -661,7 +661,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
|||||||
boolean hasRetainedDetail = existingDetails.stream()
|
boolean hasRetainedDetail = existingDetails.stream()
|
||||||
.anyMatch(detail -> requestedSet.contains(detail.getSourceDetailId()));
|
.anyMatch(detail -> requestedSet.contains(detail.getSourceDetailId()));
|
||||||
for (ReceivablePayableDetail source : sources) {
|
for (ReceivablePayableDetail source : sources) {
|
||||||
validateCandidate(settlement, source);
|
validateCandidate(settlement, source, allowSourceMismatch);
|
||||||
String sourceCurrency = Func.isEmpty(source.getCurrency()) ? LOCAL_CURRENCY : source.getCurrency();
|
String sourceCurrency = Func.isEmpty(source.getCurrency()) ? LOCAL_CURRENCY : source.getCurrency();
|
||||||
if (!hasRetainedDetail) {
|
if (!hasRetainedDetail) {
|
||||||
settlement.setCurrency(sourceCurrency);
|
settlement.setCurrency(sourceCurrency);
|
||||||
@@ -691,16 +691,17 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
|||||||
renumberDetails(settlement.getId());
|
renumberDetails(settlement.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validateCandidate(PreSettlement settlement, ReceivablePayableDetail source) {
|
private void validateCandidate(PreSettlement settlement, ReceivablePayableDetail source,
|
||||||
|
boolean allowSourceMismatch) {
|
||||||
if (!Objects.equals(source.getContractId(), settlement.getContractId())) {
|
if (!Objects.equals(source.getContractId(), settlement.getContractId())) {
|
||||||
throw new ServiceException("仅可选择当前合同的应收应付明细");
|
throw new ServiceException("仅可选择当前合同的应收应付明细");
|
||||||
}
|
}
|
||||||
if (!Objects.equals(source.getSettlementType(), settlement.getSettlementType())) {
|
if (!Objects.equals(source.getSettlementType(), settlement.getSettlementType())) {
|
||||||
throw new ServiceException("应收应付明细的结算类型不一致");
|
throw new ServiceException("应收应付明细的结算类型不一致");
|
||||||
}
|
}
|
||||||
if (!Objects.equals(source.getProjectId(), settlement.getProjectId())
|
if (!allowSourceMismatch && (!Objects.equals(source.getProjectId(), settlement.getProjectId())
|
||||||
|| !Objects.equals(source.getDeptId(), settlement.getDeptId())
|
|| !Objects.equals(source.getDeptId(), settlement.getDeptId())
|
||||||
|| !List.of(settlement.getPayerName(), settlement.getPayeeName()).contains(source.getCustomerName())) {
|
|| !List.of(settlement.getPayerName(), settlement.getPayeeName()).contains(source.getCustomerName()))) {
|
||||||
throw new ServiceException("应收应付明细的项目、所属组织或客商与预结算单不一致");
|
throw new ServiceException("应收应付明细的项目、所属组织或客商与预结算单不一致");
|
||||||
}
|
}
|
||||||
if (!"pending".equals(source.getSettlementStatus()) || Func.isNotEmpty(source.getPreSettlementNo())
|
if (!"pending".equals(source.getSettlementStatus()) || Func.isNotEmpty(source.getPreSettlementNo())
|
||||||
@@ -712,6 +713,8 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
|||||||
private PreSettlementDetail copySourceDetail(PreSettlement settlement, ReceivablePayableDetail source) {
|
private PreSettlementDetail copySourceDetail(PreSettlement settlement, ReceivablePayableDetail source) {
|
||||||
PreSettlementDetail detail = new PreSettlementDetail();
|
PreSettlementDetail detail = new PreSettlementDetail();
|
||||||
detail.setPreSettlementId(settlement.getId());
|
detail.setPreSettlementId(settlement.getId());
|
||||||
|
// line_no 在数据库中为非空字段,插入后再统一重排前先提供临时行号。
|
||||||
|
detail.setLineNo(1);
|
||||||
detail.setSourceDetailId(source.getId());
|
detail.setSourceDetailId(source.getId());
|
||||||
detail.setDocumentNo(source.getDocumentNo());
|
detail.setDocumentNo(source.getDocumentNo());
|
||||||
detail.setWaybillId(source.getWaybillId());
|
detail.setWaybillId(source.getWaybillId());
|
||||||
@@ -1092,6 +1095,10 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String resolveSettlementType(PreSettlementSaveRequest request, ContractManage contract) {
|
private String resolveSettlementType(PreSettlementSaveRequest request, ContractManage contract) {
|
||||||
|
if (Boolean.TRUE.equals(request.getAllowSourceMismatch()) && Func.isNotEmpty(request.getSettlementType())) {
|
||||||
|
validateSettlementType(request.getSettlementType());
|
||||||
|
return request.getSettlementType();
|
||||||
|
}
|
||||||
return contractSettlementType(contract);
|
return contractSettlementType(contract);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+93
-32
@@ -37,7 +37,9 @@ import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
|
|||||||
import org.springblade.transport.mapper.ReceivablePayableChangeRecordMapper;
|
import org.springblade.transport.mapper.ReceivablePayableChangeRecordMapper;
|
||||||
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
|
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
|
||||||
import org.springblade.transport.pojo.dto.ReceivablePayableAdjustFeeRequest;
|
import org.springblade.transport.pojo.dto.ReceivablePayableAdjustFeeRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest;
|
||||||
import org.springblade.transport.pojo.dto.ReceivablePayableGenerateRequest;
|
import org.springblade.transport.pojo.dto.ReceivablePayableGenerateRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.PreSettlementSaveRequest;
|
||||||
import org.springblade.transport.pojo.dto.ReceivablePayableTransferRequest;
|
import org.springblade.transport.pojo.dto.ReceivablePayableTransferRequest;
|
||||||
import org.springblade.transport.pojo.dto.ReceivablePayableUpdateFeeRequest;
|
import org.springblade.transport.pojo.dto.ReceivablePayableUpdateFeeRequest;
|
||||||
import org.springblade.transport.pojo.entity.ContractManage;
|
import org.springblade.transport.pojo.entity.ContractManage;
|
||||||
@@ -52,9 +54,12 @@ import org.springblade.transport.pojo.vo.ReceivablePayableDetailVO;
|
|||||||
import org.springblade.transport.pojo.vo.ReceivablePayableFeeDetailVO;
|
import org.springblade.transport.pojo.vo.ReceivablePayableFeeDetailVO;
|
||||||
import org.springblade.transport.service.IContractManageService;
|
import org.springblade.transport.service.IContractManageService;
|
||||||
import org.springblade.transport.service.ICommonAddressService;
|
import org.springblade.transport.service.ICommonAddressService;
|
||||||
|
import org.springblade.transport.service.IFormalSettlementService;
|
||||||
|
import org.springblade.transport.service.IPreSettlementService;
|
||||||
import org.springblade.transport.service.IReceivablePayableDetailService;
|
import org.springblade.transport.service.IReceivablePayableDetailService;
|
||||||
import org.springblade.transport.service.IWaybillService;
|
import org.springblade.transport.service.IWaybillService;
|
||||||
import org.springblade.transport.wrapper.ReceivablePayableDetailWrapper;
|
import org.springblade.transport.wrapper.ReceivablePayableDetailWrapper;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -91,17 +96,23 @@ public class ReceivablePayableDetailServiceImpl
|
|||||||
private final IWaybillService waybillService;
|
private final IWaybillService waybillService;
|
||||||
private final IContractManageService contractManageService;
|
private final IContractManageService contractManageService;
|
||||||
private final ICommonAddressService commonAddressService;
|
private final ICommonAddressService commonAddressService;
|
||||||
|
private final IPreSettlementService preSettlementService;
|
||||||
|
private final IFormalSettlementService formalSettlementService;
|
||||||
|
|
||||||
public ReceivablePayableDetailServiceImpl(ReceivablePayableCargoFeeMapper cargoFeeMapper,
|
public ReceivablePayableDetailServiceImpl(ReceivablePayableCargoFeeMapper cargoFeeMapper,
|
||||||
ReceivablePayableChangeRecordMapper changeRecordMapper,
|
ReceivablePayableChangeRecordMapper changeRecordMapper,
|
||||||
IWaybillService waybillService,
|
IWaybillService waybillService,
|
||||||
IContractManageService contractManageService,
|
IContractManageService contractManageService,
|
||||||
ICommonAddressService commonAddressService) {
|
ICommonAddressService commonAddressService,
|
||||||
|
@Lazy IPreSettlementService preSettlementService,
|
||||||
|
@Lazy IFormalSettlementService formalSettlementService) {
|
||||||
this.cargoFeeMapper = cargoFeeMapper;
|
this.cargoFeeMapper = cargoFeeMapper;
|
||||||
this.changeRecordMapper = changeRecordMapper;
|
this.changeRecordMapper = changeRecordMapper;
|
||||||
this.waybillService = waybillService;
|
this.waybillService = waybillService;
|
||||||
this.contractManageService = contractManageService;
|
this.contractManageService = contractManageService;
|
||||||
this.commonAddressService = commonAddressService;
|
this.commonAddressService = commonAddressService;
|
||||||
|
this.preSettlementService = preSettlementService;
|
||||||
|
this.formalSettlementService = formalSettlementService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -269,34 +280,60 @@ public class ReceivablePayableDetailServiceImpl
|
|||||||
if (details.size() != request.getIds().size()) {
|
if (details.size() != request.getIds().size()) {
|
||||||
throw new ServiceException("存在无效的费用明细");
|
throw new ServiceException("存在无效的费用明细");
|
||||||
}
|
}
|
||||||
String billNo = settlementBillNo(request.getSettlementBillType());
|
validateTransferDetails(details);
|
||||||
for (ReceivablePayableDetail detail : details) {
|
List<Long> detailIds = details.stream().map(ReceivablePayableDetail::getId).toList();
|
||||||
if (!"pending".equals(detail.getSettlementStatus())) {
|
ReceivablePayableDetail first = details.get(0);
|
||||||
throw new ServiceException("仅待结算明细允许转结算");
|
if ("pre".equals(request.getSettlementBillType())) {
|
||||||
}
|
PreSettlementSaveRequest saveRequest = new PreSettlementSaveRequest();
|
||||||
if ("pre".equals(request.getSettlementBillType())) {
|
saveRequest.setContractId(first.getContractId());
|
||||||
detail.setPreSettlementNo(billNo);
|
saveRequest.setSettlementType(first.getSettlementType());
|
||||||
detail.setSettlementStatus("pre_settled");
|
saveRequest.setSourceDetailIds(detailIds);
|
||||||
} else {
|
saveRequest.setAllowSourceMismatch(true);
|
||||||
detail.setFormalSettlementNo(billNo);
|
preSettlementService.saveDraft(saveRequest);
|
||||||
detail.setSettlementStatus("formal_settled");
|
return;
|
||||||
}
|
}
|
||||||
updateById(detail);
|
FormalSettlementSaveRequest saveRequest = new FormalSettlementSaveRequest();
|
||||||
|
saveRequest.setContractId(first.getContractId());
|
||||||
|
saveRequest.setSettlementType(first.getSettlementType());
|
||||||
|
saveRequest.setSourceDetailIds(detailIds);
|
||||||
|
formalSettlementService.saveDraft(saveRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateTransferDetails(List<ReceivablePayableDetail> details) {
|
||||||
|
ReceivablePayableDetail first = details.get(0);
|
||||||
|
String settlementType = first.getSettlementType();
|
||||||
|
Long contractId = first.getContractId();
|
||||||
|
if (Func.isEmpty(settlementType) || contractId == null
|
||||||
|
|| details.stream().anyMatch(detail -> Objects.equals(detail.getIsDeleted(), 1)
|
||||||
|
|| !Objects.equals(detail.getSettlementType(), settlementType)
|
||||||
|
|| !Objects.equals(detail.getContractId(), contractId)
|
||||||
|
|| !"pending".equals(detail.getSettlementStatus())
|
||||||
|
|| Func.isNotEmpty(detail.getPreSettlementNo())
|
||||||
|
|| Func.isNotEmpty(detail.getFormalSettlementNo()))) {
|
||||||
|
throw new ServiceException("所选明细必须属于同一合同、结算类型且均为未结算状态");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IPage<Map<String, Object>> transferCandidates(IPage<?> page, String contractName, String batchNo,
|
public IPage<Map<String, Object>> transferCandidates(IPage<?> page, String contractName, String batchNo,
|
||||||
String generateStartDate, String generateEndDate, String settlementBillType) {
|
String generateStartDate, String generateEndDate, String settlementBillType,
|
||||||
|
String settlementType) {
|
||||||
ReceivablePayableDetailVO query = new ReceivablePayableDetailVO();
|
ReceivablePayableDetailVO query = new ReceivablePayableDetailVO();
|
||||||
query.setContractName(contractName);
|
query.setContractName(contractName);
|
||||||
query.setBatchNo(batchNo);
|
query.setBatchNo(batchNo);
|
||||||
query.setSettlementStatus("pending");
|
query.setSettlementStatus("pending");
|
||||||
|
query.setSettlementType(Func.isEmpty(settlementType) ? null : settlementType(settlementType));
|
||||||
query.setGenerateStartDate(parseDate(generateStartDate));
|
query.setGenerateStartDate(parseDate(generateStartDate));
|
||||||
query.setGenerateEndDate(parseDate(generateEndDate));
|
query.setGenerateEndDate(parseDate(generateEndDate));
|
||||||
IPage<ReceivablePayableDetailVO> detailPage = selectPage(new Page<>(page.getCurrent(), page.getSize()), query);
|
LambdaQueryWrapper<ReceivablePayableDetail> wrapper = buildQuery(query)
|
||||||
|
.and(item -> item.isNull(ReceivablePayableDetail::getPreSettlementNo)
|
||||||
|
.or().eq(ReceivablePayableDetail::getPreSettlementNo, ""))
|
||||||
|
.and(item -> item.isNull(ReceivablePayableDetail::getFormalSettlementNo)
|
||||||
|
.or().eq(ReceivablePayableDetail::getFormalSettlementNo, ""));
|
||||||
|
IPage<ReceivablePayableDetailVO> detailPage = ReceivablePayableDetailWrapper.build()
|
||||||
|
.pageVO(page(new Page<>(page.getCurrent(), page.getSize()), wrapper));
|
||||||
Page<Map<String, Object>> result = new Page<>(detailPage.getCurrent(), detailPage.getSize(), detailPage.getTotal());
|
Page<Map<String, Object>> result = new Page<>(detailPage.getCurrent(), detailPage.getSize(), detailPage.getTotal());
|
||||||
result.setRecords(detailPage.getRecords().stream().map(this::beanMap).toList());
|
result.setRecords(detailPage.getRecords().stream().map(this::candidateMap).toList());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,12 +558,12 @@ public class ReceivablePayableDetailServiceImpl
|
|||||||
BigDecimal total = fees.stream().map(ReceivablePayableCargoFee::getAfterAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
BigDecimal total = fees.stream().map(ReceivablePayableCargoFee::getAfterAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
BigDecimal other = total.subtract(freight).setScale(2, RoundingMode.HALF_UP);
|
BigDecimal other = total.subtract(freight).setScale(2, RoundingMode.HALF_UP);
|
||||||
ReceivablePayableDetail detail = new ReceivablePayableDetail();
|
ReceivablePayableDetail detail = new ReceivablePayableDetail();
|
||||||
detail.setDocumentNo(nextDocumentNo());
|
detail.setDocumentNo(nextDocumentNo(settlementType));
|
||||||
detail.setSettlementType(settlementType);
|
detail.setSettlementType(settlementType);
|
||||||
detail.setProjectId(waybill.getProjectId());
|
detail.setProjectId(contract == null ? waybill.getProjectId() : contract.getProjectId());
|
||||||
detail.setProjectName(waybill.getProjectName());
|
detail.setProjectName(contract == null ? waybill.getProjectName() : contract.getProjectName());
|
||||||
detail.setDeptId(waybill.getDeptId());
|
detail.setDeptId(contract == null ? waybill.getDeptId() : contract.getOrganizationId());
|
||||||
detail.setDeptName(waybill.getDeptName());
|
detail.setDeptName(contract == null ? waybill.getDeptName() : contract.getOrganizationName());
|
||||||
detail.setFeeDate(waybill.getEndDate() == null ? LocalDate.now() : waybill.getEndDate());
|
detail.setFeeDate(waybill.getEndDate() == null ? LocalDate.now() : waybill.getEndDate());
|
||||||
detail.setCustomerName("payable".equals(settlementType)
|
detail.setCustomerName("payable".equals(settlementType)
|
||||||
? (contract == null ? waybill.getCarrierName() : contract.getPartyA())
|
? (contract == null ? waybill.getCarrierName() : contract.getPartyA())
|
||||||
@@ -926,9 +963,36 @@ public class ReceivablePayableDetailServiceImpl
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> beanMap(ReceivablePayableDetailVO detail) {
|
private Map<String, Object> candidateMap(ReceivablePayableDetailVO detail) {
|
||||||
Map<String, Object> map = new LinkedHashMap<>();
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
BeanUtil.copyProperties(detail, map);
|
map.put("id", detail.getId());
|
||||||
|
map.put("documentNo", detail.getDocumentNo());
|
||||||
|
map.put("settlementType", detail.getSettlementType());
|
||||||
|
map.put("projectName", detail.getProjectName());
|
||||||
|
map.put("deptName", detail.getDeptName());
|
||||||
|
map.put("feeDate", detail.getFeeDate());
|
||||||
|
map.put("customerName", detail.getCustomerName());
|
||||||
|
map.put("contractNo", detail.getContractNo());
|
||||||
|
map.put("contractName", detail.getContractName());
|
||||||
|
map.put("preSettlementNo", detail.getPreSettlementNo());
|
||||||
|
map.put("formalSettlementNo", detail.getFormalSettlementNo());
|
||||||
|
map.put("waybillNo", detail.getWaybillNo());
|
||||||
|
map.put("vehicleNo", detail.getVehicleNo());
|
||||||
|
map.put("transportType", detail.getTransportType());
|
||||||
|
map.put("cargoName", detail.getCargoName());
|
||||||
|
map.put("cargoType", detail.getCargoType());
|
||||||
|
map.put("transportQuantity", detail.getTransportQuantity());
|
||||||
|
map.put("quantityUnit", detail.getQuantityUnit());
|
||||||
|
map.put("mileage", detail.getMileage());
|
||||||
|
map.put("batchNo", detail.getBatchNo());
|
||||||
|
map.put("unitPrice", detail.getUnitPrice());
|
||||||
|
map.put("currency", detail.getCurrency());
|
||||||
|
map.put("freightAmount", detail.getFreightAmount());
|
||||||
|
map.put("otherFeeAmount", detail.getOtherFeeAmount());
|
||||||
|
map.put("totalAmount", detail.getTotalAmount());
|
||||||
|
map.put("settlementStatus", detail.getSettlementStatus());
|
||||||
|
map.put("settlementStatusName", detail.getSettlementStatusName());
|
||||||
|
map.put("remark", detail.getRemark());
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,12 +1050,9 @@ public class ReceivablePayableDetailServiceImpl
|
|||||||
return Func.isEmpty(value) ? null : LocalDate.parse(value);
|
return Func.isEmpty(value) ? null : LocalDate.parse(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized String nextDocumentNo() {
|
private synchronized String nextDocumentNo(String settlementType) {
|
||||||
return "YS" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + System.currentTimeMillis() % 100000;
|
String prefix = "payable".equals(settlementType) ? "YF" : "YS";
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized String settlementBillNo(String type) {
|
|
||||||
String prefix = "pre".equals(type) ? "YJ" : "ZJ";
|
|
||||||
return prefix + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + System.currentTimeMillis() % 100000;
|
return prefix + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + System.currentTimeMillis() % 100000;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+205
@@ -0,0 +1,205 @@
|
|||||||
|
package org.springblade.transport.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
|
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||||
|
import org.springblade.core.secure.utils.AuthUtil;
|
||||||
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.system.cache.UserCache;
|
||||||
|
import org.springblade.transport.mapper.FormalSettlementDetailFeeMapper;
|
||||||
|
import org.springblade.transport.mapper.FormalSettlementDetailMapper;
|
||||||
|
import org.springblade.transport.mapper.FormalSettlementMapper;
|
||||||
|
import org.springblade.transport.mapper.SettlementAdjustmentDetailMapper;
|
||||||
|
import org.springblade.transport.mapper.SettlementAdjustmentMapper;
|
||||||
|
import org.springblade.transport.pojo.dto.SettlementAdjustmentSaveRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.SettlementAdjustmentStatusRequest;
|
||||||
|
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||||
|
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
|
||||||
|
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
|
||||||
|
import org.springblade.transport.pojo.entity.SettlementAdjustment;
|
||||||
|
import org.springblade.transport.pojo.entity.SettlementAdjustmentDetail;
|
||||||
|
import org.springblade.transport.pojo.vo.SettlementAdjustmentVO;
|
||||||
|
import org.springblade.transport.service.ISettlementAdjustmentService;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementAdjustmentMapper, SettlementAdjustment>
|
||||||
|
implements ISettlementAdjustmentService {
|
||||||
|
private static final String DRAFT = "draft";
|
||||||
|
private static final String REVIEWING = "reviewing";
|
||||||
|
private static final String APPROVED = "approved";
|
||||||
|
private static final String RETURNED = "returned";
|
||||||
|
private final SettlementAdjustmentDetailMapper detailMapper;
|
||||||
|
private final FormalSettlementMapper formalMapper;
|
||||||
|
private final FormalSettlementDetailMapper formalDetailMapper;
|
||||||
|
private final FormalSettlementDetailFeeMapper formalFeeMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<SettlementAdjustmentVO> selectPage(IPage<SettlementAdjustment> page, SettlementAdjustmentVO query) {
|
||||||
|
LambdaQueryWrapper<SettlementAdjustment> wrapper = Wrappers.<SettlementAdjustment>lambdaQuery()
|
||||||
|
.like(Func.isNotEmpty(query.getAdjustmentNo()), SettlementAdjustment::getAdjustmentNo, query.getAdjustmentNo())
|
||||||
|
.like(Func.isNotEmpty(query.getFormalSettlementNo()), SettlementAdjustment::getFormalSettlementNo, query.getFormalSettlementNo())
|
||||||
|
.like(Func.isNotEmpty(query.getCustomerName()), SettlementAdjustment::getCustomerName, query.getCustomerName())
|
||||||
|
.like(Func.isNotEmpty(query.getProjectName()), SettlementAdjustment::getProjectName, query.getProjectName())
|
||||||
|
.like(Func.isNotEmpty(query.getDeptName()), SettlementAdjustment::getDeptName, query.getDeptName())
|
||||||
|
.eq(Func.isNotEmpty(query.getSettlementType()), SettlementAdjustment::getSettlementType, query.getSettlementType())
|
||||||
|
.eq(Func.isNotEmpty(query.getApprovalStatus()), SettlementAdjustment::getApprovalStatus, query.getApprovalStatus())
|
||||||
|
.ge(query.getCreateStartDate() != null, SettlementAdjustment::getCreateTime,
|
||||||
|
query.getCreateStartDate() == null ? null : query.getCreateStartDate().atStartOfDay())
|
||||||
|
.lt(query.getCreateEndDate() != null, SettlementAdjustment::getCreateTime,
|
||||||
|
query.getCreateEndDate() == null ? null : query.getCreateEndDate().plusDays(1).atStartOfDay())
|
||||||
|
.orderByDesc(SettlementAdjustment::getCreateTime);
|
||||||
|
return page(page, wrapper).convert(this::toVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SettlementAdjustmentVO detail(Long id) {
|
||||||
|
SettlementAdjustment adjustment = existing(id);
|
||||||
|
SettlementAdjustmentVO vo = toVO(adjustment);
|
||||||
|
vo.setDetails(detailMapper.selectList(Wrappers.<SettlementAdjustmentDetail>lambdaQuery()
|
||||||
|
.eq(SettlementAdjustmentDetail::getAdjustmentId, id).orderByAsc(SettlementAdjustmentDetail::getCreateTime)));
|
||||||
|
vo.setFormalDetails(formalDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
|
||||||
|
.eq(FormalSettlementDetail::getFormalSettlementId, adjustment.getFormalSettlementId())
|
||||||
|
.orderByAsc(FormalSettlementDetail::getLineNo)));
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Map<String, Object>> candidateFormalSettlements(String keyword) {
|
||||||
|
List<FormalSettlement> rows = formalMapper.selectList(Wrappers.<FormalSettlement>lambdaQuery()
|
||||||
|
.eq(FormalSettlement::getApprovalStatus, APPROVED)
|
||||||
|
.like(Func.isNotEmpty(keyword), FormalSettlement::getFormalSettlementNo, keyword)
|
||||||
|
.orderByDesc(FormalSettlement::getCreateTime));
|
||||||
|
List<Map<String, Object>> result = new ArrayList<>();
|
||||||
|
for (FormalSettlement row : rows) {
|
||||||
|
Map<String, Object> item = new HashMap<>();
|
||||||
|
item.put("id", row.getId()); item.put("formalSettlementNo", row.getFormalSettlementNo());
|
||||||
|
item.put("settlementType", row.getSettlementType()); item.put("settlementTypeName", typeName(row.getSettlementType()));
|
||||||
|
item.put("projectName", row.getProjectName()); item.put("deptName", row.getDeptName());
|
||||||
|
item.put("customerName", "receivable".equals(row.getSettlementType()) ? row.getPayerName() : row.getPayeeName());
|
||||||
|
item.put("contractNo", row.getContractNo()); item.put("contractName", row.getContractName());
|
||||||
|
item.put("settlementAmount", row.getSettlementAmount()); item.put("kingdeeSyncStatus", row.getKingdeeSyncStatus());
|
||||||
|
result.add(item);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Map<String, Object>> formalDetails(Long formalSettlementId) {
|
||||||
|
FormalSettlement settlement = formalMapper.selectById(formalSettlementId);
|
||||||
|
if (settlement == null || !APPROVED.equals(settlement.getApprovalStatus())) throw new ServiceException("仅审批通过的正式结算单可调整");
|
||||||
|
List<Map<String, Object>> result = new ArrayList<>();
|
||||||
|
List<FormalSettlementDetail> details = formalDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
|
||||||
|
.eq(FormalSettlementDetail::getFormalSettlementId, formalSettlementId).orderByAsc(FormalSettlementDetail::getLineNo));
|
||||||
|
for (FormalSettlementDetail detail : details) {
|
||||||
|
for (FormalSettlementDetailFee fee : formalFeeMapper.selectList(Wrappers.<FormalSettlementDetailFee>lambdaQuery()
|
||||||
|
.eq(FormalSettlementDetailFee::getFormalSettlementDetailId, detail.getId()).orderByAsc(FormalSettlementDetailFee::getLineNo))) {
|
||||||
|
Map<String, Object> item = new HashMap<>();
|
||||||
|
item.put("formalSettlementDetailId", detail.getId()); item.put("formalSettlementDetailFeeId", fee.getId());
|
||||||
|
item.put("documentNo", detail.getDocumentNo()); item.put("cargoName", fee.getCargoName());
|
||||||
|
item.put("cargoType", fee.getCargoType()); item.put("feeType", Func.isEmpty(fee.getCargoType()) ? "运输费用" : fee.getCargoType());
|
||||||
|
item.put("feeItem", Func.isEmpty(fee.getCargoName()) ? "结算调整" : fee.getCargoName()); item.put("originalAmountTax", fee.getSettlementAmountTax());
|
||||||
|
item.put("remark", fee.getRemark()); result.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Long saveDraft(SettlementAdjustmentSaveRequest request) {
|
||||||
|
if (request.getFormalSettlementId() == null) throw new ServiceException("请选择关联正式结算单");
|
||||||
|
if (Func.isEmpty(request.getDetails())) throw new ServiceException("请至少添加一条调整费用");
|
||||||
|
FormalSettlement formal = formalMapper.selectById(request.getFormalSettlementId());
|
||||||
|
if (formal == null || !APPROVED.equals(formal.getApprovalStatus())) throw new ServiceException("仅审批通过的正式结算单可调整");
|
||||||
|
SettlementAdjustment adjustment = request.getId() == null ? new SettlementAdjustment() : editable(request.getId());
|
||||||
|
if (adjustment.getId() != null) detailMapper.delete(Wrappers.<SettlementAdjustmentDetail>lambdaQuery().eq(SettlementAdjustmentDetail::getAdjustmentId, adjustment.getId()));
|
||||||
|
if (adjustment.getId() == null) { adjustment.setAdjustmentNo(nextNo()); adjustment.setApprovalStatus(DRAFT); }
|
||||||
|
adjustment.setFormalSettlementId(formal.getId()); adjustment.setFormalSettlementNo(formal.getFormalSettlementNo());
|
||||||
|
adjustment.setSettlementType(formal.getSettlementType()); adjustment.setProjectName(formal.getProjectName());
|
||||||
|
adjustment.setDeptName(formal.getDeptName()); adjustment.setCustomerName("receivable".equals(formal.getSettlementType()) ? formal.getPayerName() : formal.getPayeeName());
|
||||||
|
adjustment.setContractNo(formal.getContractNo()); adjustment.setContractName(formal.getContractName());
|
||||||
|
adjustment.setKingdeeSyncStatus(formal.getKingdeeSyncStatus());
|
||||||
|
adjustment.setOriginalSettlementAmount(money(formal.getSettlementAmount())); adjustment.setRemark(limit(request.getRemark(), 200));
|
||||||
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
|
if (request.getDetails() != null) for (SettlementAdjustmentSaveRequest.Detail row : request.getDetails()) {
|
||||||
|
FormalSettlementDetailFee fee = validateFee(formal.getId(), row.getFormalSettlementDetailId(), row.getFormalSettlementDetailFeeId());
|
||||||
|
if (adjustment.getId() == null) save(adjustment);
|
||||||
|
SettlementAdjustmentDetail detail = new SettlementAdjustmentDetail(); detail.setAdjustmentId(adjustment.getId());
|
||||||
|
detail.setFormalSettlementDetailId(row.getFormalSettlementDetailId()); detail.setFormalSettlementDetailFeeId(row.getFormalSettlementDetailFeeId());
|
||||||
|
detail.setFeeType(row.getFeeType()); detail.setFeeItem(row.getFeeItem()); detail.setOriginalAmountTax(fee.getSettlementAmountTax());
|
||||||
|
detail.setAdjustmentAmountTax(row.getAdjustmentAmountTax() == null ? BigDecimal.ZERO : row.getAdjustmentAmountTax());
|
||||||
|
detail.setAdjustmentAmountNoTax(row.getAdjustmentAmountNoTax()); detail.setRemark(limit(row.getRemark(), 200)); detailMapper.insert(detail);
|
||||||
|
total = total.add(detail.getAdjustmentAmountTax());
|
||||||
|
}
|
||||||
|
adjustment.setAdjustmentAmount(total); adjustment.setAdjustedSettlementAmount(adjustment.getOriginalSettlementAmount().add(total)); saveOrUpdate(adjustment);
|
||||||
|
return adjustment.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override @Transactional(rollbackFor = Exception.class)
|
||||||
|
public void removeDraft(Long id) { SettlementAdjustment item = editable(id); detailMapper.delete(Wrappers.<SettlementAdjustmentDetail>lambdaQuery().eq(SettlementAdjustmentDetail::getAdjustmentId, id)); removeById(item.getId()); }
|
||||||
|
@Override public void submit(SettlementAdjustmentStatusRequest request) { changeStatus(request.getId(), DRAFT, REVIEWING, "审批中", null); }
|
||||||
|
@Override public void returnBill(SettlementAdjustmentStatusRequest request) { changeStatus(request.getId(), REVIEWING, RETURNED, "已驳回", limit(request.getReason(), 200)); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public String repush(Long adjustmentId) {
|
||||||
|
SettlementAdjustment adjustment = existing(adjustmentId);
|
||||||
|
if (!APPROVED.equals(adjustment.getApprovalStatus())) throw new ServiceException("仅审批通过的结算调整单允许重新推送");
|
||||||
|
FormalSettlement formal = formalMapper.selectById(adjustment.getFormalSettlementId());
|
||||||
|
if (formal == null || !"synced".equals(formal.getKingdeeSyncStatus())) throw new ServiceException("关联正式结算单尚未推送金蝶,无需重新推送");
|
||||||
|
String kingdeeNo = "K3AP" + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
|
||||||
|
formal.setKingdeeBillNo(kingdeeNo); formal.setSyncedTime(LocalDateTime.now()); formalMapper.updateById(formal);
|
||||||
|
return kingdeeNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void approve(SettlementAdjustmentStatusRequest request) {
|
||||||
|
SettlementAdjustment adjustment = existing(request.getId());
|
||||||
|
if (!REVIEWING.equals(adjustment.getApprovalStatus())) throw new ServiceException("仅审批中的结算调整单允许审核");
|
||||||
|
FormalSettlement formal = formalMapper.selectById(adjustment.getFormalSettlementId());
|
||||||
|
if (formal == null || !APPROVED.equals(formal.getApprovalStatus())) throw new ServiceException("关联正式结算单状态已变化,无法审批");
|
||||||
|
for (SettlementAdjustmentDetail item : detailMapper.selectList(Wrappers.<SettlementAdjustmentDetail>lambdaQuery().eq(SettlementAdjustmentDetail::getAdjustmentId, adjustment.getId()))) {
|
||||||
|
FormalSettlementDetailFee fee = validateFee(formal.getId(), item.getFormalSettlementDetailId(), item.getFormalSettlementDetailFeeId());
|
||||||
|
fee.setSettlementAmountTax(money(fee.getSettlementAmountTax()).add(money(item.getAdjustmentAmountTax())));
|
||||||
|
if (item.getAdjustmentAmountNoTax() != null) fee.setSettlementAmountNoTax(money(fee.getSettlementAmountNoTax()).add(item.getAdjustmentAmountNoTax()));
|
||||||
|
fee.setAdjustAmount(money(fee.getAdjustAmount()).add(item.getAdjustmentAmountTax())); formalFeeMapper.updateById(fee);
|
||||||
|
}
|
||||||
|
for (FormalSettlementDetail detail : formalDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery().eq(FormalSettlementDetail::getFormalSettlementId, formal.getId()))) {
|
||||||
|
List<FormalSettlementDetailFee> fees = formalFeeMapper.selectList(Wrappers.<FormalSettlementDetailFee>lambdaQuery().eq(FormalSettlementDetailFee::getFormalSettlementDetailId, detail.getId()));
|
||||||
|
detail.setSettlementAmountTax(fees.stream().map(FormalSettlementDetailFee::getSettlementAmountTax).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||||
|
detail.setSettlementAmountNoTax(fees.stream().map(FormalSettlementDetailFee::getSettlementAmountNoTax).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||||
|
detail.setAdjustAmount(detail.getSettlementAmountTax().subtract(money(detail.getOriginalAmount()))); formalDetailMapper.updateById(detail);
|
||||||
|
}
|
||||||
|
BigDecimal amount = formalDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery().eq(FormalSettlementDetail::getFormalSettlementId, formal.getId())).stream().map(FormalSettlementDetail::getSettlementAmountTax).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
formal.setSettlementAmount(amount); formal.setLocalSettlementAmount(amount.multiply(formal.getExchangeRate() == null ? BigDecimal.ONE : formal.getExchangeRate())); formalMapper.updateById(formal);
|
||||||
|
adjustment.setKingdeeSyncStatus(formal.getKingdeeSyncStatus()); adjustment.setApprovalStatus(APPROVED); adjustment.setCurrentNode("审批通过"); adjustment.setCurrentProcessor(AuthUtil.getUserName()); adjustment.setApprovedTime(LocalDateTime.now()); updateById(adjustment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeStatus(Long id, String from, String to, String node, String reason) { SettlementAdjustment item = existing(id); if (!from.equals(item.getApprovalStatus())) throw new ServiceException("当前状态不允许此操作"); item.setApprovalStatus(to); item.setCurrentNode(node); if (reason != null) item.setRemark(reason); updateById(item); }
|
||||||
|
private SettlementAdjustment existing(Long id) { SettlementAdjustment item = getById(id); if (item == null || Objects.equals(item.getIsDeleted(), 1)) throw new ServiceException("结算调整单不存在"); return item; }
|
||||||
|
private SettlementAdjustment editable(Long id) { SettlementAdjustment item = existing(id); if (!(DRAFT.equals(item.getApprovalStatus()) || RETURNED.equals(item.getApprovalStatus()))) throw new ServiceException("仅草稿或驳回的调整单可编辑"); return item; }
|
||||||
|
private FormalSettlementDetailFee validateFee(Long formalId, Long detailId, Long feeId) { FormalSettlementDetail detail = formalDetailMapper.selectById(detailId); FormalSettlementDetailFee fee = formalFeeMapper.selectById(feeId); if (detail == null || fee == null || !Objects.equals(detail.getFormalSettlementId(), formalId) || !Objects.equals(fee.getFormalSettlementDetailId(), detailId)) throw new ServiceException("费用明细不存在或不属于关联正式结算单"); return fee; }
|
||||||
|
private SettlementAdjustmentVO toVO(SettlementAdjustment item) { SettlementAdjustmentVO vo = new SettlementAdjustmentVO(); org.springframework.beans.BeanUtils.copyProperties(item, vo); vo.setCreateUserName(UserCache.getUserRealName(item.getCreateUser())); vo.setApprovalStatusName(statusName(item.getApprovalStatus())); vo.setSettlementTypeName(typeName(item.getSettlementType())); return vo; }
|
||||||
|
private String statusName(String value) { return Map.of(DRAFT, "草稿", REVIEWING, "审批中", APPROVED, "审批通过", RETURNED, "已驳回").getOrDefault(value, value); }
|
||||||
|
private String typeName(String value) { return "receivable".equals(value) ? "应收" : "应付"; }
|
||||||
|
private synchronized String nextNo() { String prefix = "TZ" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); long count = count(Wrappers.<SettlementAdjustment>lambdaQuery().likeRight(SettlementAdjustment::getAdjustmentNo, prefix)); return prefix + String.format("%04d", count + 1); }
|
||||||
|
private BigDecimal money(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; }
|
||||||
|
private String limit(String value, int max) { if (value != null && value.length() > max) throw new ServiceException("内容不能超过" + max + "个字"); return value; }
|
||||||
|
}
|
||||||
+561
@@ -0,0 +1,561 @@
|
|||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
* <p>Author: Chill Zhuang (bladejava@qq.com)</p>
|
||||||
|
*/
|
||||||
|
package org.springblade.transport.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springblade.core.log.exception.ServiceException;
|
||||||
|
import org.springblade.core.mp.base.BaseServiceImpl;
|
||||||
|
import org.springblade.core.secure.utils.AuthUtil;
|
||||||
|
import org.springblade.core.tool.jackson.JsonUtil;
|
||||||
|
import org.springblade.core.tool.utils.BeanUtil;
|
||||||
|
import org.springblade.core.tool.utils.Func;
|
||||||
|
import org.springblade.transport.excel.CargoReconciliationExcel;
|
||||||
|
import org.springblade.transport.excel.CargoReconciliationFailureExcel;
|
||||||
|
import org.springblade.transport.excel.VehicleReconciliationExcel;
|
||||||
|
import org.springblade.transport.excel.VehicleReconciliationFailureExcel;
|
||||||
|
import org.springblade.transport.mapper.FormalSettlementDetailFeeMapper;
|
||||||
|
import org.springblade.transport.mapper.FormalSettlementDetailMapper;
|
||||||
|
import org.springblade.transport.mapper.FormalSettlementMapper;
|
||||||
|
import org.springblade.transport.mapper.FormalSettlementSourceMapper;
|
||||||
|
import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
|
||||||
|
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
|
||||||
|
import org.springblade.transport.mapper.TransportReconciliationChangeRecordMapper;
|
||||||
|
import org.springblade.transport.mapper.TransportReconciliationExternalMapper;
|
||||||
|
import org.springblade.transport.mapper.TransportReconciliationInternalMapper;
|
||||||
|
import org.springblade.transport.mapper.TransportReconciliationMapper;
|
||||||
|
import org.springblade.transport.pojo.dto.TransportReconciliationManualMatchRequest;
|
||||||
|
import org.springblade.transport.pojo.dto.TransportReconciliationSaveRequest;
|
||||||
|
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||||
|
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
|
||||||
|
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
|
||||||
|
import org.springblade.transport.pojo.entity.FormalSettlementSource;
|
||||||
|
import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee;
|
||||||
|
import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliation;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationChangeRecord;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationExternal;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliationInternal;
|
||||||
|
import org.springblade.transport.pojo.vo.TransportReconciliationVO;
|
||||||
|
import org.springblade.transport.service.ITransportReconciliationService;
|
||||||
|
import org.springblade.transport.wrapper.TransportReconciliationWrapper;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 运输对账单服务实现类
|
||||||
|
*
|
||||||
|
* @author Chill
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TransportReconciliationServiceImpl
|
||||||
|
extends BaseServiceImpl<TransportReconciliationMapper, TransportReconciliation>
|
||||||
|
implements ITransportReconciliationService {
|
||||||
|
|
||||||
|
private static final String VEHICLE = "vehicle";
|
||||||
|
private static final String CARGO = "cargo";
|
||||||
|
private static final String UNFINISHED = "unfinished";
|
||||||
|
private static final String COMPLETED = "completed";
|
||||||
|
private static final String MATCHED = "matched";
|
||||||
|
private static final String UNMATCHED = "unmatched";
|
||||||
|
private static final String DUPLICATE = "suspected_duplicate";
|
||||||
|
private final FormalSettlementMapper formalSettlementMapper;
|
||||||
|
private final FormalSettlementSourceMapper formalSourceMapper;
|
||||||
|
private final FormalSettlementDetailMapper formalDetailMapper;
|
||||||
|
private final FormalSettlementDetailFeeMapper formalDetailFeeMapper;
|
||||||
|
private final ReceivablePayableDetailMapper receivablePayableMapper;
|
||||||
|
private final ReceivablePayableCargoFeeMapper cargoFeeMapper;
|
||||||
|
private final TransportReconciliationInternalMapper internalMapper;
|
||||||
|
private final TransportReconciliationExternalMapper externalMapper;
|
||||||
|
private final TransportReconciliationChangeRecordMapper changeRecordMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<TransportReconciliationVO> selectPage(IPage<TransportReconciliation> page, TransportReconciliationVO query) {
|
||||||
|
LambdaQueryWrapper<TransportReconciliation> wrapper = Wrappers.<TransportReconciliation>lambdaQuery()
|
||||||
|
.like(Func.isNotEmpty(query.getReconciliationNo()), TransportReconciliation::getReconciliationNo, query.getReconciliationNo())
|
||||||
|
.like(Func.isNotEmpty(query.getFormalSettlementNo()), TransportReconciliation::getFormalSettlementNo, query.getFormalSettlementNo())
|
||||||
|
.like(Func.isNotEmpty(query.getPreSettlementNos()), TransportReconciliation::getPreSettlementNos, query.getPreSettlementNos())
|
||||||
|
.like(Func.isNotEmpty(query.getProjectName()), TransportReconciliation::getProjectName, query.getProjectName())
|
||||||
|
.like(Func.isNotEmpty(query.getDeptName()), TransportReconciliation::getDeptName, query.getDeptName())
|
||||||
|
.like(Func.isNotEmpty(query.getContractNo()), TransportReconciliation::getContractNo, query.getContractNo())
|
||||||
|
.like(Func.isNotEmpty(query.getPayerName()), TransportReconciliation::getPayerName, query.getPayerName())
|
||||||
|
.like(Func.isNotEmpty(query.getPayeeName()), TransportReconciliation::getPayeeName, query.getPayeeName())
|
||||||
|
.eq(Func.isNotEmpty(query.getSettlementType()), TransportReconciliation::getSettlementType, query.getSettlementType())
|
||||||
|
.eq(Func.isNotEmpty(query.getMatchStatus()), TransportReconciliation::getMatchStatus, query.getMatchStatus())
|
||||||
|
.eq(Func.isNotEmpty(query.getReconciliationStatus()), TransportReconciliation::getReconciliationStatus, query.getReconciliationStatus())
|
||||||
|
.orderByDesc(TransportReconciliation::getCreateTime);
|
||||||
|
return page(page, wrapper).convert(item -> TransportReconciliationWrapper.build().entityVO(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IPage<FormalSettlement> formalOptions(IPage<FormalSettlement> page, String settlementType, String keyword) {
|
||||||
|
List<Long> usedIds = list(Wrappers.<TransportReconciliation>lambdaQuery()
|
||||||
|
.select(TransportReconciliation::getFormalSettlementId))
|
||||||
|
.stream().map(TransportReconciliation::getFormalSettlementId).filter(Objects::nonNull).toList();
|
||||||
|
LambdaQueryWrapper<FormalSettlement> wrapper = Wrappers.<FormalSettlement>lambdaQuery()
|
||||||
|
.eq(FormalSettlement::getApprovalStatus, "approved")
|
||||||
|
.eq(Func.isNotEmpty(settlementType), FormalSettlement::getSettlementType, settlementType)
|
||||||
|
.and(Func.isNotEmpty(keyword), value -> value.like(FormalSettlement::getFormalSettlementNo, keyword)
|
||||||
|
.or().like(FormalSettlement::getContractNo, keyword).or().like(FormalSettlement::getContractName, keyword));
|
||||||
|
if (!usedIds.isEmpty()) wrapper.notIn(FormalSettlement::getId, usedIds);
|
||||||
|
return formalSettlementMapper.selectPage(page, wrapper.orderByDesc(FormalSettlement::getCreateTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportReconciliationVO detail(Long id) {
|
||||||
|
TransportReconciliationVO vo = TransportReconciliationWrapper.build().entityVO(existing(id));
|
||||||
|
vo.setInternalDetails(internalRows(id));
|
||||||
|
vo.setExternalDetails(externalRows(id));
|
||||||
|
vo.setChangeRecords(changeRecordMapper.selectList(Wrappers.<TransportReconciliationChangeRecord>lambdaQuery()
|
||||||
|
.eq(TransportReconciliationChangeRecord::getReconciliationId, id)
|
||||||
|
.orderByDesc(TransportReconciliationChangeRecord::getChangeTime)));
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Long saveDraft(TransportReconciliationSaveRequest request) {
|
||||||
|
if (!VEHICLE.equals(request.getReconciliationMode()) && !CARGO.equals(request.getReconciliationMode())) {
|
||||||
|
throw new ServiceException("请选择正确的对账模式");
|
||||||
|
}
|
||||||
|
FormalSettlement formal = formalSettlementMapper.selectById(request.getFormalSettlementId());
|
||||||
|
if (formal == null || !"approved".equals(formal.getApprovalStatus())) throw new ServiceException("请选择已生效的正式结算单");
|
||||||
|
long occupied = count(Wrappers.<TransportReconciliation>lambdaQuery()
|
||||||
|
.eq(TransportReconciliation::getFormalSettlementId, formal.getId())
|
||||||
|
.ne(request.getId() != null, TransportReconciliation::getId, request.getId()));
|
||||||
|
if (occupied > 0) throw new ServiceException("该正式结算单已归属其他对账单");
|
||||||
|
TransportReconciliation bill = request.getId() == null ? new TransportReconciliation() : editable(request.getId());
|
||||||
|
boolean rebuild = bill.getId() == null || !Objects.equals(bill.getFormalSettlementId(), formal.getId())
|
||||||
|
|| !Objects.equals(bill.getReconciliationMode(), request.getReconciliationMode());
|
||||||
|
if (bill.getId() == null) {
|
||||||
|
bill.setReconciliationNo(nextNo());
|
||||||
|
bill.setReconciliationStatus(UNFINISHED);
|
||||||
|
bill.setMatchStatus(UNMATCHED);
|
||||||
|
bill.setBillUpdated(false);
|
||||||
|
}
|
||||||
|
copyHeader(formal, bill);
|
||||||
|
bill.setReconciliationMode(request.getReconciliationMode());
|
||||||
|
bill.setReconcilerId(AuthUtil.getUserId());
|
||||||
|
bill.setReconcilerName(Func.isEmpty(AuthUtil.getUserName()) ? "system" : AuthUtil.getUserName());
|
||||||
|
bill.setReconciliationDate(request.getReconciliationDate() == null ? LocalDate.now() : request.getReconciliationDate());
|
||||||
|
bill.setRemark(limit(request.getRemark(), 200));
|
||||||
|
saveOrUpdate(bill);
|
||||||
|
if (rebuild) {
|
||||||
|
clearDetails(bill.getId());
|
||||||
|
buildInternalRows(bill, formal);
|
||||||
|
}
|
||||||
|
refreshStats(bill.getId());
|
||||||
|
return bill.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void removeDraft(Long id) {
|
||||||
|
editable(id);
|
||||||
|
clearDetails(id);
|
||||||
|
changeRecordMapper.delete(Wrappers.<TransportReconciliationChangeRecord>lambdaQuery()
|
||||||
|
.eq(TransportReconciliationChangeRecord::getReconciliationId, id));
|
||||||
|
removeById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public List<VehicleReconciliationFailureExcel> importVehicles(Long id, List<VehicleReconciliationExcel> rows) {
|
||||||
|
TransportReconciliation bill = editable(id);
|
||||||
|
if (!VEHICLE.equals(bill.getReconciliationMode())) throw new ServiceException("当前对账模式不是整车总额对账");
|
||||||
|
resetExternal(id);
|
||||||
|
List<VehicleReconciliationFailureExcel> failures = new ArrayList<>();
|
||||||
|
for (int index = 0; index < rows.size(); index++) {
|
||||||
|
VehicleReconciliationExcel row = rows.get(index);
|
||||||
|
try {
|
||||||
|
validateExternal(row.getVehicleNo(), row.getCargoName(), row.getTransportQuantity(), row.getSettlementAmount());
|
||||||
|
TransportReconciliationExternal external = new TransportReconciliationExternal();
|
||||||
|
BeanUtil.copyProperties(row, external);
|
||||||
|
external.setReconciliationId(id); external.setExternalLineNo(index + 2);
|
||||||
|
external.setActualDepartureTime(parseTime(row.getActualDepartureTime(), "实际发货时间"));
|
||||||
|
external.setActualCompletionTime(parseTimeNullable(row.getActualCompletionTime(), "实际完成时间"));
|
||||||
|
external.setFeeItemsJson(JsonUtil.toJson(Map.of("费用项目1", money(row.getFeeItemOne()))));
|
||||||
|
external.setMatchStatus(UNMATCHED); external.setSuspectedDuplicate(false);
|
||||||
|
external.setRawDataJson(JsonUtil.toJson(row)); externalMapper.insert(external);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
VehicleReconciliationFailureExcel failure = new VehicleReconciliationFailureExcel();
|
||||||
|
BeanUtil.copyProperties(row, failure); failure.setErrorMessage("第" + (index + 2) + "行:" + exception.getMessage());
|
||||||
|
failures.add(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refreshStats(id);
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public List<CargoReconciliationFailureExcel> importCargoes(Long id, List<CargoReconciliationExcel> rows) {
|
||||||
|
TransportReconciliation bill = editable(id);
|
||||||
|
if (!CARGO.equals(bill.getReconciliationMode())) throw new ServiceException("当前对账模式不是货物明细对账");
|
||||||
|
resetExternal(id);
|
||||||
|
List<CargoReconciliationFailureExcel> failures = new ArrayList<>();
|
||||||
|
for (int index = 0; index < rows.size(); index++) {
|
||||||
|
CargoReconciliationExcel row = rows.get(index);
|
||||||
|
try {
|
||||||
|
validateExternal(row.getVehicleNo(), row.getCargoName(), row.getTransportQuantity(), row.getSettlementAmount());
|
||||||
|
TransportReconciliationExternal external = new TransportReconciliationExternal();
|
||||||
|
BeanUtil.copyProperties(row, external);
|
||||||
|
external.setReconciliationId(id); external.setExternalLineNo(index + 2);
|
||||||
|
external.setActualDepartureTime(parseTime(row.getActualDepartureTime(), "实际发货时间"));
|
||||||
|
external.setActualCompletionTime(parseTimeNullable(row.getActualCompletionTime(), "实际完成时间"));
|
||||||
|
external.setFeeItemsJson(JsonUtil.toJson(Map.of("费用项目名称1", money(row.getFeeItemOne()), "费用项目名称2", money(row.getFeeItemTwo()))));
|
||||||
|
external.setMatchStatus(UNMATCHED); external.setSuspectedDuplicate(false);
|
||||||
|
external.setRawDataJson(JsonUtil.toJson(row)); externalMapper.insert(external);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
CargoReconciliationFailureExcel failure = new CargoReconciliationFailureExcel();
|
||||||
|
BeanUtil.copyProperties(row, failure); failure.setErrorMessage("第" + (index + 2) + "行:" + exception.getMessage());
|
||||||
|
failures.add(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refreshStats(id);
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void autoMatch(Long id) {
|
||||||
|
TransportReconciliation bill = editable(id);
|
||||||
|
List<TransportReconciliationInternal> internals = internalRows(id);
|
||||||
|
List<TransportReconciliationExternal> externals = externalRows(id);
|
||||||
|
if (externals.isEmpty()) throw new ServiceException("请先导入外部账单");
|
||||||
|
resetMatches(internals, externals);
|
||||||
|
Function<TransportReconciliationExternal, String> externalKey = CARGO.equals(bill.getReconciliationMode())
|
||||||
|
? this::cargoKey : this::vehicleKey;
|
||||||
|
Map<String, List<TransportReconciliationExternal>> externalGroups = externals.stream().collect(Collectors.groupingBy(externalKey));
|
||||||
|
Map<String, List<TransportReconciliationInternal>> internalGroups = internals.stream().collect(Collectors.groupingBy(
|
||||||
|
CARGO.equals(bill.getReconciliationMode()) ? this::cargoKey : this::vehicleKey));
|
||||||
|
for (Map.Entry<String, List<TransportReconciliationExternal>> entry : externalGroups.entrySet()) {
|
||||||
|
List<TransportReconciliationExternal> externalGroup = entry.getValue();
|
||||||
|
List<TransportReconciliationInternal> internalGroup = internalGroups.getOrDefault(entry.getKey(), List.of());
|
||||||
|
if (externalGroup.size() == 1 && internalGroup.size() == 1
|
||||||
|
&& equalMoney(externalGroup.get(0).getSettlementAmount(), internalGroup.get(0).getSettlementAmount())) {
|
||||||
|
link(internalGroup.get(0), externalGroup.get(0));
|
||||||
|
} else if (externalGroup.size() > 1) {
|
||||||
|
for (TransportReconciliationExternal external : externalGroup) {
|
||||||
|
external.setSuspectedDuplicate(true); external.setMatchStatus(DUPLICATE); externalMapper.updateById(external);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
refreshStats(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void manualMatch(TransportReconciliationManualMatchRequest request) {
|
||||||
|
editable(request.getReconciliationId());
|
||||||
|
TransportReconciliationInternal internal = internalMapper.selectById(request.getInternalId());
|
||||||
|
TransportReconciliationExternal external = externalMapper.selectById(request.getExternalId());
|
||||||
|
if (internal == null || external == null || !Objects.equals(internal.getReconciliationId(), request.getReconciliationId())
|
||||||
|
|| !Objects.equals(external.getReconciliationId(), request.getReconciliationId())) throw new ServiceException("匹配明细不存在");
|
||||||
|
unlinkInternal(internal);
|
||||||
|
if (external.getMatchedInternalId() != null) {
|
||||||
|
TransportReconciliationInternal old = internalMapper.selectById(external.getMatchedInternalId());
|
||||||
|
if (old != null) unlinkInternal(old);
|
||||||
|
}
|
||||||
|
link(internal, external);
|
||||||
|
refreshStats(request.getReconciliationId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void unmatch(Long internalId) {
|
||||||
|
TransportReconciliationInternal internal = internalMapper.selectById(internalId);
|
||||||
|
if (internal == null) throw new ServiceException("内部账单明细不存在");
|
||||||
|
editable(internal.getReconciliationId());
|
||||||
|
unlinkInternal(internal);
|
||||||
|
refreshStats(internal.getReconciliationId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void adjustInternal(TransportReconciliationInternal row) {
|
||||||
|
TransportReconciliationInternal internal = internalMapper.selectById(row.getId());
|
||||||
|
if (internal == null) throw new ServiceException("内部账单明细不存在");
|
||||||
|
editable(internal.getReconciliationId());
|
||||||
|
internal.setTransportQuantity(nonNegative(row.getTransportQuantity(), "运输量"));
|
||||||
|
internal.setUnitPrice(nonNegative(row.getUnitPrice(), "运输单价"));
|
||||||
|
internal.setMileage(nonNegative(row.getMileage(), "里程"));
|
||||||
|
internal.setFreightAmount(nonNegative(row.getFreightAmount(), "运输费"));
|
||||||
|
internal.setFeeItemsJson(row.getFeeItemsJson());
|
||||||
|
internal.setSettlementAmount(nonNegative(row.getSettlementAmount(), "结算金额"));
|
||||||
|
internal.setUpdateResult("manually_adjusted");
|
||||||
|
internalMapper.updateById(internal);
|
||||||
|
refreshStats(internal.getReconciliationId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void updateByMatch(Long id) {
|
||||||
|
TransportReconciliation bill = editable(id);
|
||||||
|
List<TransportReconciliationInternal> internals = assertAllMatched(bill);
|
||||||
|
if (money(bill.getExternalAmount()).compareTo(money(bill.getPaidAmount())) < 0) {
|
||||||
|
throw new ServiceException("导入账单匹配金额小于已付金额,不能更新内部账单");
|
||||||
|
}
|
||||||
|
Map<Long, TransportReconciliationExternal> externalMap = externalRows(id).stream()
|
||||||
|
.collect(Collectors.toMap(TransportReconciliationExternal::getId, Function.identity()));
|
||||||
|
for (TransportReconciliationInternal internal : internals) {
|
||||||
|
TransportReconciliationExternal external = externalMap.get(internal.getMatchedExternalId());
|
||||||
|
if (VEHICLE.equals(bill.getReconciliationMode()) && hasMultipleCargo(internal.getFormalSettlementDetailId())) {
|
||||||
|
internal.setUpdateResult("skipped_multi_cargo");
|
||||||
|
internal.setUpdateMessage("一车多货,已跳过自动更新,请人工调整货物费用");
|
||||||
|
internalMapper.updateById(internal);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
applyAmount(bill, internal, external);
|
||||||
|
}
|
||||||
|
recalculateSettlement(bill);
|
||||||
|
bill.setBillUpdated(true);
|
||||||
|
updateById(bill);
|
||||||
|
refreshStats(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public void complete(Long id) {
|
||||||
|
TransportReconciliation bill = editable(id);
|
||||||
|
assertAllMatched(bill);
|
||||||
|
refreshStats(id);
|
||||||
|
bill = existing(id);
|
||||||
|
if (bill.getDifferenceCount() != 0 || money(bill.getDifferenceQuantity()).compareTo(BigDecimal.ZERO) != 0
|
||||||
|
|| money(bill.getDifferenceAmount()).compareTo(BigDecimal.ZERO) != 0) {
|
||||||
|
throw new ServiceException("差异单数、差异货量和差异金额必须全部为0才可完成对账");
|
||||||
|
}
|
||||||
|
bill.setReconciliationStatus(COMPLETED);
|
||||||
|
bill.setCompletedTime(LocalDateTime.now());
|
||||||
|
updateById(bill);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildInternalRows(TransportReconciliation bill, FormalSettlement formal) {
|
||||||
|
List<FormalSettlementDetail> details = formalDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
|
||||||
|
.eq(FormalSettlementDetail::getFormalSettlementId, formal.getId()).orderByAsc(FormalSettlementDetail::getLineNo));
|
||||||
|
int lineNo = 1;
|
||||||
|
for (FormalSettlementDetail detail : details) {
|
||||||
|
List<FormalSettlementDetailFee> fees = formalDetailFeeMapper.selectList(Wrappers.<FormalSettlementDetailFee>lambdaQuery()
|
||||||
|
.eq(FormalSettlementDetailFee::getFormalSettlementDetailId, detail.getId()).orderByAsc(FormalSettlementDetailFee::getLineNo));
|
||||||
|
if (CARGO.equals(bill.getReconciliationMode()) && !fees.isEmpty()) {
|
||||||
|
for (FormalSettlementDetailFee fee : fees) insertInternal(bill.getId(), detail, fee, lineNo++);
|
||||||
|
} else {
|
||||||
|
insertInternal(bill.getId(), detail, null, lineNo++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertInternal(Long billId, FormalSettlementDetail detail, FormalSettlementDetailFee fee, int lineNo) {
|
||||||
|
TransportReconciliationInternal row = new TransportReconciliationInternal();
|
||||||
|
BeanUtil.copyProperties(detail, row);
|
||||||
|
row.setId(null); row.setReconciliationId(billId); row.setFormalSettlementDetailId(detail.getId());
|
||||||
|
row.setSourceDetailId(detail.getSourceDetailId()); row.setLineNo(lineNo); row.setMatchResult(UNMATCHED); row.setUpdateResult("not_updated");
|
||||||
|
if (fee != null) {
|
||||||
|
row.setFormalSettlementDetailFeeId(fee.getId()); row.setSourceCargoFeeId(fee.getSourceFeeId());
|
||||||
|
row.setCargoName(fee.getCargoName()); row.setCargoType(fee.getCargoType()); row.setTransportQuantity(fee.getTransportQuantity());
|
||||||
|
row.setQuantityUnit(fee.getQuantityUnit()); row.setMileage(fee.getMileage()); row.setUnitPrice(fee.getUnitPrice());
|
||||||
|
row.setFreightAmount(fee.getFreightAmount()); row.setFeeItemsJson(fee.getFeeItemsJson()); row.setSettlementAmount(fee.getSettlementAmountTax());
|
||||||
|
ReceivablePayableCargoFee sourceFee = fee.getSourceFeeId() == null ? null : cargoFeeMapper.selectById(fee.getSourceFeeId());
|
||||||
|
if (sourceFee != null) { row.setSpecification(sourceFee.getSpecification()); row.setModel(sourceFee.getModel()); }
|
||||||
|
} else {
|
||||||
|
row.setSettlementAmount(detail.getSettlementAmountTax());
|
||||||
|
}
|
||||||
|
internalMapper.insert(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyAmount(TransportReconciliation bill, TransportReconciliationInternal internal, TransportReconciliationExternal external) {
|
||||||
|
BigDecimal before = money(internal.getSettlementAmount());
|
||||||
|
BigDecimal after = money(external.getSettlementAmount());
|
||||||
|
if (internal.getFormalSettlementDetailFeeId() != null) {
|
||||||
|
FormalSettlementDetailFee fee = formalDetailFeeMapper.selectById(internal.getFormalSettlementDetailFeeId());
|
||||||
|
fee.setSettlementAmountTax(after); fee.setAdjustAmount(after.subtract(money(fee.getOriginalAmount()))); formalDetailFeeMapper.updateById(fee);
|
||||||
|
if (internal.getSourceCargoFeeId() != null) {
|
||||||
|
ReceivablePayableCargoFee sourceFee = cargoFeeMapper.selectById(internal.getSourceCargoFeeId());
|
||||||
|
if (sourceFee != null) { sourceFee.setAfterAmount(after); sourceFee.setAdjustAmount(after.subtract(money(sourceFee.getOriginalAmount()))); cargoFeeMapper.updateById(sourceFee); }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FormalSettlementDetail detail = formalDetailMapper.selectById(internal.getFormalSettlementDetailId());
|
||||||
|
detail.setSettlementAmountTax(after); detail.setAdjustAmount(after.subtract(money(detail.getOriginalAmount()))); formalDetailMapper.updateById(detail);
|
||||||
|
ReceivablePayableDetail source = receivablePayableMapper.selectById(internal.getSourceDetailId());
|
||||||
|
if (source != null) { source.setTotalAmount(after); receivablePayableMapper.updateById(source); }
|
||||||
|
}
|
||||||
|
internal.setSettlementAmount(after); internal.setUpdateResult("updated"); internal.setUpdateMessage("已按外部账单更新"); internalMapper.updateById(internal);
|
||||||
|
TransportReconciliationChangeRecord record = new TransportReconciliationChangeRecord();
|
||||||
|
record.setReconciliationId(bill.getId()); record.setInternalDetailId(internal.getId()); record.setFormalSettlementId(bill.getFormalSettlementId());
|
||||||
|
record.setFormalSettlementDetailId(internal.getFormalSettlementDetailId()); record.setSourceDetailId(internal.getSourceDetailId());
|
||||||
|
record.setDocumentNo(internal.getDocumentNo()); record.setCargoName(internal.getCargoName()); record.setBeforeAmount(before); record.setAfterAmount(after);
|
||||||
|
record.setBeforeDataJson(JsonUtil.toJson(Map.of("settlementAmount", before))); record.setAfterDataJson(JsonUtil.toJson(external));
|
||||||
|
record.setOperatorId(AuthUtil.getUserId()); record.setOperatorName(AuthUtil.getUserName()); record.setChangeTime(LocalDateTime.now());
|
||||||
|
record.setChangeReason("运输对账按匹配结果更新"); changeRecordMapper.insert(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recalculateSettlement(TransportReconciliation bill) {
|
||||||
|
List<FormalSettlementDetail> details = formalDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
|
||||||
|
.eq(FormalSettlementDetail::getFormalSettlementId, bill.getFormalSettlementId()));
|
||||||
|
for (FormalSettlementDetail detail : details) {
|
||||||
|
List<FormalSettlementDetailFee> fees = formalDetailFeeMapper.selectList(Wrappers.<FormalSettlementDetailFee>lambdaQuery()
|
||||||
|
.eq(FormalSettlementDetailFee::getFormalSettlementDetailId, detail.getId()));
|
||||||
|
if (!fees.isEmpty()) {
|
||||||
|
BigDecimal total = fees.stream().map(FormalSettlementDetailFee::getSettlementAmountTax).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
detail.setSettlementAmountTax(total); detail.setAdjustAmount(total.subtract(money(detail.getOriginalAmount()))); formalDetailMapper.updateById(detail);
|
||||||
|
ReceivablePayableDetail source = receivablePayableMapper.selectById(detail.getSourceDetailId());
|
||||||
|
if (source != null) { source.setTotalAmount(total); receivablePayableMapper.updateById(source); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BigDecimal total = details.stream().map(FormalSettlementDetail::getSettlementAmountTax).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
FormalSettlement formal = formalSettlementMapper.selectById(bill.getFormalSettlementId());
|
||||||
|
formal.setSettlementAmount(total); formal.setLocalSettlementAmount(total.multiply(formal.getExchangeRate() == null ? BigDecimal.ONE : formal.getExchangeRate()));
|
||||||
|
formalSettlementMapper.updateById(formal);
|
||||||
|
bill.setSettlementAmount(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TransportReconciliationInternal> assertAllMatched(TransportReconciliation bill) {
|
||||||
|
List<TransportReconciliationInternal> internals = internalRows(bill.getId());
|
||||||
|
List<TransportReconciliationExternal> externals = externalRows(bill.getId());
|
||||||
|
if (internals.isEmpty() || externals.isEmpty() || internals.size() != externals.size()
|
||||||
|
|| internals.stream().anyMatch(item -> !MATCHED.equals(item.getMatchResult()))
|
||||||
|
|| externals.stream().anyMatch(item -> !MATCHED.equals(item.getMatchStatus()) || Boolean.TRUE.equals(item.getSuspectedDuplicate()))) {
|
||||||
|
throw new ServiceException("所有内外部账单明细必须一一匹配且不存在疑似重复");
|
||||||
|
}
|
||||||
|
return internals;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshStats(Long id) {
|
||||||
|
TransportReconciliation bill = existing(id);
|
||||||
|
List<TransportReconciliationInternal> internals = internalRows(id);
|
||||||
|
List<TransportReconciliationExternal> externals = externalRows(id);
|
||||||
|
int matched = (int) internals.stream().filter(item -> MATCHED.equals(item.getMatchResult())).count();
|
||||||
|
bill.setInternalBillCount(internals.size()); bill.setExternalBillCount(externals.size());
|
||||||
|
int internalUnmatched = (int) internals.stream().filter(item -> !MATCHED.equals(item.getMatchResult())).count();
|
||||||
|
int externalUnmatched = (int) externals.stream().filter(item -> !MATCHED.equals(item.getMatchStatus())).count();
|
||||||
|
bill.setMatchedCount(matched); bill.setUnmatchedCount(internalUnmatched + externalUnmatched);
|
||||||
|
bill.setDifferenceCount(Math.abs(internals.size() - externals.size()) + Math.min(internalUnmatched, externalUnmatched));
|
||||||
|
bill.setInternalQuantity(sumInternalQuantity(internals)); bill.setExternalQuantity(sumExternalQuantity(externals));
|
||||||
|
bill.setDifferenceQuantity(bill.getInternalQuantity().subtract(bill.getExternalQuantity()).abs());
|
||||||
|
bill.setInternalAmount(sumInternalAmount(internals)); bill.setExternalAmount(sumExternalAmount(externals));
|
||||||
|
bill.setDifferenceAmount(bill.getInternalAmount().subtract(bill.getExternalAmount()).abs());
|
||||||
|
bill.setMatchStatus(internals.size() > 0 && internals.size() == externals.size() && matched == internals.size() ? MATCHED : matched > 0 ? "partial" : UNMATCHED);
|
||||||
|
updateById(bill);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void link(TransportReconciliationInternal internal, TransportReconciliationExternal external) {
|
||||||
|
internal.setMatchedExternalId(external.getId()); internal.setMatchedExternalLineNo(external.getExternalLineNo()); internal.setMatchResult(MATCHED); internalMapper.updateById(internal);
|
||||||
|
external.setMatchedInternalId(internal.getId()); external.setMatchStatus(MATCHED); external.setSuspectedDuplicate(false); externalMapper.updateById(external);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unlinkInternal(TransportReconciliationInternal internal) {
|
||||||
|
if (internal.getMatchedExternalId() != null) {
|
||||||
|
TransportReconciliationExternal external = externalMapper.selectById(internal.getMatchedExternalId());
|
||||||
|
if (external != null) { external.setMatchedInternalId(null); external.setMatchStatus(UNMATCHED); external.setSuspectedDuplicate(false); externalMapper.updateById(external); }
|
||||||
|
}
|
||||||
|
internal.setMatchedExternalId(null); internal.setMatchedExternalLineNo(null); internal.setMatchResult(UNMATCHED); internalMapper.updateById(internal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetMatches(List<TransportReconciliationInternal> internals, List<TransportReconciliationExternal> externals) {
|
||||||
|
for (TransportReconciliationInternal internal : internals) { internal.setMatchedExternalId(null); internal.setMatchedExternalLineNo(null); internal.setMatchResult(UNMATCHED); internalMapper.updateById(internal); }
|
||||||
|
for (TransportReconciliationExternal external : externals) { external.setMatchedInternalId(null); external.setMatchStatus(UNMATCHED); external.setSuspectedDuplicate(false); externalMapper.updateById(external); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetExternal(Long id) {
|
||||||
|
List<TransportReconciliationInternal> internals = internalRows(id);
|
||||||
|
for (TransportReconciliationInternal internal : internals) { internal.setMatchedExternalId(null); internal.setMatchedExternalLineNo(null); internal.setMatchResult(UNMATCHED); internalMapper.updateById(internal); }
|
||||||
|
externalMapper.delete(Wrappers.<TransportReconciliationExternal>lambdaQuery().eq(TransportReconciliationExternal::getReconciliationId, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearDetails(Long id) {
|
||||||
|
internalMapper.delete(Wrappers.<TransportReconciliationInternal>lambdaQuery().eq(TransportReconciliationInternal::getReconciliationId, id));
|
||||||
|
externalMapper.delete(Wrappers.<TransportReconciliationExternal>lambdaQuery().eq(TransportReconciliationExternal::getReconciliationId, id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyHeader(FormalSettlement formal, TransportReconciliation bill) {
|
||||||
|
bill.setFormalSettlementId(formal.getId()); bill.setFormalSettlementNo(formal.getFormalSettlementNo()); bill.setSettlementType(formal.getSettlementType());
|
||||||
|
bill.setProjectId(formal.getProjectId()); bill.setProjectName(formal.getProjectName()); bill.setDeptId(formal.getDeptId()); bill.setDeptName(formal.getDeptName());
|
||||||
|
bill.setContractId(formal.getContractId()); bill.setContractNo(formal.getContractNo()); bill.setContractName(formal.getContractName());
|
||||||
|
bill.setPayerName(formal.getPayerName()); bill.setPayeeName(formal.getPayeeName()); bill.setCurrency(formal.getCurrency());
|
||||||
|
bill.setSettlementAmount(money(formal.getSettlementAmount())); bill.setPaidAmount(money(formal.getPaidAmount()));
|
||||||
|
List<String> preNos = formalSourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery().eq(FormalSettlementSource::getFormalSettlementId, formal.getId()))
|
||||||
|
.stream().map(FormalSettlementSource::getPreSettlementNo).filter(Func::isNotEmpty).toList();
|
||||||
|
bill.setPreSettlementNos(String.join(",", preNos));
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransportReconciliation existing(Long id) {
|
||||||
|
TransportReconciliation bill = getById(id);
|
||||||
|
if (bill == null) throw new ServiceException("运输对账单不存在");
|
||||||
|
return bill;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransportReconciliation editable(Long id) {
|
||||||
|
TransportReconciliation bill = existing(id);
|
||||||
|
if (!UNFINISHED.equals(bill.getReconciliationStatus())) throw new ServiceException("已完成的运输对账单禁止修改或删除");
|
||||||
|
return bill;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TransportReconciliationInternal> internalRows(Long id) {
|
||||||
|
return internalMapper.selectList(Wrappers.<TransportReconciliationInternal>lambdaQuery().eq(TransportReconciliationInternal::getReconciliationId, id).orderByAsc(TransportReconciliationInternal::getLineNo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TransportReconciliationExternal> externalRows(Long id) {
|
||||||
|
return externalMapper.selectList(Wrappers.<TransportReconciliationExternal>lambdaQuery().eq(TransportReconciliationExternal::getReconciliationId, id).orderByAsc(TransportReconciliationExternal::getExternalLineNo));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasMultipleCargo(Long formalDetailId) {
|
||||||
|
return formalDetailFeeMapper.selectCount(Wrappers.<FormalSettlementDetailFee>lambdaQuery().eq(FormalSettlementDetailFee::getFormalSettlementDetailId, formalDetailId)) > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateExternal(String vehicleNo, String cargoName, BigDecimal quantity, BigDecimal amount) {
|
||||||
|
if (Func.isEmpty(vehicleNo)) throw new ServiceException("车牌号不能为空");
|
||||||
|
if (Func.isEmpty(cargoName)) throw new ServiceException("货物名称不能为空");
|
||||||
|
nonNegative(quantity, "运输量"); nonNegative(amount, "结算金额");
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDateTime parseTime(String value, String field) {
|
||||||
|
if (Func.isEmpty(value)) throw new ServiceException(field + "不能为空");
|
||||||
|
LocalDateTime result = parseTimeNullable(value, field);
|
||||||
|
if (result == null) throw new ServiceException(field + "格式错误");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LocalDateTime parseTimeNullable(String value, String field) {
|
||||||
|
if (Func.isEmpty(value)) return null;
|
||||||
|
for (String pattern : List.of("yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm")) {
|
||||||
|
try { return LocalDateTime.parse(value.trim(), DateTimeFormatter.ofPattern(pattern)); } catch (DateTimeParseException ignored) { }
|
||||||
|
}
|
||||||
|
throw new ServiceException(field + "格式应为yyyy-MM-dd HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
private String vehicleKey(TransportReconciliationInternal row) { return key(row.getVehicleNo(), row.getCargoName(), row.getActualDepartureTime(), row.getBatchNo(), row.getTransportQuantity()); }
|
||||||
|
private String vehicleKey(TransportReconciliationExternal row) { return key(row.getVehicleNo(), row.getCargoName(), row.getActualDepartureTime(), row.getBatchNo(), row.getTransportQuantity()); }
|
||||||
|
private String cargoKey(TransportReconciliationInternal row) { return key(row.getVehicleNo(), row.getDepartureAddress(), row.getArrivalAddress(), row.getCargoName(), row.getActualDepartureTime(), row.getTransportQuantity()); }
|
||||||
|
private String cargoKey(TransportReconciliationExternal row) { return key(row.getVehicleNo(), row.getDepartureAddress(), row.getArrivalAddress(), row.getCargoName(), row.getActualDepartureTime(), row.getTransportQuantity()); }
|
||||||
|
private String key(Object... values) { StringBuilder builder = new StringBuilder(); for (Object value : values) builder.append(normal(value)).append('|'); return builder.toString(); }
|
||||||
|
private String normal(Object value) { if (value == null) return ""; if (value instanceof BigDecimal decimal) return decimal.stripTrailingZeros().toPlainString(); return value.toString().trim().replaceAll("\\s+", "").toLowerCase(); }
|
||||||
|
private boolean equalMoney(BigDecimal left, BigDecimal right) { return money(left).compareTo(money(right)) == 0; }
|
||||||
|
private BigDecimal money(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; }
|
||||||
|
private BigDecimal nonNegative(BigDecimal value, String field) { if (value == null || value.compareTo(BigDecimal.ZERO) < 0) throw new ServiceException(field + "不能小于0"); return value; }
|
||||||
|
private String limit(String value, int max) { if (value != null && value.length() > max) throw new ServiceException("备注不能超过" + max + "个字"); return value; }
|
||||||
|
private BigDecimal sumInternalQuantity(List<TransportReconciliationInternal> rows) { return rows.stream().map(TransportReconciliationInternal::getTransportQuantity).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
||||||
|
private BigDecimal sumExternalQuantity(List<TransportReconciliationExternal> rows) { return rows.stream().map(TransportReconciliationExternal::getTransportQuantity).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
||||||
|
private BigDecimal sumInternalAmount(List<TransportReconciliationInternal> rows) { return rows.stream().map(TransportReconciliationInternal::getSettlementAmount).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
||||||
|
private BigDecimal sumExternalAmount(List<TransportReconciliationExternal> rows) { return rows.stream().map(TransportReconciliationExternal::getSettlementAmount).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add); }
|
||||||
|
private String nextNo() { return "DZ" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")); }
|
||||||
|
}
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
/** BladeX Commercial License Agreement. Copyright (c) 2018-2099, https://bladex.cn. Author: Chill Zhuang. */
|
||||||
|
package org.springblade.transport.wrapper;
|
||||||
|
|
||||||
|
import org.springblade.core.mp.support.BaseEntityWrapper;
|
||||||
|
import org.springblade.core.tool.utils.BeanUtil;
|
||||||
|
import org.springblade.system.cache.UserCache;
|
||||||
|
import org.springblade.transport.pojo.entity.TransportReconciliation;
|
||||||
|
import org.springblade.transport.pojo.vo.TransportReconciliationVO;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/** 运输对账单包装类。 @author Chill */
|
||||||
|
public class TransportReconciliationWrapper extends BaseEntityWrapper<TransportReconciliation, TransportReconciliationVO> {
|
||||||
|
public static TransportReconciliationWrapper build() { return new TransportReconciliationWrapper(); }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TransportReconciliationVO entityVO(TransportReconciliation entity) {
|
||||||
|
TransportReconciliationVO vo = Objects.requireNonNull(BeanUtil.copyProperties(entity, TransportReconciliationVO.class));
|
||||||
|
vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser()));
|
||||||
|
vo.setUpdateUserName(UserCache.getUserRealName(entity.getUpdateUser()));
|
||||||
|
vo.setReconciliationModeName("cargo".equals(entity.getReconciliationMode()) ? "货物明细" : "整车总额");
|
||||||
|
vo.setReconciliationStatusName("completed".equals(entity.getReconciliationStatus()) ? "已完成" : "未完成");
|
||||||
|
vo.setMatchStatusName(switch (entity.getMatchStatus() == null ? "" : entity.getMatchStatus()) {
|
||||||
|
case "matched" -> "已匹配";
|
||||||
|
case "partial" -> "部分匹配";
|
||||||
|
default -> "未匹配";
|
||||||
|
});
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
-- 结算管理 / 结算调整单
|
||||||
|
CREATE TABLE IF NOT EXISTS `blade_settlement_adjustment` (
|
||||||
|
`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, `update_time` datetime DEFAULT NULL, `status` int(11) DEFAULT '1', `is_deleted` int(11) DEFAULT '0',
|
||||||
|
`adjustment_no` varchar(100) NOT NULL COMMENT '结算调整单号', `formal_settlement_id` bigint(20) NOT NULL COMMENT '关联正式结算单ID',
|
||||||
|
`formal_settlement_no` varchar(100) NOT NULL COMMENT '关联正式结算单号', `settlement_type` varchar(30) NOT NULL,
|
||||||
|
`project_name` varchar(100) DEFAULT NULL, `dept_name` varchar(100) DEFAULT NULL, `customer_name` varchar(200) DEFAULT NULL,
|
||||||
|
`contract_no` varchar(100) DEFAULT NULL, `contract_name` varchar(100) DEFAULT NULL,
|
||||||
|
`adjustment_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '调整金额',
|
||||||
|
`original_settlement_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '原结算金额',
|
||||||
|
`adjusted_settlement_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '调整后结算金额',
|
||||||
|
`approval_status` varchar(30) NOT NULL DEFAULT 'draft', `current_node` varchar(100) DEFAULT NULL,
|
||||||
|
`current_processor` varchar(200) DEFAULT NULL, `kingdee_sync_status` varchar(30) DEFAULT NULL COMMENT '关联正式单金蝶状态', `remark` varchar(200) DEFAULT NULL, `approved_time` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`), UNIQUE KEY `uk_settlement_adjustment_no` (`tenant_id`,`adjustment_no`),
|
||||||
|
KEY `idx_adjustment_formal` (`formal_settlement_id`), KEY `idx_adjustment_status` (`approval_status`), KEY `idx_adjustment_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='结算调整单';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `blade_settlement_adjustment_detail` (
|
||||||
|
`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, `update_time` datetime DEFAULT NULL, `status` int(11) DEFAULT '1', `is_deleted` int(11) DEFAULT '0',
|
||||||
|
`adjustment_id` bigint(20) NOT NULL, `formal_settlement_detail_id` bigint(20) NOT NULL, `formal_settlement_detail_fee_id` bigint(20) NOT NULL,
|
||||||
|
`fee_type` varchar(100) DEFAULT NULL, `fee_item` varchar(200) DEFAULT NULL, `original_amount_tax` decimal(18,2) DEFAULT NULL,
|
||||||
|
`adjustment_amount_tax` decimal(18,2) NOT NULL DEFAULT '0.00', `adjustment_amount_no_tax` decimal(18,2) DEFAULT NULL,
|
||||||
|
`remark` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_adjustment_detail_bill` (`adjustment_id`),
|
||||||
|
KEY `idx_adjustment_detail_fee` (`formal_settlement_detail_fee_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='结算调整费用明细';
|
||||||
|
|
||||||
|
INSERT INTO `blade_menu` (`id`,`parent_id`,`code`,`name`,`alias`,`path`,`source`,`sort`,`category`,`action`,`is_open`,`component`,`remark`,`is_deleted`) VALUES
|
||||||
|
(2090000000000001060,2090000000000001000,'settlement_adjustment','结算调整单','settlement_adjustment','/settlement/settlement-adjustment','iconfont icon-caidanguanli',5,1,0,1,NULL,'',0),
|
||||||
|
(2090000000000001061,2090000000000001060,'settlement_adjustment_view','查看','settlement_adjustment_view','','',1,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000001062,2090000000000001060,'settlement_adjustment_add','新增','settlement_adjustment_add','','',2,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000001063,2090000000000001060,'settlement_adjustment_edit','编辑','settlement_adjustment_edit','','',3,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000001064,2090000000000001060,'settlement_adjustment_delete','删除','settlement_adjustment_delete','','',4,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000001065,2090000000000001060,'settlement_adjustment_submit','提交审批','settlement_adjustment_submit','','',5,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000001066,2090000000000001060,'settlement_adjustment_approve','审批','settlement_adjustment_approve','','',6,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000001067,2090000000000001060,'settlement_adjustment_repush','重新推送金蝶','settlement_adjustment_repush','','',7,2,0,1,NULL,'',0)
|
||||||
|
ON DUPLICATE KEY UPDATE `name`=VALUES(`name`),`path`=VALUES(`path`),`is_deleted`=0;
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
-- 结算管理 / 运输对账
|
||||||
|
CREATE TABLE IF NOT EXISTS `blade_transport_reconciliation` (
|
||||||
|
`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, `update_time` datetime DEFAULT NULL, `status` int(11) DEFAULT 1, `is_deleted` int(11) DEFAULT 0,
|
||||||
|
`reconciliation_no` varchar(100) NOT NULL, `formal_settlement_id` bigint(20) NOT NULL, `formal_settlement_no` varchar(100) NOT NULL,
|
||||||
|
`pre_settlement_nos` varchar(1000) DEFAULT NULL, `settlement_type` varchar(30) NOT NULL, `reconciliation_mode` varchar(30) NOT NULL,
|
||||||
|
`project_id` bigint(20) DEFAULT NULL, `project_name` varchar(100) DEFAULT NULL, `dept_id` bigint(20) DEFAULT NULL, `dept_name` varchar(100) DEFAULT NULL,
|
||||||
|
`contract_id` bigint(20) DEFAULT NULL, `contract_no` varchar(100) DEFAULT NULL, `contract_name` varchar(200) DEFAULT NULL,
|
||||||
|
`payer_name` varchar(200) DEFAULT NULL, `payee_name` varchar(200) DEFAULT NULL, `currency` varchar(20) DEFAULT 'RMB',
|
||||||
|
`settlement_amount` decimal(18,2) DEFAULT 0, `paid_amount` decimal(18,2) DEFAULT 0, `reconciler_id` bigint(20) DEFAULT NULL,
|
||||||
|
`reconciler_name` varchar(100) DEFAULT NULL, `reconciliation_date` date DEFAULT NULL, `reconciliation_status` varchar(30) NOT NULL DEFAULT 'unfinished',
|
||||||
|
`match_status` varchar(30) NOT NULL DEFAULT 'unmatched', `internal_bill_count` int(11) DEFAULT 0, `external_bill_count` int(11) DEFAULT 0,
|
||||||
|
`difference_count` int(11) DEFAULT 0, `internal_quantity` decimal(18,6) DEFAULT 0, `external_quantity` decimal(18,6) DEFAULT 0,
|
||||||
|
`difference_quantity` decimal(18,6) DEFAULT 0, `internal_amount` decimal(18,2) DEFAULT 0, `external_amount` decimal(18,2) DEFAULT 0,
|
||||||
|
`difference_amount` decimal(18,2) DEFAULT 0, `matched_count` int(11) DEFAULT 0, `unmatched_count` int(11) DEFAULT 0,
|
||||||
|
`bill_updated` tinyint(1) DEFAULT 0, `completed_time` datetime DEFAULT NULL, `remark` varchar(200) DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`), UNIQUE KEY `uk_transport_reconciliation_no` (`tenant_id`,`reconciliation_no`),
|
||||||
|
KEY `idx_transport_reconciliation_formal` (`formal_settlement_id`), KEY `idx_transport_reconciliation_type` (`settlement_type`),
|
||||||
|
KEY `idx_transport_reconciliation_status` (`reconciliation_status`,`match_status`), KEY `idx_transport_reconciliation_create` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='运输对账单';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `blade_transport_reconciliation_internal` (
|
||||||
|
`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, `update_time` datetime DEFAULT NULL, `status` int(11) DEFAULT 1, `is_deleted` int(11) DEFAULT 0,
|
||||||
|
`reconciliation_id` bigint(20) NOT NULL, `formal_settlement_detail_id` bigint(20) NOT NULL, `formal_settlement_detail_fee_id` bigint(20) DEFAULT NULL,
|
||||||
|
`source_detail_id` bigint(20) DEFAULT NULL, `source_cargo_fee_id` bigint(20) DEFAULT NULL, `line_no` int(11) NOT NULL,
|
||||||
|
`document_no` varchar(100) DEFAULT NULL, `waybill_no` varchar(100) DEFAULT NULL, `vehicle_no` varchar(100) DEFAULT NULL,
|
||||||
|
`departure_address` varchar(500) DEFAULT NULL, `arrival_address` varchar(500) DEFAULT NULL, `actual_departure_time` datetime DEFAULT NULL,
|
||||||
|
`actual_completion_time` datetime DEFAULT NULL, `transport_type` varchar(100) DEFAULT NULL, `cargo_name` varchar(500) DEFAULT NULL,
|
||||||
|
`cargo_type` varchar(500) DEFAULT NULL, `specification` varchar(200) DEFAULT NULL, `model` varchar(200) DEFAULT NULL,
|
||||||
|
`transport_quantity` decimal(18,6) DEFAULT NULL, `quantity_unit` varchar(50) DEFAULT NULL, `mileage` decimal(18,2) DEFAULT NULL,
|
||||||
|
`batch_no` varchar(100) DEFAULT NULL, `unit_price` decimal(18,2) DEFAULT NULL, `freight_amount` decimal(18,2) DEFAULT 0,
|
||||||
|
`fee_items_json` longtext, `settlement_amount` decimal(18,2) DEFAULT 0, `matched_external_id` bigint(20) DEFAULT NULL,
|
||||||
|
`matched_external_line_no` int(11) DEFAULT NULL, `match_result` varchar(30) DEFAULT 'unmatched', `update_result` varchar(30) DEFAULT 'not_updated',
|
||||||
|
`update_message` varchar(500) DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_recon_internal_bill` (`reconciliation_id`),
|
||||||
|
KEY `idx_recon_internal_source` (`source_detail_id`), KEY `idx_recon_internal_match` (`matched_external_id`),
|
||||||
|
KEY `idx_recon_internal_vehicle` (`vehicle_no`,`cargo_name`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='运输对账内部账单快照';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `blade_transport_reconciliation_external` (
|
||||||
|
`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, `update_time` datetime DEFAULT NULL, `status` int(11) DEFAULT 1, `is_deleted` int(11) DEFAULT 0,
|
||||||
|
`reconciliation_id` bigint(20) NOT NULL, `external_line_no` int(11) NOT NULL, `vehicle_no` varchar(100) DEFAULT NULL,
|
||||||
|
`departure_address` varchar(500) DEFAULT NULL, `arrival_address` varchar(500) DEFAULT NULL, `actual_departure_time` datetime DEFAULT NULL,
|
||||||
|
`actual_completion_time` datetime DEFAULT NULL, `transport_type` varchar(100) DEFAULT NULL, `cargo_name` varchar(500) DEFAULT NULL,
|
||||||
|
`cargo_type` varchar(500) DEFAULT NULL, `specification` varchar(200) DEFAULT NULL, `model` varchar(200) DEFAULT NULL,
|
||||||
|
`transport_quantity` decimal(18,6) DEFAULT NULL, `quantity_unit` varchar(50) DEFAULT NULL, `mileage` decimal(18,2) DEFAULT NULL,
|
||||||
|
`batch_no` varchar(100) DEFAULT NULL, `unit_price` decimal(18,2) DEFAULT NULL, `freight_amount` decimal(18,2) DEFAULT 0,
|
||||||
|
`fee_items_json` longtext, `settlement_amount` decimal(18,2) DEFAULT 0, `suspected_duplicate` tinyint(1) DEFAULT 0,
|
||||||
|
`match_status` varchar(30) DEFAULT 'unmatched', `matched_internal_id` bigint(20) DEFAULT NULL, `error_message` varchar(500) DEFAULT NULL,
|
||||||
|
`raw_data_json` longtext, PRIMARY KEY (`id`), KEY `idx_recon_external_bill` (`reconciliation_id`), KEY `idx_recon_external_line` (`reconciliation_id`,`external_line_no`),
|
||||||
|
KEY `idx_recon_external_match` (`matched_internal_id`), KEY `idx_recon_external_status` (`match_status`,`suspected_duplicate`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='运输对账外部账单';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `blade_transport_reconciliation_change_record` (
|
||||||
|
`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, `update_time` datetime DEFAULT NULL, `status` int(11) DEFAULT 1, `is_deleted` int(11) DEFAULT 0,
|
||||||
|
`reconciliation_id` bigint(20) NOT NULL, `internal_detail_id` bigint(20) DEFAULT NULL, `formal_settlement_id` bigint(20) DEFAULT NULL,
|
||||||
|
`formal_settlement_detail_id` bigint(20) DEFAULT NULL, `source_detail_id` bigint(20) DEFAULT NULL, `document_no` varchar(100) DEFAULT NULL,
|
||||||
|
`cargo_name` varchar(500) DEFAULT NULL, `before_amount` decimal(18,2) DEFAULT 0, `after_amount` decimal(18,2) DEFAULT 0,
|
||||||
|
`before_data_json` longtext, `after_data_json` longtext, `operator_id` bigint(20) DEFAULT NULL, `operator_name` varchar(100) DEFAULT NULL,
|
||||||
|
`change_time` datetime DEFAULT NULL, `change_reason` varchar(200) DEFAULT NULL, PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_recon_change_bill` (`reconciliation_id`), KEY `idx_recon_change_source` (`source_detail_id`), KEY `idx_recon_change_time` (`change_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='运输对账账单变更记录';
|
||||||
|
|
||||||
|
INSERT INTO `blade_menu` (`id`,`parent_id`,`code`,`name`,`alias`,`path`,`source`,`sort`,`category`,`action`,`is_open`,`component`,`remark`,`is_deleted`) VALUES
|
||||||
|
(2090000000000002060,2090000000000001000,'transport_reconciliation','运输对账','transport_reconciliation','/settlement/transport-reconciliation','iconfont icon-caidanguanli',5,1,0,1,NULL,'',0),
|
||||||
|
(2090000000000002061,2090000000000002060,'transport_reconciliation_view','查看','transport_reconciliation_view','', '',1,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000002062,2090000000000002060,'transport_reconciliation_add','新增','transport_reconciliation_add','', '',2,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000002063,2090000000000002060,'transport_reconciliation_edit','编辑','transport_reconciliation_edit','', '',3,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000002064,2090000000000002060,'transport_reconciliation_delete','删除','transport_reconciliation_delete','', '',4,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000002065,2090000000000002060,'transport_reconciliation_import','导入外部账单','transport_reconciliation_import','', '',5,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000002066,2090000000000002060,'transport_reconciliation_match','开始匹配','transport_reconciliation_match','', '',6,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000002067,2090000000000002060,'transport_reconciliation_update','按匹配结果更新','transport_reconciliation_update','', '',7,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000002068,2090000000000002060,'transport_reconciliation_complete','完成对账','transport_reconciliation_complete','', '',8,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000002069,2090000000000002060,'transport_reconciliation_export','导出','transport_reconciliation_export','', '',9,2,0,1,NULL,'',0),
|
||||||
|
(2090000000000002070,2090000000000002060,'transport_reconciliation_adjust','明细调整','transport_reconciliation_adjust','', '',10,2,0,1,NULL,'',0)
|
||||||
|
ON DUPLICATE KEY UPDATE `name`=VALUES(`name`),`path`=VALUES(`path`),`is_deleted`=0;
|
||||||
Reference in New Issue
Block a user