huminmin
12 小时以前 174c4a75e9dac46cf42399646bf49283583a43f5
增加废品库存列表和导出接口,以及修改发货逻辑
已添加2个文件
已修改15个文件
396 ■■■■ 文件已修改
doc/20260609_add_stock_type_to_shipping_product_detail.sql 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260609_add_type_to_stock_uninventory.sql 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ShippingProductDetail.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockUninventoryDto.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/execl/StockUnInventoryExportData.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockUninventory.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockUninventoryService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/ShippingProductDetailMapper.xml 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInventoryMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockUninventoryMapper.xml 152 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260609_add_stock_type_to_shipping_product_detail.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
ALTER TABLE shipping_product_detail
    ADD COLUMN stock_type VARCHAR(20) NOT NULL DEFAULT 'qualified' COMMENT '库存类型:qualified/waste' AFTER product_model_id;
doc/20260609_add_type_to_stock_uninventory.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
ALTER TABLE stock_uninventory
    ADD COLUMN type VARCHAR(20) NOT NULL DEFAULT 'unqualified' COMMENT '库存类型:unqualified/waste' AFTER qualitity;
UPDATE stock_uninventory
SET type = 'unqualified'
WHERE type IS NULL OR type = '';
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
@@ -45,6 +45,7 @@
        stockUninventoryDto.setRecordType(String.valueOf(recordType));
        stockUninventoryDto.setQualitity(quantity);
        stockUninventoryDto.setProductModelId(productModelId);
        stockUninventoryDto.setType(resolveUninventoryType(recordType));
        stockUninventoryService.addStockInRecordOnly(stockUninventoryDto);
    }
@@ -63,6 +64,7 @@
        stockUninventoryDto.setQualitity(quantity);
        stockUninventoryDto.setProductModelId(productModelId);
        stockUninventoryDto.setBatchNo(batchNo);
        stockUninventoryDto.setType(resolveUninventoryType(recordType));
        stockUninventoryService.addStockInRecordOnly(stockUninventoryDto);
    }
@@ -80,6 +82,7 @@
        stockUninventoryDto.setRecordType(String.valueOf(recordType));
        stockUninventoryDto.setQualitity(quantity);
        stockUninventoryDto.setProductModelId(productModelId);
        stockUninventoryDto.setType("unqualified");
        stockUninventoryService.subtractStockUninventory(stockUninventoryDto);
    }
@@ -211,16 +214,33 @@
    //删除出库记录
    public void deleteStockOutRecord(Long recordId, String recordType) {
        StockOutRecord one = stockOutRecordService.getOne(new QueryWrapper<StockOutRecord>()
        java.util.List<StockOutRecord> list = stockOutRecordService.list(new QueryWrapper<StockOutRecord>()
                .lambda().eq(StockOutRecord::getRecordId, recordId)
                .eq(StockOutRecord::getRecordType, recordType), false);
        if (ObjectUtils.isNotEmpty(one)) {
            if (ReviewStatusEnum.APPROVED.getCode().equals(one.getApprovalStatus())) {
                stockOutRecordService.batchDelete(Collections.singletonList(one.getId()));
            } else {
                stockOutRecordService.removeById(one.getId());
                .eq(StockOutRecord::getRecordType, recordType));
        if (ObjectUtils.isNotEmpty(list)) {
            java.util.List<Long> approvedIds = new java.util.ArrayList<>();
            java.util.List<Long> pendingIds = new java.util.ArrayList<>();
            for (StockOutRecord stockOutRecord : list) {
                if (ReviewStatusEnum.APPROVED.getCode().equals(stockOutRecord.getApprovalStatus())) {
                    approvedIds.add(stockOutRecord.getId());
                } else {
                    pendingIds.add(stockOutRecord.getId());
                }
            }
            if (!approvedIds.isEmpty()) {
                stockOutRecordService.batchDelete(approvedIds);
            }
            if (!pendingIds.isEmpty()) {
                stockOutRecordService.removeByIds(pendingIds);
            }
        }
    }
    private String resolveUninventoryType(String recordType) {
        if (com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode().equals(recordType)) {
            return "waste";
        }
        return "unqualified";
    }
}
src/main/java/com/ruoyi/sales/pojo/ShippingProductDetail.java
@@ -62,4 +62,7 @@
    @ApiModelProperty("产品型号id")
    private Long productModelId;
    @ApiModelProperty("库存类型:qualified/waste")
    private String stockType;
}
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -29,6 +29,10 @@
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.pojo.ShippingProductDetail;
import com.ruoyi.sales.service.ShippingInfoService;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockUninventoryDto;
import com.ruoyi.stock.service.StockInventoryService;
import com.ruoyi.stock.service.StockUninventoryService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
@@ -62,6 +66,8 @@
    private final ShippingProductDetailMapper shippingProductDetailMapper;
    private final ApprovalTemplateMapper approvalTemplateMapper;
    private final ApprovalInstanceService approvalInstanceService;
    private final StockInventoryService stockInventoryService;
    private final StockUninventoryService stockUninventoryService;
    @Override
    public IPage<ShippingInfoDto> listPage(Page page, ShippingInfo req) {
@@ -85,7 +91,7 @@
                throw new RuntimeException("发货信息不存在");
            }
            for (ShippingProductDetail shippingProductDetail : shippingProductDetails) {
                stockUtils.substractStock(shippingProductDetail.getProductModelId(), shippingProductDetail.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId(), shippingProductDetail.getBatchNo());
                addShippingStockOutRecord(shippingProductDetail, req.getId());
            }
        }
        byId.setExpressNumber(req.getExpressNumber());
