1、调整凭证
2、调整对账 3、解决正式结算冲突bug
This commit is contained in:
+9
-1
@@ -180,7 +180,15 @@ public class TransportReconciliationController extends BladeController {
|
||||
@PostMapping("/update-by-match")
|
||||
@ApiOperationSupport(order = 13)
|
||||
@Operation(summary = "按匹配结果更新账单")
|
||||
public R updateByMatch(@RequestParam Long id) { reconciliationService.updateByMatch(id); return R.success("账单更新完成"); }
|
||||
public R updateByMatch(@RequestParam Long id,
|
||||
@RequestBody(required = false) TransportReconciliationVO request) {
|
||||
if (request == null) {
|
||||
request = new TransportReconciliationVO();
|
||||
}
|
||||
request.setId(id);
|
||||
reconciliationService.updateByMatch(request);
|
||||
return R.success("账单更新完成");
|
||||
}
|
||||
|
||||
@PostMapping("/complete")
|
||||
@ApiOperationSupport(order = 14)
|
||||
|
||||
+14
@@ -10,9 +10,23 @@ package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlement;
|
||||
|
||||
/** 正式结算单 Mapper。 @author Chill */
|
||||
@Mapper
|
||||
public interface FormalSettlementMapper extends BaseMapper<FormalSettlement> {
|
||||
|
||||
/**
|
||||
* 按正式结算单号查询,包含逻辑删除记录,用于新增时复用软删除单据。
|
||||
*/
|
||||
@Select("SELECT * FROM blade_formal_settlement WHERE tenant_id = #{tenantId} AND formal_settlement_no = #{formalSettlementNo} ORDER BY is_deleted ASC, id DESC LIMIT 1")
|
||||
FormalSettlement selectByFormalSettlementNoIncludingDeleted(@Param("tenantId") String tenantId,
|
||||
@Param("formalSettlementNo") String formalSettlementNo);
|
||||
|
||||
/** 恢复逻辑删除的正式结算单主记录。 */
|
||||
@Update("UPDATE blade_formal_settlement SET is_deleted = 0 WHERE tenant_id = #{tenantId} AND id = #{id} AND is_deleted = 1")
|
||||
int restoreByIdIncludingDeleted(@Param("tenantId") String tenantId, @Param("id") Long id);
|
||||
}
|
||||
|
||||
+14
@@ -3,9 +3,23 @@ package org.springblade.transport.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
import org.springblade.transport.pojo.entity.TransportReconciliation;
|
||||
|
||||
/** 运输对账单 Mapper。 @author Chill */
|
||||
@Mapper
|
||||
public interface TransportReconciliationMapper extends BaseMapper<TransportReconciliation> {
|
||||
|
||||
/**
|
||||
* 按对账单号查询,包含逻辑删除记录,用于新增时复用软删除单据。
|
||||
*/
|
||||
@Select("SELECT * FROM blade_transport_reconciliation WHERE tenant_id = #{tenantId} AND reconciliation_no = #{reconciliationNo} ORDER BY is_deleted ASC, id DESC LIMIT 1")
|
||||
TransportReconciliation selectByReconciliationNoIncludingDeleted(@Param("tenantId") String tenantId,
|
||||
@Param("reconciliationNo") String reconciliationNo);
|
||||
|
||||
/** 恢复逻辑删除的运输对账单主记录。 */
|
||||
@Update("UPDATE blade_transport_reconciliation SET is_deleted = 0 WHERE tenant_id = #{tenantId} AND id = #{id} AND is_deleted = 1")
|
||||
int restoreByIdIncludingDeleted(@Param("tenantId") String tenantId, @Param("id") Long id);
|
||||
}
|
||||
|
||||
+1
-1
@@ -36,6 +36,6 @@ public interface ITransportReconciliationService extends BaseService<TransportRe
|
||||
void manualMatch(TransportReconciliationManualMatchRequest request);
|
||||
void unmatch(Long internalId);
|
||||
void adjustInternal(TransportReconciliationInternal row);
|
||||
void updateByMatch(Long id);
|
||||
void updateByMatch(TransportReconciliationVO request);
|
||||
void complete(Long id);
|
||||
}
|
||||
|
||||
+24
-4
@@ -309,7 +309,27 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
if (Func.isEmpty(request.getSourcePreSettlementIds()) && Func.isEmpty(request.getSourceDetailIds())) {
|
||||
throw new ServiceException("请至少选择一张预结算单或一条应收应付明细");
|
||||
}
|
||||
FormalSettlement settlement = request.getId() == null ? new FormalSettlement() : editable(request.getId());
|
||||
FormalSettlement settlement;
|
||||
boolean restoring = false;
|
||||
if (request.getId() != null) {
|
||||
settlement = editable(request.getId());
|
||||
} else {
|
||||
String requestedNo = Func.isEmpty(request.getFormalSettlementNo()) ? null : request.getFormalSettlementNo().trim();
|
||||
FormalSettlement existing = requestedNo == null ? null
|
||||
: baseMapper.selectByFormalSettlementNoIncludingDeleted(AuthUtil.getTenantId(), requestedNo);
|
||||
if (existing != null && !Objects.equals(existing.getIsDeleted(), 1)) {
|
||||
throw new ServiceException("正式结算单号" + requestedNo + "已存在");
|
||||
}
|
||||
if (existing != null) {
|
||||
settlement = existing;
|
||||
baseMapper.restoreByIdIncludingDeleted(AuthUtil.getTenantId(), existing.getId());
|
||||
settlement.setIsDeleted(0);
|
||||
restoring = true;
|
||||
} else {
|
||||
settlement = new FormalSettlement();
|
||||
}
|
||||
if (requestedNo != null) settlement.setFormalSettlementNo(requestedNo);
|
||||
}
|
||||
boolean creating = settlement.getId() == null;
|
||||
if (settlement.getId() != null) releaseSources(settlement);
|
||||
List<PreSettlement> sources = Func.isEmpty(request.getSourcePreSettlementIds()) ? List.of()
|
||||
@@ -332,8 +352,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
|| "terminated".equals(contract.getContractStage())) {
|
||||
throw new ServiceException("合同未审核完成,不可转正式结算单");
|
||||
}
|
||||
if (settlement.getId() == null) {
|
||||
settlement.setFormalSettlementNo(nextNo(settlementType));
|
||||
if (creating || restoring) {
|
||||
if (Func.isEmpty(settlement.getFormalSettlementNo())) settlement.setFormalSettlementNo(nextNo(settlementType));
|
||||
settlement.setApprovalStatus(DRAFT);
|
||||
settlement.setCurrentNode("草稿");
|
||||
settlement.setSourceType(sources.isEmpty() ? "应收应付" : directDetails.isEmpty() ? "预结算合并" : "混合来源");
|
||||
@@ -364,7 +384,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
applySummaryRequest(settlement.getId(), request.getSummaryFees());
|
||||
refreshSettlementAmount(settlement);
|
||||
rebuildInvoices(settlement, request.getInvoices());
|
||||
if (!creating) {
|
||||
if (!creating && !restoring) {
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整",
|
||||
"保存正式结算单" + settlement.getFormalSettlementNo(), request.getRemark());
|
||||
}
|
||||
|
||||
+130
-17
@@ -161,15 +161,40 @@ public class TransportReconciliationServiceImpl
|
||||
.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())
|
||||
TransportReconciliation bill;
|
||||
boolean restoring = false;
|
||||
if (request.getId() != null) {
|
||||
bill = editable(request.getId());
|
||||
} else {
|
||||
String reconciliationNo = nextNo();
|
||||
TransportReconciliation deletedBill = baseMapper.selectByReconciliationNoIncludingDeleted(
|
||||
AuthUtil.getTenantId(), reconciliationNo);
|
||||
if (deletedBill != null && !Objects.equals(deletedBill.getIsDeleted(), 1)) {
|
||||
throw new ServiceException("对账单号" + reconciliationNo + "已存在");
|
||||
}
|
||||
if (deletedBill == null) {
|
||||
bill = new TransportReconciliation();
|
||||
} else {
|
||||
baseMapper.restoreByIdIncludingDeleted(AuthUtil.getTenantId(), deletedBill.getId());
|
||||
deletedBill.setIsDeleted(0);
|
||||
bill = deletedBill;
|
||||
restoring = true;
|
||||
}
|
||||
bill.setReconciliationNo(reconciliationNo);
|
||||
}
|
||||
boolean rebuild = restoring || 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);
|
||||
}
|
||||
if (restoring) {
|
||||
bill.setReconciliationStatus(UNFINISHED);
|
||||
bill.setMatchStatus(UNMATCHED);
|
||||
bill.setBillUpdated(false);
|
||||
bill.setCompletedTime(null);
|
||||
}
|
||||
copyHeader(formal, bill);
|
||||
bill.setReconciliationMode(request.getReconciliationMode());
|
||||
bill.setReconcilerId(AuthUtil.getUserId());
|
||||
@@ -473,6 +498,11 @@ public class TransportReconciliationServiceImpl
|
||||
if (source.getReconciliationId() != null && !Objects.equals(source.getReconciliationId(), target.getReconciliationId())) {
|
||||
throw new ServiceException("内部账单明细不属于当前对账单");
|
||||
}
|
||||
target.setVehicleNo(source.getVehicleNo());
|
||||
target.setDepartureAddress(source.getDepartureAddress());
|
||||
target.setArrivalAddress(source.getArrivalAddress());
|
||||
target.setCargoName(source.getCargoName());
|
||||
target.setCargoType(source.getCargoType());
|
||||
target.setTransportQuantity(nonNegative(source.getTransportQuantity(), "运输量"));
|
||||
target.setUnitPrice(nonNegative(source.getUnitPrice(), "运输单价"));
|
||||
target.setMileage(source.getMileage());
|
||||
@@ -584,28 +614,111 @@ public class TransportReconciliationServiceImpl
|
||||
|
||||
@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()
|
||||
public void updateByMatch(TransportReconciliationVO request) {
|
||||
if (request == null || request.getId() == null) throw new ServiceException("运输对账单不存在");
|
||||
TransportReconciliation bill = editable(request.getId());
|
||||
List<TransportReconciliationInternal> incomingInternals = request.getInternalDetails();
|
||||
List<TransportReconciliationExternal> incomingExternals = request.getExternalDetails();
|
||||
if (incomingInternals == null) incomingInternals = internalRows(request.getId());
|
||||
if (incomingExternals == null) incomingExternals = externalRows(request.getId());
|
||||
applySnapshots(bill, incomingInternals, incomingExternals);
|
||||
List<TransportReconciliationInternal> internals = rematch(request.getId());
|
||||
Map<Long, TransportReconciliationExternal> externalMap = externalRows(request.getId()).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);
|
||||
copyExternalToInternal(internal, external);
|
||||
internalMapper.updateById(internal);
|
||||
updateWaybill(internal, external);
|
||||
}
|
||||
recalculateSettlement(bill);
|
||||
bill.setBillUpdated(true);
|
||||
updateById(bill);
|
||||
refreshStats(id);
|
||||
refreshStats(request.getId());
|
||||
}
|
||||
|
||||
private List<TransportReconciliationInternal> rematch(Long reconciliationId) {
|
||||
List<TransportReconciliationInternal> internals = internalRows(reconciliationId);
|
||||
List<TransportReconciliationExternal> externals = externalRows(reconciliationId);
|
||||
if (externals.isEmpty()) throw new ServiceException("请先导入外部账单");
|
||||
resetMatches(internals, externals);
|
||||
Map<String, List<TransportReconciliationExternal>> externalGroups = externals.stream()
|
||||
.collect(Collectors.groupingBy(this::matchKey));
|
||||
Map<String, List<TransportReconciliationInternal>> internalGroups = internals.stream()
|
||||
.collect(Collectors.groupingBy(this::matchKey));
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
return assertAllMatched(existing(reconciliationId));
|
||||
}
|
||||
|
||||
private void copyExternalToInternal(TransportReconciliationInternal internal,
|
||||
TransportReconciliationExternal external) {
|
||||
internal.setVehicleNo(external.getVehicleNo());
|
||||
internal.setDepartureAddress(external.getDepartureAddress());
|
||||
internal.setArrivalAddress(external.getArrivalAddress());
|
||||
internal.setActualDepartureTime(external.getActualDepartureTime());
|
||||
internal.setActualCompletionTime(external.getActualCompletionTime());
|
||||
internal.setTransportType(external.getTransportType());
|
||||
internal.setCargoName(external.getCargoName());
|
||||
internal.setCargoType(external.getCargoType());
|
||||
internal.setSpecification(external.getSpecification());
|
||||
internal.setModel(external.getModel());
|
||||
internal.setTransportQuantity(external.getTransportQuantity());
|
||||
internal.setQuantityUnit(external.getQuantityUnit());
|
||||
internal.setMileage(external.getMileage());
|
||||
internal.setBatchNo(external.getBatchNo());
|
||||
internal.setUnitPrice(external.getUnitPrice());
|
||||
internal.setFreightAmount(external.getFreightAmount());
|
||||
internal.setFeeItemsJson(external.getFeeItemsJson());
|
||||
internal.setSettlementAmount(external.getSettlementAmount());
|
||||
}
|
||||
|
||||
private void updateWaybill(TransportReconciliationInternal internal,
|
||||
TransportReconciliationExternal external) {
|
||||
Long waybillId = null;
|
||||
FormalSettlementDetail detail = internal.getFormalSettlementDetailId() == null ? null
|
||||
: formalDetailMapper.selectById(internal.getFormalSettlementDetailId());
|
||||
if (detail != null) waybillId = detail.getWaybillId();
|
||||
if (waybillId == null && internal.getSourceDetailId() != null) {
|
||||
ReceivablePayableDetail source = receivablePayableMapper.selectById(internal.getSourceDetailId());
|
||||
if (source != null) waybillId = source.getWaybillId();
|
||||
}
|
||||
Waybill waybill = waybillId == null ? null : waybillMapper.selectById(waybillId);
|
||||
if (waybill == null && Func.isNotEmpty(internal.getWaybillNo())) {
|
||||
waybill = waybillMapper.selectOne(Wrappers.<Waybill>lambdaQuery()
|
||||
.eq(Waybill::getWaybillNo, internal.getWaybillNo()));
|
||||
}
|
||||
if (waybill == null) return;
|
||||
waybill.setVehicleNo(external.getVehicleNo());
|
||||
waybill.setDepartureAddress(external.getDepartureAddress());
|
||||
waybill.setArrivalAddress(external.getArrivalAddress());
|
||||
waybill.setStartDate(external.getActualDepartureTime() == null ? null : external.getActualDepartureTime().toLocalDate());
|
||||
waybill.setEndDate(external.getActualCompletionTime() == null ? null : external.getActualCompletionTime().toLocalDate());
|
||||
waybill.setTransportType(external.getTransportType());
|
||||
waybill.setCargoName(external.getCargoName());
|
||||
waybill.setCargoType(external.getCargoType());
|
||||
waybill.setSpecification(external.getSpecification());
|
||||
waybill.setModel(external.getModel());
|
||||
waybill.setQuantity(external.getTransportQuantity());
|
||||
waybill.setQuantityUnit(external.getQuantityUnit());
|
||||
waybill.setMileage(external.getMileage());
|
||||
waybill.setBatchNo(external.getBatchNo());
|
||||
waybill.setUnitPrice(external.getUnitPrice());
|
||||
waybill.setFreightJson(external.getFeeItemsJson());
|
||||
waybill.setOtherFeeTotal(money(external.getSettlementAmount()).subtract(money(external.getFreightAmount())).max(BigDecimal.ZERO));
|
||||
waybillMapper.updateById(waybill);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user