fix: 改为补充发货信息才扣减库存、销售订单缺失审批通过状态、审批流程编号重复
已修改7个文件
174 ■■■■■ 文件已修改
doc/河南鹤壁天沐钢化玻璃厂.sql 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/utils/DailyRedisCounter.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ShippingInfoService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 103 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/ºÓÄϺױÚÌìãå¸Ö»¯²£Á§³§.sql
@@ -88,3 +88,6 @@
ALTER TABLE `sales_ledger`
    ADD COLUMN `delivery_status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '发货状态:0-未发货,1-已发货' AFTER `delivery_date`;
ALTER TABLE `product-inventory-management-hbtmblc`.`sales_ledger`
    MODIFY COLUMN `delivery_status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '发货状态:1-未发货,2-审批中,3-审批不通过,4-审批通过,5-已发货' AFTER `delivery_date`;
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -254,12 +254,9 @@
                
                if (salesLedger != null) {
                    if(status.equals(2)){
                        // å®¡æ‰¹å®Œæˆ -> è‡ªåŠ¨æ‰£é™¤åº“å­˜
                        try {
                            shippingInfoService.deductStockByOrder(salesLedger.getId(), null);
                        } catch (IOException e) {
                            throw new RuntimeException("自动扣除库存失败: " + e.getMessage());
                        }
                        // å®¡æ‰¹å®Œæˆ -> ä¿®æ”¹çŠ¶æ€ä¸ºå®¡æ ¸é€šè¿‡ï¼Œä¸æ‰£é™¤åº“å­˜ (扣除库存移至发货台账补充信息阶段)
                        updateSalesLedgerDeliveryStatus(salesLedger.getId(), 4);
                        updateShippingInfoStatusByOrder(salesLedger.getId(), "审核通过");
                    } else if(status.equals(3)){
                        updateSalesLedgerDeliveryStatus(salesLedger.getId(), 3);
                        // æ›´æ–°å…³è”的发货记录为审核拒绝
@@ -271,6 +268,7 @@
                }
            }
        }
        // ç»‘定附件
        if(!CollectionUtils.isEmpty(approveNode.getTempFileIds()) && approveNode.getApproveNodeStatus() == 1){
            tempFileService.migrateTempFilesToFormal(approveNode.getId(), approveNode.getTempFileIds(), FileNameType.ApproveNode.getValue());
src/main/java/com/ruoyi/approve/utils/DailyRedisCounter.java
@@ -10,7 +10,9 @@
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
@@ -75,29 +77,41 @@
     * @return ä»Šæ—¥è‡ªå¢žåŽçš„计数值
     */
    public long incrementAndGetByDb() {
        String approveId = redisTemplate.opsForValue().get(approvalNumberPrefix + ":approveNum");
        if(approveId == null){
        String today = LocalDate.now().format(DATE_FORMAT);
        String key = approvalNumberPrefix + ":approveNum:" + today;
        String lockKey = "lock:" + key;
        if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
            Boolean acquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(acquired)) {
                try {
                    if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {
            StartAndEndDateDto dateTime = getDateTime();
            LambdaQueryWrapper<ApproveProcess> approveProcessLambdaQueryWrapper = new LambdaQueryWrapper<>();
            approveProcessLambdaQueryWrapper
                    .eq(ApproveProcess::getApproveDelete,0)
                    .gt(ApproveProcess::getCreateTime,dateTime.getStartDate())
                    .lt(ApproveProcess::getCreateTime,dateTime.getEndDate());
            Long aLong = approveProcessMapper.selectCount(approveProcessLambdaQueryWrapper);
            if(aLong == null){
                redisTemplate.opsForValue().set(approvalNumberPrefix + ":approveNum","1",1L, TimeUnit.HOURS);
                return 1;
            }else{
                aLong += 1;
                redisTemplate.opsForValue().set(approvalNumberPrefix + ":approveNum",aLong.toString(),1L, TimeUnit.HOURS);
                return aLong;
                        Long count = approveProcessMapper.selectCount(approveProcessLambdaQueryWrapper);
                        long initialCount = (count == null) ? 0 : count;
                        redisTemplate.opsForValue().set(key, String.valueOf(initialCount), 24, TimeUnit.HOURS);
                    }
                } finally {
                    // é‡Šæ”¾é”
                    redisTemplate.delete(lockKey);
            }
        }else{
            Long num = Long.parseLong(approveId) + 1;
            redisTemplate.opsForValue().set(approvalNumberPrefix + ":approveNum",num.toString(),1L, TimeUnit.HOURS);
            return Long.parseLong(approveId);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return incrementAndGetByDb(); // é€’归重试
            }
        }
        return redisTemplate.opsForValue().increment(key);
    }
    /**
@@ -115,12 +129,9 @@
     * è®¡ç®—距离次日凌晨的秒数
     */
    private long calculateSecondsUntilMidnight() {
        LocalDate tomorrow = LocalDate.now().plusDays(1);
        LocalDate midnight = tomorrow.atStartOfDay().toLocalDate();
        return java.time.Duration.between(
                LocalDate.now().atTime(23, 59, 59),
                midnight.atTime(0, 0, 0)
        ).getSeconds() + 1;
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime midnight = now.toLocalDate().plusDays(1).atStartOfDay();
        return Duration.between(now, midnight).getSeconds();
    }
    /**
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
@@ -143,9 +143,9 @@
    private LocalDate deliveryDate;
    /**
     * å‘货状态:1-未发货,2-审批中,3-审批不通过,4-已发货
     * å‘货状态:1-未发货,2-审批中,3-审批不通过,4-审批通过,5-已发货
     */
    @ApiModelProperty("发货状态:1-未发货,2-审批中,3-审批不通过,4-已发货")
    @ApiModelProperty("发货状态:1-未发货,2-审批中,3-审批不通过,4-审批通过,5-已发货")
    private Integer deliveryStatus;
    @TableField(exist = false)
src/main/java/com/ruoyi/sales/service/ShippingInfoService.java
@@ -21,7 +21,6 @@
    boolean deductStock(ShippingInfoDto req) throws IOException;
    boolean deductStockByOrder(Long salesLedgerId, ShippingInfoDto req) throws IOException;
    boolean delete(List<Long> ids);
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -461,7 +461,7 @@
                        .map(SalesLedgerProductImportDto::getTaxInclusiveTotalPrice)
                        .reduce(BigDecimal.ZERO, BigDecimal::add));
                //  å‘货状态
                salesLedger.setDeliveryStatus(4);
                salesLedger.setDeliveryStatus(1);
                salesLedgerMapper.insert(salesLedger);
                for (SalesLedgerProductImportDto salesLedgerProductImportDto : salesLedgerProductImportDtos) {
@@ -734,7 +734,7 @@
                salesLedger.setDeliveryStatus(1);
                salesLedgerMapper.insert(salesLedger);
            } else {
                if (salesLedger.getDeliveryStatus() == 4) {
                if (salesLedger.getDeliveryStatus() == 5) {
                    throw new ServiceException("订单已发货,禁止编辑");
                }
                salesLedgerMapper.updateById(salesLedger);
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -81,97 +81,40 @@
        if (byId == null) {
            throw new RuntimeException("发货信息不存在");
        }
        return deductStockByOrder(byId.getSalesLedgerId(), req);
        //扣减库存
        if(!"已发货".equals(byId.getStatus())){
            SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(byId.getSalesLedgerProductId());
            if (salesLedgerProduct != null) {
                stockUtils.substractStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId());
    }
    @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);
        SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerId);
        if (salesLedger == null) {
            throw new RuntimeException("关联订单不存在");
        }
        // æ£€æŸ¥è¯¥è®¢å•下是否还有未发货的记录
        List<ShippingInfo> unsentShippings = this.list(new LambdaQueryWrapper<ShippingInfo>()
                .eq(ShippingInfo::getSalesLedgerId, salesLedgerId)
        // æ›´æ–°è®¢å•状态为 5-已发货 (如果所有发货记录都已发货)
        SalesLedger salesLedger = salesLedgerMapper.selectById(byId.getSalesLedgerId());
        if (salesLedger != null && !Integer.valueOf(5).equals(salesLedger.getDeliveryStatus())) {
            List<ShippingInfo> unsent = this.list(new LambdaQueryWrapper<ShippingInfo>()
                    .eq(ShippingInfo::getSalesLedgerId, byId.getSalesLedgerId())
                .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);
            if (CollectionUtils.isEmpty(unsent)) {
                salesLedger.setDeliveryStatus(5);
            salesLedgerMapper.updateById(salesLedger);
        }
        }
        //  è¿ç§»å½“前记录涉及的文件
        if (req != null && req.getId() != null && CollectionUtils.isNotEmpty(req.getTempFileIds())) {
        // è¿ç§»æ–‡ä»¶
        if(CollectionUtils.isNotEmpty(req.getTempFileIds())){
            tempFileService.migrateTempFilesToFormal(req.getId(), req.getTempFileIds(), FileNameType.SHIP.getValue());
        }
        return true;
        return update ;
    }
    @Override
    public boolean delete(List<Long> ids) {
        List<ShippingInfo> shippingInfos = shippingInfoMapper.selectList(new LambdaQueryWrapper<ShippingInfo>()