From 9468068e746f25fc2d4bd618b9a07166f269e32a Mon Sep 17 00:00:00 2001
From: zss <zss@example.com>
Date: 星期二, 12 五月 2026 17:32:18 +0800
Subject: [PATCH] feat(stock): 不合格库存批次号自动生成及使用

---
 src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java |   12 +--
 src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java                |    3 +
 src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java             |   97 ++++++++++++++++++++++++++++++-
 src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java            |   11 +++
 src/main/resources/mapper/sales/ShippingInfoMapper.xml                                  |    2 
 src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java                         |   18 ++++++
 src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml                 |    9 ++
 7 files changed, 139 insertions(+), 13 deletions(-)

diff --git a/src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java b/src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java
index c312d8a..6cfae1b 100644
--- a/src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java
+++ b/src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java
@@ -1,6 +1,7 @@
 package com.ruoyi.procurementrecord.bean.dto;
 
 import com.ruoyi.procurementrecord.pojo.ReturnSaleProduct;
+import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.math.BigDecimal;
@@ -17,6 +18,7 @@
     //鏈��璐ф暟閲�
     private BigDecimal unQuantity;
 
+    //鎬婚��璐ф暟閲�
     private BigDecimal totalReturnNum;
 
     // 閫�璐ф�讳环
@@ -24,4 +26,13 @@
 
     // 閫�璐ф�讳环
     private BigDecimal taxInclusiveUnitPrice;
+
+    @Schema(description = "鍑哄簱鍗曞彿")
+    private String outboundBatches;
+
+    @Schema(description = "鎵规鍙�")
+    private String batchNo;
+
+    @Schema(description = "鍙戣揣鍑哄簱鏁伴噺")
+    private BigDecimal stockOutNum;
 }
diff --git a/src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java b/src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java
index b2e2626..9f87aef 100644
--- a/src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java
+++ b/src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java
@@ -11,6 +11,9 @@
     @Schema(description = "鍑哄簱鍗昳d")
     private Long id;
 
+    @Schema(description = "浜у搧瑙勬牸id")
+    private Long productModelId;
+
     @Schema(description = "浜у搧澶х被")
     private String productCategory;
 
diff --git a/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java b/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
index f1e3185..dd250cf 100644
--- a/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
+++ b/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
@@ -21,7 +21,6 @@
 import com.ruoyi.procurementrecord.service.ReturnSaleProductService;
 import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.sales.mapper.SalesLedgerMapper;
-import com.ruoyi.sales.pojo.SalesLedger;
 import com.ruoyi.sales.pojo.ShippingInfo;
 import com.ruoyi.sales.service.ShippingInfoService;
 import lombok.RequiredArgsConstructor;
@@ -91,8 +90,6 @@
     @Override
     public ShippingInfoVo getReturnManagementDtoByShippingIdId(Long shippingId) {
         ShippingInfo byId = shippingInfoService.getById(shippingId);
-        SalesLedger salesLedger = salesLedgerMapper.selectById(byId.getSalesLedgerId());
-//        BeanUtils.copyProperties(salesLedger, salesLedgerDto);
         ShippingInfoVo shippingInfoVo = new ShippingInfoVo();
         shippingInfoVo.setShippingInfo(byId);
         List<ShippingProductVo> shippingProductVos = shippingInfoService.getReturnManagementDtoById(byId.getId());
@@ -105,6 +102,7 @@
         ReturnManagement byId = this.getById(returnManagementId);
         List<ReturnSaleProductDto> list = returnSaleProductService.listReturnSaleProduct(returnManagementId);
         byId.setStatus(1);
+        byId.setSettler(SecurityUtils.getLoginUser().getNickName());
         updateById(byId);
         SalesRefundAmountOrderDto salesRefundAmountOrder = new SalesRefundAmountOrderDto();
         salesRefundAmountOrder.setReturnManagementId(returnManagementId);
@@ -117,11 +115,11 @@
             salesRefundAmountOrder.setRefundedAmount(new BigDecimal(0));
             // 鏄惁鏈夎川閲忛棶棰�
             if (returnSaleProduct.getIsQuality() == 1) {
-                // 鏈夎川閲忛棶棰橈紝鍏ヤ笉鍚堟牸搴�
-                stockUtils.addUnStock(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInQualifiedRecordTypeEnum.RETURN_UNSTOCK_IN.getCode(),returnSaleProduct.getId());
+                // 鏈夎川閲忛棶棰橈紝鍏ヤ笉鍚堟牸搴�(甯︽壒娆�)
+                stockUtils.addUnStockWithBatchNo(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInQualifiedRecordTypeEnum.RETURN_UNSTOCK_IN.getCode(),returnSaleProduct.getId(),returnSaleProduct.getBatchNo());
             }else{
-                // 鏃犺川閲忛棶棰橈紝鍏ュ悎鏍煎簱
-                stockUtils.addStock(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInQualifiedRecordTypeEnum.RETURN_HE_IN.getCode(),returnSaleProduct.getId());
+                // 鏃犺川閲忛棶棰橈紝鍏ュ悎鏍煎簱(甯︽壒娆�)
+                stockUtils.addStockWithBatchNo(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInQualifiedRecordTypeEnum.RETURN_HE_IN.getCode(),returnSaleProduct.getId(),returnSaleProduct.getBatchNo());
             }
         }
         salesRefundAmountOrder.setRefundAmount(bigDecimal);
