16 小时以前 d48849cfcc2e45340921edab4685a8f260793b50
Merge remote-tracking branch 'origin/dev_长治_祥雨远见' into dev_长治_祥雨远见
已添加6个文件
已修改10个文件
504 ■■■■ 文件已修改
doc/长治-祥雨远见.sql 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/utils/DailyRedisCounter.java 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductFrozenController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ShipmentApprovalController.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductFrozenMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProductFrozen.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ISalesLedgerProductFrozenService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ISalesLedgerProductService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductFrozenServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductFrozenMapper.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/³¤ÖÎ-ÏéÓêÔ¶¼û.sql
@@ -3,3 +3,14 @@
    ADD COLUMN `worker_name`        varchar(100) NULL COMMENT '指派工人姓名',
    ADD COLUMN `handover_user_id`   bigint       NULL COMMENT '交接人员ID',
    ADD COLUMN `handover_user_name` varchar(100) NULL COMMENT '交接人员名称';
CREATE TABLE `sales_ledger_product_frozen`
(
    `id`                      bigint(20)     NOT NULL AUTO_INCREMENT COMMENT '主键id',
    `sales_ledger_product_id` bigint(20)     NOT NULL COMMENT '销售订单产品id',
    `product_model_id`        bigint(20)     NOT NULL COMMENT '产品规格id',
    `frozen_quantity`         decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '冻结数量',
    `create_time`             datetime                DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (`id`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4 COMMENT ='销售产品冻结库存表';
src/main/java/com/ruoyi/approve/utils/DailyRedisCounter.java
@@ -5,21 +5,21 @@
import com.ruoyi.approve.pojo.ApproveProcess;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.TimeUnit;
//基于redis的一个每日计数器
@Component
public class DailyRedisCounter {
public class DailyRedisCounter implements ApplicationRunner {
    private static final String KEY_PREFIX = "daily_counter:";
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
    private final StringRedisTemplate redisTemplate;
@@ -30,6 +30,44 @@
    @Value("${ruoyi.approvalNumberPrefix}")
    private String approvalNumberPrefix;
    @Autowired
    private ApproveProcessMapper approveProcessMapper;
    @Override
    public void run(ApplicationArguments args) {
        syncFromDb();
    }
    /**
     * å¼ºåˆ¶ä»Žæ•°æ®åº“同步当前计数到 Redis
     */
    public void syncFromDb() {
        String key = approvalNumberPrefix + ":approveNum";
        String lockKey = key + ":sync_lock";
        Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(lock)) {
            try {
                long dbCount = getCountFromDb();
                redisTemplate.opsForValue().set(key, String.valueOf(dbCount), calculateSecondsUntilMidnight(), TimeUnit.SECONDS);
            } finally {
                redisTemplate.delete(lockKey);
            }
        }
    }
    /**
     * ä»Žæ•°æ®åº“获取今日审批单总量
     */
    private long getCountFromDb() {
        StartAndEndDateDto dateTime = getDateTime();
        LambdaQueryWrapper<ApproveProcess> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ApproveProcess::getApproveDelete, 0)
                .ge(ApproveProcess::getCreateTime, dateTime.getStartDate())
                .lt(ApproveProcess::getCreateTime, dateTime.getEndDate());
        Long count = approveProcessMapper.selectCount(wrapper);
        return count == null ? 0 : count;
    }
    /**查缓存
     * èŽ·å–æŒ‡å®šè®¡æ•°å™¨åœ¨ä»Šæ—¥çš„æ•°å€¼ï¼Œå¹¶è‡ªå¢ž1
@@ -49,21 +87,14 @@
        return count;
    }
    @Autowired
    private ApproveProcessMapper approveProcessMapper;
    /**
     * èŽ·å–å½“å‰æ—¶é—´çš„  å¼€å§‹æ—¥æœŸ  ï¼Œç»“束日期
     * @return
     */
    public static StartAndEndDateDto getDateTime(){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        Date date = new Date();
        Calendar cal = Calendar.getInstance();
        cal.setTime(date);
        cal.add(Calendar.DATE,1);
        String startDateTime = simpleDateFormat.format(date);
        String endDateTime = simpleDateFormat.format(cal.getTime());
        LocalDate now = LocalDate.now();
        String startDateTime = now.toString();
        String endDateTime = now.plusDays(1).toString();
        StartAndEndDateDto startAndEndDateDto = new StartAndEndDateDto();
        startAndEndDateDto.setStartDate(startDateTime);
        startAndEndDateDto.setEndDate(endDateTime);
@@ -75,29 +106,36 @@
     * @return ä»Šæ—¥è‡ªå¢žåŽçš„计数值
     */
    public long incrementAndGetByDb() {
        String approveId = redisTemplate.opsForValue().get(approvalNumberPrefix + ":approveNum");
        if(approveId == null){
            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;
            }
        }else{
            Long num = Long.parseLong(approveId) + 1;
            redisTemplate.opsForValue().set(approvalNumberPrefix + ":approveNum",num.toString(),1L, TimeUnit.HOURS);
            return Long.parseLong(approveId);
        }
        String key = approvalNumberPrefix + ":approveNum";
        //  èŽ·å–çŽ°æœ‰å€¼
        String approveId = redisTemplate.opsForValue().get(key);
        if (approveId == null) {
            //  ç¼“存不存在进行初始化
            String lockKey = key + ":lock";
            Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
            if (Boolean.TRUE.equals(lock)) {
                try {
                    approveId = redisTemplate.opsForValue().get(key);
                    if (approveId == null) {
                        long count = getCountFromDb();
                        long nextVal = count + 1;
                        redisTemplate.opsForValue().set(key, String.valueOf(nextVal), calculateSecondsUntilMidnight(), TimeUnit.SECONDS);
                        return nextVal;
                    }
                } finally {
                    redisTemplate.delete(lockKey);
                }
            } else {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                return incrementAndGetByDb();
            }
        }
        return redisTemplate.opsForValue().increment(key);
    }
    /**
@@ -115,12 +153,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 java.time.Duration.between(now, midnight).getSeconds();
    }
    /**
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
@@ -21,7 +21,9 @@
import java.math.BigDecimal;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
@@ -35,6 +37,7 @@
    /**
     * ä¸åˆæ ¼å…¥åº“
     *
     * @param productModelId
     * @param quantity
     * @param recordType
@@ -51,6 +54,7 @@
    /**
     * ä¸åˆæ ¼å‡ºåº“
     *
     * @param productModelId
     * @param quantity
     * @param recordType
@@ -67,6 +71,7 @@
    /**
     * åˆæ ¼å…¥åº“
     *
     * @param productModelId
     * @param quantity
     * @param recordType
@@ -83,6 +88,7 @@
    /**
     * åˆæ ¼å‡ºåº“
     *
     * @param productModelId
     * @param quantity
     * @param recordType
@@ -99,19 +105,23 @@
    //不合格库存删除
    public void deleteStockInRecord(Long recordId, String recordType) {
        StockInRecord one = stockInRecordService.getOne(new QueryWrapper<StockInRecord>()
        List<StockInRecord> stockInRecords = stockInRecordService.list(new QueryWrapper<StockInRecord>()
                .lambda().eq(StockInRecord::getRecordId, recordId)
                .eq(StockInRecord::getRecordType, recordType));
        if (ObjectUtils.isNotEmpty(one)) {
            stockInRecordService.batchDelete(Collections.singletonList(one.getId()));
        if (ObjectUtils.isNotEmpty(stockInRecords)) {
            List<Long> collect = stockInRecords.stream().map(StockInRecord::getId).collect(Collectors.toList());
            stockInRecordService.batchDelete(collect);
        }
    }
    public void deleteStockOutRecord(Long recordId, String recordType) {
        StockOutRecord one = stockOutRecordService.getOne(new QueryWrapper<StockOutRecord>()
        List<StockOutRecord> stockOutRecords = stockOutRecordService.list(new QueryWrapper<StockOutRecord>()
                .lambda().eq(StockOutRecord::getRecordId, recordId)
                .eq(StockOutRecord::getRecordType, recordType));
        if (ObjectUtils.isNotEmpty(one)) {
            stockOutRecordService.batchDelete(Collections.singletonList(one.getId()));
        if (ObjectUtils.isNotEmpty(stockOutRecords)) {
            List<Long> collect = stockOutRecords.stream().map(StockOutRecord::getId).collect(Collectors.toList());
            stockOutRecordService.batchDelete(collect);
        }
    }
}
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductFrozenController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.sales.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * é”€å”®äº§å“å†»ç»“库存表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author deslrey
 * @since 2026-04-09
 */
@RestController
@RequestMapping("/sales-ledger-product-frozen")
public class SalesLedgerProductFrozenController {
}
src/main/java/com/ruoyi/sales/controller/ShipmentApprovalController.java
@@ -86,8 +86,11 @@
//            }
            //出库
            stockUtils.addStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId());
            //  å®¡æ‰¹åŒæ„å¹¶å‘出物料,释放 locked_quantity
            salesLedgerProductService.revertFrozenStock(salesLedgerProduct.getId());
            //  æ‰£å‡åº“存总数
            stockUtils.substractStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId());
        }
        return AjaxResult.success();