@@ -145,9 +151,6 @@
        this.save(req);
        req.getBatchNoDetailList().forEach(item -> item.setShippingInfoId(req.getId()));
        shippingProductDetailMapper.insert(req.getBatchNoDetailList());
        for (ShippingProductDetail shippingProductDetail : req.getBatchNoDetailList()) {
            stockUtils.substractStock(shippingProductDetail.getProductModelId(), shippingProductDetail.getQuantity(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), req.getId(), shippingProductDetail.getBatchNo());
        }
        // ä¿å­˜æ–‡ä»¶
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.IMAGE, RecordTypeEnum.SHIPPING_INFO, req.getId(), req.getStorageBlobDTOs());
        return true;
@@ -192,4 +195,40 @@
        approvalInstanceService.add(approvalInstance);
        return true;
    }
    private void addShippingStockOutRecord(ShippingProductDetail shippingProductDetail, Long shippingInfoId) {
        String stockType = shippingProductDetail.getStockType();
        if (stockType != null) {
            stockType = stockType.trim().toLowerCase();
        }
        if ("waste".equals(stockType) || "unqualified".equals(stockType)) {
            StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
            stockUninventoryDto.setRecordId(shippingInfoId);
            stockUninventoryDto.setRecordType(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
            stockUninventoryDto.setQualitity(shippingProductDetail.getQuantity());
            stockUninventoryDto.setProductModelId(shippingProductDetail.getProductModelId());
            stockUninventoryDto.setBatchNo(normalizeBatchNo(shippingProductDetail.getBatchNo()));
            stockUninventoryDto.setType("waste");
            stockUninventoryService.addStockOutRecordOnly(stockUninventoryDto);
            return;
        }
        if (!"qualified".equals(stockType)) {
            throw new RuntimeException("发货明细库存类型无效,只支持 qualified æˆ– waste");
        }
        StockInventoryDto stockInventoryDto = new StockInventoryDto();
        stockInventoryDto.setRecordId(shippingInfoId);
        stockInventoryDto.setRecordType(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
        stockInventoryDto.setQualitity(shippingProductDetail.getQuantity());
        stockInventoryDto.setProductModelId(shippingProductDetail.getProductModelId());
        stockInventoryDto.setBatchNo(normalizeBatchNo(shippingProductDetail.getBatchNo()));
        stockInventoryService.addStockOutRecordOnly(stockInventoryDto);
    }
    private String normalizeBatchNo(String batchNo) {
        if (batchNo == null) {
            return null;
        }
        String normalized = batchNo.trim();
        return normalized.isEmpty() ? null : normalized;
    }
}
src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java
@@ -30,7 +30,15 @@
    @GetMapping("/pagestockUninventory")
    @Operation(summary = "分页查询库存")
    public R pagestockUninventory(Page page, StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setType("unqualified");
        IPage<StockUninventoryDto> stockUninventoryDtoIPage = stockUninventoryService.pageStockUninventory(page, stockUninventoryDto);
        return R.ok(stockUninventoryDtoIPage);
    }
    @GetMapping("/pageWasteQuery")
    @Operation(summary = "废品查询页面分页查询")
    public R pageWasteQuery(Page page, StockUninventoryDto stockUninventoryDto) {
        IPage<StockUninventoryDto> stockUninventoryDtoIPage = stockUninventoryService.pageWasteQuery(page, stockUninventoryDto);
        return R.ok(stockUninventoryDtoIPage);
    }
@@ -39,6 +47,9 @@
    public R addstockUninventory(@RequestBody StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode()));
        stockUninventoryDto.setRecordId(0L);
        if (stockUninventoryDto.getType() == null || stockUninventoryDto.getType().trim().isEmpty()) {
            stockUninventoryDto.setType("unqualified");
        }
        return R.ok(stockUninventoryService.addStockUninventory(stockUninventoryDto));
    }
