已添加3个文件
已修改28个文件
793 ■■■■■ 文件已修改
doc/20260609_add_stock_type_to_sales_ledger_product.sql 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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/common/enums/StockInQualifiedRecordTypeEnum.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ShippingProductDetail.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/SalesLedgerVo.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockUninventoryDto.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/execl/StockUnInventoryExportData.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockInRecord.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockUninventory.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockUninventoryService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/ShippingInfoMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | 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 222 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260609_add_stock_type_to_sales_ledger_product.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
ALTER TABLE sales_ledger_product
    ADD COLUMN stock_type VARCHAR(20) NOT NULL DEFAULT 'qualified' COMMENT '库存类型:qualified/waste' AFTER product_model_id;
UPDATE sales_ledger_product
SET stock_type = 'qualified'
WHERE stock_type IS NULL OR stock_type = '';
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/common/enums/StockInQualifiedRecordTypeEnum.java
@@ -18,6 +18,7 @@
    DEFECTIVE_PASS("11", "不合格-让步放行"),
    RETURN_HE_IN("14", "销售退货-合格入库"),
    RETURN_UNSTOCK_IN("15", "销售退货-不合格入库"),
    RETURN_WASTE_IN("16", "销售退货-废品入库"),
    PICK_RETURN_IN("20", "领料退料-合格入库"),
    FEED_RETURN_IN("22", "生产退料-合格入库");
src/main/java/com/ruoyi/procurementrecord/bean/dto/ReturnSaleProductDto.java
@@ -35,4 +35,7 @@
    @Schema(description = "发货出库数量")
    private BigDecimal stockOutNum;
    @Schema(description = "原发货库存类型:qualified/waste")
    private String stockType;
}
src/main/java/com/ruoyi/procurementrecord/bean/vo/ShippingProductVo.java
@@ -40,4 +40,7 @@
    @Schema(description = "含税单价")
    private BigDecimal taxInclusiveUnitPrice;
    @Schema(description = "原发货库存类型:qualified/waste")
    private String stockType;
}
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
@@ -32,6 +32,7 @@
import java.math.BigDecimal;
import java.util.List;
import java.util.Locale;
/**
 * @author :yys
@@ -112,13 +113,35 @@
        for (ReturnSaleProductDto returnSaleProduct : list) {
            bigDecimal = bigDecimal.add(returnSaleProduct.getPrice());
            salesRefundAmountOrder.setRefundedAmount(new BigDecimal(0));
            // æ˜¯å¦æœ‰è´¨é‡é—®é¢˜
            if (returnSaleProduct.getIsQuality() == 1) {
                // æœ‰è´¨é‡é—®é¢˜ï¼Œå…¥ä¸åˆæ ¼åº“(带批次)
                stockUtils.addUnStockWithBatchNo(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInQualifiedRecordTypeEnum.RETURN_UNSTOCK_IN.getCode(),returnSaleProduct.getId(),returnSaleProduct.getBatchNo());
            }else{
                // æ— è´¨é‡é—®é¢˜ï¼Œå…¥åˆæ ¼åº“(带批次)
                stockUtils.addStockWithBatchNo(returnSaleProduct.getProductModelId(),returnSaleProduct.getNum(), StockInQualifiedRecordTypeEnum.RETURN_HE_IN.getCode(),returnSaleProduct.getId(),returnSaleProduct.getBatchNo());
            String stockType = normalizeStockType(returnSaleProduct.getStockType());
            boolean hasQualityIssue = Integer.valueOf(1).equals(returnSaleProduct.getIsQuality());
            if ("waste".equals(stockType)) {
                // åºŸå“å‘货退回后仍回废品库,不再回合格/不合格库。
                stockUtils.addWasteStockWithBatchNo(
                        returnSaleProduct.getProductModelId(),
                        returnSaleProduct.getNum(),
                        StockInQualifiedRecordTypeEnum.RETURN_WASTE_IN.getCode(),
                        returnSaleProduct.getId(),
                        returnSaleProduct.getBatchNo()
                );
            } else if (hasQualityIssue) {
                // åˆæ ¼å‘货且有质量问题,回不合格库。
                stockUtils.addUnStockWithBatchNo(
                        returnSaleProduct.getProductModelId(),
                        returnSaleProduct.getNum(),
                        StockInQualifiedRecordTypeEnum.RETURN_UNSTOCK_IN.getCode(),
                        returnSaleProduct.getId(),
                        returnSaleProduct.getBatchNo()
                );
            } else {
                // åˆæ ¼å‘货且无质量问题,回合格库。
                stockUtils.addStockWithBatchNo(
                        returnSaleProduct.getProductModelId(),
                        returnSaleProduct.getNum(),
                        StockInQualifiedRecordTypeEnum.RETURN_HE_IN.getCode(),
                        returnSaleProduct.getId(),
                        returnSaleProduct.getBatchNo()
                );
            }
        }
        salesRefundAmountOrder.setRefundAmount(bigDecimal);
@@ -135,4 +158,12 @@
        return returnManagementDtoById;
    }
    private String normalizeStockType(String stockType) {
        if (ObjectUtils.isEmpty(stockType)) {
            return "qualified";
        }
        String normalized = stockType.trim().toLowerCase(Locale.ROOT);
        return normalized.isEmpty() ? "qualified" : normalized;
    }
}
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,27 @@
        stockUninventoryDto.setQualitity(quantity);
        stockUninventoryDto.setProductModelId(productModelId);
        stockUninventoryDto.setBatchNo(batchNo);
        stockUninventoryDto.setType(resolveUninventoryType(recordType));
        stockUninventoryService.addStockInRecordOnly(stockUninventoryDto);
    }
    /**
     * åºŸå“å…¥åº“带批次号
     *
     * @param productModelId äº§å“è§„æ ¼ID
     * @param quantity æ•°é‡
     * @param recordType è®°å½•类型
     * @param recordId ä¸šåŠ¡ID
     * @param batchNo æ‰¹æ¬¡å·
     */
    public void addWasteStockWithBatchNo(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);
        stockUninventoryDto.setType("waste");
        stockUninventoryService.addStockInRecordOnly(stockUninventoryDto);
    }
