This commit is contained in:
2026-08-20 06:30:50 +08:00
parent df6d417e40
commit 8beb542c98
10 changed files with 558 additions and 32 deletions
@@ -24,12 +24,12 @@ package org.springblade.transport.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import cn.idev.excel.FastExcel;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.excel.util.ExcelUtil;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.secure.annotation.PreAuth;
@@ -43,6 +43,7 @@ import org.springblade.transport.pojo.vo.ReceivablePayableChangeRecordVO;
import org.springblade.transport.pojo.vo.ReceivablePayableDetailVO;
import org.springblade.transport.pojo.vo.ReceivablePayableFeeDetailVO;
import org.springblade.transport.service.IReceivablePayableDetailService;
import org.springblade.system.cache.DictCache;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -51,7 +52,13 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/**
* 应收应付明细控制器
@@ -160,7 +167,56 @@ public class ReceivablePayableDetailController extends BladeController {
@ApiOperationSupport(order = 10)
@Operation(summary = "导出应收应付明细")
public void exportReceivablePayableDetail(ReceivablePayableDetailVO query, HttpServletResponse response) {
IPage<ReceivablePayableDetailVO> page = detailService.selectPage(Condition.getPage(new Query()), query);
ExcelUtil.export(response, "应收应付明细" + DateUtil.time(), "应收应付明细", page.getRecords(), ReceivablePayableDetailVO.class);
List<ReceivablePayableDetailVO> records = detailService.selectList(query);
Set<String> feeItemNames = new LinkedHashSet<>();
records.forEach(row -> {
if (row.getFeeItems() != null) feeItemNames.addAll(row.getFeeItems().keySet());
});
List<List<String>> head = new ArrayList<>();
String[] baseHeaders = {"单据号", "项目名称", "所属组织", "费用日期", "客商名称", "合同编号", "合同名称", "来源",
"预结算单号", "正式结算单号", "运单号", "车号", "运输类型", "货物名称", "货物类型", "运输总量", "里程(KM",
"批次号", "运输单价"};
for (String header : baseHeaders) head.add(List.of(header));
feeItemNames.forEach(name -> head.add(List.of(name)));
for (String header : new String[] {"费用合计", "状态", "创建人", "创建时间"}) head.add(List.of(header));
List<List<Object>> rows = records.stream().map(row -> {
List<Object> values = new ArrayList<>();
values.add(row.getDocumentNo()); values.add(row.getProjectName()); values.add(row.getDeptName()); values.add(row.getFeeDate());
values.add(row.getCustomerName()); values.add(row.getContractNo()); values.add(row.getContractName()); values.add(row.getSourceType());
values.add(row.getPreSettlementNo()); values.add(row.getFormalSettlementNo()); values.add(row.getWaybillNo()); values.add(row.getVehicleNo());
values.add(transportTypeName(row.getTransportType())); values.add(row.getCargoName()); values.add(row.getCargoType());
values.add(row.getTransportQuantity()); values.add(row.getMileage() != null && row.getMileage().compareTo(BigDecimal.valueOf(-1)) == 0 ? null : row.getMileage());
values.add(row.getBatchNo()); values.add(money(row.getUnitPrice(), row.getCurrency()));
feeItemNames.forEach(name -> values.add(money(decimal(row.getFeeItems() == null ? null : row.getFeeItems().get(name)), row.getCurrency())));
values.add(money(row.getTotalAmount(), row.getCurrency())); values.add(row.getSettlementStatusName()); values.add(row.getCreateUserName()); values.add(row.getCreateTime());
return values;
}).toList();
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode("应收应付明细" + DateUtil.time(), StandardCharsets.UTF_8) + ".xlsx");
try {
FastExcel.write(response.getOutputStream()).head(head).sheet("应收应付明细").doWrite(rows);
} catch (Exception exception) {
throw new IllegalStateException("导出应收应付明细失败", exception);
}
}
private String transportTypeName(String value) {
if (value == null || value.isBlank()) return value;
String name = DictCache.getValue("transport_type", value);
return name == null || name.isBlank() ? value : name;
}
private String money(BigDecimal value, String currency) {
if (value == null) return "-";
return value.setScale(2, java.math.RoundingMode.HALF_UP).toPlainString() + " " + (currency == null || currency.isBlank() ? "RMB" : currency);
}
private BigDecimal decimal(Object value) {
if (value == null || String.valueOf(value).isBlank()) return null;
try { return new BigDecimal(String.valueOf(value)); } catch (NumberFormatException ignored) { return null; }
}
}
@@ -42,6 +42,9 @@ public interface ILoadingManageService extends BaseService<LoadingManage> {
boolean complete(Long id);
/** 运单完成后检查关联运单状态,全部完成时自动完成配载单。 */
boolean completeIfAllWaybillsCompleted(String loadingNo);
BusinessRemoveResultVO batchComplete(String ids);
}
@@ -28,6 +28,7 @@ import org.springblade.transport.pojo.dto.ReceivablePayableAdjustFeeRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableGenerateRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableTransferRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableUpdateFeeRequest;
import org.springblade.transport.pojo.entity.MasterOrder;
import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
import org.springblade.transport.pojo.vo.ReceivablePayableChangeRecordVO;
import org.springblade.transport.pojo.vo.ReceivablePayableDetailVO;
@@ -45,6 +46,8 @@ public interface IReceivablePayableDetailService extends BaseService<ReceivableP
IPage<ReceivablePayableDetailVO> selectPage(IPage<ReceivablePayableDetail> page, ReceivablePayableDetailVO query);
List<ReceivablePayableDetailVO> selectList(ReceivablePayableDetailVO query);
ReceivablePayableFeeDetailVO feeDetail(Long id);
IPage<ReceivablePayableChangeRecordVO> changeRecords(IPage<?> page, Long detailId);
@@ -69,4 +72,7 @@ public interface IReceivablePayableDetailService extends BaseService<ReceivableP
/** 完成运单后按合同系统计费模式自动生成应收、应付明细。 */
void generateForCompletedWaybills(List<Long> waybillIds);
/** 关闭总单调度后按合同系统计费模式自动生成总单应收、应付明细。 */
void generateForClosedMasterOrder(MasterOrder masterOrder);
}
@@ -263,8 +263,8 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
LoadingManage loadingManage = loadEditable(id, true);
if (!Objects.equals(loadingManage.getBusinessStatus(), STATUS_RUNNING)
&& !(Objects.equals(loadingManage.getBusinessStatus(), STATUS_PENDING)
&& allAssociatedWaybillsRunning(loadingManage))) {
throw new ServiceException("仅进行中状态允许完成");
&& allAssociatedWaybillsCompletable(loadingManage))) {
throw new ServiceException("仅进行中,或关联运单全部进行中/已完成的待执行配载单允许完成");
}
loadingManage.setBusinessStatus(STATUS_COMPLETED);
boolean result = updateById(loadingManage);
@@ -272,6 +272,38 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean completeIfAllWaybillsCompleted(String loadingNo) {
if (Func.isEmpty(loadingNo)) {
return false;
}
LoadingManage loadingManage = getOne(Wrappers.<LoadingManage>lambdaQuery()
.eq(LoadingManage::getLoadingNo, loadingNo)
.eq(LoadingManage::getIsDeleted, 0)
.last("FOR UPDATE"), false);
if (loadingManage == null
|| Objects.equals(loadingManage.getBusinessStatus(), STATUS_COMPLETED)
|| Objects.equals(loadingManage.getBusinessStatus(), STATUS_CANCELLED)
|| Objects.equals(loadingManage.getBusinessStatus(), STATUS_DRAFT)) {
return false;
}
List<Long> waybillIdList = waybillIds(loadingManage.getWaybillIdsJson());
if (Func.isEmpty(waybillIdList)) {
return false;
}
List<Waybill> waybillList = waybillMapper.selectList(Wrappers.<Waybill>lambdaQuery()
.eq(Waybill::getIsDeleted, 0)
.in(Waybill::getId, waybillIdList));
boolean allCompleted = waybillList.size() == waybillIdList.size()
&& waybillList.stream().allMatch(waybill -> Objects.equals(waybill.getBusinessStatus(), STATUS_COMPLETED));
if (!allCompleted) {
return false;
}
loadingManage.setBusinessStatus(STATUS_COMPLETED);
return updateById(loadingManage);
}
@Override
@Transactional(rollbackFor = Exception.class)
public BusinessRemoveResultVO batchComplete(String ids) {
@@ -292,7 +324,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
return result;
}
private boolean allAssociatedWaybillsRunning(LoadingManage loadingManage) {
private boolean allAssociatedWaybillsCompletable(LoadingManage loadingManage) {
List<Long> waybillIdList = waybillIds(loadingManage.getWaybillIdsJson());
if (Func.isEmpty(waybillIdList)) {
return false;
@@ -300,8 +332,11 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
List<Waybill> waybillList = waybillMapper.selectList(Wrappers.<Waybill>lambdaQuery()
.eq(Waybill::getIsDeleted, 0)
.in(Waybill::getId, waybillIdList));
return waybillList.size() == waybillIdList.size()
&& waybillList.stream().allMatch(waybill -> Objects.equals(waybill.getBusinessStatus(), STATUS_RUNNING));
if (waybillList.size() != waybillIdList.size()) {
return false;
}
return waybillList.stream().allMatch(waybill -> Objects.equals(waybill.getBusinessStatus(), STATUS_RUNNING))
|| waybillList.stream().allMatch(waybill -> Objects.equals(waybill.getBusinessStatus(), STATUS_COMPLETED));
}
private LambdaQueryWrapper<LoadingManage> buildQuery(LoadingManageVO loadingManage) {
@@ -24,6 +24,7 @@ import org.springblade.transport.pojo.vo.MasterOrderVO;
import org.springblade.transport.service.IMasterOrderService;
import org.springblade.transport.service.IContractManageService;
import org.springblade.transport.service.IProjectApplyService;
import org.springblade.transport.service.IReceivablePayableDetailService;
import org.springblade.transport.service.ITransportPlanService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.TransportBusinessSupport;
@@ -53,13 +54,15 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
private final ITransportPlanService transportPlanService;
private final IProjectApplyService projectApplyService;
private final IContractManageService contractManageService;
private final IReceivablePayableDetailService receivablePayableDetailService;
public MasterOrderServiceImpl(IWaybillService waybillService, ITransportPlanService transportPlanService, IProjectApplyService projectApplyService,
IContractManageService contractManageService) {
IContractManageService contractManageService, IReceivablePayableDetailService receivablePayableDetailService) {
this.waybillService = waybillService;
this.transportPlanService = transportPlanService;
this.projectApplyService = projectApplyService;
this.contractManageService = contractManageService;
this.receivablePayableDetailService = receivablePayableDetailService;
}
@Override
@@ -124,7 +127,11 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
throw new ServiceException("当前状态不允许关闭调度");
}
masterOrder.setBusinessStatus("closed");
return updateById(masterOrder);
boolean updated = updateById(masterOrder);
if (updated) {
receivablePayableDetailService.generateForClosedMasterOrder(masterOrder);
}
return updated;
}
@Override
@@ -653,6 +653,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
Set<Long> existingSourceIds = existingDetails.stream().map(PreSettlementDetail::getSourceDetailId)
.collect(Collectors.toSet());
List<Long> addedIds = distinctIds.stream().filter(id -> !existingSourceIds.contains(id)).toList();
List<ReceivablePayableDetail> addedSources = new ArrayList<>();
if (!addedIds.isEmpty()) {
List<ReceivablePayableDetail> sources = sourceDetailMapper.selectBatchIds(addedIds);
if (sources.size() != addedIds.size()) {
@@ -683,12 +684,20 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
if (affected != 1) {
throw new ServiceException("单据" + source.getDocumentNo() + "已被其他预结算单选择");
}
saveChange(settlement.getId(), "结算明细项", null, "新增",
"新增单据号" + source.getDocumentNo(), "");
addedSources.add(source);
}
updateById(settlement);
}
renumberDetails(settlement.getId());
if (!addedSources.isEmpty()) {
Map<Long, Integer> detailLineMap = listDetails(settlement.getId()).stream()
.collect(Collectors.toMap(PreSettlementDetail::getSourceDetailId, PreSettlementDetail::getLineNo,
(left, right) -> left));
for (ReceivablePayableDetail source : addedSources) {
saveChange(settlement.getId(), "结算明细项", detailLineMap.get(source.getId()), "新增",
"新增单据号" + source.getDocumentNo(), "");
}
}
}
private void validateCandidate(PreSettlement settlement, ReceivablePayableDetail source,
@@ -883,7 +892,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
row.setManualFlag(1);
if (row.getId() == null) summaryFeeMapper.insert(row); else summaryFeeMapper.updateById(row);
retainedManualIds.add(row.getId());
saveChange(settlementId, "合计费用项", row.getLineNo(), requestRow.getId() == null ? "新增" : "调整",
saveChange(settlementId, "合计费用项", null, requestRow.getId() == null ? "新增" : "调整",
row.getFeeItem() + "金额" + row.getSettlementAmount(), "");
continue;
}
@@ -907,14 +916,14 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
row.setRemark(limitRemark(requestRow.getRemark(), 50));
summaryFeeMapper.updateById(row);
if (before.compareTo(row.getAdjustAmount()) != 0) {
saveChange(settlementId, "合计费用项", row.getLineNo(), "调整",
saveChange(settlementId, "合计费用项", null, "调整",
"【调整金额】从【" + before + "】调整为【" + row.getAdjustAmount() + "", "");
}
}
for (PreSettlementSummaryFee manualRow : manualRows) {
if (!retainedManualIds.contains(manualRow.getId())) {
summaryFeeMapper.deleteById(manualRow.getId());
saveChange(settlementId, "合计费用项", manualRow.getLineNo(), "删除",
saveChange(settlementId, "合计费用项", null, "删除",
"删除" + manualRow.getFeeItem() + "费用" + manualRow.getSettlementAmount(), "");
}
}
@@ -36,6 +36,7 @@ import org.springblade.system.cache.UserCache;
import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
import org.springblade.transport.mapper.ReceivablePayableChangeRecordMapper;
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
import org.springblade.transport.mapper.MasterOrderMapper;
import org.springblade.transport.pojo.dto.ReceivablePayableAdjustFeeRequest;
import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableGenerateRequest;
@@ -44,6 +45,7 @@ import org.springblade.transport.pojo.dto.ReceivablePayableTransferRequest;
import org.springblade.transport.pojo.dto.ReceivablePayableUpdateFeeRequest;
import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.CommonAddress;
import org.springblade.transport.pojo.entity.MasterOrder;
import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee;
import org.springblade.transport.pojo.entity.ReceivablePayableChangeRecord;
import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
@@ -91,8 +93,11 @@ public class ReceivablePayableDetailServiceImpl
extends BaseServiceImpl<ReceivablePayableDetailMapper, ReceivablePayableDetail>
implements IReceivablePayableDetailService {
private static final String SOURCE_MASTER_ORDER = "总单系统生成";
private final ReceivablePayableCargoFeeMapper cargoFeeMapper;
private final ReceivablePayableChangeRecordMapper changeRecordMapper;
private final MasterOrderMapper masterOrderMapper;
private final IWaybillService waybillService;
private final IContractManageService contractManageService;
private final ICommonAddressService commonAddressService;
@@ -101,6 +106,7 @@ public class ReceivablePayableDetailServiceImpl
public ReceivablePayableDetailServiceImpl(ReceivablePayableCargoFeeMapper cargoFeeMapper,
ReceivablePayableChangeRecordMapper changeRecordMapper,
MasterOrderMapper masterOrderMapper,
IWaybillService waybillService,
IContractManageService contractManageService,
ICommonAddressService commonAddressService,
@@ -108,6 +114,7 @@ public class ReceivablePayableDetailServiceImpl
@Lazy IFormalSettlementService formalSettlementService) {
this.cargoFeeMapper = cargoFeeMapper;
this.changeRecordMapper = changeRecordMapper;
this.masterOrderMapper = masterOrderMapper;
this.waybillService = waybillService;
this.contractManageService = contractManageService;
this.commonAddressService = commonAddressService;
@@ -120,6 +127,11 @@ public class ReceivablePayableDetailServiceImpl
return ReceivablePayableDetailWrapper.build().pageVO(page(page, buildQuery(query)));
}
@Override
public List<ReceivablePayableDetailVO> selectList(ReceivablePayableDetailVO query) {
return ReceivablePayableDetailWrapper.build().listVO(list(buildQuery(query)));
}
@Override
public ReceivablePayableFeeDetailVO feeDetail(Long id) {
ReceivablePayableDetail detail = getExisting(id);
@@ -127,6 +139,10 @@ public class ReceivablePayableDetailServiceImpl
.eq(ReceivablePayableCargoFee::getDetailId, detail.getId())
.eq(ReceivablePayableCargoFee::getIsDeleted, 0)
.orderByAsc(ReceivablePayableCargoFee::getCreateTime));
// 运输量的单位取明细对应运单单位,兼容历史费用行误存计费单位的情况。
if (Func.isNotEmpty(detail.getQuantityUnit())) {
rows.forEach(row -> row.setQuantityUnit(detail.getQuantityUnit()));
}
ReceivablePayableFeeDetailVO result = buildFeeDetail(rows);
LinkedHashSet<String> feeItemNames = new LinkedHashSet<>(contractFeeItemNames(detail.getContractId()));
feeItemNames.addAll(result.getFeeItemNames());
@@ -157,6 +173,9 @@ public class ReceivablePayableDetailServiceImpl
if (Func.isEmpty(request.getContractId())) {
throw new ServiceException("请选择需要更新费用的合同");
}
if (Func.isEmpty(request.getBillingPlanId())) {
throw new ServiceException("请选择需要更新的合同计费方案");
}
List<ReceivablePayableDetail> details = list(buildUpdateQuery(request));
if (Func.isEmpty(details)) {
throw new ServiceException("没有可更新的待结算明细");
@@ -217,14 +236,65 @@ public class ReceivablePayableDetailServiceImpl
.eq(ReceivablePayableCargoFee::getIsDeleted, 0));
Map<Long, ReceivablePayableCargoFee> existingMap = existingRows.stream()
.collect(java.util.stream.Collectors.toMap(ReceivablePayableCargoFee::getId, row -> row));
if (request.getRows().size() != existingRows.size()) {
Set<Long> submittedExistingIds = request.getRows().stream()
.map(ReceivablePayableAdjustFeeRequest.AdjustRow::getId)
.filter(Objects::nonNull)
.collect(java.util.stream.Collectors.toSet());
if (!submittedExistingIds.equals(existingMap.keySet())) {
throw new ServiceException("费用调整行数据不完整");
}
Set<String> allowedFeeItems = new LinkedHashSet<>(contractFeeItemNames(detail.getContractId()));
existingRows.forEach(row -> allowedFeeItems.addAll(parseMap(row.getFeeItemsJson()).keySet()));
List<String> changes = new ArrayList<>();
List<ReceivablePayableCargoFee> allRows = new ArrayList<>(existingRows);
for (ReceivablePayableAdjustFeeRequest.AdjustRow adjusted : request.getRows()) {
ReceivablePayableCargoFee existing = existingMap.get(adjusted.getId());
if (adjusted.getRemark() != null && adjusted.getRemark().length() > 200) {
throw new ServiceException("备注不能超过200个字");
}
if (Boolean.TRUE.equals(adjusted.getManualFee())) {
if (Func.isEmpty(adjusted.getFeeItemName())) {
throw new ServiceException("请填写手工费用项目");
}
if (!List.of("charge", "deduct").contains(adjusted.getFeeType())) {
throw new ServiceException("手工费用类型不正确");
}
validateNonNegative(adjusted.getAmount(), "手工费用金额");
BigDecimal signedAmount = money(adjusted.getAmount())
.multiply("deduct".equals(adjusted.getFeeType()) ? BigDecimal.valueOf(-1) : BigDecimal.ONE);
Map<String, BigDecimal> manualItems = new LinkedHashMap<>();
manualItems.put(adjusted.getFeeItemName().trim(), signedAmount);
boolean newManualRow = existing == null;
if (newManualRow) {
existing = new ReceivablePayableCargoFee();
existing.setDetailId(detail.getId());
existing.setLineNo("ADJ-" + System.currentTimeMillis());
existing.setOriginalAmount(BigDecimal.ZERO);
}
String oldName = existing.getCargoName();
BigDecimal oldAmount = money(existing.getAfterAmount());
// cargoName 表示运单货物名称,手工收费项名称仅保存到费用项目 JSON。
existing.setCargoName(detail.getCargoName());
existing.setBillingFactor("手工调整");
existing.setBillingType("deduct".equals(adjusted.getFeeType()) ? "手工扣费" : "手工收费");
existing.setTransportQuantity(detail.getTransportQuantity());
existing.setQuantityUnit(detail.getQuantityUnit());
existing.setMileage(detail.getMileage());
existing.setFreightAmount(BigDecimal.ZERO);
existing.setFeeItemsJson(JsonUtil.toJson(manualItems));
existing.setAdjustAmount(signedAmount.subtract(money(existing.getOriginalAmount())));
existing.setAfterAmount(signedAmount);
existing.setRemark(adjusted.getRemark());
if (newManualRow) {
cargoFeeMapper.insert(existing);
allRows.add(existing);
} else {
cargoFeeMapper.updateById(existing);
}
changes.add("【手工费用】从[" + oldName + " " + formatValue(oldAmount) + "]调整为["
+ existing.getBillingType() + " " + existing.getCargoName() + " " + formatValue(signedAmount) + "]");
continue;
}
if (existing == null) {
throw new ServiceException("存在无效的费用调整行");
}
@@ -244,6 +314,10 @@ public class ReceivablePayableDetailServiceImpl
appendChange(changes, "计费数量", existing.getTransportQuantity(), adjusted.getTransportQuantity());
appendChange(changes, "里程", existing.getMileage(), adjusted.getMileage());
appendChange(changes, "运输费", existing.getFreightAmount(), adjusted.getFreightAmount());
if (!Objects.equals(existing.getRemark(), adjusted.getRemark())) {
changes.add("【备注】从[" + Objects.toString(existing.getRemark(), "") + "]调整为["
+ Objects.toString(adjusted.getRemark(), "") + "]");
}
Map<String, Object> oldFeeItems = parseMap(existing.getFeeItemsJson());
for (String name : allowedFeeItems) {
appendChange(changes, name, decimal(oldFeeItems.get(name)), money(feeItems.get(name)));
@@ -254,6 +328,7 @@ public class ReceivablePayableDetailServiceImpl
existing.setMileage(money(adjusted.getMileage()));
existing.setFreightAmount(freightAmount);
existing.setFeeItemsJson(JsonUtil.toJson(feeItems));
existing.setRemark(adjusted.getRemark());
existing.setAdjustAmount(afterAmount.subtract(money(existing.getOriginalAmount())));
existing.setAfterAmount(afterAmount);
cargoFeeMapper.updateById(existing);
@@ -264,7 +339,7 @@ public class ReceivablePayableDetailServiceImpl
for (int i = 0; i < changes.size(); i++) {
saveChangeRecord(detail, changes.get(i), request.getAdjustReason(), String.format("%04d", i + 1));
}
refreshAdjustedDetail(detail, existingRows);
refreshAdjustedDetail(detail, allRows);
}
@Override
@@ -420,6 +495,50 @@ public class ReceivablePayableDetailServiceImpl
}
}
@Override
@Transactional(propagation = org.springframework.transaction.annotation.Propagation.REQUIRES_NEW,
rollbackFor = Exception.class)
public void generateForClosedMasterOrder(MasterOrder masterOrder) {
if (masterOrder == null || Func.isEmpty(masterOrder.getId()) || Func.isEmpty(masterOrder.getContractId())) {
return;
}
ContractManage contract = contractManageService.getById(masterOrder.getContractId());
if (contract == null || !isSystemGeneration(contract)) return;
try {
List<Waybill> masterGoods = masterOrderGoods(masterOrder);
if (masterGoods.isEmpty()) return;
List<ReceivablePayableCargoFee> matchedFees = new ArrayList<>();
for (Waybill masterGoodsItem : masterGoods) {
String planId = matchedPlanId(masterGoodsItem, contract);
if (Func.isEmpty(planId)) continue;
matchedFees.addAll(calculatedFees(masterGoodsItem, contract, planId, true));
}
if (matchedFees.isEmpty()) return;
normalizeMasterFeeLines(matchedFees);
BigDecimal contractUnitPrice = resolveContractUnitPrice(matchedFees);
for (String settlementType : List.of("payable", "receivable")) {
if (existsByMasterOrder(masterOrder.getMasterNo(), settlementType)) continue;
ReceivablePayableDetail detail = buildMasterOrderDetail(masterOrder, contract, masterGoods,
settlementType, matchedFees, contractUnitPrice);
save(detail);
for (ReceivablePayableCargoFee fee : matchedFees) {
ReceivablePayableCargoFee copy = BeanUtil.copyProperties(fee, ReceivablePayableCargoFee.class);
copy.setId(null);
copy.setDetailId(detail.getId());
copy.setWaybillId(null);
cargoFeeMapper.insert(copy);
}
}
} catch (Exception exception) {
log.error("自动生成总单费用明细失败,masterOrderId:{}, masterNo:{}, contractId:{}, failureReason:{}",
masterOrder.getId(), masterOrder.getMasterNo(), masterOrder.getContractId(), exception.getMessage(), exception);
if (exception instanceof RuntimeException runtimeException) {
throw runtimeException;
}
throw new RuntimeException(exception);
}
}
private boolean isSystemGeneration(ContractManage contract) {
if (Func.isNotEmpty(contract.getFeeGenerationMode())) {
return "system".equalsIgnoreCase(contract.getFeeGenerationMode()) || "系统生成".equals(contract.getFeeGenerationMode());
@@ -525,9 +644,12 @@ public class ReceivablePayableDetailServiceImpl
LambdaQueryWrapper<Waybill> wrapper = Wrappers.<Waybill>lambdaQuery()
.eq(Waybill::getIsDeleted, 0)
.eq(Waybill::getContractId, request.getContractId())
.eq(Waybill::getBusinessStatus, "completed")
.notInSql(Waybill::getId, "select waybill_id from blade_receivable_payable_detail where is_deleted = 0"
+ (Func.isNotEmpty(request.getSettlementType()) ? " and settlement_type = '" + settlementType(request.getSettlementType()) + "'" : ""));
.eq(Waybill::getBusinessStatus, "completed");
String targetSettlementType = settlementType(request.getSettlementType());
wrapper.notInSql(Waybill::getId,
"select waybill_id from blade_receivable_payable_detail"
+ " where is_deleted = 0 and waybill_id is not null and settlement_type = '"
+ targetSettlementType + "'");
if (Func.isNotEmpty(request.getBatchNo())) {
wrapper.like(Waybill::getBatchNo, request.getBatchNo());
}
@@ -553,7 +675,7 @@ public class ReceivablePayableDetailServiceImpl
}
private ReceivablePayableDetail buildDetail(Waybill waybill, ContractManage contract, String settlementType,
List<ReceivablePayableCargoFee> fees, BigDecimal unitPrice) {
List<ReceivablePayableCargoFee> fees, BigDecimal unitPrice) {
BigDecimal freight = fees.stream().filter(this::isFreight).map(ReceivablePayableCargoFee::getAfterAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal total = fees.stream().map(ReceivablePayableCargoFee::getAfterAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal other = total.subtract(freight).setScale(2, RoundingMode.HALF_UP);
@@ -580,7 +702,7 @@ public class ReceivablePayableDetailServiceImpl
detail.setCargoType(waybill.getCargoType());
detail.setTransportQuantity(waybill.getQuantity());
detail.setQuantityUnit(waybill.getQuantityUnit());
detail.setMileage(waybill.getMileage());
detail.setMileage(normalizeGeneratedMileage(waybill.getMileage()));
detail.setBatchNo(waybill.getBatchNo());
detail.setUnitPrice(unitPrice);
detail.setCurrency("RMB");
@@ -593,6 +715,80 @@ public class ReceivablePayableDetailServiceImpl
return detail;
}
private ReceivablePayableDetail buildMasterOrderDetail(MasterOrder masterOrder, ContractManage contract,
List<Waybill> masterGoods, String settlementType,
List<ReceivablePayableCargoFee> fees, BigDecimal unitPrice) {
Waybill masterWaybill = masterGoods.get(0);
ReceivablePayableDetail detail = buildDetail(masterWaybill, contract, settlementType, fees, unitPrice);
detail.setSourceType(SOURCE_MASTER_ORDER);
detail.setWaybillId(null);
detail.setWaybillNo(masterOrder.getMasterNo());
detail.setVehicleNo(null);
detail.setTransportType(masterOrder.getTransportOrganizationType());
detail.setCargoName(joinMasterGoodsField(masterGoods, Waybill::getCargoName));
detail.setCargoType(joinMasterGoodsField(masterGoods, Waybill::getCargoType));
detail.setTransportQuantity(masterGoods.stream().map(Waybill::getQuantity)
.filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
detail.setQuantityUnit(commonMasterGoodsValue(masterGoods, Waybill::getQuantityUnit));
detail.setMileage(null);
detail.setBatchNo(null);
detail.setRemark(masterOrder.getRemark());
return detail;
}
private List<Waybill> masterOrderGoods(MasterOrder masterOrder) {
List<Waybill> result = new ArrayList<>();
for (Map<String, Object> goods : parseList(masterOrder.getGoodsJson())) {
BigDecimal quantity = decimal(goods.get("quantity"));
if (quantity.compareTo(BigDecimal.ZERO) <= 0) continue;
Waybill masterGoodsItem = new Waybill();
masterGoodsItem.setProjectId(masterOrder.getProjectId());
masterGoodsItem.setProjectName(masterOrder.getProjectName());
masterGoodsItem.setContractId(masterOrder.getContractId());
masterGoodsItem.setContractName(masterOrder.getContractName());
masterGoodsItem.setCustomerName(masterOrder.getCustomerName());
masterGoodsItem.setTransportType(masterOrder.getTransportOrganizationType());
masterGoodsItem.setCargoName(stringValue(goods, "cargoName"));
masterGoodsItem.setCargoType(stringValue(goods, "cargoType"));
masterGoodsItem.setSpecification(stringValue(goods, "specification"));
masterGoodsItem.setModel(stringValue(goods, "model"));
masterGoodsItem.setQuantity(quantity);
masterGoodsItem.setQuantityUnit(stringValue(goods, "quantityUnit"));
masterGoodsItem.setDepartureName(masterOrder.getDepartureName());
masterGoodsItem.setDepartureAddress(masterOrder.getDepartureAddress());
masterGoodsItem.setArrivalName(masterOrder.getArrivalName());
masterGoodsItem.setArrivalAddress(masterOrder.getArrivalAddress());
masterGoodsItem.setEndDate(masterOrder.getPlanEndTime() == null ? LocalDate.now()
: masterOrder.getPlanEndTime().toLocalDate());
masterGoodsItem.setMasterNo(masterOrder.getMasterNo());
masterGoodsItem.setWaybillNo(masterOrder.getMasterNo());
masterGoodsItem.setGoodsJson(JsonUtil.toJson(goods));
masterGoodsItem.setRemark(masterOrder.getRemark());
result.add(masterGoodsItem);
}
return result;
}
private void normalizeMasterFeeLines(List<ReceivablePayableCargoFee> fees) {
for (int index = 0; index < fees.size(); index++) {
ReceivablePayableCargoFee fee = fees.get(index);
fee.setWaybillId(null);
fee.setLineNo(String.format("%04d", index + 1));
}
}
private String joinMasterGoodsField(List<Waybill> masterGoods,
java.util.function.Function<Waybill, String> getter) {
return masterGoods.stream().map(getter).filter(Func::isNotEmpty).distinct()
.collect(java.util.stream.Collectors.joining(","));
}
private String commonMasterGoodsValue(List<Waybill> masterGoods,
java.util.function.Function<Waybill, String> getter) {
List<String> values = masterGoods.stream().map(getter).filter(Func::isNotEmpty).distinct().toList();
return values.size() == 1 ? values.get(0) : "";
}
private BigDecimal resolveContractUnitPrice(List<ReceivablePayableCargoFee> fees) {
return fees.stream()
.filter(this::isFreight)
@@ -631,7 +827,7 @@ public class ReceivablePayableDetailServiceImpl
cargoFee.setQuantityUnit(waybill.getQuantityUnit());
cargoFee.setPriceUnit(waybill.getPriceUnit());
cargoFee.setUnitPrice(unitPrice);
cargoFee.setMileage(waybill.getMileage());
cargoFee.setMileage(normalizeGeneratedMileage(waybill.getMileage()));
cargoFee.setFreightAmount(freightAmount);
cargoFee.setFeeItemsJson(JsonUtil.toJson(feeItems));
cargoFee.setOriginalAmount(total);
@@ -647,9 +843,7 @@ public class ReceivablePayableDetailServiceImpl
private List<ReceivablePayableCargoFee> calculatedFees(Waybill waybill, ContractManage contract, String planId, boolean matchOnly) {
List<Map<String, Object>> plans = parseList(contract == null ? null : contract.getBillingPlanJson());
Map<String, Object> plan = "__matched__".equals(planId) ? plans.stream().filter(this::isDefaultPlan).findFirst().orElseGet(() -> plans.isEmpty() ? null : plans.get(plans.size() - 1)) : plans.stream().filter(item -> Objects.equals(stringValue(item, "id"), planId)
|| Objects.equals(stringValue(item, "planId"), planId)).findFirst()
.orElseGet(() -> plans.stream().filter(this::isDefaultPlan).findFirst().orElse(null));
Map<String, Object> plan = resolveBillingPlan(plans, planId);
if (plan == null || !(plan.get("rules") instanceof List<?>)) return matchOnly ? List.of() : List.of(buildCargoFee(null, waybill));
List<ReceivablePayableCargoFee> result = new ArrayList<>();
int line = 1;
@@ -664,9 +858,9 @@ public class ReceivablePayableDetailServiceImpl
fee.setWaybillId(waybill.getId()); fee.setLineNo(String.format("%04d", line++));
fee.setCargoName(stringValue(rule, "feeItem", "费用")); fee.setCargoType(waybill.getCargoType());
fee.setBillingFactor(stringValue(rule, "billingElement", "")); fee.setBillingType(stringValue(rule, "billingType", ""));
fee.setTransportQuantity(measure(rule, waybill)); fee.setQuantityUnit(stringValue(rule, "billingUnit", waybill.getQuantityUnit()));
fee.setTransportQuantity(measure(rule, waybill)); fee.setQuantityUnit(waybill.getQuantityUnit());
fee.setPriceUnit(stringValue(rule, "billingUnit", waybill.getPriceUnit())); fee.setUnitPrice(decimal(rule.get("unitPrice")));
fee.setMileage(waybill.getMileage()); fee.setFreightAmount(isFreightRule(rule) ? amount : BigDecimal.ZERO);
fee.setMileage(normalizeGeneratedMileage(waybill.getMileage())); fee.setFreightAmount(isFreightRule(rule) ? amount : BigDecimal.ZERO);
fee.setFeeItemsJson(JsonUtil.toJson(Map.of(fee.getCargoName(), amount)));
fee.setOriginalAmount(amount); fee.setAdjustAmount(BigDecimal.ZERO); fee.setAfterAmount(amount); fee.setRemark(stringValue(rule, "remark", waybill.getRemark()));
result.add(fee);
@@ -674,6 +868,24 @@ public class ReceivablePayableDetailServiceImpl
return result.isEmpty() && !matchOnly ? List.of(buildCargoFee(null, waybill)) : result;
}
private Map<String, Object> resolveBillingPlan(List<Map<String, Object>> plans, String planId) {
if ("__matched__".equals(planId)) {
return plans.stream().filter(this::isDefaultPlan).findFirst()
.orElseGet(() -> plans.isEmpty() ? null : plans.get(plans.size() - 1));
}
if (Func.isEmpty(planId)) {
return plans.stream().filter(this::isDefaultPlan).findFirst()
.orElseGet(() -> plans.isEmpty() ? null : plans.get(plans.size() - 1));
}
return plans.stream().filter(plan -> Objects.equals(stringValue(plan, "id"), planId)
|| Objects.equals(stringValue(plan, "planId"), planId)
|| Objects.equals(stringValue(plan, "name"), planId)
|| Objects.equals(stringValue(plan, "planName"), planId)
|| Objects.equals(stringValue(plan, "billingPlanName"), planId))
.findFirst()
.orElseThrow(() -> new ServiceException("合同计费方案不存在或已变更,请重新选择"));
}
private boolean isDefaultPlan(Map<String, Object> plan) {
Object value = plan.get("defaultPlan");
return Boolean.TRUE.equals(value) || "true".equalsIgnoreCase(String.valueOf(value));
@@ -851,6 +1063,10 @@ public class ReceivablePayableDetailServiceImpl
}
private void rebuildDetailFee(ReceivablePayableDetail detail, String billingPlanId) {
if (SOURCE_MASTER_ORDER.equals(detail.getSourceType())) {
rebuildMasterOrderDetailFee(detail, billingPlanId);
return;
}
Waybill waybill = waybillService.getById(detail.getWaybillId());
if (waybill == null) {
throw new ServiceException("关联运单不存在");
@@ -869,6 +1085,38 @@ public class ReceivablePayableDetailServiceImpl
updateById(detail);
}
private void rebuildMasterOrderDetailFee(ReceivablePayableDetail detail, String billingPlanId) {
MasterOrder masterOrder = masterOrderMapper.selectOne(Wrappers.<MasterOrder>lambdaQuery()
.eq(MasterOrder::getMasterNo, detail.getWaybillNo())
.eq(MasterOrder::getIsDeleted, 0));
if (masterOrder == null) {
throw new ServiceException("关联总单不存在");
}
ContractManage contract = contractManageService.getById(detail.getContractId());
List<Waybill> masterGoods = masterOrderGoods(masterOrder);
List<ReceivablePayableCargoFee> fees = masterGoods.stream()
.flatMap(goods -> calculatedFees(goods, contract, billingPlanId).stream()).toList();
normalizeMasterFeeLines(fees);
cargoFeeMapper.delete(Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
.eq(ReceivablePayableCargoFee::getDetailId, detail.getId()));
fees.forEach(fee -> {
fee.setDetailId(detail.getId());
fee.setWaybillId(null);
cargoFeeMapper.insert(fee);
});
BigDecimal freight = fees.stream().filter(this::isFreight)
.map(ReceivablePayableCargoFee::getAfterAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal total = fees.stream().map(ReceivablePayableCargoFee::getAfterAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
detail.setFreightAmount(freight);
detail.setOtherFeeAmount(total.subtract(freight));
detail.setTotalAmount(total);
detail.setUnitPrice(resolveContractUnitPrice(fees));
detail.setFeeItemsJson(JsonUtil.toJson(fees.stream().collect(HashMap<String, BigDecimal>::new,
(map, fee) -> map.put(fee.getCargoName(), fee.getAfterAmount()), HashMap::putAll)));
updateById(detail);
}
private void closeDetails(List<Long> ids, String settlementType) {
if (Func.isEmpty(ids)) {
throw new ServiceException("请选择需要关闭的明细");
@@ -927,6 +1175,14 @@ public class ReceivablePayableDetailServiceImpl
.eq(ReceivablePayableDetail::getIsDeleted, 0)) > 0;
}
private boolean existsByMasterOrder(String masterNo, String settlementType) {
return count(Wrappers.<ReceivablePayableDetail>lambdaQuery()
.eq(ReceivablePayableDetail::getSourceType, SOURCE_MASTER_ORDER)
.eq(ReceivablePayableDetail::getWaybillNo, masterNo)
.eq(ReceivablePayableDetail::getSettlementType, settlementType)
.eq(ReceivablePayableDetail::getIsDeleted, 0)) > 0;
}
private String settlementType(String value) {
if (Func.isEmpty(value)) return "receivable";
if (!List.of("receivable", "payable").contains(value)) {
@@ -960,9 +1216,96 @@ public class ReceivablePayableDetailServiceImpl
map.put("transportType", waybill.getTransportType());
map.put("carrierType", waybill.getCarrierType());
map.put("cargoInfo", waybill.getCargoName());
map.put("departureAddress", waybill.getDepartureAddress());
map.put("departureContact", waybill.getDepartureContact());
map.put("arrivalAddress", waybill.getArrivalAddress());
map.put("arrivalContact", waybill.getArrivalContact());
map.put("unitPrice", waybill.getUnitPrice());
map.put("freight", waybillFreight(waybill));
map.put("otherFeeTotal", waybillOtherFee(waybill));
map.put("freightTotal", waybillFreightTotal(waybill));
map.put("contractName", waybill.getContractName());
map.put("planName", waybill.getPlanName());
map.put("batchNo", waybill.getBatchNo());
map.put("originalNo", waybill.getOriginalNo());
map.put("masterNo", waybill.getMasterNo());
map.put("remark", Func.isNotEmpty(waybill.getRemark()) ? waybill.getRemark() : waybill.getTaskRemark());
map.put("createTime", waybill.getCreateTime());
map.put("updateTime", waybill.getUpdateTime());
map.put("businessStatusName", waybillStatusName(waybill.getBusinessStatus()));
return map;
}
private BigDecimal waybillFreight(Waybill waybill) {
Map<String, Object> freight = waybillFreightMap(waybill);
Object value = firstValue(freight, "freight", "freightAmount", "transportFee");
if (value != null) return decimal(value);
if (freight.get("freightItems") instanceof List<?> items) {
BigDecimal total = BigDecimal.ZERO;
boolean hasAmount = false;
for (Object item : items) {
if (!(item instanceof Map<?, ?> source)) continue;
Object amount = firstValue(stringMap(source), "freightAmount", "amount", "totalAmount");
if (amount == null) continue;
total = total.add(decimal(amount));
hasAmount = true;
}
if (hasAmount) return total;
}
return null;
}
private BigDecimal waybillOtherFee(Waybill waybill) {
Map<String, Object> freight = waybillFreightMap(waybill);
Object value = firstValue(freight, "otherFeeTotal", "otherFreightAmount", "otherAmount");
return value == null ? waybill.getOtherFeeTotal() : decimal(value);
}
private BigDecimal waybillFreightTotal(Waybill waybill) {
Map<String, Object> freight = waybillFreightMap(waybill);
Object total = firstValue(freight, "freightTotal", "totalFreight", "totalFreightAmount", "totalAmount");
if (total != null) return decimal(total);
BigDecimal freightAmount = waybillFreight(waybill);
BigDecimal otherFeeAmount = waybillOtherFee(waybill);
if (freightAmount == null && otherFeeAmount == null) return null;
return money(freightAmount).add(money(otherFeeAmount));
}
private Map<String, Object> waybillFreightMap(Waybill waybill) {
if (Func.isEmpty(waybill.getFreightJson())) return new LinkedHashMap<>();
try {
Object parsed = JsonUtil.parse(waybill.getFreightJson(), Object.class);
if (parsed instanceof Map<?, ?> source) return stringMap(source);
if (parsed instanceof List<?> list && !list.isEmpty() && list.get(0) instanceof Map<?, ?> source) {
return stringMap(source);
}
} catch (Exception ignored) {
// 兼容历史费用 JSON 异常数据。
}
return new LinkedHashMap<>();
}
private Map<String, Object> stringMap(Map<?, ?> source) {
Map<String, Object> result = new LinkedHashMap<>();
source.forEach((key, value) -> result.put(String.valueOf(key), value));
return result;
}
private Object firstValue(Map<String, Object> map, String... keys) {
for (String key : keys) if (map.get(key) != null && !String.valueOf(map.get(key)).isBlank()) return map.get(key);
return null;
}
private String waybillStatusName(String status) {
return switch (status == null ? "" : status) {
case "completed" -> "已完成";
case "processing", "running", "in_progress", "inProgress" -> "进行中";
case "pending", "created" -> "待执行";
case "cancelled", "canceled" -> "已取消";
default -> status;
};
}
private Map<String, Object> candidateMap(ReceivablePayableDetailVO detail) {
Map<String, Object> map = new LinkedHashMap<>();
map.put("id", detail.getId());
@@ -1038,6 +1381,10 @@ public class ReceivablePayableDetailServiceImpl
return value == null ? BigDecimal.ZERO : value.setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal normalizeGeneratedMileage(BigDecimal mileage) {
return mileage != null && mileage.compareTo(BigDecimal.valueOf(-1)) == 0 ? null : mileage;
}
private String formatMoney(BigDecimal value) {
return money(value).toPlainString();
}
@@ -425,7 +425,7 @@ public class ShippingTemplateServiceImpl extends BaseServiceImpl<ShippingTemplat
}
private synchronized String nextCode() {
String prefix = "MBJH-" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
String prefix = "MB-" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
List<ShippingTemplate> latestList = list(Wrappers.<ShippingTemplate>lambdaQuery()
.select(ShippingTemplate::getTemplateCode)
.likeRight(ShippingTemplate::getTemplateCode, prefix)
@@ -37,11 +37,13 @@ import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import org.springblade.transport.mapper.WaybillMapper;
import org.springblade.transport.pojo.entity.LoadingManage;
import org.springblade.transport.pojo.entity.ProcessConfig;
import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.BusinessRemoveResultVO;
import org.springblade.transport.pojo.vo.LoadingManageVO;
import org.springblade.transport.pojo.vo.WaybillVO;
import org.springblade.transport.service.ILoadingManageService;
import org.springblade.transport.service.IProcessConfigService;
import org.springblade.transport.service.IReceivablePayableDetailService;
import org.springblade.transport.service.IWaybillService;
import org.springblade.transport.support.TransportBusinessSupport;
@@ -72,6 +74,9 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
@jakarta.annotation.Resource
private ILoadingManageService loadingManageService;
@jakarta.annotation.Resource
private IProcessConfigService processConfigService;
@jakarta.annotation.Resource
@org.springframework.context.annotation.Lazy
private IReceivablePayableDetailService receivablePayableDetailService;
@@ -93,9 +98,14 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
boolean created = Func.isEmpty(waybill.getId());
if (!created) {
Waybill oldRecord = loadEditable(waybill.getId(), true);
assertNotLoaded(oldRecord);
waybill.setWaybillNo(oldRecord.getWaybillNo());
waybill.setLoadingNo(oldRecord.getLoadingNo());
waybill.setDeptId(oldRecord.getDeptId());
waybill.setDeptName(oldRecord.getDeptName());
} else {
waybill.setLoadingNo(null);
fillProjectProcessConfig(waybill);
}
prepare(waybill);
if (created && Func.isEmpty(waybill.getWaybillNo())) {
@@ -105,6 +115,33 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
return saveOrUpdate(waybill);
}
private void fillProjectProcessConfig(Waybill waybill) {
if (Func.isNotEmpty(waybill.getProcessJson()) || Func.isEmpty(waybill.getProjectId())) {
return;
}
String projectId = String.valueOf(waybill.getProjectId());
processConfigService.list(Wrappers.<ProcessConfig>lambdaQuery()
.eq(ProcessConfig::getStatus, 1)
.eq(ProcessConfig::getIsDeleted, 0)
.like(ProcessConfig::getProjectIds, projectId)
.orderByDesc(ProcessConfig::getCreateTime))
.stream()
.filter(processConfig -> containsProjectId(processConfig.getProjectIds(), projectId))
.map(ProcessConfig::getNodeConfigJson)
.filter(Func::isNotEmpty)
.findFirst()
.ifPresent(waybill::setProcessJson);
}
private boolean containsProjectId(String projectIds, String projectId) {
if (Func.isEmpty(projectIds)) {
return false;
}
return List.of(projectIds.split(",")).stream()
.map(String::trim)
.anyMatch(projectId::equals);
}
@Override
@Transactional(rollbackFor = Exception.class)
public BusinessRemoveResultVO removeWaybill(String ids) {
@@ -223,20 +260,21 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
target.setPlanId(source.getPlanId());
target.setPlanName(source.getPlanName());
target.setMasterNo(source.getMasterNo());
target.setLoadingNo(source.getLoadingNo());
target.setLoadingNo(null);
target.setBatchNo(source.getBatchNo());
target.setRelationNo(source.getRelationNo());
target.setCurrentProcessNode(source.getCurrentProcessNode());
target.setGoodsJson(source.getGoodsJson());
target.setCarrierJson(source.getCarrierJson());
target.setTaskInfoJson(source.getTaskInfoJson());
target.setProcessJson(source.getProcessJson());
target.setProcessJson(null);
target.setRouteJson(source.getRouteJson());
target.setFreightJson(source.getFreightJson());
target.setAttachmentsJson(source.getAttachmentsJson());
target.setRemark(source.getRemark());
target.setBusinessStatus("pending");
target.setWaybillNo(nextCode());
fillProjectProcessConfig(target);
prepare(target);
validate(target);
save(target);
@@ -247,6 +285,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
@Transactional(rollbackFor = Exception.class)
public boolean changeRoute(Waybill waybill) {
Waybill oldRecord = loadEditable(waybill.getId(), true);
assertNotLoaded(oldRecord);
if ("completed".equals(oldRecord.getBusinessStatus()) || "cancelled".equals(oldRecord.getBusinessStatus())) {
throw new ServiceException("当前运单状态不允许变更运输路线");
}
@@ -265,6 +304,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
@Transactional(rollbackFor = Exception.class)
public boolean cancel(Long id) {
Waybill waybill = loadEditable(id, true);
assertNotLoaded(waybill);
if ("completed".equals(waybill.getBusinessStatus()) || "cancelled".equals(waybill.getBusinessStatus())) {
throw new ServiceException("当前状态不允许取消");
}
@@ -276,6 +316,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
@Transactional(rollbackFor = Exception.class)
public boolean reassign(Long id) {
Waybill waybill = loadEditable(id, true);
assertNotLoaded(waybill);
if (!"pending".equals(waybill.getBusinessStatus())) {
throw new ServiceException("仅待执行运单允许重新派单");
}
@@ -294,6 +335,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
boolean updated = updateById(waybill);
if (updated) {
receivablePayableDetailService.generateForCompletedWaybills(List.of(waybill.getId()));
loadingManageService.completeIfAllWaybillsCompleted(waybill.getLoadingNo());
}
return updated;
}
@@ -636,7 +678,13 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
}
private boolean shouldSkipDelete(Waybill waybill) {
return false;
return Func.isNotEmpty(waybill.getLoadingNo());
}
private void assertNotLoaded(Waybill waybill) {
if (Func.isNotEmpty(waybill.getLoadingNo())) {
throw new ServiceException("运单已关联配载单,请在配载单中修改");
}
}
private void validateRoadTaskInfo(Waybill waybill) {