调整业务问题

This commit is contained in:
2026-09-02 03:22:22 +08:00
parent 3edfb72d7d
commit e5b8ff5d4c
21 changed files with 249 additions and 63 deletions
@@ -74,9 +74,11 @@ public class InvoiceApplicationSaveRequest implements Serializable {
private String unit;
private BigDecimal quantity;
private BigDecimal unitPriceNoTax;
private BigDecimal amountNoTax;
private BigDecimal amountWithTax;
private BigDecimal taxRate;
private BigDecimal taxAmount;
private BigDecimal totalAmount;
private String remark;
}
}
@@ -51,8 +51,10 @@ public class InvoiceApplicationLine extends TenantEntity {
private String unit;
private BigDecimal quantity;
private BigDecimal unitPriceNoTax;
private BigDecimal amountNoTax;
private BigDecimal amountWithTax;
private BigDecimal taxRate;
private BigDecimal taxAmount;
private BigDecimal totalAmount;
private String remark;
}
@@ -9,6 +9,7 @@
*/
package org.springblade.transport.pojo.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -65,6 +66,10 @@ public class PreSettlementDetailFee extends TenantEntity {
@Schema(description = "费用项JSON")
private String feeItemsJson;
@Schema(description = "命中计费规则JSON")
@TableField(exist = false)
private String billingRulesJson;
@Schema(description = "原金额")
private BigDecimal originalAmount;
@@ -65,15 +65,24 @@ public class BillLedgerController extends BladeController {
return R.data(billLedgerService.availableOptions(keyword, deptId, selectedId));
}
@PostMapping("/submit")
@GetMapping("/available-page")
@ApiOperationSupport(order = 5)
@Operation(summary = "付款申请可用汇票分页")
public R<IPage<BillLedgerVO>> availablePage(Query pageQuery,
@RequestParam(required = false) String keyword, @RequestParam(required = false) Long deptId,
@RequestParam(required = false) Long selectedId) {
return R.data(billLedgerService.availablePage(Condition.getPage(pageQuery), keyword, deptId, selectedId));
}
@PostMapping("/submit")
@ApiOperationSupport(order = 6)
@Operation(summary = "新增或编辑汇票台账")
public R<Long> submit(@RequestBody BillLedgerSaveRequest request) {
return R.data(billLedgerService.submit(request));
}
@PostMapping("/remove")
@ApiOperationSupport(order = 6)
@ApiOperationSupport(order = 7)
@Operation(summary = "删除汇票台账")
public R remove(@RequestParam Long id) {
billLedgerService.removeLedger(id);
@@ -174,15 +174,22 @@ public class WaybillController extends BladeController {
return R.status(waybillService.submit(waybill));
}
@PostMapping("/remove")
@PostMapping("/save-draft")
@ApiOperationSupport(order = 10)
@Operation(summary = "保存草稿", description = "传入waybill")
public R saveDraft(@RequestBody Waybill waybill) {
return R.status(waybillService.saveDraft(waybill));
}
@PostMapping("/remove")
@ApiOperationSupport(order = 11)
@Operation(summary = "逻辑删除", description = "传入ids")
public R<BusinessRemoveResultVO> remove(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
return R.data(waybillService.removeWaybill(ids));
}
@GetMapping("/export-waybill-manage")
@ApiOperationSupport(order = 11)
@ApiOperationSupport(order = 12)
@Operation(summary = "导出运单管理")
public void exportWaybill(WaybillVO waybill, @RequestParam(required = false) String ids, HttpServletResponse response) {
List<WaybillExcel> list = waybillService.exportWaybill(waybill, ids);
@@ -190,7 +197,7 @@ public class WaybillController extends BladeController {
}
@PostMapping("/import-waybill-manage")
@ApiOperationSupport(order = 12)
@ApiOperationSupport(order = 13)
@Operation(summary = "导入运单管理", description = "传入excel")
public R importWaybill(MultipartFile file, HttpServletResponse response) {
List<WaybillExcel> failureList = waybillService.importWaybill(ExcelUtil.read(file, WaybillExcel.class));
@@ -202,14 +209,14 @@ public class WaybillController extends BladeController {
}
@GetMapping("/export-template")
@ApiOperationSupport(order = 13)
@ApiOperationSupport(order = 14)
@Operation(summary = "导出模板")
public void exportTemplate(HttpServletResponse response) {
ExcelUtil.export(response, "运单管理模板", "运单管理导入模板", new ArrayList<WaybillExcel>(), WaybillExcel.class);
}
@GetMapping("/import-batch/export-template")
@ApiOperationSupport(order = 14)
@ApiOperationSupport(order = 15)
@Operation(summary = "导出运单批量导入模板")
public void exportImportBatchTemplate(HttpServletResponse response) {
ExcelUtil.export(
@@ -223,56 +230,56 @@ public class WaybillController extends BladeController {
}
@PostMapping("/copy")
@ApiOperationSupport(order = 15)
@ApiOperationSupport(order = 16)
@Operation(summary = "复制", description = "传入id")
public R<WaybillVO> copy(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.data(waybillService.copy(id));
}
@PostMapping("/change-route")
@ApiOperationSupport(order = 16)
@ApiOperationSupport(order = 17)
@Operation(summary = "变更运输路线", description = "传入运单路线与变更记录")
public R changeRoute(@RequestBody Waybill waybill) {
return R.status(waybillService.changeRoute(waybill));
}
@PostMapping("/maintain-mileage")
@ApiOperationSupport(order = 17)
@ApiOperationSupport(order = 18)
@Operation(summary = "维护里程", description = "仅已完成且未生成结算单的运单允许维护")
public R maintainMileage(@RequestBody WaybillMileageRequest request) {
return R.status(waybillService.maintainMileage(request));
}
@PostMapping("/cancel")
@ApiOperationSupport(order = 18)
@ApiOperationSupport(order = 19)
@Operation(summary = "取消", description = "传入id")
public R cancel(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.status(waybillService.cancel(id));
}
@PostMapping("/reassign")
@ApiOperationSupport(order = 19)
@ApiOperationSupport(order = 20)
@Operation(summary = "重新派单", description = "传入id")
public R reassign(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.status(waybillService.reassign(id));
}
@PostMapping("/complete")
@ApiOperationSupport(order = 20)
@ApiOperationSupport(order = 21)
@Operation(summary = "完成", description = "传入id")
public R complete(@Parameter(description = "主键", required = true) @RequestParam Long id) {
return R.status(waybillService.complete(id));
}
@PostMapping("/batch-complete")
@ApiOperationSupport(order = 21)
@ApiOperationSupport(order = 22)
@Operation(summary = "批量完成", description = "传入ids")
public R<BusinessRemoveResultVO> batchComplete(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
return R.data(waybillService.batchComplete(ids));
}
@PostMapping("/road-loading")
@ApiOperationSupport(order = 22)
@ApiOperationSupport(order = 23)
@Operation(summary = "公路配载", description = "传入ids")
public R<LoadingManageVO> roadLoading(@Parameter(description = "主键集合", required = true) @RequestParam String ids) {
return R.data(waybillService.roadLoading(ids));
@@ -19,6 +19,7 @@ public interface IBillLedgerService extends BaseService<BillLedger> {
BillLedgerVO detail(Long id);
Map<String, Long> expiryCounts();
List<BillLedgerVO> availableOptions(String keyword, Long deptId, Long selectedId);
IPage<BillLedgerVO> availablePage(IPage<BillLedger> page, String keyword, Long deptId, Long selectedId);
Long submit(BillLedgerSaveRequest request);
void removeLedger(Long id);
}
@@ -43,6 +43,7 @@ public interface IWaybillService extends BaseService<Waybill> {
IPage<WaybillVO> selectWaybillPage(IPage<Waybill> page, WaybillVO waybill);
WaybillVO detail(Long id);
boolean submit(Waybill waybill);
boolean saveDraft(Waybill waybill);
BusinessRemoveResultVO removeWaybill(String ids);
List<WaybillExcel> exportWaybill(WaybillVO waybill, String ids);
List<WaybillExcel> importWaybill(List<WaybillExcel> data);
@@ -7,6 +7,7 @@ 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 com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseServiceImpl;
@@ -86,6 +87,23 @@ public class BillLedgerServiceImpl extends BaseServiceImpl<BillLedgerMapper, Bil
@Override
public List<BillLedgerVO> availableOptions(String keyword, Long deptId, Long selectedId) {
return availableList(keyword, deptId, selectedId);
}
@Override
public IPage<BillLedgerVO> availablePage(IPage<BillLedger> page, String keyword, Long deptId,
Long selectedId) {
List<BillLedgerVO> available = availableList(keyword, deptId, selectedId);
long current = Math.max(page.getCurrent(), 1);
long size = Math.max(page.getSize(), 1);
long from = Math.min((current - 1) * size, available.size());
long to = Math.min(from + size, available.size());
Page<BillLedgerVO> result = new Page<>(current, size, available.size());
result.setRecords(available.subList((int) from, (int) to));
return result;
}
private List<BillLedgerVO> availableList(String keyword, Long deptId, Long selectedId) {
return list(Wrappers.<BillLedger>lambdaQuery()
.and(Func.isNotEmpty(keyword), wrapper -> wrapper.like(BillLedger::getBillNo, keyword)
.or().like(BillLedger::getIssuerName, keyword)
@@ -94,8 +112,7 @@ public class BillLedgerServiceImpl extends BaseServiceImpl<BillLedgerMapper, Bil
.gt(BillLedger::getAvailableBalance, BigDecimal.ZERO)
.or(selectedId != null, child -> child.eq(BillLedger::getId, selectedId)))
.orderByAsc(BillLedger::getMaturityDate)
.orderByDesc(BillLedger::getCreateTime)
.last("limit 200")).stream()
.orderByDesc(BillLedger::getCreateTime)).stream()
.filter(item -> selectedId != null && Objects.equals(item.getId(), selectedId)
|| departmentAvailable(item, deptId))
.map(item -> BillLedgerWrapper.build().entityVO(item))
@@ -47,6 +47,7 @@ import org.springblade.transport.mapper.InvoiceApplicationMapper;
import org.springblade.transport.mapper.InvoiceApplicationRecordMapper;
import org.springblade.transport.mapper.InvoiceApplicationSettlementMapper;
import org.springblade.transport.mapper.InvoiceApplicationSheetMapper;
import org.springblade.transport.mapper.WaybillMapper;
import org.springblade.transport.pojo.dto.InvoiceApplicationSaveRequest;
import org.springblade.transport.pojo.dto.InvoiceApplicationStatusRequest;
import org.springblade.transport.pojo.entity.CustomerArchive;
@@ -61,6 +62,7 @@ import org.springblade.transport.pojo.entity.InvoiceApplicationLine;
import org.springblade.transport.pojo.entity.InvoiceApplicationRecord;
import org.springblade.transport.pojo.entity.InvoiceApplicationSettlement;
import org.springblade.transport.pojo.entity.InvoiceApplicationSheet;
import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.InvoiceApplicationSheetVO;
import org.springblade.transport.pojo.vo.InvoiceApplicationVO;
import org.springblade.transport.service.IInvoiceApplicationService;
@@ -100,6 +102,7 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
private static final String VOIDED = "voided";
private final InvoiceApplicationSettlementMapper settlementRelationMapper;
private final InvoiceApplicationSheetMapper sheetMapper;
private final WaybillMapper waybillMapper;
private final InvoiceApplicationLineMapper lineMapper;
private final InvoiceApplicationDetailMapper applicationDetailMapper;
private final InvoiceApplicationRecordMapper recordMapper;
@@ -237,8 +240,23 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
List<Long> ids = distinctIds(settlementIds);
if (ids.isEmpty()) return List.of();
assertCompatible(ids.stream().map(this::availableSettlement).toList());
return formalSettlementDetailMapper.selectList(Wrappers.<FormalSettlementDetail>lambdaQuery()
.in(FormalSettlementDetail::getFormalSettlementId, ids).orderByAsc(FormalSettlementDetail::getLineNo));
List<FormalSettlementDetail> details = formalSettlementDetailMapper.selectList(
Wrappers.<FormalSettlementDetail>lambdaQuery()
.in(FormalSettlementDetail::getFormalSettlementId, ids)
.orderByAsc(FormalSettlementDetail::getLineNo));
List<Long> waybillIds = details.stream().map(FormalSettlementDetail::getWaybillId)
.filter(Objects::nonNull).distinct().toList();
if (!waybillIds.isEmpty()) {
Map<Long, Waybill> waybillMap = waybillMapper.selectBatchIds(waybillIds).stream()
.collect(Collectors.toMap(Waybill::getId, Function.identity(), (first, duplicate) -> first));
details.forEach(detail -> {
Waybill waybill = waybillMap.get(detail.getWaybillId());
if (waybill == null) return;
if (waybill.getStartDate() != null) detail.setActualDepartureTime(waybill.getStartDate().atStartOfDay());
if (waybill.getEndDate() != null) detail.setActualCompletionTime(waybill.getEndDate().atStartOfDay());
});
}
return details;
}
@Override
@@ -455,14 +473,21 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
for (InvoiceApplicationSaveRequest.LineRow line : sheet.getLines()) {
required(line.getGoodsCategory(), "商品和服务分类");
required(line.getGoodsName(), "货物或服务简称");
nonNegative(line.getQuantity(), "数量");
nonNegative(line.getUnitPriceNoTax(), "不含税单价");
BigDecimal amount = nonNegative(line.getAmountWithTax(), "含税金额");
BigDecimal quantity = nonNegative(line.getQuantity(), "数量");
BigDecimal unitPriceNoTax = nonNegative(line.getUnitPriceNoTax(), "不含税单价")
.setScale(2, RoundingMode.HALF_UP);
BigDecimal amountNoTax = quantity.multiply(unitPriceNoTax)
.setScale(2, RoundingMode.HALF_UP);
BigDecimal taxRate = nonNegative(line.getTaxRate(), "税率");
if (taxRate.compareTo(BigDecimal.valueOf(100)) > 0) throw new ServiceException("税率必须在0-100之间");
line.setTaxAmount(calculateTax(amount, taxRate));
BigDecimal taxAmount = calculateTax(amountNoTax, taxRate);
BigDecimal totalAmount = amountNoTax.add(taxAmount).setScale(2, RoundingMode.HALF_UP);
line.setAmountNoTax(amountNoTax);
line.setTaxAmount(taxAmount);
line.setTotalAmount(totalAmount);
line.setAmountWithTax(totalAmount);
line.setRemark(limit(line.getRemark(), 200, "商品行备注"));
total = total.add(amount);
total = total.add(totalAmount);
}
}
return total.setScale(2, RoundingMode.HALF_UP);
@@ -534,7 +559,7 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
InvoiceApplicationSheet sheet = new InvoiceApplicationSheet();
sheet.setInvoiceApplicationId(applicationId);
sheet.setSheetNo(sheetNo++);
sheet.setInvoiceAmount(sheetRow.getLines().stream().map(InvoiceApplicationSaveRequest.LineRow::getAmountWithTax)
sheet.setInvoiceAmount(sheetRow.getLines().stream().map(InvoiceApplicationSaveRequest.LineRow::getTotalAmount)
.map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add));
sheetMapper.insert(sheet);
int lineNo = 1;
@@ -730,9 +755,7 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
}
private BigDecimal calculateTax(BigDecimal amount, BigDecimal rate) {
if (rate.compareTo(BigDecimal.ZERO) == 0) return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
return amount.subtract(amount.divide(BigDecimal.ONE.add(rate.divide(BigDecimal.valueOf(100), 8,
RoundingMode.HALF_UP)), 8, RoundingMode.HALF_UP)).setScale(2, RoundingMode.HALF_UP);
return amount.multiply(rate).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
}
private BigDecimal nonNegative(BigDecimal value, String name) {
@@ -663,6 +663,7 @@ public class LoadingManageServiceImpl extends BaseServiceImpl<LoadingManageMappe
.set(Waybill::getEscortName, loadingManage.getEscortName())
.set(Waybill::getEscortPhone, loadingManage.getEscortPhone())
.set(Waybill::getMileage, loadingManage.getMileage())
.set(STATUS_COMPLETED.equals(status), Waybill::getEndDate, LocalDate.now())
.set(Waybill::getEstimatedStartTime, toDateTime(loadingManage.getEstimatedStartDate()))
.set(Waybill::getEstimatedEndTime, toDateTime(loadingManage.getEstimatedEndDate()))
.set(Waybill::getTaskRemark, loadingManage.getTaskRemark())
@@ -141,6 +141,10 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
masterOrder.setBusinessStatus("closed");
boolean updated = updateById(masterOrder);
if (updated) {
waybillsByMasterNo(masterOrder.getMasterNo()).forEach(waybill -> {
waybill.setEndDate(LocalDate.now());
waybillService.updateById(waybill);
});
receivablePayableDetailService.generateForClosedMasterOrder(masterOrder);
}
return updated;
@@ -369,8 +373,7 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
if (Func.isEmpty(string(dispatch, "priceUnit"))) throw new ServiceException("单价单位不能为空");
if (!available.containsKey(key)) throw new ServiceException("调度货物必须来自总单配置");
LocalDate startDate = date(dispatch, "estimatedStartTime"); LocalDate endDate = date(dispatch, "estimatedEndTime");
if (startDate == null || endDate == null) throw new ServiceException("预计发货日期预计完成日期不能为空");
if (startDate.isAfter(endDate)) throw new ServiceException("预计发货日期不能晚于预计完成日期");
if (startDate != null && endDate != null && startDate.isAfter(endDate)) throw new ServiceException("预计发货日期不能晚于预计完成日期");
if ("运单".equals(string(dispatch, "documentType", "运单"))) {
validateCarrier(dispatch, availableCarrierContracts);
}
@@ -424,10 +427,11 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
|| !Objects.equals(carrierContract.getPartyB(), carrierName))) {
throw new ServiceException("所选承运商不属于总单客户合同对应项目的有效承运商合同乙方");
}
boolean road = string(dispatch, "transportType", "").toLowerCase().contains("road") || string(dispatch, "transportType", "").contains("公路");
String transportType = string(dispatch, "transportType", "");
boolean road = transportType.toLowerCase().contains("road") || transportType.contains("公路");
String mileage = string(dispatch, "mileage");
if (!road) {
if (Func.isEmpty(string(dispatch, "vehicleNo")) || Func.isEmpty(string(dispatch, "captainName")) || Func.isEmpty(string(dispatch, "driverPhone")) || Func.isEmpty(string(dispatch, "containerNo")) || Func.isEmpty(string(dispatch, "cabinNo")) || (Func.isNotEmpty(mileage) && decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) <= 0) || ("承运商".equals(carrierType) && Func.isEmpty(string(dispatch, "carrierName")))) {
if (Func.isEmpty(string(dispatch, "vehicleNo")) || Func.isEmpty(string(dispatch, "driverPhone")) || (Func.isNotEmpty(mileage) && decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) <= 0) || ("承运商".equals(carrierType) && Func.isEmpty(string(dispatch, "carrierName")))) {
throw new ServiceException("非公路运输的承运信息不完整");
}
return;
@@ -439,6 +443,11 @@ public class MasterOrderServiceImpl extends BaseServiceImpl<MasterOrderMapper, M
}
if (Func.isEmpty(string(dispatch, "driverName")) || Func.isEmpty(string(dispatch, "driverPhone")) || Func.isEmpty(string(dispatch, "trailerVehicleNo")) || Func.isEmpty(string(dispatch, "escortName")) || Func.isEmpty(string(dispatch, "escortPhone")) || (Func.isNotEmpty(mileage) && decimal(dispatch, "mileage").compareTo(BigDecimal.ZERO) < 0)) throw new ServiceException("自运或网货平台的车辆与人员信息不完整");
}
private boolean isWaterTransport(String transportType) {
String value = transportType == null ? "" : transportType.trim().toLowerCase();
return "river".equals(value) || "water".equals(value) || "sl".equals(value)
|| value.contains("水路") || value.contains("水运");
}
private BigDecimal dispatchedQuantity(String masterNo, String segmentNo) {
return dispatchedGoods(masterNo, segmentNo).values().stream().reduce(BigDecimal.ZERO, BigDecimal::add);
}
@@ -73,7 +73,6 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** 付款申请服务实现。 @author Chill */
@@ -257,7 +256,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
throw new ServiceException("当前状态不允许提交");
}
validateNoTailPayment(entity);
validateProjectAdvanceContractFile(entity);
validateProjectAdvanceContract(entity);
validateSelectedBill(entity, false);
validateQuota(entity);
entity.setApprovalStatus(REVIEWING);
@@ -358,33 +357,14 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
}
}
private void validateProjectAdvanceContractFile(PaymentApplication entity) {
private void validateProjectAdvanceContract(PaymentApplication entity) {
if (!"project_advance".equals(entity.getPaymentType())) return;
ContractManage contract = entity.getContractId() == null ? null : contractManageMapper.selectById(entity.getContractId());
if (contract == null || Objects.equals(contract.getIsDeleted(), 1)) {
throw new ServiceException("项目预付必须选择有效合同");
}
if (!hasDownloadableFile(contract.getContractFileJson())
&& !hasDownloadableFile(entity.getAttachmentsJson(), "contract")) {
throw new ServiceException("请上传合同签章文件");
}
}
private boolean hasDownloadableFile(String value) {
return hasDownloadableFile(value, null);
}
private boolean hasDownloadableFile(String value, String attachmentType) {
if (Func.isEmpty(value)) return false;
try {
Object parsed = JsonUtil.parse(value, List.class);
if (!(parsed instanceof List<?> files)) return false;
return files.stream().anyMatch(file -> file instanceof Map<?, ?> fields
&& (attachmentType == null || Objects.equals(attachmentType, fields.get("attachmentType")))
&& List.of("url", "link", "fileUrl", "downloadUrl", "src", "domain").stream()
.anyMatch(key -> Func.isNotEmpty(fields.get(key))));
} catch (Exception exception) {
return false;
if (!"承运商合同".equals(contract.getContractCategory())) {
throw new ServiceException("付款申请只能选择承运商合同");
}
}
@@ -235,6 +235,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
result.put("deptName", contract.getOrganizationName());
result.put("partyA", contract.getPartyA());
result.put("partyB", contract.getPartyB());
result.put("contractCategory", contract.getContractCategory());
result.put("fundDemand", project == null ? null : project.getFundDemand());
result.put("settlementMode", project == null ? null : project.getSettlementMode());
result.put("settlementType", contractSettlementType(contract));
@@ -570,10 +571,20 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
if (detail == null || Objects.equals(detail.getIsDeleted(), 1)) {
throw new ServiceException("预结算明细不存在");
}
return detailFeeMapper.selectList(Wrappers.<PreSettlementDetailFee>lambdaQuery()
List<PreSettlementDetailFee> fees = detailFeeMapper.selectList(Wrappers.<PreSettlementDetailFee>lambdaQuery()
.eq(PreSettlementDetailFee::getPreSettlementDetailId, detailId)
.eq(PreSettlementDetailFee::getIsDeleted, 0)
.orderByAsc(PreSettlementDetailFee::getCreateTime));
List<Long> sourceFeeIds = fees.stream().map(PreSettlementDetailFee::getSourceFeeId)
.filter(Objects::nonNull).toList();
if (sourceFeeIds.isEmpty()) return fees;
Map<Long, ReceivablePayableCargoFee> sourceFeeMap = sourceFeeMapper.selectBatchIds(sourceFeeIds).stream()
.collect(Collectors.toMap(ReceivablePayableCargoFee::getId, Function.identity(), (left, right) -> left));
fees.forEach(fee -> {
ReceivablePayableCargoFee sourceFee = sourceFeeMap.get(fee.getSourceFeeId());
if (sourceFee != null) fee.setBillingRulesJson(sourceFee.getBillingRulesJson());
});
return fees;
}
@Override
@@ -462,7 +462,7 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
transportPlan.setArrivalPhone(TransportBusinessSupport.trimToNull(transportPlan.getArrivalPhone()));
transportPlan.setGoodsJson(TransportBusinessSupport.trimToNull(transportPlan.getGoodsJson()));
transportPlan.setAttachmentsJson(TransportBusinessSupport.trimToNull(transportPlan.getAttachmentsJson()));
transportPlan.setDataSource(TransportBusinessSupport.trimToNull(transportPlan.getDataSource()));
transportPlan.setDataSource(TransportBusinessSupport.normalizeTransportPlanDataSource(transportPlan.getDataSource()));
transportPlan.setBusinessStatus(TransportBusinessSupport.trimToNull(transportPlan.getBusinessStatus()));
transportPlan.setDeptName(TransportBusinessSupport.trimToNull(transportPlan.getDeptName()));
transportPlan.setRemark(TransportBusinessSupport.trimToNull(transportPlan.getRemark()));
@@ -106,6 +106,20 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
@Override
@Transactional(rollbackFor = Exception.class)
public boolean submit(Waybill waybill) {
prepareForSave(waybill);
validate(waybill);
return saveOrUpdate(waybill);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveDraft(Waybill waybill) {
prepareForSave(waybill);
waybill.setBusinessStatus(STATUS_DRAFT);
return saveOrUpdate(waybill);
}
private void prepareForSave(Waybill waybill) {
boolean created = Func.isEmpty(waybill.getId());
if (!created) {
Waybill oldRecord = loadEditable(waybill.getId(), true);
@@ -124,8 +138,6 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
if (created && Func.isEmpty(waybill.getWaybillNo())) {
waybill.setWaybillNo(nextCode());
}
validate(waybill);
return saveOrUpdate(waybill);
}
private void fillProjectProcessConfig(Waybill waybill) {
@@ -372,6 +384,7 @@ public class WaybillServiceImpl extends BaseServiceImpl<WaybillMapper, Waybill>
throw new ServiceException("当前状态不允许完成");
}
waybill.setBusinessStatus("completed");
waybill.setEndDate(LocalDate.now());
boolean updated = updateById(waybill);
if (updated) {
receivablePayableDetailService.generateForCompletedWaybills(List.of(waybill.getId()));
@@ -41,6 +41,10 @@ import java.util.regex.Pattern;
*/
public final class TransportBusinessSupport {
public static final String DATA_SOURCE_BATCH_IMPORT = "批量导入";
public static final String DATA_SOURCE_MANUAL = "手工创建";
public static final String DATA_SOURCE_EXTERNAL = "外部系统";
private static final Pattern PHONE_PATTERN = Pattern.compile("^(1\\d{10}|0\\d{2,3}-?\\d{7,8})$");
private static final BigDecimal MIN_LONGITUDE = new BigDecimal("-180");
private static final BigDecimal MAX_LONGITUDE = new BigDecimal("180");
@@ -87,6 +91,25 @@ public final class TransportBusinessSupport {
return trimValue.isEmpty() ? null : trimValue;
}
/**
* 归一化运输计划数据来源,保证列表只出现约定的三种来源。
*
* @param value 原始数据来源
* @return 批量导入、手工创建或外部系统
*/
public static String normalizeTransportPlanDataSource(String value) {
String source = trimToNull(value);
if (Func.isEmpty(source)) {
return DATA_SOURCE_MANUAL;
}
return switch (source) {
case DATA_SOURCE_BATCH_IMPORT -> DATA_SOURCE_BATCH_IMPORT;
case DATA_SOURCE_MANUAL, "手动创建", "手动录入", "手工录入", "手动", "模板生成", "计划调度", "多联总单调度" -> DATA_SOURCE_MANUAL;
case DATA_SOURCE_EXTERNAL -> DATA_SOURCE_EXTERNAL;
default -> DATA_SOURCE_EXTERNAL;
};
}
public static void validateRequired(String value, String message) {
if (Func.isEmpty(trimToNull(value))) {
throw new ServiceException(message);
@@ -29,6 +29,7 @@ import org.springblade.core.tool.utils.Func;
import org.springblade.system.cache.UserCache;
import org.springblade.transport.pojo.entity.TransportPlan;
import org.springblade.transport.pojo.vo.TransportPlanVO;
import org.springblade.transport.support.TransportBusinessSupport;
import java.util.Objects;
@@ -48,6 +49,7 @@ public class TransportPlanWrapper extends BaseEntityWrapper<TransportPlan, Trans
TransportPlanVO transportPlanVO = Objects.requireNonNull(BeanUtil.copyProperties(transportPlan, TransportPlanVO.class));
transportPlanVO.setCreateUserName(UserCache.getUserRealName(transportPlan.getCreateUser()));
transportPlanVO.setUpdateUserName(UserCache.getUserRealName(transportPlan.getUpdateUser()));
transportPlanVO.setDataSource(TransportBusinessSupport.normalizeTransportPlanDataSource(transportPlan.getDataSource()));
Long currentDeptId = Func.firstLong(AuthUtil.getDeptId());
transportPlanVO.setReadonly(currentDeptId != null && !Objects.equals(transportPlan.getDeptId(), currentDeptId));
transportPlanVO.setBusinessStatusName(businessStatusName(transportPlan.getBusinessStatus()));
@@ -104,10 +104,12 @@ CREATE TABLE IF NOT EXISTS `blade_invoice_application_line` (
`goods_name` varchar(100) NOT NULL,
`unit` varchar(30) DEFAULT NULL,
`quantity` decimal(18,4) DEFAULT NULL,
`unit_price_no_tax` decimal(18,4) DEFAULT NULL,
`amount_with_tax` decimal(18,2) NOT NULL DEFAULT '0.00',
`unit_price_no_tax` decimal(18,2) DEFAULT NULL,
`amount_no_tax` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '不含税金额',
`amount_with_tax` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '含税金额(兼容历史字段)',
`tax_rate` decimal(8,4) NOT NULL DEFAULT '0.0000',
`tax_amount` decimal(18,2) NOT NULL DEFAULT '0.00',
`total_amount` decimal(18,2) NOT NULL DEFAULT '0.00' COMMENT '含税合计',
`remark` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_invoice_sheet_line` (`invoice_sheet_id`,`line_no`),
@@ -0,0 +1,61 @@
-- 开票申请商品行补齐不含税金额与含税合计字段。
-- 使用 information_schema 判断字段是否存在,重复执行不会报重复列错误。
SET @invoice_line_schema = DATABASE();
SET @invoice_line_sql = (
SELECT IF(
COUNT(*) > 0,
'ALTER TABLE `blade_invoice_application_line` MODIFY COLUMN `unit_price_no_tax` decimal(18,2) DEFAULT NULL',
'SELECT 1'
)
FROM information_schema.columns
WHERE table_schema = @invoice_line_schema
AND table_name = 'blade_invoice_application_line'
AND column_name = 'unit_price_no_tax'
AND (numeric_precision <> 18 OR numeric_scale <> 2)
);
PREPARE invoice_line_stmt FROM @invoice_line_sql;
EXECUTE invoice_line_stmt;
DEALLOCATE PREPARE invoice_line_stmt;
SET @invoice_line_sql = (
SELECT IF(
COUNT(*) = 0,
'ALTER TABLE `blade_invoice_application_line` ADD COLUMN `amount_no_tax` decimal(18,2) NOT NULL DEFAULT ''0.00'' COMMENT ''不含税金额'' AFTER `unit_price_no_tax`',
'SELECT 1'
)
FROM information_schema.columns
WHERE table_schema = @invoice_line_schema
AND table_name = 'blade_invoice_application_line'
AND column_name = 'amount_no_tax'
);
PREPARE invoice_line_stmt FROM @invoice_line_sql;
EXECUTE invoice_line_stmt;
DEALLOCATE PREPARE invoice_line_stmt;
SET @invoice_line_sql = (
SELECT IF(
COUNT(*) = 0,
'ALTER TABLE `blade_invoice_application_line` ADD COLUMN `total_amount` decimal(18,2) NOT NULL DEFAULT ''0.00'' COMMENT ''含税合计'' AFTER `tax_amount`',
'SELECT 1'
)
FROM information_schema.columns
WHERE table_schema = @invoice_line_schema
AND table_name = 'blade_invoice_application_line'
AND column_name = 'total_amount'
);
PREPARE invoice_line_stmt FROM @invoice_line_sql;
EXECUTE invoice_line_stmt;
DEALLOCATE PREPARE invoice_line_stmt;
UPDATE `blade_invoice_application_line`
SET `amount_no_tax` = ROUND(
`amount_with_tax` / (1 + `tax_rate` / 100),
2
)
WHERE `amount_no_tax` = 0
AND `amount_with_tax` > 0;
UPDATE `blade_invoice_application_line`
SET `total_amount` = ROUND(`amount_no_tax` + `tax_amount`, 2),
`amount_with_tax` = ROUND(`amount_no_tax` + `tax_amount`, 2);
+1 -1
View File
@@ -196,7 +196,7 @@ CREATE TABLE `blade_transport_plan` (
`goods_json` text DEFAULT NULL COMMENT '货物信息',
`freight_json` text DEFAULT NULL COMMENT '费用信息',
`attachments_json` text DEFAULT NULL COMMENT '附件',
`data_source` varchar(100) DEFAULT NULL COMMENT '数据来源',
`data_source` varchar(100) DEFAULT '手工创建' COMMENT '数据来源',
`business_status` varchar(100) DEFAULT NULL COMMENT '业务状态',
`dispatcher_user_id` bigint(20) DEFAULT NULL COMMENT '调度人ID',
`dispatcher_user_name` varchar(100) DEFAULT NULL COMMENT '调度人姓名',
@@ -0,0 +1,17 @@
-- 运输计划数据来源统一为:批量导入、手工创建、外部系统
ALTER TABLE `blade_transport_plan`
MODIFY COLUMN `data_source` varchar(100) DEFAULT '手工创建' COMMENT '数据来源';
UPDATE `blade_transport_plan`
SET `data_source` = CASE
WHEN TRIM(COALESCE(`data_source`, '')) = '批量导入' THEN '批量导入'
WHEN TRIM(COALESCE(`data_source`, '')) IN (
'手工创建', '手动创建', '手动录入', '手工录入', '手动',
'模板生成', '计划调度', '多联总单调度'
) THEN '手工创建'
WHEN TRIM(COALESCE(`data_source`, '')) = '外部系统' THEN '外部系统'
ELSE '外部系统'
END
WHERE `data_source` IS NULL
OR TRIM(`data_source`) = ''
OR TRIM(`data_source`) NOT IN ('批量导入', '手工创建', '外部系统');