src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductFrozenMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.sales.mapper;
import com.ruoyi.sales.pojo.SalesLedgerProductFrozen;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
 * <p>
 * é”€å”®äº§å“å†»ç»“库存表 Mapper æŽ¥å£
 * </p>
 *
 * @author deslrey
 * @since 2026-04-09
 */
public interface SalesLedgerProductFrozenMapper extends BaseMapper<SalesLedgerProductFrozen> {
}
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProductFrozen.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package com.ruoyi.sales.pojo;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
 * <p>
 * é”€å”®äº§å“å†»ç»“库存表
 * </p>
 *
 * @author deslrey
 * @since 2026-04-09
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sales_ledger_product_frozen")
@ApiModel(value="SalesLedgerProductFrozen对象", description="销售产品冻结库存表")
public class SalesLedgerProductFrozen implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "主键id")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "销售订单产品id")
    private Long salesLedgerProductId;
    @ApiModelProperty(value = "产品规格id")
    private Long productModelId;
    @ApiModelProperty(value = "冻结数量")
    private BigDecimal frozenQuantity;
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
}
src/main/java/com/ruoyi/sales/service/ISalesLedgerProductFrozenService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.sales.service;
import com.ruoyi.sales.pojo.SalesLedgerProductFrozen;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * é”€å”®äº§å“å†»ç»“库存表 æœåŠ¡ç±»
 * </p>
 *
 * @author deslrey
 * @since 2026-04-09
 */