@@ -80,6 +102,7 @@
        stockUninventoryDto.setRecordType(String.valueOf(recordType));
        stockUninventoryDto.setQualitity(quantity);
        stockUninventoryDto.setProductModelId(productModelId);
        stockUninventoryDto.setType("unqualified");
        stockUninventoryService.subtractStockUninventory(stockUninventoryDto);
    }
@@ -211,16 +234,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/dto/SalesLedgerDto.java
@@ -61,6 +61,9 @@
    @Schema(description = "交货日期")
    private LocalDate deliveryDate;
    @Schema(description = "库存类型:qualified/waste")
    private String stockType;
    private List<StorageBlobDTO> storageBlobDTOs;
    private List<StorageBlobVO> StorageBlobVOs;
}
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
@@ -125,6 +125,9 @@
     */
    private Long productModelId;
    @Schema(description = "库存类型:qualified/waste")
    private String stockType;
    @Schema(description = "登记人")
    private String register;
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/SalesLedgerServiceImpl.java
@@ -585,6 +585,15 @@
        // 4. å¤„理子表数据
        List<SalesLedgerProduct> productList = salesLedgerDto.getProductData();
        if (productList != null && !productList.isEmpty()) {
            String ledgerStockType = StringUtils.trim(salesLedgerDto.getStockType());
            if (StringUtils.isEmpty(ledgerStockType)) {
                ledgerStockType = "qualified";
            }
            for (SalesLedgerProduct product : productList) {
                if (StringUtils.isEmpty(StringUtils.trim(product.getStockType()))) {
                    product.setStockType(ledgerStockType);
                }
            }
            handleSalesLedgerProducts(salesLedger.getId(), productList, EnumUtil.fromCode(SaleEnum.class, salesLedgerDto.getType()));
            updateMainContractAmount(
                    salesLedger.getId(),
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -26,9 +26,14 @@
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.mapper.ShippingProductDetailMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
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 +67,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 +92,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());
@@ -142,12 +149,13 @@
    @Override
    public boolean add(ShippingInfoDto req) {
        String ledgerStockType = resolveLedgerStockType(req.getSalesLedgerProductId());
        this.save(req);
        req.getBatchNoDetailList().forEach(item -> item.setShippingInfoId(req.getId()));
        req.getBatchNoDetailList().forEach(item -> {
            item.setShippingInfoId(req.getId());
            item.setStockType(ledgerStockType);
        });
        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 +200,71 @@
        approvalInstanceService.add(approvalInstance);
        return true;
    }
    private void addShippingStockOutRecord(ShippingProductDetail shippingProductDetail, Long shippingInfoId) {
        ShippingInfo shippingInfo = this.getById(shippingInfoId);
        String stockType = resolveLedgerStockType(shippingInfo != null ? shippingInfo.getSalesLedgerProductId() : null);
        if (stockType == null) {
            stockType = normalizeStockType(shippingProductDetail.getStockType());
        }
        String detailStockType = normalizeStockType(shippingProductDetail.getStockType());
        if (detailStockType != null && stockType != null && !stockType.equals(detailStockType)) {
            throw new RuntimeException("发货明细库存类型与销售台账产品库存类型不一致");
        }
        if ("waste".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;
    }
    private String normalizeStockType(String stockType) {
        if (stockType == null) {
            return null;
        }
        String normalized = stockType.trim().toLowerCase();
        if (normalized.isEmpty()) {
            return null;
        }
        if ("unqualified".equals(normalized)) {
            return "waste";
        }
        return normalized;
    }
    private String resolveLedgerStockType(Long salesLedgerProductId) {
        if (salesLedgerProductId == null) {
            return null;
        }
        SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(salesLedgerProductId);
        if (salesLedgerProduct == null) {
            throw new RuntimeException("销售台账产品不存在");
        }
        String stockType = normalizeStockType(salesLedgerProduct.getStockType());
        return stockType == null ? "qualified" : stockType;
    }
}
src/main/java/com/ruoyi/sales/vo/SalesLedgerVo.java
@@ -10,4 +10,6 @@
public class SalesLedgerVo extends SalesLedger {
    private List<StorageBlobVO> storageBlobVOs;
    private String stockType;
}
src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java
@@ -30,8 +30,29 @@
    @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);
    }
    @GetMapping("/getWasteBatchNoQty")
    @Operation(summary = "查询废品库存对应批号和数量")
    public R getWasteBatchNoQty(Page page, StockInventoryDto stockInventoryDto) {
        IPage<StockUninventoryDto> stockUninventoryDtoIPage = stockUninventoryService.getWasteBatchNoQty(page, stockInventoryDto);
        return R.ok(stockUninventoryDtoIPage);
    }
    @GetMapping("/getWasteByModelId")
    @Operation(summary = "根据产品规格ID获取废品库存记录")
    public R getWasteByModelId(Long productModelId) {
        return R.ok(stockUninventoryService.getWasteByModelId(productModelId));
    }
    @PostMapping("/addstockUninventory")