@@ -48,6 +59,9 @@
    public R subtractstockUninventory(@RequestBody StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setRecordType(String.valueOf(StockOutQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()));
        stockUninventoryDto.setRecordId(0L);
        if (stockUninventoryDto.getType() == null || stockUninventoryDto.getType().trim().isEmpty()) {
            stockUninventoryDto.setType("unqualified");
        }
        return R.ok(stockUninventoryService.subtractStockUninventory(stockUninventoryDto));
    }
@@ -56,6 +70,9 @@
    public R addStockInRecordOnly(@RequestBody StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode()));
        stockUninventoryDto.setRecordId(0L);
        if (stockUninventoryDto.getType() == null || stockUninventoryDto.getType().trim().isEmpty()) {
            stockUninventoryDto.setType("unqualified");
        }
        return R.ok(stockUninventoryService.addStockInRecordOnly(stockUninventoryDto));
    }
@@ -64,15 +81,25 @@
    public R addStockOutRecordOnly(@RequestBody StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setRecordType(String.valueOf(StockOutQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()));
        stockUninventoryDto.setRecordId(0L);
        if (stockUninventoryDto.getType() == null || stockUninventoryDto.getType().trim().isEmpty()) {
            stockUninventoryDto.setType("unqualified");
        }
        return R.ok(stockUninventoryService.addStockOutRecordOnly(stockUninventoryDto));
    }
    @PostMapping("/exportStockUninventory")
    @Operation(summary = "导出库存")
    public void exportStockUninventory(HttpServletResponse response, StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setType("unqualified");
        stockUninventoryService.exportStockUninventory(response,stockUninventoryDto);
    }
    @PostMapping("/exportWasteQuery")
    @Operation(summary = "导出废品查询页面数据")
    public void exportWasteQuery(HttpServletResponse response, StockUninventoryDto stockUninventoryDto) {
        stockUninventoryService.exportWasteQuery(response, stockUninventoryDto);
    }
    @PostMapping("/frozenStock")
    @Operation(summary = "冻结库存")
src/main/java/com/ruoyi/stock/dto/StockUninventoryDto.java
@@ -10,6 +10,7 @@
    private String productName;
    private String model;
    private String unit;
    private Long topParentProductId;
    //入库类型
src/main/java/com/ruoyi/stock/execl/StockUnInventoryExportData.java
@@ -4,6 +4,7 @@
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class StockUnInventoryExportData {
@@ -21,18 +22,23 @@
    @Excel(name = "批号")
    private String batchNo;
    @Excel(name = "库存类型")
    private String type;
    @Excel(name = "库存数量")
    private BigDecimal qualitity;
    @Excel(name = "冻结数量")
    private BigDecimal lockedQuantity;
    @Excel(name = "可用数量")
    private BigDecimal unLockedQuantity;
    @Excel(name = "备注")
    private String remark;
//
//    @Excel(name = "最新更新时间")
//    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
//    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
//    private LocalDateTime updateTime;
    @Excel(name = "最新更新时间", width = 20, dateFormat = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
}
src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java
@@ -25,6 +25,8 @@
    IPage<StockUninventoryDto> pageStockUninventory(Page page, @Param("ew") StockUninventoryDto stockUninventoryDto);
    IPage<StockUninventoryDto> pageWasteQuery(Page page, @Param("ew") StockUninventoryDto stockUninventoryDto);
    int updateSubtractStockUnInventory(@Param("ew") StockUninventoryDto stockUninventoryDto);
    BigDecimal selectPendingOutQuantity(@Param("productModelId") Long productModelId, @Param("batchNo") String batchNo, @Param("type") String type);
@@ -32,4 +34,6 @@
    int updateAddStockUnInventory(@Param("ew") StockUninventoryDto stockUninventoryDto);
    List<StockUnInventoryExportData> listStockInventoryExportData(@Param("ew") StockUninventoryDto stockUninventoryDto);
    List<StockUnInventoryExportData> listWasteQueryExportData(@Param("ew") StockUninventoryDto stockUninventoryDto);
}
src/main/java/com/ruoyi/stock/pojo/StockUninventory.java
@@ -41,6 +41,9 @@
    @Schema(description = "数量")
    private BigDecimal qualitity;
    @Schema(description = "类型  unqualified ä¸åˆæ ¼ waste åºŸå“")
    private String type;
    @TableField(fill = FieldFill.INSERT)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