public interface ISalesLedgerProductFrozenService extends IService<SalesLedgerProductFrozen> {
}
src/main/java/com/ruoyi/sales/service/ISalesLedgerProductService.java
@@ -23,6 +23,8 @@
    int deleteSalesLedgerProductByIds(Long[] ids);
    void revertFrozenStock(Long salesLedgerProductId);
    int addOrUpdateSalesLedgerProduct(SalesLedgerProduct salesLedgerProduct);
    IPage<SalesLedgerProductDto> listPage(Page page, SalesLedgerProductDto salesLedgerProduct);
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductFrozenServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.sales.service.impl;
import com.ruoyi.sales.pojo.SalesLedgerProductFrozen;
import com.ruoyi.sales.mapper.SalesLedgerProductFrozenMapper;
import com.ruoyi.sales.service.ISalesLedgerProductFrozenService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * é”€å”®äº§å“å†»ç»“库存表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author deslrey
 * @since 2026-04-09
 */
@Service
public class SalesLedgerProductFrozenServiceImpl extends ServiceImpl<SalesLedgerProductFrozenMapper, SalesLedgerProductFrozen> implements ISalesLedgerProductFrozenService {
}
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -26,8 +26,10 @@
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.SalesLedgerProductFrozen;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.ISalesLedgerProductService;
import com.ruoyi.sales.service.ISalesLedgerProductFrozenService;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.pojo.StockInventory;
import lombok.AllArgsConstructor;
@@ -92,6 +94,8 @@
    private StockInventoryMapper stockInventoryMapper;
    @Autowired
    private ProductOrderServiceImpl productOrderServiceImpl;
    @Autowired
    private ISalesLedgerProductFrozenService salesLedgerProductFrozenService;
    @Override
    public SalesLedgerProduct selectSalesLedgerProductById(Long id) {
@@ -158,6 +162,8 @@
            return 0;
        }
        checkProductionDataExist(Arrays.asList(ids));
        // 1. å…ˆæŸ¥è¯¢è¦åˆ é™¤çš„子表记录,获取对应的 salesLedgerId
        List<SalesLedgerProduct> deletedProducts = salesLedgerProductMapper.selectBatchIds(Arrays.asList(ids));
        if (deletedProducts.isEmpty()) {
@@ -180,6 +186,10 @@
        int result = salesLedgerProductMapper.deleteBatchIds(Arrays.asList(ids));
        //删除对应的生产订单
        deleteProductionData(Arrays.asList(ids));
        for (Long id : ids) {
            revertFrozenStock(id);
        }
        // 3. å¯¹æ¯ä¸ªä¸»è¡¨ID进行金额更新
        for (Long salesLedgerId : mainIds) {
@@ -220,8 +230,12 @@
        if (salesLedgerProduct.getId() == null) {
            salesLedgerProduct.setRegisterDate(LocalDateTime.now());
            result = salesLedgerProductMapper.insert(salesLedgerProduct);
            addProductionData(salesLedgerProduct);
            processStockAndProduction(salesLedgerProduct);
        } else {
            checkProductionDataExist(Collections.singletonList(salesLedgerProduct.getId()));
            revertFrozenStock(salesLedgerProduct.getId());
            //查询原本的产品型号id
            salesLedgerProduct.setFutureTickets(salesLedgerProduct.getQuantity());
            result = salesLedgerProductMapper.updateById(salesLedgerProduct);
@@ -229,7 +243,7 @@
            deleteProductionData(Arrays.asList(salesLedgerProduct.getId()));
            // åˆ é™¤ç”Ÿäº§æ ¸ç®—数据
            addProductionData(salesLedgerProduct);
            processStockAndProduction(salesLedgerProduct);
        }
        // å¦‚果插入或更新成功,并且有 salesLedgerId,才继续更新主表金额
@@ -262,17 +276,87 @@
        return result;
    }
    public void processStockAndProduction(SalesLedgerProduct salesLedgerProduct) {
        BigDecimal salesQty = salesLedgerProduct.getQuantity();
        if (salesQty == null) salesQty = BigDecimal.ZERO;
        BigDecimal frozenQty = BigDecimal.ZERO;
        BigDecimal productionQty = salesQty;
        //  æŸ¥è¯¢å¯ç”¨åº“å­˜
        StockInventory stockInventory = stockInventoryMapper.selectOne(new LambdaQueryWrapper<StockInventory>()
                .eq(StockInventory::getProductModelId, salesLedgerProduct.getProductModelId())
                .last("for update"));
        if (stockInventory != null) {
            BigDecimal quality = stockInventory.getQualitity() != null ? stockInventory.getQualitity() : BigDecimal.ZERO;
            BigDecimal lockedQty = stockInventory.getLockedQuantity() != null ? stockInventory.getLockedQuantity() : BigDecimal.ZERO;
            BigDecimal availableStock = quality.subtract(lockedQty);
            if (availableStock.compareTo(BigDecimal.ZERO) > 0) {
                // æœ‰å¯ç”¨åº“å­˜
                if (availableStock.compareTo(salesQty) >= 0) {
                    frozenQty = salesQty;
                    productionQty = BigDecimal.ZERO;
                } else {
                    frozenQty = availableStock;
                    productionQty = salesQty.subtract(frozenQty);
                }
            }
        }
        // å†»ç»“库存
        if (frozenQty.compareTo(BigDecimal.ZERO) > 0) {
            SalesLedgerProductFrozen frozenRecord = new SalesLedgerProductFrozen();
            frozenRecord.setSalesLedgerProductId(salesLedgerProduct.getId());
            frozenRecord.setProductModelId(salesLedgerProduct.getProductModelId());
            frozenRecord.setFrozenQuantity(frozenQty);
            frozenRecord.setCreateTime(LocalDateTime.now());
            salesLedgerProductFrozenService.save(frozenRecord);
            if (stockInventory != null) {
                BigDecimal currentLocked = stockInventory.getLockedQuantity() != null ? stockInventory.getLockedQuantity() : BigDecimal.ZERO;
                stockInventory.setLockedQuantity(currentLocked.add(frozenQty));
                stockInventoryMapper.updateById(stockInventory);
            }
        }
        // å¦‚果需要生产,则生成生产数据
        if (productionQty.compareTo(BigDecimal.ZERO) > 0) {
            addProductionData(salesLedgerProduct, productionQty);
        }
    }
    public void revertFrozenStock(Long salesLedgerProductId) {
        SalesLedgerProductFrozen frozenRecord = salesLedgerProductFrozenService.getOne(new LambdaQueryWrapper<SalesLedgerProductFrozen>()
                        .eq(SalesLedgerProductFrozen::getSalesLedgerProductId, salesLedgerProductId)
                        .last("limit 1"));
        if (frozenRecord != null) {
            BigDecimal frozenQty = frozenRecord.getFrozenQuantity();
            if (frozenQty != null && frozenQty.compareTo(BigDecimal.ZERO) > 0) {
                StockInventory stockInventory = stockInventoryMapper.selectOne(new LambdaQueryWrapper<StockInventory>()
                        .eq(StockInventory::getProductModelId, frozenRecord.getProductModelId())
                        .last("for update"));
                if (stockInventory != null) {
                    BigDecimal currentLocked = stockInventory.getLockedQuantity() != null ? stockInventory.getLockedQuantity() : BigDecimal.ZERO;
                    stockInventory.setLockedQuantity(currentLocked.subtract(frozenQty));
                    stockInventoryMapper.updateById(stockInventory);
                }
            }
            salesLedgerProductFrozenService.removeById(frozenRecord.getId());
        }
    }
    /**
     * æ–°å¢žç”Ÿäº§æ•°æ®
     */
    public void addProductionData(SalesLedgerProduct salesLedgerProduct) {
    public void addProductionData(SalesLedgerProduct salesLedgerProduct, BigDecimal productionQty) {
        ProductOrder productOrder = new ProductOrder();
        productOrder.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId());
        productOrder.setProductModelId(salesLedgerProduct.getProductModelId());
        productOrder.setSaleLedgerProductId(salesLedgerProduct.getId());
        String string = productOrderServiceImpl.generateNextOrderNo(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")));
        productOrder.setNpsNo(string);
        productOrder.setQuantity(salesLedgerProduct.getQuantity());//需求数量
        productOrder.setQuantity(productionQty);//需求数量
        productOrder.setCompleteQuantity(BigDecimal.ZERO);//完成数量
        productOrderMapper.insert(productOrder);
@@ -320,7 +404,7 @@
                    ProductWorkOrder productWorkOrder = new ProductWorkOrder();
                    productWorkOrder.setProductProcessRouteItemId(productProcessRouteItem.getId());
                    productWorkOrder.setProductOrderId(productOrder.getId());
                    productWorkOrder.setPlanQuantity(salesLedgerProduct.getQuantity());
                    productWorkOrder.setPlanQuantity(productionQty);
                    productWorkOrder.setWorkOrderNo(workOrderNoStr);
                    productWorkOrder.setStatus(1);
@@ -334,6 +418,50 @@
    }
    /**
     * æ£€æŸ¥æ˜¯å¦æœ‰æŠ¥å·¥æ•°æ®
     */
    public void checkProductionDataExist(List<Long> productIds) {
        if (org.springframework.util.CollectionUtils.isEmpty(productIds)) {
            return;
        }
        //  æŸ¥è¯¢productOrder
        List<ProductOrder> productOrders = productOrderMapper.selectList(new LambdaQueryWrapper<ProductOrder>()
                .in(ProductOrder::getSaleLedgerProductId, productIds));
        if (!CollectionUtils.isEmpty(productOrders)) {
            List<Long> orderIds = productOrders.stream()
                    .map(ProductOrder::getId)
                    .collect(Collectors.toList());
            //  æŸ¥è¯¢processRouteItems
            List<ProductProcessRouteItem> allRouteItems = productProcessRouteItemMapper.selectList(new LambdaQueryWrapper<ProductProcessRouteItem>()
                    .in(ProductProcessRouteItem::getProductOrderId, orderIds));
            if (!CollectionUtils.isEmpty(allRouteItems)) {
                //  èŽ·å–å·¥åºé¡¹ID
                List<Long> routeItemIds = allRouteItems.stream()
                        .map(ProductProcessRouteItem::getId)
                        .collect(Collectors.toList());
                //  æŸ¥è¯¢å…³è”的工单ID
                List<ProductWorkOrder> workOrders = productWorkOrderMapper.selectList(new LambdaQueryWrapper<ProductWorkOrder>()
                        .in(ProductWorkOrder::getProductProcessRouteItemId, routeItemIds));
                if (!CollectionUtils.isEmpty(workOrders)) {
                    List<Long> workOrderIds = workOrders.stream()
                            .map(ProductWorkOrder::getId)
                            .collect(Collectors.toList());
                    //  æŸ¥è¯¢å…³è”的生产主表ID是否有数据
                    Long count = productionProductMainMapper.selectCount(new LambdaQueryWrapper<ProductionProductMain>()
                            .in(ProductionProductMain::getWorkOrderId, workOrderIds));
                    if (count != null && count > 0L) {
                        throw new RuntimeException("当前销售订单已有报工数据,不能删除和修改");
                    }
                }
            }
        }
    }
    /**
     * åˆ é™¤ç”Ÿäº§æ•°æ®
     */
    public void deleteProductionData(List<Long> productIds) {
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -437,8 +437,8 @@
                    salesLedgerProduct.setApproveStatus(0);
                    salesLedgerProduct.setPendingInvoiceTotal(salesLedgerProductImportDto.getTaxInclusiveTotalPrice());
                    salesLedgerProductMapper.insert(salesLedgerProduct);
                    // æ·»åŠ ç”Ÿäº§æ•°æ®
                    salesLedgerProductServiceImpl.addProductionData(salesLedgerProduct);
                    // æ·»åŠ ç”Ÿäº§æ•°æ®ï¼Œå¹¶å¤„ç†å†»ç»“åº“å­˜
                    salesLedgerProductServiceImpl.processStockAndProduction(salesLedgerProduct);
                }
            }
@@ -539,6 +539,11 @@
                .collect(Collectors.toList());
        //删除生产数据
        salesLedgerProductServiceImpl.deleteProductionData(productIds);
        // è¿˜åŽŸå†»ç»“çš„ç‰©ç†åº“å­˜
        for (Long productId : productIds) {
            salesLedgerProductServiceImpl.revertFrozenStock(productId);
        }
        // æ‰¹é‡åˆ é™¤äº§å“å­è¡¨
        if (!productIds.isEmpty()) {
@@ -737,8 +742,8 @@
                salesLedgerProduct.setNoInvoiceAmount(salesLedgerProduct.getTaxInclusiveTotalPrice());
                salesLedgerProduct.setPendingInvoiceTotal(salesLedgerProduct.getTaxInclusiveTotalPrice());
                salesLedgerProductMapper.insert(salesLedgerProduct);
                // æ·»åŠ ç”Ÿäº§æ•°æ®
                salesLedgerProductServiceImpl.addProductionData(salesLedgerProduct);
                // æ·»åŠ ç”Ÿäº§æ•°æ®ï¼Œå¹¶å¤„ç†å†»ç»“åº“å­˜
                salesLedgerProductServiceImpl.processStockAndProduction(salesLedgerProduct);
            }
        }
    }
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -15,14 +15,19 @@
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.SalesLedgerProductFrozen;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.ISalesLedgerProductFrozenService;
import com.ruoyi.sales.service.ShippingInfoService;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.pojo.StockInventory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;
@@ -45,6 +50,10 @@
    private StockUtils stockUtils;
    @Autowired
    private CommonFileServiceImpl commonFileService;
    @Autowired
    private ISalesLedgerProductFrozenService salesLedgerProductFrozenService;
    @Autowired
    private StockInventoryMapper stockInventoryMapper;
    @Autowired
    private ApproveProcessServiceImpl approveProcessService;
