1、新增结算调整单
2、新增运输对账
This commit is contained in:
+5
-4
@@ -118,11 +118,12 @@ public class ReceivablePayableDetailController extends BladeController {
|
||||
public R<IPage<Map<String, Object>>> transferCandidates(Query query,
|
||||
@RequestParam(required = false) String contractName,
|
||||
@RequestParam(required = false) String batchNo,
|
||||
@RequestParam(required = false) String generateStartDate,
|
||||
@RequestParam(required = false) String generateEndDate,
|
||||
@RequestParam(required = false) String settlementBillType) {
|
||||
@RequestParam(required = false) String generateStartDate,
|
||||
@RequestParam(required = false) String generateEndDate,
|
||||
@RequestParam(required = false) String settlementBillType,
|
||||
@RequestParam(required = false) String settlementType) {
|
||||
return R.data(detailService.transferCandidates(Condition.getPage(query), contractName, batchNo,
|
||||
generateStartDate, generateEndDate, settlementBillType));
|
||||
generateStartDate, generateEndDate, settlementBillType, settlementType));
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
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);
|
||||
|
||||
|
||||
+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());
|
||||
saveOrUpdate(settlement);
|
||||
if (request.getSourceDetailIds() != null) {
|
||||
synchronizeDetails(settlement, request.getSourceDetailIds());
|
||||
synchronizeDetails(settlement, request.getSourceDetailIds(), Boolean.TRUE.equals(request.getAllowSourceMismatch()));
|
||||
}
|
||||
settlement.setExchangeRate(normalizeRate(request.getExchangeRate(), settlement.getCurrency()));
|
||||
rebuildSummaryFees(settlement.getId(), true);
|
||||
@@ -637,7 +637,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
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<PreSettlementDetail> existingDetails = listDetails(settlement.getId());
|
||||
Set<Long> requestedSet = new LinkedHashSet<>(distinctIds);
|
||||
@@ -661,7 +661,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
boolean hasRetainedDetail = existingDetails.stream()
|
||||
.anyMatch(detail -> requestedSet.contains(detail.getSourceDetailId()));
|
||||
for (ReceivablePayableDetail source : sources) {
|
||||
validateCandidate(settlement, source);
|
||||
validateCandidate(settlement, source, allowSourceMismatch);
|
||||
String sourceCurrency = Func.isEmpty(source.getCurrency()) ? LOCAL_CURRENCY : source.getCurrency();
|
||||
if (!hasRetainedDetail) {
|
||||
settlement.setCurrency(sourceCurrency);
|
||||
@@ -691,16 +691,17 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
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())) {
|
||||
throw new ServiceException("仅可选择当前合同的应收应付明细");
|
||||
}
|
||||
if (!Objects.equals(source.getSettlementType(), settlement.getSettlementType())) {
|
||||
throw new ServiceException("应收应付明细的结算类型不一致");
|
||||
}
|
||||
if (!Objects.equals(source.getProjectId(), settlement.getProjectId())
|
||||
if (!allowSourceMismatch && (!Objects.equals(source.getProjectId(), settlement.getProjectId())
|
||||
|| !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("应收应付明细的项目、所属组织或客商与预结算单不一致");
|
||||
}
|
||||
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) {
|
||||
PreSettlementDetail detail = new PreSettlementDetail();
|
||||
detail.setPreSettlementId(settlement.getId());
|
||||
// line_no 在数据库中为非空字段,插入后再统一重排前先提供临时行号。
|
||||
detail.setLineNo(1);
|
||||
detail.setSourceDetailId(source.getId());
|
||||
detail.setDocumentNo(source.getDocumentNo());
|
||||
detail.setWaybillId(source.getWaybillId());
|
||||
@@ -1092,6 +1095,10 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
+93
-32
@@ -37,7 +37,9 @@ import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
|
||||
import org.springblade.transport.mapper.ReceivablePayableChangeRecordMapper;
|
||||
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
|
||||
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.PreSettlementSaveRequest;
|
||||
import org.springblade.transport.pojo.dto.ReceivablePayableTransferRequest;
|
||||
import org.springblade.transport.pojo.dto.ReceivablePayableUpdateFeeRequest;
|
||||
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.service.IContractManageService;
|
||||
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.IWaybillService;
|
||||
import org.springblade.transport.wrapper.ReceivablePayableDetailWrapper;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -91,17 +96,23 @@ public class ReceivablePayableDetailServiceImpl
|
||||
private final IWaybillService waybillService;
|
||||
private final IContractManageService contractManageService;
|
||||
private final ICommonAddressService commonAddressService;
|
||||
private final IPreSettlementService preSettlementService;
|
||||
private final IFormalSettlementService formalSettlementService;
|
||||
|
||||
public ReceivablePayableDetailServiceImpl(ReceivablePayableCargoFeeMapper cargoFeeMapper,
|
||||
ReceivablePayableChangeRecordMapper changeRecordMapper,
|
||||
IWaybillService waybillService,
|
||||
IContractManageService contractManageService,
|
||||
ICommonAddressService commonAddressService) {
|
||||
IWaybillService waybillService,
|
||||
IContractManageService contractManageService,
|
||||
ICommonAddressService commonAddressService,
|
||||
@Lazy IPreSettlementService preSettlementService,
|
||||
@Lazy IFormalSettlementService formalSettlementService) {
|
||||
this.cargoFeeMapper = cargoFeeMapper;
|
||||
this.changeRecordMapper = changeRecordMapper;
|
||||
this.waybillService = waybillService;
|
||||
this.contractManageService = contractManageService;
|
||||
this.commonAddressService = commonAddressService;
|
||||
this.preSettlementService = preSettlementService;
|
||||
this.formalSettlementService = formalSettlementService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -269,34 +280,60 @@ public class ReceivablePayableDetailServiceImpl
|
||||
if (details.size() != request.getIds().size()) {
|
||||
throw new ServiceException("存在无效的费用明细");
|
||||
}
|
||||
String billNo = settlementBillNo(request.getSettlementBillType());
|
||||
for (ReceivablePayableDetail detail : details) {
|
||||
if (!"pending".equals(detail.getSettlementStatus())) {
|
||||
throw new ServiceException("仅待结算明细允许转结算");
|
||||
}
|
||||
if ("pre".equals(request.getSettlementBillType())) {
|
||||
detail.setPreSettlementNo(billNo);
|
||||
detail.setSettlementStatus("pre_settled");
|
||||
} else {
|
||||
detail.setFormalSettlementNo(billNo);
|
||||
detail.setSettlementStatus("formal_settled");
|
||||
}
|
||||
updateById(detail);
|
||||
validateTransferDetails(details);
|
||||
List<Long> detailIds = details.stream().map(ReceivablePayableDetail::getId).toList();
|
||||
ReceivablePayableDetail first = details.get(0);
|
||||
if ("pre".equals(request.getSettlementBillType())) {
|
||||
PreSettlementSaveRequest saveRequest = new PreSettlementSaveRequest();
|
||||
saveRequest.setContractId(first.getContractId());
|
||||
saveRequest.setSettlementType(first.getSettlementType());
|
||||
saveRequest.setSourceDetailIds(detailIds);
|
||||
saveRequest.setAllowSourceMismatch(true);
|
||||
preSettlementService.saveDraft(saveRequest);
|
||||
return;
|
||||
}
|
||||
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
|
||||
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();
|
||||
query.setContractName(contractName);
|
||||
query.setBatchNo(batchNo);
|
||||
query.setSettlementStatus("pending");
|
||||
query.setSettlementType(Func.isEmpty(settlementType) ? null : settlementType(settlementType));
|
||||
query.setGenerateStartDate(parseDate(generateStartDate));
|
||||
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());
|
||||
result.setRecords(detailPage.getRecords().stream().map(this::beanMap).toList());
|
||||
result.setRecords(detailPage.getRecords().stream().map(this::candidateMap).toList());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -521,12 +558,12 @@ public class ReceivablePayableDetailServiceImpl
|
||||
BigDecimal total = fees.stream().map(ReceivablePayableCargoFee::getAfterAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal other = total.subtract(freight).setScale(2, RoundingMode.HALF_UP);
|
||||
ReceivablePayableDetail detail = new ReceivablePayableDetail();
|
||||
detail.setDocumentNo(nextDocumentNo());
|
||||
detail.setDocumentNo(nextDocumentNo(settlementType));
|
||||
detail.setSettlementType(settlementType);
|
||||
detail.setProjectId(waybill.getProjectId());
|
||||
detail.setProjectName(waybill.getProjectName());
|
||||
detail.setDeptId(waybill.getDeptId());
|
||||
detail.setDeptName(waybill.getDeptName());
|
||||
detail.setProjectId(contract == null ? waybill.getProjectId() : contract.getProjectId());
|
||||
detail.setProjectName(contract == null ? waybill.getProjectName() : contract.getProjectName());
|
||||
detail.setDeptId(contract == null ? waybill.getDeptId() : contract.getOrganizationId());
|
||||
detail.setDeptName(contract == null ? waybill.getDeptName() : contract.getOrganizationName());
|
||||
detail.setFeeDate(waybill.getEndDate() == null ? LocalDate.now() : waybill.getEndDate());
|
||||
detail.setCustomerName("payable".equals(settlementType)
|
||||
? (contract == null ? waybill.getCarrierName() : contract.getPartyA())
|
||||
@@ -926,9 +963,36 @@ public class ReceivablePayableDetailServiceImpl
|
||||
return map;
|
||||
}
|
||||
|
||||
private Map<String, Object> beanMap(ReceivablePayableDetailVO detail) {
|
||||
private Map<String, Object> candidateMap(ReceivablePayableDetailVO detail) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -986,12 +1050,9 @@ public class ReceivablePayableDetailServiceImpl
|
||||
return Func.isEmpty(value) ? null : LocalDate.parse(value);
|
||||
}
|
||||
|
||||
private synchronized String nextDocumentNo() {
|
||||
return "YS" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + System.currentTimeMillis() % 100000;
|
||||
}
|
||||
|
||||
private synchronized String settlementBillNo(String type) {
|
||||
String prefix = "pre".equals(type) ? "YJ" : "ZJ";
|
||||
private synchronized String nextDocumentNo(String settlementType) {
|
||||
String prefix = "payable".equals(settlementType) ? "YF" : "YS";
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user