diff --git a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
index b7e0478..8a6925e 100644
--- a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
+++ b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
@@ -49,6 +49,24 @@
     }
 
     /**
+     * 涓嶅悎鏍煎叆搴撳甫鎵规鍙�
+     *
+     * @param productModelId
+     * @param quantity
+     * @param recordType
+     * @param recordId
+     */
+    public void addUnStockWithBatchNo(Long productModelId, BigDecimal quantity, String recordType, Long recordId, String batchNo) {
+        StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
+        stockUninventoryDto.setRecordId(recordId);
+        stockUninventoryDto.setRecordType(String.valueOf(recordType));
+        stockUninventoryDto.setQualitity(quantity);
+        stockUninventoryDto.setProductModelId(productModelId);
+        stockUninventoryDto.setBatchNo(batchNo);
+        stockUninventoryService.addStockInRecordOnly(stockUninventoryDto);
+    }
+
+    /**
      * 涓嶅悎鏍煎嚭搴�
      *
      * @param productModelId
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
index 4835b09..4c7d157 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
@@ -4,8 +4,12 @@
 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.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.mapper.ProductModelMapper;
+import com.ruoyi.basic.pojo.ProductModel;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -14,21 +18,22 @@
 import com.ruoyi.stock.dto.StockOutRecordDto;
 import com.ruoyi.stock.dto.StockUninventoryDto;
 import com.ruoyi.stock.execl.StockUnInventoryExportData;
+import com.ruoyi.stock.mapper.StockInventoryMapper;
 import com.ruoyi.stock.mapper.StockUninventoryMapper;
+import com.ruoyi.stock.pojo.StockInRecord;
 import com.ruoyi.stock.pojo.StockInventory;
 import com.ruoyi.stock.pojo.StockUninventory;
 import com.ruoyi.stock.service.StockInRecordService;
 import com.ruoyi.stock.service.StockOutRecordService;
 import com.ruoyi.stock.service.StockUninventoryService;
-import lombok.AllArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
 import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import jakarta.servlet.http.HttpServletResponse;
-
 import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.List;
 
 /**
@@ -47,6 +52,8 @@
     private final StockUninventoryMapper stockUninventoryMapper;
     private final StockOutRecordService stockOutRecordService;
     private final StockInRecordService stockInRecordService;
+    private final ProductModelMapper productModelMapper;
+    private final StockInventoryMapper stockInventoryMapper;
 
     @Override
     public IPage<StockUninventoryDto> pageStockUninventory(Page page, StockUninventoryDto stockUninventoryDto) {
@@ -120,7 +127,11 @@
         stockInRecordDto.setRecordId(stockUninventoryDto.getRecordId());
         stockInRecordDto.setRecordType(stockUninventoryDto.getRecordType());
         stockInRecordDto.setStockInNum(stockUninventoryDto.getQualitity());
-        stockInRecordDto.setBatchNo(stockUninventoryDto.getBatchNo());
+        String batchNo = StringUtils.trim(stockUninventoryDto.getBatchNo());
+        if (StringUtils.isEmpty(batchNo)) {
+            batchNo = generateAutoBatchNo(stockUninventoryDto.getProductModelId());
+        }
+        stockInRecordDto.setBatchNo(batchNo);
         stockInRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
         stockInRecordDto.setType("1");
         stockInRecordDto.setRemark(stockUninventoryDto.getRemark());
@@ -200,4 +211,80 @@
         stockUninventory.setLockedQuantity(stockUninventory.getLockedQuantity().subtract(stockInventoryDto.getLockedQuantity()));
         return this.updateById(stockUninventory);
     }
+
+    //瑙勫垯鐢熸垚锛�20260424-浜у搧缂栧彿-001
+    private String generateAutoBatchNo(Long productModelId) {
+        if (productModelId == null) {
+            throw new ServiceException("浜у搧瑙勬牸ID涓嶈兘涓虹┖");
+        }
+        ProductModel productModel = productModelMapper.selectById(productModelId);
+        if (productModel == null) {
+            throw new ServiceException("浜у搧瑙勬牸涓嶅瓨鍦紝ID=" + productModelId);
+        }
+        String productCode = StringUtils.trim(productModel.getProductCode());
+        if (StringUtils.isEmpty(productCode)) {
+            throw new ServiceException("浜у搧瑙勬牸鏈淮鎶や骇鍝佺紪鐮侊紝ID=" + productModelId);
+        }
+
+        String dateText = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
+        String prefix = dateText + "-" + productCode + "-";
+        int maxSequence = resolveMaxSequence(prefix);
+        int sequence = maxSequence + 1;
+        while (sequence < 1_000_000) {
+            String batchNo = prefix + String.format("%03d", sequence);
+            if (!isBatchNoExists(batchNo)) {
+                return batchNo;
+            }
+            sequence++;
+        }
+        throw new ServiceException("鎵瑰彿搴忓彿瓒呭嚭鑼冨洿锛岃妫�鏌ユ壒鍙锋暟鎹�");
+    }
+
+    private int resolveMaxSequence(String prefix) {
+        int maxSequence = 0;
+        List<StockInventory> stockInventoryList = stockInventoryMapper.selectList(
+                Wrappers.<StockInventory>lambdaQuery()
+                        .select(StockInventory::getBatchNo)
+                        .likeRight(StockInventory::getBatchNo, prefix));
+        for (StockInventory stockInventory : stockInventoryList) {
+            maxSequence = Math.max(maxSequence, parseSequence(stockInventory.getBatchNo(), prefix));
+        }
+
+        List<StockInRecord> stockInRecordList = stockInRecordService.list(
+                Wrappers.<StockInRecord>lambdaQuery()
+                        .select(StockInRecord::getBatchNo)
+                        .likeRight(StockInRecord::getBatchNo, prefix));
+        for (StockInRecord stockInRecord : stockInRecordList) {
+            maxSequence = Math.max(maxSequence, parseSequence(stockInRecord.getBatchNo(), prefix));
+        }
+        return maxSequence;
+    }
+
+    private int parseSequence(String batchNo, String prefix) {
+        if (StringUtils.isEmpty(batchNo) || StringUtils.isEmpty(prefix) || !batchNo.startsWith(prefix)) {
+            return 0;
+        }
+        String sequenceText = batchNo.substring(prefix.length());
+        if (StringUtils.isEmpty(sequenceText) || !sequenceText.matches("\\d+")) {
+            return 0;
+        }
+        try {
+            return Integer.parseInt(sequenceText);
+        } catch (NumberFormatException ignored) {
+            return 0;
+        }
+    }
+
+    private boolean isBatchNoExists(String batchNo) {
+        if (StringUtils.isEmpty(batchNo)) {
+            return false;
+        }
+        Long inventoryCount = stockInventoryMapper.selectCount(
+                Wrappers.<StockInventory>lambdaQuery().eq(StockInventory::getBatchNo, batchNo));
+        if (inventoryCount != null && inventoryCount > 0) {
+            return true;
+        }
+        return stockInRecordService.count(
+                Wrappers.<StockInRecord>lambdaQuery().eq(StockInRecord::getBatchNo, batchNo)) > 0;
+    }
 }
diff --git a/src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml b/src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml
index 656a250..616e8a7 100644
--- a/src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml
+++ b/src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml
@@ -16,6 +16,9 @@
                pm.model                                     as model,
                pm.unit                                      as unit,
                rsp.*,
+            sor.outbound_batches,
+            sor.stock_out_num,
+            sor.batch_no,
                GREATEST(sor.stock_out_num - COALESCE(rsp.num, 0), 0) AS un_quantity,
                COALESCE(rs.total_return_num, 0)                             AS total_return_num
         FROM return_sale_product rsp
@@ -34,11 +37,15 @@
         where rm.id =#{returnManagementId}
     </select>
     <select id="listReturnSaleProduct" resultType="com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto">
-        select rsp.*,slp.tax_inclusive_unit_price ,slp.tax_inclusive_total_price*rsp.num as price
+        select rsp.*,
+               sor.batch_no,
+               slp.tax_inclusive_unit_price ,
+               slp.tax_inclusive_total_price*rsp.num as price
         from return_sale_product rsp
                  LEFT JOIN return_management rm ON rm.id = rsp.return_management_id
                  LEFT JOIN shipping_info si ON si.id = rm.shipping_id
                  LEFT JOIN sales_ledger_product slp ON si.sales_ledger_product_id = slp.id and slp.type = 1
+                 LEFT JOIN stock_out_record sor ON rsp.stock_out_record_id = sor.id
         where rsp.return_management_id = #{returnManagementId}
     </select>
 
diff --git a/src/main/resources/mapper/sales/ShippingInfoMapper.xml b/src/main/resources/mapper/sales/ShippingInfoMapper.xml
index aa73dac..762c2d4 100644
--- a/src/main/resources/mapper/sales/ShippingInfoMapper.xml
+++ b/src/main/resources/mapper/sales/ShippingInfoMapper.xml
@@ -72,6 +72,7 @@
             slp.product_category,
             slp.specification_model,
             slp.unit,
+            slp.product_model_id,
             sor.outbound_batches,
             sor.stock_out_num,
             sor.batch_no,
@@ -97,6 +98,7 @@
                 si.id = #{shippingId}
             </if>
         </where>
+        order by sor.id
     </select>
     <select id="getShippingInfoByCustomerName" resultType="com.ruoyi.sales.pojo.ShippingInfo">
         select * from shipping_info si

--
Gitblit v1.9.3