@@ -67,6 +76,26 @@
        //扣减库存
        if(!"已发货".equals(byId.getStatus())){
            SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(byId.getSalesLedgerProductId());
            //  åŒæ­¥æ‰£å‡æˆ–释放冻结的库存
            SalesLedgerProductFrozen frozenRecord = salesLedgerProductFrozenService.getOne(new LambdaQueryWrapper<SalesLedgerProductFrozen>()
                    .eq(SalesLedgerProductFrozen::getSalesLedgerProductId, byId.getSalesLedgerProductId())
                    .last("limit 1"));
            if (frozenRecord != null) {
                java.math.BigDecimal frozenQty = frozenRecord.getFrozenQuantity();
                if (frozenQty != null && frozenQty.compareTo(BigDecimal.ZERO) > 0) {
                    StockInventory stockInventory = stockInventoryMapper.selectOne(new LambdaQueryWrapper<StockInventory>()
                            .eq(StockInventory::getProductModelId, frozenRecord.getProductModelId())
                            .last("for update"));
                    if (stockInventory != null) {
                        BigDecimal currentLocked = stockInventory.getLockedQuantity() != null ? stockInventory.getLockedQuantity() : BigDecimal.ZERO;
                        stockInventory.setLockedQuantity(currentLocked.subtract(frozenQty));
                        stockInventoryMapper.updateById(stockInventory);
                    }
                }
                salesLedgerProductFrozenService.removeById(frozenRecord.getId());
            }
            //  æ‰£å‡åº“å­˜
            stockUtils.substractStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId());
        }
        byId.setExpressNumber(req.getExpressNumber());
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -1,16 +1,18 @@
package com.ruoyi.stock.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.SalesLedgerProductFrozen;
import com.ruoyi.sales.service.ISalesLedgerProductFrozenService;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockOutRecordDto;
@@ -46,6 +48,7 @@
    private StockInRecordService stockInRecordService;
    private StockOutRecordService stockOutRecordService;
    private SalesLedgerProductMapper salesLedgerProductMapper;
    private ISalesLedgerProductFrozenService salesLedgerProductFrozenService;
    @Override
    public IPage<StockInventoryDto> pagestockInventory(Page page, StockInventoryDto stockInventoryDto) {
        return stockInventoryMapper.pagestockInventory(page, stockInventoryDto);
@@ -204,7 +207,25 @@
        if (stockInventory.getLockedQuantity().compareTo(stockInventoryDto.getLockedQuantity())<0) {
            throw new RuntimeException("解冻数量不能超过冻结数量");
        }
        stockInventory.setLockedQuantity(stockInventory.getLockedQuantity().subtract(stockInventoryDto.getLockedQuantity()));
        BigDecimal newLockedQty = stockInventory.getLockedQuantity().subtract(stockInventoryDto.getLockedQuantity());
        //  ä¸èƒ½å°äºŽç³»ç»Ÿå·²ç»‘定的冻结数量
        List<SalesLedgerProductFrozen> frozenList = salesLedgerProductFrozenService.list(new LambdaQueryWrapper<SalesLedgerProductFrozen>()
                .eq(SalesLedgerProductFrozen::getProductModelId, stockInventory.getProductModelId()));
        BigDecimal systemFrozenTotal = BigDecimal.ZERO;
        if (frozenList != null) {
            for (SalesLedgerProductFrozen frozen : frozenList) {
                if (frozen.getFrozenQuantity() != null) {
                    systemFrozenTotal = systemFrozenTotal.add(frozen.getFrozenQuantity());
                }
            }
        }
        if (newLockedQty.compareTo(systemFrozenTotal) < 0) {
            throw new RuntimeException("操作失败,当前产品被销售订单硬性冻结数量为: " + systemFrozenTotal + ",剩余解冻数量不能低于此数值");
        }
        stockInventory.setLockedQuantity(newLockedQty);
        return this.updateById(stockInventory);
    }
}
src/main/resources/mapper/sales/SalesLedgerProductFrozenMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.sales.mapper.SalesLedgerProductFrozenMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.sales.pojo.SalesLedgerProductFrozen">
        <id column="id" property="id" />
        <result column="sales_ledger_product_id" property="salesLedgerProductId" />
        <result column="product_model_id" property="productModelId" />
        <result column="frozen_quantity" property="frozenQuantity" />
        <result column="create_time" property="createTime" />
    </resultMap>
</mapper>
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -8,12 +8,13 @@
        SELECT
        T1.*,
        CASE
        WHEN (IFNULL(t2.qualitity, 0) - IFNULL(t2.locked_quantity, 0)) >= IFNULL(T1.quantity, 0) THEN 1
        WHEN (IFNULL(t2.qualitity, 0) - IFNULL(t2.locked_quantity, 0) + IFNULL(t3.frozen_quantity, 0)) >= IFNULL(T1.quantity, 0) THEN 1
        ELSE 0
        END as has_sufficient_stock
        FROM
        sales_ledger_product T1
        LEFT JOIN stock_inventory t2 ON T1.product_model_id = t2.product_model_id
        LEFT JOIN sales_ledger_product_frozen t3 ON T1.id = t3.sales_ledger_product_id
        <where>
            <if test="salesLedgerProduct.salesLedgerId != null">
                AND T1.sales_ledger_id = #{salesLedgerProduct.salesLedgerId}