@@ -39,6 +60,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 +72,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 +83,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 +94,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,11 @@
    private String productName;
    private String model;
    private String unit;
    private Long productId;
    private Long topParentProductId;
    private String manufacturerName;
    private BigDecimal pendingOutQuantity;
    private String sourceText;
    //入库类型
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 {
@@ -16,23 +17,36 @@
    @Excel(name = "规格")
    private String model;
    @Excel(name = "厂家")
    private String manufacturerName;
    @Excel(name = "来源")
    private String sourceText;
    @Excel(name = "单位")
    private String unit;
    @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;
    private String source;
}
src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockUninventoryDto;
import com.ruoyi.stock.execl.StockUnInventoryExportData;
import com.ruoyi.stock.pojo.StockUninventory;
@@ -25,6 +26,12 @@
    IPage<StockUninventoryDto> pageStockUninventory(Page page, @Param("ew") StockUninventoryDto stockUninventoryDto);
    IPage<StockUninventoryDto> pageWasteQuery(Page page, @Param("ew") StockUninventoryDto stockUninventoryDto);
    IPage<StockUninventoryDto> getWasteBatchNoQty(Page page, @Param("ew") StockInventoryDto stockInventoryDto);
    List<StockUninventoryDto> getWasteByModelId(@Param("productModelId") Long productModelId);
    int updateSubtractStockUnInventory(@Param("ew") StockUninventoryDto stockUninventoryDto);
    BigDecimal selectPendingOutQuantity(@Param("productModelId") Long productModelId, @Param("batchNo") String batchNo, @Param("type") String type);