src/main/java/com/ruoyi/stock/service/StockUninventoryService.java
@@ -21,6 +21,8 @@
    IPage<StockUninventoryDto> pageStockUninventory(Page page, StockUninventoryDto stockUninventoryDto);
    IPage<StockUninventoryDto> pageWasteQuery(Page page, StockUninventoryDto stockUninventoryDto);
    Integer addStockUninventory(StockUninventoryDto stockUninventoryDto);
    Integer subtractStockUninventory(StockUninventoryDto stockUninventoryDto);
@@ -31,6 +33,8 @@
    void exportStockUninventory(HttpServletResponse response, StockUninventoryDto stockUninventoryDto);
    void exportWasteQuery(HttpServletResponse response, StockUninventoryDto stockUninventoryDto);
    Boolean frozenStock(StockInventoryDto stockInventoryDto);
    Boolean thawStock(StockInventoryDto stockInventoryDto);
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
@@ -43,6 +43,9 @@
@AllArgsConstructor
public class StockInRecordServiceImpl extends ServiceImpl<StockInRecordMapper, StockInRecord> implements StockInRecordService {
    private static final String UNQUALIFIED_TYPE = "unqualified";
    private static final String WASTE_TYPE = "waste";
    private StockInRecordMapper stockInRecordMapper;
    private StockInventoryMapper stockInventoryMapper;
    private StockUninventoryMapper stockUninventoryMapper;
@@ -102,8 +105,10 @@
                    stockInventoryMapper.updateSubtractStockInventory(stockInRecordDto);
                }
            }else if (stockInRecord.getType().equals("1")) {
                String uninventoryType = resolveUninventoryTypeByInRecordType(stockInRecord.getRecordType());
                LambdaQueryWrapper<StockUninventory> eq = new LambdaQueryWrapper<StockUninventory>()
                        .eq(StockUninventory::getProductModelId, stockInRecord.getProductModelId());
                        .eq(StockUninventory::getProductModelId, stockInRecord.getProductModelId())
                        .eq(StockUninventory::getType, uninventoryType);
                if (StringUtils.isEmpty(stockInRecord.getBatchNo())) {
                    eq.isNull(StockUninventory::getBatchNo);
                } else {
@@ -117,6 +122,7 @@
                    stockUninventoryDto.setProductModelId(stockUninventory.getProductModelId());
                    stockUninventoryDto.setBatchNo(stockUninventory.getBatchNo());
                    stockUninventoryDto.setQualitity(stockInRecord.getStockInNum());
                    stockUninventoryDto.setType(uninventoryType);
                    stockUninventoryMapper.updateSubtractStockUnInventory(stockUninventoryDto);
                }
            }
