| src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/basic/pojo/ProductModel.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/sales/pojo/SalesLedger.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/sales/service/ShippingInfoService.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -27,11 +27,14 @@ import com.ruoyi.purchase.service.impl.PurchaseLedgerServiceImpl; import com.ruoyi.sales.mapper.*; import com.ruoyi.sales.pojo.CommonFile; import com.ruoyi.sales.pojo.SalesLedger; import com.ruoyi.sales.pojo.SalesLedgerProduct; import com.ruoyi.sales.pojo.SalesQuotation; import com.ruoyi.sales.pojo.ShippingInfo; import com.ruoyi.sales.service.ShippingInfoService; import com.ruoyi.sales.service.impl.CommonFileServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -81,6 +84,13 @@ @Autowired private PurchaseLedgerServiceImpl purchaseLedgerServiceImpl; @Autowired private SalesLedgerMapper salesLedgerMapper; @Autowired @Lazy private ShippingInfoService shippingInfoService; public ApproveProcess getApproveById(String id) { @@ -232,24 +242,34 @@ } salesQuotationMapper.updateById(salesQuote); } // 出库审批修改 // 出库审批修改 (订单级别) if(approveProcess.getApproveType().equals(7)){ String[] split = approveProcess.getApproveReason().split(":"); ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>() .eq(ShippingInfo::getShippingNo, split[1]) .orderByDesc(ShippingInfo::getCreateTime) .last("limit 1")); if(shippingInfo != null){ if(status.equals(2)){ shippingInfo.setStatus("审核通过"); }else if(status.equals(3)){ shippingInfo.setStatus("审核拒绝"); }else if(status.equals(1)){ shippingInfo.setStatus("审核中"); if (split.length > 1) { String identifier = split[1]; // 查找销售台账 SalesLedger salesLedger = salesLedgerMapper.selectOne(new LambdaQueryWrapper<SalesLedger>() .eq(SalesLedger::getSalesContractNo, identifier) .last("limit 1")); if (salesLedger != null) { if(status.equals(2)){ // 审批完成 -> 自动扣除库存 try { shippingInfoService.deductStockByOrder(salesLedger.getId(), null); } catch (IOException e) { throw new RuntimeException("自动扣除库存失败: " + e.getMessage()); } } else if(status.equals(3)){ updateSalesLedgerDeliveryStatus(salesLedger.getId(), 3); // 更新关联的发货记录为审核拒绝 updateShippingInfoStatusByOrder(salesLedger.getId(), "审核拒绝"); } else if(status.equals(1)){ updateSalesLedgerDeliveryStatus(salesLedger.getId(), 2); updateShippingInfoStatusByOrder(salesLedger.getId(), "审核中"); } } shippingInfoMapper.updateById(shippingInfo); } } // 绑定附件 if(!CollectionUtils.isEmpty(approveNode.getTempFileIds()) && approveNode.getApproveNodeStatus() == 1){ @@ -257,6 +277,23 @@ } } private void updateShippingInfoStatusByOrder(Long salesLedgerId, String statusText) { if (salesLedgerId == null) return; shippingInfoMapper.update(null, new UpdateWrapper<ShippingInfo>() .lambda() .set(ShippingInfo::getStatus, statusText) .eq(ShippingInfo::getSalesLedgerId, salesLedgerId)); } private void updateSalesLedgerDeliveryStatus(Long salesLedgerId, Integer status) { if (salesLedgerId == null) return; SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerId); if (salesLedger != null) { salesLedger.setDeliveryStatus(status); salesLedgerMapper.updateById(salesLedger); } } @Override public void updateApproveNode(ApproveNode approveNode) throws IOException { // 审批节点状态:1同意,2拒绝,0尚未审核 src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -29,6 +29,8 @@ import com.ruoyi.purchase.pojo.PurchaseLedger; import com.ruoyi.sales.mapper.CommonFileMapper; import com.ruoyi.sales.mapper.ShippingInfoMapper; import com.ruoyi.sales.mapper.SalesLedgerMapper; import com.ruoyi.sales.pojo.SalesLedger; import com.ruoyi.sales.pojo.CommonFile; import com.ruoyi.sales.pojo.ShippingInfo; import com.ruoyi.sales.service.impl.CommonFileServiceImpl; @@ -79,6 +81,9 @@ private CommonFileServiceImpl commonFileService; @Autowired private ISysNoticeService sysNoticeService; @Autowired private SalesLedgerMapper salesLedgerMapper; @Override public void addApprove(ApproveProcessVO approveProcessVO) throws Exception { @@ -191,19 +196,28 @@ // 发货审批查询 else if (record.getApproveType() == 7) { String reason = record.getApproveReason(); // 格式为 "xx:...-..." String reason = record.getApproveReason(); // 格式为 "xx:CONTRACT_NO" if (StringUtils.hasText(reason) && reason.contains(":")) { // 提取冒号后面的发货单号 String shippingNo = reason.split(":")[1]; // 根据发货单号查询发货台账记录 ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>() .eq(ShippingInfo::getShippingNo, shippingNo) // 提取冒号后面的标识符 (可能是合同号或发货单号) String identifier = reason.split(":")[1]; // 1. 优先尝试找销售台账 (新逻辑:合同号同步审批) SalesLedger ledger = salesLedgerMapper.selectOne(new LambdaQueryWrapper<SalesLedger>() .eq(SalesLedger::getSalesContractNo, identifier) .last("limit 1")); if (shippingInfo != null) { // 使用发货台账的 销售台账ID 去查附件 if (ledger != null) { allFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>() .eq(CommonFile::getCommonId, shippingInfo.getSalesLedgerId()) .eq(CommonFile::getCommonId, ledger.getId()) .eq(CommonFile::getType, FileNameType.SALE.getValue())); } else { // 2. 回退到旧逻辑:发货单号 ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>() .eq(ShippingInfo::getShippingNo, identifier) .last("limit 1")); if (shippingInfo != null) { allFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>() .eq(CommonFile::getCommonId, shippingInfo.getSalesLedgerId()) .eq(CommonFile::getType, FileNameType.SALE.getValue())); } } } } src/main/java/com/ruoyi/basic/pojo/ProductModel.java
@@ -39,7 +39,7 @@ /** * 单位 */ // @Excel(name = "单位") @Excel(name = "单位") private String unit; /** src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java
@@ -1,18 +1,22 @@ package com.ruoyi.sales.controller; // import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; // import com.ruoyi.approve.mapper.ApproveProcessMapper; // import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl; // import com.ruoyi.approve.vo.ApproveProcessVO; // import com.ruoyi.common.enums.FileNameType; import com.ruoyi.approve.service.IApproveProcessService; import com.ruoyi.approve.vo.ApproveProcessVO; import com.ruoyi.common.utils.OrderUtils; // import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.framework.aspectj.lang.annotation.Log; import com.ruoyi.framework.aspectj.lang.enums.BusinessType; // import com.ruoyi.framework.security.LoginUser; import com.ruoyi.framework.security.LoginUser; import com.ruoyi.framework.web.controller.BaseController; import com.ruoyi.framework.web.domain.AjaxResult; // import com.ruoyi.other.service.impl.TempFileServiceImpl; @@ -38,7 +42,7 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; // import java.time.LocalDate; import java.util.ArrayList; import java.time.LocalDate; import java.util.List; /** @@ -56,8 +60,8 @@ // @Autowired // private CommonFileServiceImpl commonFileService; // @Autowired // private ApproveProcessServiceImpl approveProcessService; @Autowired private IApproveProcessService approveProcessService; // @Autowired // private StockUtils stockUtils; @@ -67,6 +71,9 @@ @Autowired private ISalesLedgerService salesLedgerService; @Autowired private com.ruoyi.sales.mapper.SalesLedgerProductMapper salesLedgerProductMapper; @GetMapping("/listPage") @@ -81,65 +88,56 @@ @Transactional(rollbackFor = Exception.class) @Log(title = "发货信息管理", businessType = BusinessType.INSERT) public AjaxResult add(@RequestBody ShippingInfoDto req) throws Exception { // LoginUser loginUser = SecurityUtils.getLoginUser(); String sh = OrderUtils.countTodayByCreateTime(shippingInfoMapper, "SH"); LoginUser loginUser = SecurityUtils.getLoginUser(); if (req.getSalesLedgerId() == null) { return AjaxResult.error("关联订单 ID 不能为空"); } /* // 原有的单产品发货及审批逻辑 // 发货审批 SalesLedger salesLedger = salesLedgerService.getById(req.getSalesLedgerId()); if (salesLedger == null) { return AjaxResult.error("关联订单不存在"); } // 检查是否已经在审批中或已发货 if (salesLedger.getDeliveryStatus() != null && salesLedger.getDeliveryStatus() >= 2 && !salesLedger.getDeliveryStatus().equals(3)) { return AjaxResult.error("该订单已在审批中或已发货,无法重复发起"); } String shNo = OrderUtils.countTodayByCreateTime(shippingInfoMapper, "SH"); // 发起订审批 ApproveProcessVO approveProcessVO = new ApproveProcessVO(); approveProcessVO.setApproveType(7); approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId()); approveProcessVO.setApproveReason(req.getType() + ":" +sh); // 审批理由包含合同号,用于后续审批通过后找回订单 approveProcessVO.setApproveReason("发货审批:" + salesLedger.getSalesContractNo()); approveProcessVO.setApproveUserIds(req.getApproveUserIds()); approveProcessVO.setApproveUser(loginUser.getUserId()); approveProcessVO.setApproveTime(LocalDate.now().toString()); approveProcessService.addApprove(approveProcessVO); // 添加发货消息 req.setShippingNo(sh); req.setStatus("待审核"); boolean save = shippingInfoService.save(req); return save ? AjaxResult.success() : AjaxResult.error(); */ // 按整个订单发货,不需审批 if (req.getSalesLedgerId() == null) { return AjaxResult.error("请选择发货订单"); // 更新销售台账状态为 2 (审批中) salesLedger.setDeliveryStatus(2); salesLedgerService.updateById(salesLedger); // 为订单下的每一个产品生成发货台账记录 (显示单个产品) List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>() .eq(SalesLedgerProduct::getSalesLedgerId, salesLedger.getId())); if (CollectionUtils.isNotEmpty(products)) { for (SalesLedgerProduct product : products) { ShippingInfo si = new ShippingInfo(); si.setSalesLedgerId(salesLedger.getId()); si.setSalesLedgerProductId(product.getId()); si.setShippingNo(shNo); si.setStatus("待审核"); si.setType(req.getType()); // 来源/类型 shippingInfoService.save(si); } } // 获取该订单下的所有产品 SalesLedgerProduct query = new SalesLedgerProduct(); query.setSalesLedgerId(req.getSalesLedgerId()); List<SalesLedgerProduct> productList = salesLedgerProductService.selectSalesLedgerProductList(query); if (CollectionUtils.isEmpty(productList)) { return AjaxResult.error("该订单下无待发货产品"); } // 为每个产品创建发货信息 List<ShippingInfo> shippingInfoList = new ArrayList<>(); for (SalesLedgerProduct product : productList) { ShippingInfo shippingInfo = new ShippingInfo(); shippingInfo.setSalesLedgerId(req.getSalesLedgerId()); shippingInfo.setSalesLedgerProductId(product.getId()); shippingInfo.setShippingNo(sh); shippingInfo.setStatus("审核通过"); // 直接设为审核通过,跳过审批 shippingInfo.setType(req.getType()); shippingInfo.setExpressCompany(req.getExpressCompany()); shippingInfo.setExpressNumber(req.getExpressNumber()); shippingInfo.setShippingDate(req.getShippingDate()); shippingInfo.setShippingCarNumber(req.getShippingCarNumber()); shippingInfoList.add(shippingInfo); } boolean save = shippingInfoService.saveBatch(shippingInfoList); if (save) { // 更新销售台账的发货状态为已发货 SalesLedger salesLedger = new SalesLedger(); salesLedger.setId(req.getSalesLedgerId()); salesLedger.setDeliveryStatus(1); salesLedgerService.updateById(salesLedger); } return save ? AjaxResult.success() : AjaxResult.error(); return AjaxResult.success("发货审批已发起"); } @ApiOperation("发货扣库存") src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
@@ -143,9 +143,9 @@ private LocalDate deliveryDate; /** * 发货状态:0-未发货,1-已发货 * 发货状态:1-未发货,2-审批中,3-审批不通过,4-已发货 */ @ApiModelProperty("发货状态:0-未发货,1-已发货") @ApiModelProperty("发货状态:1-未发货,2-审批中,3-审批不通过,4-已发货") private Integer deliveryStatus; @TableField(exist = false) src/main/java/com/ruoyi/sales/service/ShippingInfoService.java
@@ -21,6 +21,8 @@ boolean deductStock(ShippingInfoDto req) throws IOException; boolean deductStockByOrder(Long salesLedgerId, ShippingInfoDto req) throws IOException; boolean delete(List<Long> ids); List<SalesLedgerProductDto> getReturnManagementDtoById( Long shippingId); src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -14,9 +14,19 @@ import com.ruoyi.sales.dto.ShippingInfoDto; import com.ruoyi.sales.mapper.SalesLedgerProductMapper; import com.ruoyi.sales.mapper.ShippingInfoMapper; import com.ruoyi.sales.pojo.SalesLedger; import com.ruoyi.sales.pojo.SalesLedgerProduct; import com.ruoyi.sales.pojo.ShippingInfo; import com.ruoyi.sales.service.ShippingInfoService; import com.ruoyi.sales.mapper.SalesLedgerMapper; import com.ruoyi.stock.mapper.StockInventoryMapper; import com.ruoyi.stock.pojo.StockInventory; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -49,6 +59,12 @@ @Autowired private ApproveProcessServiceImpl approveProcessService; @Autowired private SalesLedgerMapper salesLedgerMapper; @Autowired private StockInventoryMapper stockInventoryMapper; @Override public IPage<ShippingInfoDto> listPage(Page page, ShippingInfo req) { IPage<ShippingInfoDto> listPage = shippingInfoMapper.listPage(page, req); @@ -59,27 +75,101 @@ } @Override @Transactional(rollbackFor = Exception.class) public boolean deductStock(ShippingInfoDto req) throws IOException { ShippingInfo byId = this.getById(req.getId()); if (byId == null) { throw new RuntimeException("发货信息不存在"); } //扣减库存 if(!"已发货".equals(byId.getStatus())){ SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(byId.getSalesLedgerProductId()); stockUtils.substractStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId()); return deductStockByOrder(byId.getSalesLedgerId(), req); } @Override @Transactional(rollbackFor = Exception.class) public boolean deductStockByOrder(Long salesLedgerId, ShippingInfoDto req) throws IOException { if (salesLedgerId == null) { throw new RuntimeException("关联订单不可为空"); } byId.setExpressNumber(req.getExpressNumber()); byId.setExpressCompany(req.getExpressCompany()); byId.setStatus("已发货"); byId.setShippingCarNumber(req.getShippingCarNumber()); byId.setShippingDate(req.getShippingDate()); boolean update = this.updateById(byId); // 迁移文件 if(CollectionUtils.isNotEmpty(req.getTempFileIds())){ SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerId); if (salesLedger == null) { throw new RuntimeException("关联订单不存在"); } // 检查该订单下是否还有未发货的记录 List<ShippingInfo> unsentShippings = this.list(new LambdaQueryWrapper<ShippingInfo>() .eq(ShippingInfo::getSalesLedgerId, salesLedgerId) .ne(ShippingInfo::getStatus, "已发货")); // 仅在存在未发货记录时执行库存扣减 if (CollectionUtils.isNotEmpty(unsentShippings)) { // 获取该订单下所有的产品信息进行汇总扣减 List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>() .eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerId)); if (CollectionUtils.isEmpty(products)) { throw new RuntimeException("该订单下无产品信息,无法扣减库存"); } // 汇总需求并校验库存 Map<Long, BigDecimal> modelQtyMap = new HashMap<>(); for (SalesLedgerProduct p : products) { if (p.getProductModelId() == null) continue; modelQtyMap.put(p.getProductModelId(), modelQtyMap.getOrDefault(p.getProductModelId(), BigDecimal.ZERO).add(p.getQuantity())); } for (Map.Entry<Long, BigDecimal> entry : modelQtyMap.entrySet()) { Long modelId = entry.getKey(); BigDecimal totalNeeded = entry.getValue(); StockInventory stock = stockInventoryMapper.selectOne(new LambdaQueryWrapper<StockInventory>() .eq(StockInventory::getProductModelId, modelId)); if (stock == null) { throw new RuntimeException("产品规格ID:" + modelId + " 库存记录不存在"); } BigDecimal locked = stock.getLockedQuantity() == null ? BigDecimal.ZERO : stock.getLockedQuantity(); BigDecimal available = stock.getQualitity().subtract(locked); if (totalNeeded.compareTo(available) > 0) { throw new RuntimeException("产品规格ID:" + modelId + " 总计需求 " + totalNeeded + ",可用库存 " + available + ",库存充足校验未通过"); } } // 执行订单下所有产品的库存扣减 for (SalesLedgerProduct p : products) { if (p.getProductModelId() == null) continue; // 使用 businessId = salesLedgerId 或当前 req.getId() stockUtils.substractStock(p.getProductModelId(), p.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), salesLedgerId); } } // 更新该订单下所有的发货记录状态为已发货 LambdaUpdateWrapper<ShippingInfo> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.set(ShippingInfo::getStatus, "已发货") .eq(ShippingInfo::getSalesLedgerId, salesLedgerId); if (req != null) { if (req.getExpressNumber() != null) updateWrapper.set(ShippingInfo::getExpressNumber, req.getExpressNumber()); if (req.getExpressCompany() != null) updateWrapper.set(ShippingInfo::getExpressCompany, req.getExpressCompany()); if (req.getShippingCarNumber() != null) updateWrapper.set(ShippingInfo::getShippingCarNumber, req.getShippingCarNumber()); if (req.getShippingDate() != null) updateWrapper.set(ShippingInfo::getShippingDate, req.getShippingDate()); } this.update(updateWrapper); // 更新订单状态为 4-已发货 if (!Integer.valueOf(4).equals(salesLedger.getDeliveryStatus())) { salesLedger.setDeliveryStatus(4); salesLedgerMapper.updateById(salesLedger); } // 迁移当前记录涉及的文件 if (req != null && req.getId() != null && CollectionUtils.isNotEmpty(req.getTempFileIds())) { tempFileService.migrateTempFilesToFormal(req.getId(), req.getTempFileIds(), FileNameType.SHIP.getValue()); } return update ; return true; } @Override