@@ -32,4 +39,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/StockInRecord.java
@@ -46,7 +46,7 @@
    @Schema(description = "预警数量")
    private BigDecimal warnNum;
    @Schema(description = "类型  0合格入库 1不合格入库")
    @Schema(description = "类型  0合格入库 1不合格入库 2废品入库")
    private String type;
    @Schema(description = "创建时间")
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
@@ -9,6 +9,8 @@
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <p>
 * ä¸åˆæ ¼åº“存表 æœåŠ¡ç±»
@@ -21,6 +23,12 @@
    IPage<StockUninventoryDto> pageStockUninventory(Page page, StockUninventoryDto stockUninventoryDto);
    IPage<StockUninventoryDto> pageWasteQuery(Page page, StockUninventoryDto stockUninventoryDto);
    IPage<StockUninventoryDto> getWasteBatchNoQty(Page page, StockInventoryDto stockInventoryDto);
    List<StockUninventoryDto> getWasteByModelId(Long productModelId);
    Integer addStockUninventory(StockUninventoryDto stockUninventoryDto);
    Integer subtractStockUninventory(StockUninventoryDto stockUninventoryDto);
@@ -31,6 +39,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;
@@ -101,9 +104,11 @@
                    stockInRecordDto.setQualitity(stockInRecord.getStockInNum());
                    stockInventoryMapper.updateSubtractStockInventory(stockInRecordDto);
                }
            }else if (stockInRecord.getType().equals("1")) {
            }else if (stockInRecord.getType().equals("1") || stockInRecord.getType().equals("2")) {
                String uninventoryType = resolveUninventoryTypeByInRecordType(stockInRecord.getType());
                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 {
@@ -269,9 +278,10 @@
                    } else {
                        stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
                    }
                } else if ("1".equals(stockInRecord.getType())) {
                } else if ("1".equals(stockInRecord.getType()) || "2".equals(stockInRecord.getType())) {
                    // ä¸åˆæ ¼å…¥åº“ -> å…ˆæŸ¥åº“存,存在则更新,不存在则新增
                    StockUninventory stockUninventory = getStockUninventory(stockInRecord.getProductModelId(), stockInRecord.getBatchNo());
                    String uninventoryType = resolveUninventoryTypeByInRecordType(stockInRecord.getType());
                    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 stockInType) {
        if ("2".equals(stockInType)) {
            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);
                }
            }
@@ -136,10 +142,19 @@
    public void exportStockOutRecord(HttpServletResponse response, StockOutRecordDto stockOutRecordDto) {
        List<StockOutRecordExportData> list = stockOutRecordMapper.listStockOutRecordExportData(stockOutRecordDto);
        for (StockOutRecordExportData stockInRecordExportData : list) {
            if (stockInRecordExportData.getType().equals("0")) {
                stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockOutQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
            }else {
                stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockInQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
            if (StringUtils.isEmpty(stockInRecordExportData.getRecordType())) {
                continue;
            }
            StockOutQualifiedRecordTypeEnum outRecordTypeEnum =
                    EnumUtil.fromCode(StockOutQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType()));
            if (outRecordTypeEnum != null) {
                stockInRecordExportData.setRecordType(outRecordTypeEnum.getValue());
                continue;
            }
            StockInQualifiedRecordTypeEnum inRecordTypeEnum =
                    EnumUtil.fromCode(StockInQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType()));
            if (inRecordTypeEnum != null) {
                stockInRecordExportData.setRecordType(inRecordTypeEnum.getValue());
            }
        }
        ExcelUtil<StockOutRecordExportData> util = new ExcelUtil<>(StockOutRecordExportData.class);