@@ -149,9 +155,12 @@
        return stockInventoryMapper.selectOne(eq);
    }
    private StockUninventory getStockUninventory(Long productModelId, String batchNo) {
    private StockUninventory getStockUninventory(Long productModelId, String batchNo, String uninventoryType) {
        LambdaQueryWrapper<StockUninventory> eq = new LambdaQueryWrapper<>();
        eq.eq(StockUninventory::getProductModelId, productModelId);
        if (StringUtils.isNotEmpty(uninventoryType)) {
            eq.eq(StockUninventory::getType, uninventoryType);
        }
        if (StringUtils.isEmpty(batchNo)) {
            eq.isNull(StockUninventory::getBatchNo);
        } else {
@@ -271,7 +280,8 @@
                    }
                } else if ("1".equals(stockInRecord.getType())) {
                    // ä¸åˆæ ¼å…¥åº“ -> å…ˆæŸ¥åº“存,存在则更新,不存在则新增
                    StockUninventory stockUninventory = getStockUninventory(stockInRecord.getProductModelId(), stockInRecord.getBatchNo());
                    String uninventoryType = resolveUninventoryTypeByInRecordType(stockInRecord.getRecordType());
                    StockUninventory stockUninventory = getStockUninventory(stockInRecord.getProductModelId(), stockInRecord.getBatchNo(), uninventoryType);
                    StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
                    stockUninventoryDto.setProductModelId(stockInRecord.getProductModelId());
                    stockUninventoryDto.setBatchNo(stockInRecord.getBatchNo());
@@ -279,11 +289,13 @@
                    stockUninventoryDto.setRemark(stockInRecord.getRemark());
                    stockUninventoryDto.setManufacturerId(stockInRecord.getManufacturerId());
                    stockUninventoryDto.setSource(stockInRecord.getSource());
                    stockUninventoryDto.setType(uninventoryType);
                    if (stockUninventory == null) {
                        stockUninventoryMapper.insert(new StockUninventory() {{
                            setProductModelId(stockInRecord.getProductModelId());
                            setQualitity(finalStockInNum);
                            setBatchNo(stockInRecord.getBatchNo());
                            setType(uninventoryType);
                            setRemark(stockInRecord.getRemark());
                            setManufacturerId(stockInRecord.getManufacturerId());
                            setSource(stockInRecord.getSource());
@@ -298,6 +310,13 @@
        return items.size();
    }
    private String resolveUninventoryTypeByInRecordType(String recordType) {
        if (StockInQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode().equals(recordType)) {
            return WASTE_TYPE;
        }
        return UNQUALIFIED_TYPE;
    }
    private void adjustPurchaseInboundAuditFields(StockInRecord stockInRecord, BigDecimal finalStockInNum) {
        if (stockInRecord == null || finalStockInNum == null) {
            return;
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
@@ -47,6 +47,9 @@
@Service
@RequiredArgsConstructor
public class StockOutRecordServiceImpl extends ServiceImpl<StockOutRecordMapper, StockOutRecord> implements StockOutRecordService {
    private static final String UNQUALIFIED_TYPE = "unqualified";
    private static final String WASTE_TYPE = "waste";
    private final StockOutRecordMapper stockOutRecordMapper;
    private final StockInventoryMapper stockInventoryMapper;
    private final StockUninventoryMapper stockUninventoryMapper;
@@ -110,8 +113,10 @@
                }
            }
            else if (stockOutRecord.getType().equals("1")) {
                String uninventoryType = resolveUninventoryTypeByOutRecordType(stockOutRecord.getRecordType());
                LambdaQueryWrapper<StockUninventory> wrapper = new LambdaQueryWrapper<StockUninventory>()
                        .eq(StockUninventory::getProductModelId, stockOutRecord.getProductModelId());
                        .eq(StockUninventory::getProductModelId, stockOutRecord.getProductModelId())
                        .eq(StockUninventory::getType, uninventoryType);
                if (StringUtils.isEmpty(stockOutRecord.getBatchNo())) {
                    wrapper.isNull(StockUninventory::getBatchNo);
                } else {
@@ -125,6 +130,7 @@
                    stockUninventoryDto.setProductModelId(stockUninventory.getProductModelId());
                    stockUninventoryDto.setQualitity(stockOutRecord.getStockOutNum());
                    stockUninventoryDto.setBatchNo(stockUninventory.getBatchNo());
                    stockUninventoryDto.setType(uninventoryType);
                    stockUninventoryMapper.updateAddStockUnInventory(stockUninventoryDto);
                }
            }
@@ -195,7 +201,8 @@
                    stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto);
                } else if ("1".equals(stockOutRecord.getType())) {
                    // ä¸åˆæ ¼å‡ºåº“ -> å…ˆæŸ¥åº“存是否存在,存在才扣减
                    StockUninventory stockUninventory = getStockUninventory(stockOutRecord.getProductModelId(), stockOutRecord.getBatchNo());
                    String uninventoryType = resolveUninventoryTypeByOutRecordType(stockOutRecord.getRecordType());
                    StockUninventory stockUninventory = getStockUninventory(stockOutRecord.getProductModelId(), stockOutRecord.getBatchNo(), uninventoryType);
                    if (stockUninventory == null) {
                        throw new BaseException("不合格库存记录不存在,出库批次:" + stockOutRecord.getOutboundBatches());
                    }
@@ -203,6 +210,7 @@
                    stockUninventoryDto.setProductModelId(stockOutRecord.getProductModelId());
                    stockUninventoryDto.setBatchNo(stockOutRecord.getBatchNo());
                    stockUninventoryDto.setQualitity(stockOutRecord.getStockOutNum());
                    stockUninventoryDto.setType(uninventoryType);
                    stockUninventoryMapper.updateSubtractStockUnInventory(stockUninventoryDto);
                }
            }
@@ -221,9 +229,12 @@
        return stockInventoryMapper.selectOne(eq);
    }
    private StockUninventory getStockUninventory(Long productModelId, String batchNo) {
    private StockUninventory getStockUninventory(Long productModelId, String batchNo, String uninventoryType) {
        LambdaQueryWrapper<StockUninventory> eq = new LambdaQueryWrapper<>();
        eq.eq(StockUninventory::getProductModelId, productModelId);
        if (StringUtils.isNotEmpty(uninventoryType)) {
            eq.eq(StockUninventory::getType, uninventoryType);
        }
        if (StringUtils.isEmpty(batchNo)) {
            eq.isNull(StockUninventory::getBatchNo);
        } else {
@@ -231,4 +242,11 @@
        }
        return stockUninventoryMapper.selectOne(eq);
    }
    private String resolveUninventoryTypeByOutRecordType(String recordType) {
        if (StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode().equals(recordType)) {
            return WASTE_TYPE;
        }
        return UNQUALIFIED_TYPE;
    }
}
src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
@@ -48,7 +48,7 @@
@RequiredArgsConstructor
public class StockUninventoryServiceImpl extends ServiceImpl<StockUninventoryMapper, StockUninventory> implements StockUninventoryService {
    private static final String UNQUALIFIED_TYPE = "unqualified";
    private final StockUninventoryMapper stockUninventoryMapper;
    private final StockOutRecordService stockOutRecordService;
    private final StockInRecordService stockInRecordService;
@@ -61,10 +61,18 @@
    }
    @Override
    public IPage<StockUninventoryDto> pageWasteQuery(Page page, StockUninventoryDto stockUninventoryDto) {
        return stockUninventoryMapper.pageWasteQuery(page, stockUninventoryDto);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addStockUninventory(StockUninventoryDto stockUninventoryDto) {
        String inventoryType = resolveInventoryType(stockUninventoryDto);
        stockUninventoryDto.setType(inventoryType);
        LambdaQueryWrapper<StockUninventory> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StockUninventory::getProductModelId, stockUninventoryDto.getProductModelId());
        wrapper.eq(StockUninventory::getType, inventoryType);
        if (StringUtils.isEmpty(stockUninventoryDto.getBatchNo())) {
            stockUninventoryDto.setBatchNo(null);
            wrapper.isNull(StockUninventory::getBatchNo);
@@ -89,6 +97,8 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer subtractStockUninventory(StockUninventoryDto stockUninventoryDto) {
        String inventoryType = resolveInventoryType(stockUninventoryDto);
        stockUninventoryDto.setType(inventoryType);
        //  æ–°å¢žå‡ºåº“记录
        StockOutRecordDto stockOutRecordDto = new StockOutRecordDto();
@@ -99,7 +109,9 @@
        stockOutRecordDto.setBatchNo(stockUninventoryDto.getBatchNo());
        stockOutRecordDto.setType("1");
        stockOutRecordService.add(stockOutRecordDto);
        StockUninventory oldStockInventory = stockUninventoryMapper.selectOne(new QueryWrapper<StockUninventory>().lambda().eq(StockUninventory::getProductModelId, stockUninventoryDto.getProductModelId()));
        StockUninventory oldStockInventory = stockUninventoryMapper.selectOne(new QueryWrapper<StockUninventory>().lambda()
                .eq(StockUninventory::getProductModelId, stockUninventoryDto.getProductModelId())
                .eq(StockUninventory::getType, inventoryType));
        if (ObjectUtils.isEmpty(oldStockInventory)) {
            throw new RuntimeException("产品库存不存在");
        }else {
@@ -111,6 +123,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addStockInRecordOnly(StockUninventoryDto stockUninventoryDto) {
        stockUninventoryDto.setType(resolveInventoryType(stockUninventoryDto));
        StockInRecordDto stockInRecordDto = new StockInRecordDto();
        stockInRecordDto.setRecordId(stockUninventoryDto.getRecordId());
        stockInRecordDto.setRecordType(stockUninventoryDto.getRecordType());
@@ -130,8 +143,11 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addStockOutRecordOnly(StockUninventoryDto stockUninventoryDto) {
        String inventoryType = resolveInventoryType(stockUninventoryDto);
        stockUninventoryDto.setType(inventoryType);
        LambdaQueryWrapper<StockUninventory> eq = new LambdaQueryWrapper<>();
        eq.eq(StockUninventory::getProductModelId, stockUninventoryDto.getProductModelId());
        eq.eq(StockUninventory::getType, inventoryType);
        if (StringUtils.isEmpty(stockUninventoryDto.getBatchNo())) {
            eq.isNull(StockUninventory::getBatchNo);
        } else {
@@ -177,6 +193,13 @@
    }
    @Override
    public void exportWasteQuery(HttpServletResponse response, StockUninventoryDto stockUninventoryDto) {
        List<StockUnInventoryExportData> list = stockUninventoryMapper.listWasteQueryExportData(stockUninventoryDto);
        ExcelUtil<StockUnInventoryExportData> util = new ExcelUtil<>(StockUnInventoryExportData.class);
        util.exportExcel(response, list, "废品查询信息");
    }
    @Override
    public Boolean frozenStock(StockInventoryDto stockInventoryDto) {
        StockUninventory stockUninventory = stockUninventoryMapper.selectById(stockInventoryDto.getId());
        if (stockUninventory.getQualitity().compareTo(stockInventoryDto.getLockedQuantity())<0) {
@@ -200,6 +223,13 @@
        return this.updateById(stockUninventory);
    }
    private String resolveInventoryType(StockUninventoryDto stockUninventoryDto) {
        if (stockUninventoryDto != null && StringUtils.isNotEmpty(stockUninventoryDto.getType())) {
            return stockUninventoryDto.getType().trim();
        }
        return UNQUALIFIED_TYPE;
    }
    //规则生成:20260424-产品编号-001
    private String generateAutoBatchNo(Long productModelId) {
        if (productModelId == null) {
src/main/resources/mapper/sales/ShippingProductDetailMapper.xml
@@ -9,21 +9,21 @@
        <result column="batch_no" property="batchNo" />
        <result column="quantity" property="quantity" />
        <result column="shipping_info_id" property="shippingInfoId" />
        <result column="product_model_id" property="productModelId" />
        <result column="stock_type" property="stockType" />
    </resultMap>
    <select id="getDetail" resultType="com.ruoyi.sales.dto.ShippingProductDetailDto">
        select si.batch_no, pm.model as specification_model, p.product_name, spd.quantity as delivery_quantity
        select spd.batch_no, pm.model as specification_model, p.product_name, spd.quantity as delivery_quantity
        from shipping_product_detail spd
                 left join stock_inventory si on si.id = spd.stock_inventory_id
                 left join product_model pm on pm.id = si.product_model_id
                 left join product_model pm on pm.id = spd.product_model_id
                 left join product p on p.id = pm.product_id
        where spd.shipping_info_id = #{id}
    </select>
    <select id="getDateilByShippingNo" resultType="com.ruoyi.sales.dto.ShippingProductDetailDto">
         select si.batch_no, pm.model as specification_model, p.product_name, spd.quantity as delivery_quantity
         select spd.batch_no, pm.model as specification_model, p.product_name, spd.quantity as delivery_quantity
         from shipping_product_detail spd
                  left join shipping_info sp on sp.id = spd.shipping_info_id
                  left join stock_inventory si on si.id = spd.stock_inventory_id
                  left join product_model pm on pm.id = si.product_model_id
                  left join product_model pm on pm.id = spd.product_model_id
                  left join product p on p.id = pm.product_id
         where sp.shipping_no = #{shippingNo}
    </select>
src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -195,6 +195,7 @@
        from stock_uninventory su
        left join product_model pm on su.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        where COALESCE(su.type, 'unqualified') != 'waste'
        ) as combined
        <where>
            <if test="ew.productName != null and ew.productName !=''">
@@ -328,6 +329,7 @@
            from stock_uninventory su
            left join product_model pm on su.product_model_id = pm.id
            left join product p on pm.product_id = p.id
            where COALESCE(su.type, 'unqualified') != 'waste'
        ) as combined
        <where>
            <if test="ew.productName != null and ew.productName !=''">
src/main/resources/mapper/stock/StockUninventoryMapper.xml
@@ -2,15 +2,51 @@
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.stock.mapper.StockUninventoryMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.stock.pojo.StockUninventory">
        <result column="id" property="id" />
        <result column="product_model_id" property="productModelId" />
        <result column="qualitity" property="qualitity" />
        <result column="type" property="type" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
        <result column="version" property="version" />
    </resultMap>
    <sql id="WasteQueryRecursiveTree">
        WITH RECURSIVE product_tree AS (
            SELECT id
            FROM product
            WHERE id = #{ew.topParentProductId}
            UNION ALL
            SELECT p.id
            FROM product p
            INNER JOIN product_tree pt ON p.parent_id = pt.id
        )
    </sql>
    <sql id="BaseWasteFromClause">
        from stock_uninventory su
        left join product_model pm on su.product_model_id = pm.id
        left join product p on pm.product_id = p.id
    </sql>
    <sql id="WastePageColumns">
        su.id,
        su.qualitity,
        su.type,
        COALESCE(su.locked_quantity, 0) as locked_quantity,
        su.product_model_id,
        su.create_time,
        su.update_time,
        su.version,
        (su.qualitity - COALESCE(su.locked_quantity, 0)) as un_locked_quantity,
        pm.model,
        pm.unit,
        p.product_name
    </sql>
    <update id="updateSubtractStockUnInventory">
        update stock_uninventory
        <set>
@@ -20,21 +56,27 @@
            <if test="ew.version != null">
                version = version + 1,
            </if>
            <if test="ew.remark != null and ew.remark !=''">
            <if test="ew.remark != null and ew.remark != ''">
                remark = #{ew.remark},
            </if>
            <if test="ew.type != null and ew.type != ''">
                type = #{ew.type},
            </if>
            update_time = now()
        </set>
        where
        product_model_id = #{ew.productModelId} and qualitity >= #{ew.qualitity}
        where product_model_id = #{ew.productModelId}
          and qualitity >= #{ew.qualitity}
        <if test="ew.type != null and ew.type != ''">
            and type = #{ew.type}
        </if>
        <if test="ew.batchNo == null">
            and batch_no is null
        </if>
        <if test="ew.batchNo != null">
            and batch_no = #{ew.batchNo}
        </if>
    </update>
    <update id="updateAddStockUnInventory">
        update stock_uninventory
        <set>
@@ -47,12 +89,18 @@
            <if test="ew.version != null">
                version = version + 1,
            </if>
            <if test="ew.remark != null and ew.remark !=''">
            <if test="ew.remark != null and ew.remark != ''">
                remark = #{ew.remark},
            </if>
            <if test="ew.type != null and ew.type != ''">
                type = #{ew.type},
            </if>
            update_time = now()
        </set>
        where product_model_id = #{ew.productModelId}
        <if test="ew.type != null and ew.type != ''">
            and type = #{ew.type}
        </if>
        <if test="ew.batchNo == null">
            and batch_no is null
        </if>
@@ -60,9 +108,12 @@
            and batch_no = #{ew.batchNo}
        </if>
    </update>
    <select id="pageStockUninventory" resultType="com.ruoyi.stock.dto.StockUninventoryDto">
        select su.id,
        select
        su.id,
        su.qualitity,
        su.type,
        COALESCE(su.locked_quantity, 0) as locked_quantity,
        su.product_model_id,
        su.create_time,
@@ -73,26 +124,83 @@
        pm.model,
        pm.unit,
        p.product_name
        from stock_uninventory su
        left join product_model pm on su.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        where 1 = 1
        <if test="ew.productName != null and ew.productName !=''">
            and p.product_name like concat('%',#{ew.productName},'%')
        </if>
        <include refid="BaseWasteFromClause" />
        <where>
            <if test="ew.type != null and ew.type != ''">
                and su.type = #{ew.type}
            </if>
            <if test="ew.productName != null and ew.productName != ''">
                and p.product_name like concat('%', #{ew.productName}, '%')
            </if>
        </where>
    </select>
    <select id="pageWasteQuery" resultType="com.ruoyi.stock.dto.StockUninventoryDto">
        <include refid="WasteQueryRecursiveTree" />
        select
        <include refid="WastePageColumns" />
        <include refid="BaseWasteFromClause" />
        <where>
            and su.type = 'waste'
            <if test="ew.productName != null and ew.productName != ''">
                and p.product_name like concat('%', #{ew.productName}, '%')
            </if>
            <if test="ew.model != null and ew.model != ''">
                and pm.model like concat('%', #{ew.model}, '%')
            </if>
            <if test="ew.batchNo != null and ew.batchNo != ''">
                and su.batch_no like concat('%', #{ew.batchNo}, '%')
            </if>
            <if test="ew.topParentProductId != null and ew.topParentProductId > 0">
                and p.id in (select id from product_tree)
            </if>
        </where>
        order by su.update_time desc, su.id desc
    </select>
    <select id="listStockInventoryExportData" resultType="com.ruoyi.stock.execl.StockUnInventoryExportData">
        select su.*,
        select
        su.*,
        pm.model,
        pm.unit,
        p.product_name
        from stock_uninventory su
        left join product_model pm on su.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        where 1 = 1
        <if test="ew.productName != null and ew.productName !=''">
            and p.product_name like concat('%',#{ew.productName},'%')
        </if>
        <include refid="BaseWasteFromClause" />
        <where>
            <if test="ew.type != null and ew.type != ''">
                and su.type = #{ew.type}
            </if>
            <if test="ew.productName != null and ew.productName != ''">
                and p.product_name like concat('%', #{ew.productName}, '%')
            </if>
        </where>
    </select>
    <select id="listWasteQueryExportData" resultType="com.ruoyi.stock.execl.StockUnInventoryExportData">
        <include refid="WasteQueryRecursiveTree" />
        select
        su.*,
        pm.model,
        pm.unit,
        p.product_name,
        (su.qualitity - COALESCE(su.locked_quantity, 0)) as un_locked_quantity
        <include refid="BaseWasteFromClause" />
        <where>
            and su.type = 'waste'
            <if test="ew.productName != null and ew.productName != ''">
                and p.product_name like concat('%', #{ew.productName}, '%')
            </if>
            <if test="ew.model != null and ew.model != ''">
                and pm.model like concat('%', #{ew.model}, '%')
            </if>
            <if test="ew.batchNo != null and ew.batchNo != ''">
                and su.batch_no like concat('%', #{ew.batchNo}, '%')
            </if>
            <if test="ew.topParentProductId != null and ew.topParentProductId > 0">
                and p.id in (select id from product_tree)
            </if>
        </where>
        order by su.update_time desc, su.id desc
    </select>
    <select id="selectPendingOutQuantity" resultType="java.math.BigDecimal">