gongchunyi
2 天以前 9571956561915d24ebfc0915117e7df24ea3058d
fix: 发货审批通过库存不扣减
已修改7个文件
323 ■■■■ 文件已修改
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/ProductModel.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java 102 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ShippingInfoService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 116 ●●●● 补丁 | 查看 | 原始文档 | 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