@@ -195,7 +210,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 +219,7 @@
                    stockUninventoryDto.setProductModelId(stockOutRecord.getProductModelId());
                    stockUninventoryDto.setBatchNo(stockOutRecord.getBatchNo());
                    stockUninventoryDto.setQualitity(stockOutRecord.getStockOutNum());
                    stockUninventoryDto.setType(uninventoryType);
                    stockUninventoryMapper.updateSubtractStockUnInventory(stockUninventoryDto);
                }
            }
@@ -221,9 +238,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 +251,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
@@ -13,6 +13,7 @@
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.stock.enums.StockInventorySourceEnum;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockOutRecordDto;
@@ -48,7 +49,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 +62,46 @@
    }
    @Override
    public IPage<StockUninventoryDto> pageWasteQuery(Page page, StockUninventoryDto stockUninventoryDto) {
        IPage<StockUninventoryDto> resultPage = stockUninventoryMapper.pageWasteQuery(page, stockUninventoryDto);
        resultPage.getRecords().forEach(dto -> {
            if (StringUtils.isNotEmpty(dto.getSource())) {
                StockInventorySourceEnum sourceEnum = StockInventorySourceEnum.fromCode(dto.getSource());
                if (sourceEnum != null) {
                    dto.setSourceText(sourceEnum.getValue());
                }
            }
        });
        return resultPage;
    }
    @Override
    public IPage<StockUninventoryDto> getWasteBatchNoQty(Page page, StockInventoryDto stockInventoryDto) {
        IPage<StockUninventoryDto> resultPage = stockUninventoryMapper.getWasteBatchNoQty(page, stockInventoryDto);
        resultPage.getRecords().forEach(dto -> {
            if (StringUtils.isNotEmpty(dto.getSource())) {
                StockInventorySourceEnum unQualifiedSourceEnum = StockInventorySourceEnum.fromCode(dto.getSource());
                if (unQualifiedSourceEnum != null) {
                    dto.setSourceText(unQualifiedSourceEnum.getValue());
                }
            }
        });
        return resultPage;
    }
    @Override
    public List<StockUninventoryDto> getWasteByModelId(Long productModelId) {
        return stockUninventoryMapper.getWasteByModelId(productModelId);
    }
    @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);
@@ -78,7 +115,11 @@
        stockInRecordDto.setStockInNum(stockUninventoryDto.getQualitity());
        stockInRecordDto.setBatchNo(stockUninventoryDto.getBatchNo());
        stockInRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
        stockInRecordDto.setType("1");
        if (UNQUALIFIED_TYPE.equals(inventoryType)) {
            stockInRecordDto.setType("1");
        } else {
            stockInRecordDto.setType("2");
        }
        stockInRecordDto.setManufacturerId(stockUninventoryDto.getManufacturerId());
        stockInRecordDto.setSource(stockUninventoryDto.getSource());
        stockInRecordService.add(stockInRecordDto);
@@ -89,6 +130,8 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer subtractStockUninventory(StockUninventoryDto stockUninventoryDto) {
        String inventoryType = resolveInventoryType(stockUninventoryDto);
        stockUninventoryDto.setType(inventoryType);
        //  æ–°å¢žå‡ºåº“记录
        StockOutRecordDto stockOutRecordDto = new StockOutRecordDto();
@@ -99,7 +142,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 +156,8 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer addStockInRecordOnly(StockUninventoryDto stockUninventoryDto) {
        String inventoryType = resolveInventoryType(stockUninventoryDto);
        stockUninventoryDto.setType(inventoryType);
        StockInRecordDto stockInRecordDto = new StockInRecordDto();
        stockInRecordDto.setRecordId(stockUninventoryDto.getRecordId());
        stockInRecordDto.setRecordType(stockUninventoryDto.getRecordType());
@@ -121,7 +168,11 @@
        }
        stockInRecordDto.setBatchNo(batchNo);
        stockInRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
        stockInRecordDto.setType("1");
        if (UNQUALIFIED_TYPE.equals(inventoryType)) {
            stockInRecordDto.setType("1");
        } else {
            stockInRecordDto.setType("2");
        }
        stockInRecordDto.setRemark(stockUninventoryDto.getRemark());
        stockInRecordService.add(stockInRecordDto);
        return 1;
@@ -130,8 +181,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 {
@@ -172,8 +226,31 @@
    @Override
    public void exportStockUninventory(HttpServletResponse response, StockUninventoryDto stockUninventoryDto) {
        List<StockUnInventoryExportData> list = stockUninventoryMapper.listStockInventoryExportData(stockUninventoryDto);
        list.forEach(item -> {
            if (StringUtils.isNotEmpty(item.getSource())) {
                StockInventorySourceEnum sourceEnum = StockInventorySourceEnum.fromCode(item.getSource());
                if (sourceEnum != null) {
                    item.setSourceText(sourceEnum.getValue());
                }
            }
        });
        ExcelUtil<StockUnInventoryExportData> util = new ExcelUtil<>(StockUnInventoryExportData.class);
        util.exportExcel(response,list, "不合格库存信息");
    }
    @Override
    public void exportWasteQuery(HttpServletResponse response, StockUninventoryDto stockUninventoryDto) {
        List<StockUnInventoryExportData> list = stockUninventoryMapper.listWasteQueryExportData(stockUninventoryDto);
        list.forEach(item -> {
            if (StringUtils.isNotEmpty(item.getSource())) {
                StockInventorySourceEnum sourceEnum = StockInventorySourceEnum.fromCode(item.getSource());
                if (sourceEnum != null) {
                    item.setSourceText(sourceEnum.getValue());
                }
            }
        });
        ExcelUtil<StockUnInventoryExportData> util = new ExcelUtil<>(StockUnInventoryExportData.class);
        util.exportExcel(response, list, "废品查询信息");
    }
    @Override
@@ -200,6 +277,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/procurementrecord/ReturnSaleProductMapper.xml
@@ -15,6 +15,7 @@
            p.product_name                                         as product_name,
               pm.model                                     as model,
               pm.unit                                      as unit,
               COALESCE(spd.stock_type, slp.stock_type, 'qualified') as stock_type,
               rsp.*,
            sor.outbound_batches,
            sor.stock_out_num,
@@ -24,8 +25,13 @@
        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 shipping_product_detail spd ON spd.shipping_info_id = si.id
                 LEFT JOIN stock_out_record sor ON rsp.stock_out_record_id = sor.id and sor.record_type = '13'
                 LEFT JOIN shipping_product_detail spd ON spd.shipping_info_id = si.id
                    AND spd.product_model_id = sor.product_model_id
                    AND (
                        (spd.batch_no = sor.batch_no)
                        OR (spd.batch_no IS NULL AND sor.batch_no IS NULL)
                    )
                 LEFT JOIN sales_ledger_product slp ON si.sales_ledger_product_id = slp.id and slp.type = 1
                left join product_model pm on slp.product_model_id = pm.id
                  LEFT JOIN product p on pm.product_id = p.id
@@ -44,13 +50,20 @@
    <select id="listReturnSaleProduct" resultType="com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto">
        select rsp.*,
               sor.batch_no,
               COALESCE(spd.stock_type, slp.stock_type, 'qualified') as stock_type,
               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
                 LEFT JOIN shipping_product_detail spd ON spd.shipping_info_id = si.id
                    AND spd.product_model_id = sor.product_model_id
                    AND (
                        (spd.batch_no = sor.batch_no)
                        OR (spd.batch_no IS NULL AND sor.batch_no IS NULL)
                    )
                 LEFT JOIN sales_ledger_product slp ON si.sales_ledger_product_id = slp.id and slp.type = 1
        where rsp.return_management_id = #{returnManagementId}
    </select>
src/main/resources/mapper/sales/SalesLedgerMapper.xml
@@ -62,10 +62,17 @@
        T1.payment_method,
        T1.delivery_date,
        DATEDIFF(T1.delivery_date, CURDATE()) AS delivery_days_diff,
        COALESCE(stock_type_info.stock_type, 'qualified') AS stock_type,
        IFNULL(shipping_status_counts.is_all_shipped, FALSE) AS is_fh
        FROM sales_ledger T1
        LEFT JOIN sys_user T2 ON T1.entry_person = T2.user_id
        LEFT JOIN (
        SELECT sales_ledger_id, MAX(stock_type) AS stock_type
        FROM sales_ledger_product
        WHERE type = 1
        GROUP BY sales_ledger_id
        ) stock_type_info ON T1.id = stock_type_info.sales_ledger_id
        LEFT JOIN (
        SELECT sales_ledger_id,
        CASE
        WHEN SUM(CASE WHEN status != '已发货' THEN 1 ELSE 0 END) = 0 THEN TRUE
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -21,6 +21,7 @@
        T1.type,
        T1.product_id,
        T1.product_model_id,
        T1.stock_type,
        T1.register,
        T1.register_date,
        T1.approve_status,
@@ -32,7 +33,12 @@
        pm.model as specification_model,
        pm.unit as unit,
        CASE
        WHEN (IFNULL(t2.qualitity, 0) - IFNULL(t2.locked_quantity, 0)) >0 THEN 1
        WHEN IFNULL(
            CASE
                WHEN T1.stock_type = 'waste' THEN t2.waste_available_quantity
                ELSE t2.qualified_available_quantity
            END, 0
        ) >= (IFNULL(T1.quantity, 0) - IFNULL(t3.shipped_quantity, 0)) THEN 1
        ELSE 0
        END as has_sufficient_stock,
        (IFNULL(T1.quantity, 0) - IFNULL(t3.shipped_quantity, 0)) as no_quantity,
@@ -52,9 +58,47 @@
        FROM
        sales_ledger_product T1
        LEFT JOIN (
        SELECT product_model_id, SUM(qualitity) as qualitity, SUM(locked_quantity) as locked_quantity
        FROM stock_inventory
        GROUP BY product_model_id
        SELECT
            t.product_model_id,
            SUM(t.qualified_available_quantity) as qualified_available_quantity,
            SUM(t.waste_available_quantity) as waste_available_quantity
        FROM (
            SELECT
                si.product_model_id,
                (IFNULL(si.qualitity, 0) - IFNULL(si.locked_quantity, 0) - IFNULL((
                    SELECT SUM(sor.stock_out_num)
                    FROM stock_out_record sor
                    WHERE sor.product_model_id = si.product_model_id
                      AND (
                        (si.batch_no IS NULL AND sor.batch_no IS NULL)
                        OR si.batch_no = sor.batch_no
                      )
                      AND sor.type = '0'
                      AND sor.approval_status IN (0, 3)
                ), 0)) as qualified_available_quantity,
                0 as waste_available_quantity
            FROM stock_inventory si
            UNION ALL
            SELECT
                su.product_model_id,
                0 as qualified_available_quantity,
                (IFNULL(su.qualitity, 0) - IFNULL(su.locked_quantity, 0) - IFNULL((
                    SELECT SUM(sor.stock_out_num)
                    FROM stock_out_record sor
                    WHERE sor.product_model_id = su.product_model_id
                      AND (
                        (su.batch_no IS NULL AND sor.batch_no IS NULL)
                        OR su.batch_no = sor.batch_no
                      )
                      AND sor.type = '1'
                      AND sor.approval_status IN (0, 3)
                ), 0)) as waste_available_quantity
            FROM stock_uninventory su
            WHERE COALESCE(su.type, 'unqualified') = 'waste'
        ) t
        GROUP BY t.product_model_id
        ) t2 ON T1.product_model_id = t2.product_model_id
        LEFT JOIN (
        SELECT sales_ledger_product_id, IFNULL(SUM(spd.quantity), 0) as shipped_quantity
src/main/resources/mapper/sales/ShippingInfoMapper.xml
@@ -73,6 +73,7 @@
            slp.specification_model,
            slp.unit,
            slp.product_model_id,
            COALESCE(spd.stock_type, slp.stock_type, 'qualified') AS stock_type,
            sor.outbound_batches,
            sor.stock_out_num,
            sor.batch_no,
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,56 @@
<!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
        left join manufacturer m on su.manufacturer_id = m.id
    </sql>
    <sql id="WastePageColumns">
        su.id,
        su.qualitity,
        su.type,
        COALESCE(su.locked_quantity, 0) as locked_quantity,
        su.product_model_id,
        p.id as product_id,
        su.batch_no,
        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,
        m.name as manufacturer_name,
        su.source
    </sql>
    <update id="updateSubtractStockUnInventory">
        update stock_uninventory
        <set>
@@ -20,21 +61,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 +94,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 +113,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 +129,152 @@
        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
        p.product_name,
        m.name as manufacturer_name
        <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,
        m.name as manufacturer_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="getWasteBatchNoQty" resultType="com.ruoyi.stock.dto.StockUninventoryDto">
        select
        su.id,
        p.product_name,
        pm.model,
        m.name as manufacturer_name,
        su.source,
        pm.unit,
        su.batch_no,
        su.qualitity,
        COALESCE(su.locked_quantity, 0) as locked_quantity,
        (su.qualitity - COALESCE(su.locked_quantity, 0)) as un_locked_quantity,
        su.remark,
        su.update_time,
        su.product_model_id
        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>
        left join manufacturer m on su.manufacturer_id = m.id
        <where>
            and su.type = 'waste'
            <if test="ew.productModelId != null and ew.productModelId > 0">
                and su.product_model_id = #{ew.productModelId}
            </if>
            <if test="ew.productId != null and ew.productId > 0">
                and p.id = #{ew.productId}
            </if>
        </where>
        order by su.batch_no
    </select>
    <select id="getWasteByModelId" resultType="com.ruoyi.stock.dto.StockUninventoryDto">
        select
            su.id,
            su.batch_no,
            COALESCE(su.locked_quantity, 0) as locked_quantity,
            (su.qualitity - IFNULL(sd.qualitity, 0)) as qualitity,
            p.product_name,
            pm.model,
            pm.unit,
            su.product_model_id
        from stock_uninventory su
        left join (
            select
                sor.product_model_id,
                sor.batch_no,
                sum(spd.quantity) as qualitity
            from shipping_product_detail spd
            inner join stock_out_record sor
                on sor.record_id = spd.shipping_info_id
               and sor.record_type = '13'
               and sor.type = '1'
               and sor.approval_status in (0, 3)
               and sor.product_model_id = spd.product_model_id
               and (sor.batch_no = spd.batch_no or (sor.batch_no is null and spd.batch_no is null))
            where spd.stock_type = 'waste'
            group by sor.product_model_id, sor.batch_no
        ) sd on sd.product_model_id = su.product_model_id
            and (sd.batch_no = su.batch_no or (sd.batch_no is null and su.batch_no is null))
        left join product_model pm on su.product_model_id = pm.id
        left join product p on pm.product_id = p.id
        where su.product_model_id = #{productModelId}
          and su.type = 'waste'
          and su.qualitity > IFNULL(sd.qualitity, 0)
        order by su.batch_no
    </select>
    <select id="selectPendingOutQuantity" resultType="java.math.BigDecimal">