From 6b4cfc6f9d660b92be99ba4e3411a3267bc57155 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期六, 18 四月 2026 15:24:33 +0800
Subject: [PATCH] feat: 销售/采购订单的扫码合格/不合格出入库功能

---
 src/main/java/com/ruoyi/purchase/dto/PurchaseScanStockDto.java                |   22 +
 src/main/java/com/ruoyi/sales/dto/SalesScanInboundDto.java                    |   23 +
 src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java          |   10 
 src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java  |  370 ++++++++++++++++++---
 src/main/resources/mapper/stock/StockInventoryMapper.xml                      |    3 
 src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java                    |   26 +
 src/main/java/com/ruoyi/stock/dto/StockUninventoryDto.java                    |   12 
 src/main/java/com/ruoyi/common/enums/StockInUnQualifiedRecordTypeEnum.java    |    4 
 src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java                |    7 
 src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java               |   77 ++++
 doc/河南鹤壁天沐钢化玻璃厂.sql                                                           |    8 
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java |   10 
 src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java           |   28 +
 src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java   |    9 
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java        |  303 +++++++++++++++++
 src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java     |   29 +
 src/main/resources/mapper/stock/StockUninventoryMapper.xml                    |    3 
 src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java     |    4 
 src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java     |    5 
 src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java      |    4 
 src/main/java/com/ruoyi/common/enums/StockOutUnQualifiedRecordTypeEnum.java   |    4 
 21 files changed, 887 insertions(+), 74 deletions(-)

diff --git "a/doc/\346\262\263\345\215\227\351\271\244\345\243\201\345\244\251\346\262\220\351\222\242\345\214\226\347\216\273\347\222\203\345\216\202.sql" "b/doc/\346\262\263\345\215\227\351\271\244\345\243\201\345\244\251\346\262\220\351\222\242\345\214\226\347\216\273\347\222\203\345\216\202.sql"
index 0585f98..b305a1e 100644
--- "a/doc/\346\262\263\345\215\227\351\271\244\345\243\201\345\244\251\346\262\220\351\222\242\345\214\226\347\216\273\347\222\203\345\216\202.sql"
+++ "b/doc/\346\262\263\345\215\227\351\271\244\345\243\201\345\244\251\346\262\220\351\222\242\345\214\226\347\216\273\347\222\203\345\216\202.sql"
@@ -122,4 +122,10 @@
     MODIFY COLUMN `stock_out_num` decimal(16, 4) NULL DEFAULT NULL COMMENT '鍑哄簱鏁伴噺' AFTER `outbound_batches`,
     MODIFY COLUMN `record_id` int NULL DEFAULT NULL COMMENT '鍑哄簱鏉ユ簮id' AFTER `stock_out_num`,
     ADD COLUMN `sales_ledger_id`         bigint NULL COMMENT '閿�鍞鍗旾D' AFTER `type`,
-    ADD COLUMN `sales_ledger_product_id` bigint NULL COMMENT '閿�鍞鍗曚骇鍝両D' AFTER `sales_ledger_id`;
\ No newline at end of file
+    ADD COLUMN `sales_ledger_product_id` bigint NULL COMMENT '閿�鍞鍗曚骇鍝両D' AFTER `sales_ledger_id`;
+
+ALTER TABLE `sales_ledger_product`
+    ADD COLUMN `stocked_quantity` decimal(18, 2) DEFAULT 0.00 COMMENT '宸插叆搴撴暟閲�' AFTER `product_stock_status`;
+
+ALTER TABLE `sales_ledger_product`
+    ADD COLUMN `remaining_quantity` decimal(18, 2) DEFAULT '0.00' COMMENT '鍓╀綑寰呭叆搴撴暟閲�' AFTER `stocked_quantity`
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java b/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
index 05a6ffb..5349ab7 100644
--- a/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
@@ -12,7 +12,9 @@
     QUALITYINSPECT_STOCK_IN("6", "璐ㄦ-鍚堟牸鍏ュ簱"),
     DEFECTIVE_PASS("11", "涓嶅悎鏍�-璁╂鏀捐"),
     RETURN_HE_IN("14", "閿�鍞��璐�-鍚堟牸鍏ュ簱"),
-    SALE_STOCK_IN("15", "閿�鍞鍗�-鍚堟牸鍏ュ簱");
+    SALE_STOCK_IN("15", "閿�鍞鍗�-鍚堟牸鍏ュ簱"),
+    SALE_SCAN_STOCK_IN("17", "閿�鍞鍗曟壂鐮�-鍚堟牸鍏ュ簱"),
+    PURCHASE_SCAN_STOCK_IN("18", "閲囪喘璁㈠崟鎵爜-鍚堟牸鍏ュ簱");
 
 
     private final String code;
diff --git a/src/main/java/com/ruoyi/common/enums/StockInUnQualifiedRecordTypeEnum.java b/src/main/java/com/ruoyi/common/enums/StockInUnQualifiedRecordTypeEnum.java
index a03e6bd..1c6db55 100644
--- a/src/main/java/com/ruoyi/common/enums/StockInUnQualifiedRecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/common/enums/StockInUnQualifiedRecordTypeEnum.java
@@ -11,7 +11,9 @@
     PRODUCTION_SCRAP("5", "鐢熶骇鎶ュ伐-鎶ュ簾"),
     CUSTOMIZATION_UNSTOCK_IN("9", "涓嶅悎鏍艰嚜瀹氫箟鍏ュ簱"),
     QUALITYINSPECT_UNSTOCK_IN("12", "璐ㄦ-涓嶅悎鏍煎叆搴�"),
-    RETURN_UNSTOCK_IN("15", "閿�鍞��璐�-涓嶅悎鏍煎叆搴�");
+    RETURN_UNSTOCK_IN("15", "閿�鍞��璐�-涓嶅悎鏍煎叆搴�"),
+    SALES_SCAN_UNSTOCK_IN("16", "閿�鍞鍗曟壂鐮�-涓嶅悎鏍煎叆搴�"),
+    PURCHASE_SCAN_UNSTOCK_IN("19", "閲囪喘璁㈠崟鎵爜-涓嶅悎鏍煎叆搴�");
 
 
     private final String code;
diff --git a/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java b/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
index 5e8fe53..3ec4a0d 100644
--- a/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
@@ -9,7 +9,9 @@
     PRODUCTION_REPORT_STOCK_OUT("3", "鐢熶骇鎶ュ伐-鍑哄簱"),
     SALE_STOCK_OUT("8", "閿�鍞�-鍑哄簱"),
     PURCHASE_RETURN_STOCK_OUT("9", "閲囪喘閫�璐�"),
-    SALE_SHIP_STOCK_OUT("13", "閿�鍞�-鍙戣揣鍑哄簱");
+    SALE_SHIP_STOCK_OUT("13", "閿�鍞�-鍙戣揣鍑哄簱"),
+    SALE_SCAN_STOCK_OUT("17", "閿�鍞鍗曟壂鐮�-鍚堟牸鍑哄簱"),
+    PURCHASE_SCAN_STOCK_OUT("16", "閲囪喘璁㈠崟鎵爜-鍚堟牸鍑哄簱");
 
     private final String code;
     private final String value;
diff --git a/src/main/java/com/ruoyi/common/enums/StockOutUnQualifiedRecordTypeEnum.java b/src/main/java/com/ruoyi/common/enums/StockOutUnQualifiedRecordTypeEnum.java
index 2ee40bd..93e1908 100644
--- a/src/main/java/com/ruoyi/common/enums/StockOutUnQualifiedRecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/common/enums/StockOutUnQualifiedRecordTypeEnum.java
@@ -7,7 +7,9 @@
 public enum StockOutUnQualifiedRecordTypeEnum implements BaseEnum<String> {
 
 
-    CUSTOMIZATION_UNSTOCK_OUT("10", "涓嶅悎鏍艰嚜瀹氫箟鍑哄簱");
+    CUSTOMIZATION_UNSTOCK_OUT("10", "涓嶅悎鏍艰嚜瀹氫箟鍑哄簱"),
+    SALE_SCAN_UNSTOCK_OUT("11", "閿�鍞鍗曟壂鐮�-涓嶅悎鏍煎嚭搴�"),
+    PURCHASE_SCAN_UNSTOCK_OUT("12", "閲囪喘璁㈠崟鎵爜-涓嶅悎鏍煎嚭搴�");
 
 
     private final String code;
diff --git a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
index b1bb2cf..4b9dd7b 100644
--- a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
+++ b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
@@ -2,13 +2,17 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
 import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper;
 import com.ruoyi.stock.dto.StockInRecordDto;
 import com.ruoyi.stock.dto.StockInventoryDto;
 import com.ruoyi.stock.dto.StockUninventoryDto;
 import com.ruoyi.stock.pojo.StockInRecord;
+import com.ruoyi.stock.pojo.StockInventory;
 import com.ruoyi.stock.pojo.StockOutRecord;
+import com.ruoyi.stock.pojo.StockUninventory;
 import com.ruoyi.stock.service.StockInRecordService;
 import com.ruoyi.stock.service.StockInventoryService;
 import com.ruoyi.stock.service.StockOutRecordService;
@@ -34,6 +38,52 @@
     private final StockOutRecordService stockOutRecordService;
 
     /**
+     * 鍚堟牸搴撳瓨鍑哄簱鍓嶆牎楠岋細鎸� stock_inventory 鍙敤鏁伴噺锛堟暟閲� 鈭� 鍐荤粨锛�
+     */
+    public void assertQualifiedAvailable(Long productModelId, BigDecimal outboundQty) {
+        if (productModelId == null) {
+            throw new ServiceException("鍑哄簱澶辫触,浜у搧瑙勬牸鏈淮鎶�");
+        }
+        if (outboundQty == null || outboundQty.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        StockInventory inv = stockInventoryService.getOne(
+                Wrappers.<StockInventory>lambdaQuery().eq(StockInventory::getProductModelId, productModelId));
+        if (inv == null) {
+            throw new ServiceException("鍑哄簱澶辫触,鍚堟牸搴撲腑鏃犺浜у搧搴撳瓨");
+        }
+        BigDecimal locked = inv.getLockedQuantity() == null ? BigDecimal.ZERO : inv.getLockedQuantity();
+        BigDecimal qty = inv.getQualitity() == null ? BigDecimal.ZERO : inv.getQualitity();
+        BigDecimal available = qty.subtract(locked);
+        if (outboundQty.compareTo(available) > 0) {
+            throw new ServiceException("鍑哄簱澶辫触,鍑哄簱鏁伴噺涓嶈兘澶т簬褰撳墠鍚堟牸搴撳瓨鍙敤鏁伴噺");
+        }
+    }
+
+    /**
+     * 涓嶅悎鏍煎簱瀛樺嚭搴撳墠鏍¢獙锛氭寜 stock_uninventory 鍙敤鏁伴噺锛堟暟閲� 鈭� 鍐荤粨锛�
+     */
+    public void assertUnqualifiedAvailable(Long productModelId, BigDecimal outboundQty) {
+        if (productModelId == null) {
+            throw new ServiceException("鍑哄簱澶辫触,浜у搧瑙勬牸鏈淮鎶�");
+        }
+        if (outboundQty == null || outboundQty.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        StockUninventory inv = stockUninventoryService.getOne(
+                Wrappers.<StockUninventory>lambdaQuery().eq(StockUninventory::getProductModelId, productModelId));
+        if (inv == null) {
+            throw new ServiceException("鍑哄簱澶辫触,涓嶅悎鏍煎簱涓棤璇ヤ骇鍝佸簱瀛�");
+        }
+        BigDecimal locked = inv.getLockedQuantity() == null ? BigDecimal.ZERO : inv.getLockedQuantity();
+        BigDecimal qty = inv.getQualitity() == null ? BigDecimal.ZERO : inv.getQualitity();
+        BigDecimal available = qty.subtract(locked);
+        if (outboundQty.compareTo(available) > 0) {
+            throw new ServiceException("鍑哄簱澶辫触,鍑哄簱鏁伴噺涓嶈兘澶т簬褰撳墠涓嶅悎鏍煎簱瀛樺彲鐢ㄦ暟閲�");
+        }
+    }
+
+    /**
      * 涓嶅悎鏍煎叆搴�
      *
      * @param productModelId
@@ -42,28 +92,41 @@
      * @param recordId
      */
     public void addUnStock(Long productModelId, BigDecimal quantity, String recordType, Long recordId) {
+        addUnStock(null, null, productModelId, quantity, recordType, recordId);
+    }
+
+    /**
+     * 涓嶅悎鏍煎叆搴擄紙甯﹂攢鍞鍗曞叧鑱旓紝鍐欏叆鍏ュ簱璁板綍锛�
+     */
+    public void addUnStock(Long salesLedgerId, Long salesLedgerProductId, Long productModelId, BigDecimal quantity, String recordType, Long recordId) {
         StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
         stockUninventoryDto.setRecordId(recordId);
         stockUninventoryDto.setRecordType(String.valueOf(recordType));
         stockUninventoryDto.setQualitity(quantity);
         stockUninventoryDto.setProductModelId(productModelId);
+        stockUninventoryDto.setSalesLedgerId(salesLedgerId);
+        stockUninventoryDto.setSalesLedgerProductId(salesLedgerProductId);
         stockUninventoryService.addStockUninventory(stockUninventoryDto);
     }
 
     /**
      * 涓嶅悎鏍煎嚭搴�
-     *
-     * @param productModelId
-     * @param quantity
-     * @param recordType
-     * @param recordId
      */
-    public void subtractUnStock(Long productModelId, BigDecimal quantity, Integer recordType, Long recordId) {
+    public void subtractUnStock(Long productModelId, BigDecimal quantity, String recordType, Long recordId) {
+        subtractUnStock(null, null, productModelId, quantity, recordType, recordId);
+    }
+
+    /**
+     * 涓嶅悎鏍煎嚭搴擄紙鍙甫閿�鍞鍗曞叧鑱斿啓鍏ュ嚭搴撹褰曪級
+     */
+    public void subtractUnStock(Long salesLedgerId, Long salesLedgerProductId, Long productModelId, BigDecimal quantity, String recordType, Long recordId) {
         StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
         stockUninventoryDto.setRecordId(recordId);
-        stockUninventoryDto.setRecordType(String.valueOf(recordType));
+        stockUninventoryDto.setRecordType(recordType);
         stockUninventoryDto.setQualitity(quantity);
         stockUninventoryDto.setProductModelId(productModelId);
+        stockUninventoryDto.setSalesLedgerId(salesLedgerId);
+        stockUninventoryDto.setSalesLedgerProductId(salesLedgerProductId);
         stockUninventoryService.subtractStockUninventory(stockUninventoryDto);
     }
 
diff --git a/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java b/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
index 2358caf..614d44e 100644
--- a/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
+++ b/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
@@ -11,6 +11,7 @@
 import com.ruoyi.framework.web.domain.AjaxResult;
 import com.ruoyi.framework.web.page.TableDataInfo;
 import com.ruoyi.purchase.dto.PurchaseLedgerDto;
+import com.ruoyi.purchase.dto.PurchaseScanStockDto;
 import com.ruoyi.purchase.mapper.PurchaseLedgerTemplateMapper;
 import com.ruoyi.purchase.mapper.SalesLedgerProductTemplateMapper;
 import com.ruoyi.purchase.pojo.PurchaseLedger;
@@ -268,4 +269,32 @@
     public AjaxResult createPurchaseNo() {
         return AjaxResult.success("鐢熸垚鎴愬姛",purchaseLedgerService.getPurchaseNo());
     }
+
+    @PostMapping("/scanInbound")
+    @ApiOperation("閲囪喘璁㈠崟鎵爜-鍚堟牸鍏ュ簱")
+    public AjaxResult scanInbound(@RequestBody PurchaseScanStockDto dto) {
+        purchaseLedgerService.scanInbound(dto);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/scanInboundUnqualified")
+    @ApiOperation("閲囪喘璁㈠崟鎵爜-涓嶅悎鏍煎叆搴�")
+    public AjaxResult scanInboundUnqualified(@RequestBody PurchaseScanStockDto dto) {
+        purchaseLedgerService.scanInboundUnqualified(dto);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/scanOutbound")
+    @ApiOperation("閲囪喘璁㈠崟鎵爜-鍚堟牸鍑哄簱")
+    public AjaxResult scanOutbound(@RequestBody PurchaseScanStockDto dto) {
+        purchaseLedgerService.scanOutbound(dto);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/scanOutboundUnqualified")
+    @ApiOperation("閲囪喘璁㈠崟鎵爜-涓嶅悎鏍煎嚭搴�")
+    public AjaxResult scanOutboundUnqualified(@RequestBody PurchaseScanStockDto dto) {
+        purchaseLedgerService.scanOutboundUnqualified(dto);
+        return AjaxResult.success();
+    }
 }
diff --git a/src/main/java/com/ruoyi/purchase/dto/PurchaseScanStockDto.java b/src/main/java/com/ruoyi/purchase/dto/PurchaseScanStockDto.java
new file mode 100644
index 0000000..00df58b
--- /dev/null
+++ b/src/main/java/com/ruoyi/purchase/dto/PurchaseScanStockDto.java
@@ -0,0 +1,22 @@
+package com.ruoyi.purchase.dto;
+
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 閲囪喘璁㈠崟鎵爜鍏ュ嚭搴�(鍚堟牸/涓嶅悎鏍�)Dto
+ */
+@Data
+@ApiModel(value = "PurchaseScanStockDto", description = "閲囪喘璁㈠崟鎵爜鍏ュ嚭搴�(鍚堟牸/涓嶅悎鏍�)")
+public class PurchaseScanStockDto {
+
+    @ApiModelProperty("閲囪喘璁㈠崟Id")
+    private Long purchaseLedgerId;
+
+    @ApiModelProperty("閲囪喘浜у搧琛屾暟鎹�")
+    private List<SalesLedgerProduct> salesLedgerProductList;
+}
diff --git a/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java b/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
index 34d19b3..f57288b 100644
--- a/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
+++ b/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
@@ -5,11 +5,11 @@
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ruoyi.framework.web.domain.AjaxResult;
 import com.ruoyi.purchase.dto.PurchaseLedgerDto;
+import com.ruoyi.purchase.dto.PurchaseScanStockDto;
 import com.ruoyi.purchase.pojo.PurchaseLedger;
 import com.ruoyi.sales.pojo.InvoiceRegistrationProduct;
 import org.springframework.web.multipart.MultipartFile;
 
-import java.io.IOException;
 import java.util.List;
 
 /**
@@ -45,4 +45,12 @@
     AjaxResult importData(MultipartFile file);
 
     PurchaseLedgerDto getPurchaseByCode(PurchaseLedgerDto purchaseLedgerDto);
+
+    void scanInbound(PurchaseScanStockDto dto);
+
+    void scanInboundUnqualified(PurchaseScanStockDto dto);
+
+    void scanOutbound(PurchaseScanStockDto dto);
+
+    void scanOutboundUnqualified(PurchaseScanStockDto dto);
 }
diff --git a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
index b23148a..2d0ceae 100644
--- a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -1,29 +1,28 @@
 package com.ruoyi.purchase.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.ruoyi.account.pojo.AccountExpense;
-import com.ruoyi.account.pojo.AccountIncome;
 import com.ruoyi.account.service.AccountExpenseService;
-import com.ruoyi.account.service.AccountIncomeService;
 import com.ruoyi.approve.pojo.ApproveProcess;
 import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
 import com.ruoyi.approve.vo.ApproveProcessVO;
 import com.ruoyi.basic.mapper.ProductMapper;
 import com.ruoyi.basic.mapper.ProductModelMapper;
 import com.ruoyi.basic.mapper.SupplierManageMapper;
-import com.ruoyi.basic.pojo.Customer;
 import com.ruoyi.basic.pojo.Product;
 import com.ruoyi.basic.pojo.ProductModel;
 import com.ruoyi.basic.pojo.SupplierManage;
 import com.ruoyi.common.enums.FileNameType;
+import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.StockOutUnQualifiedRecordTypeEnum;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.exception.base.BaseException;
-import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -32,26 +31,27 @@
 import com.ruoyi.other.mapper.TempFileMapper;
 import com.ruoyi.other.pojo.TempFile;
 import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
+import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
 import com.ruoyi.project.system.domain.SysUser;
 import com.ruoyi.project.system.mapper.SysUserMapper;
 import com.ruoyi.purchase.dto.PurchaseLedgerDto;
 import com.ruoyi.purchase.dto.PurchaseLedgerImportDto;
 import com.ruoyi.purchase.dto.PurchaseLedgerProductImportDto;
+import com.ruoyi.purchase.dto.PurchaseScanStockDto;
 import com.ruoyi.purchase.mapper.*;
 import com.ruoyi.purchase.pojo.*;
 import com.ruoyi.purchase.service.IPurchaseLedgerService;
 import com.ruoyi.quality.mapper.*;
 import com.ruoyi.quality.pojo.*;
-import com.ruoyi.sales.dto.SalesLedgerImportDto;
-import com.ruoyi.sales.dto.SalesLedgerProductImportDto;
 import com.ruoyi.sales.mapper.*;
 import com.ruoyi.sales.pojo.CommonFile;
 import com.ruoyi.sales.pojo.InvoiceRegistrationProduct;
 import com.ruoyi.sales.pojo.SalesLedger;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
-import lombok.RequiredArgsConstructor;
+import com.ruoyi.stock.dto.StockInventoryDto;
+import com.ruoyi.stock.service.StockInventoryService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.FilenameUtils;
 import org.springframework.beans.BeanUtils;
@@ -78,6 +78,8 @@
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+import static com.ruoyi.common.enums.SaleEnum.PURCHASE;
+
 /**
  * 閲囪喘鍙拌处Service涓氬姟灞傚鐞�
  *
@@ -88,68 +90,72 @@
 @Slf4j
 public class PurchaseLedgerServiceImpl extends ServiceImpl<PurchaseLedgerMapper, PurchaseLedger> implements IPurchaseLedgerService {
     @Autowired
-    private  AccountExpenseService accountExpenseService;
+    private AccountExpenseService accountExpenseService;
     @Autowired
-    private  PurchaseLedgerMapper purchaseLedgerMapper;
+    private PurchaseLedgerMapper purchaseLedgerMapper;
 
     @Autowired
-    private  SalesLedgerMapper salesLedgerMapper;
+    private SalesLedgerMapper salesLedgerMapper;
     @Autowired
-    private  SalesLedgerProductMapper salesLedgerProductMapper;
+    private SalesLedgerProductMapper salesLedgerProductMapper;
 
     @Autowired
-    private  SysUserMapper userMapper;
+    private SysUserMapper userMapper;
 
     @Autowired
-    private  TempFileMapper tempFileMapper;
+    private TempFileMapper tempFileMapper;
 
     @Autowired
-    private  CommonFileMapper commonFileMapper;
+    private CommonFileMapper commonFileMapper;
 
     @Autowired
-    private  SupplierManageMapper supplierManageMapper;
+    private SupplierManageMapper supplierManageMapper;
 
     @Autowired
-    private  ProductMapper productMapper;
+    private ProductMapper productMapper;
 
     @Autowired
-    private  ProductModelMapper productModelMapper;
+    private ProductModelMapper productModelMapper;
 
     @Autowired
-    private  SysUserMapper sysUserMapper;
+    private SysUserMapper sysUserMapper;
 
     @Autowired
-    private  TicketRegistrationMapper ticketRegistrationMapper;
+    private TicketRegistrationMapper ticketRegistrationMapper;
 
     @Autowired
-    private  ProductRecordMapper productRecordMapper;
+    private ProductRecordMapper productRecordMapper;
 
     @Autowired
-    private  PaymentRegistrationMapper paymentRegistrationMapper;
+    private PaymentRegistrationMapper paymentRegistrationMapper;
     @Autowired
-    private  InvoiceRegistrationProductMapper invoiceRegistrationProductMapper;
+    private InvoiceRegistrationProductMapper invoiceRegistrationProductMapper;
     @Autowired
-    private  StringRedisTemplate redisTemplate;
+    private StringRedisTemplate redisTemplate;
     @Autowired
-    private  QualityInspectMapper qualityInspectMapper;
+    private QualityInspectMapper qualityInspectMapper;
     @Autowired
-    private  CommonFileServiceImpl commonFileService;
+    private CommonFileServiceImpl commonFileService;
     @Autowired
-    private  QualityTestStandardBindingMapper qualityTestStandardBindingMapper;
+    private QualityTestStandardBindingMapper qualityTestStandardBindingMapper;
     @Autowired
-    private  QualityTestStandardParamMapper qualityTestStandardParamMapper;
+    private QualityTestStandardParamMapper qualityTestStandardParamMapper;
     @Autowired
-    private  QualityTestStandardMapper qualityTestStandardMapper;
+    private QualityTestStandardMapper qualityTestStandardMapper;
     @Autowired
-    private  QualityInspectParamMapper qualityInspectParamMapper;
+    private QualityInspectParamMapper qualityInspectParamMapper;
     @Autowired
-    private  ApproveProcessServiceImpl approveProcessService;
+    private ApproveProcessServiceImpl approveProcessService;
     @Autowired
-    private  ProcurementRecordMapper procurementRecordStorageMapper;
+    private ProcurementRecordMapper procurementRecordStorageMapper;
     @Autowired
-    private  PurchaseLedgerTemplateMapper purchaseLedgerTemplateMapper;
+    private PurchaseLedgerTemplateMapper purchaseLedgerTemplateMapper;
     @Autowired
-    private  SalesLedgerProductTemplateMapper salesLedgerProductTemplateMapper;
+    private SalesLedgerProductTemplateMapper salesLedgerProductTemplateMapper;
+    @Autowired
+    private StockInventoryService stockInventoryService;
+    @Autowired
+    private StockUtils stockUtils;
     @Value("${file.upload-dir}")
     private String uploadDir;
 
@@ -159,7 +165,7 @@
         if (StringUtils.isNotBlank(purchaseLedger.getPurchaseContractNumber())) {
             queryWrapper.like(PurchaseLedger::getPurchaseContractNumber, purchaseLedger.getPurchaseContractNumber());
         }
-        if(purchaseLedger.getSupplierId()!=null){
+        if (purchaseLedger.getSupplierId() != null) {
             queryWrapper.eq(PurchaseLedger::getSupplierId, purchaseLedger.getSupplierId());
         }
         if (purchaseLedger.getApprovalStatus() != null) {
@@ -244,12 +250,12 @@
         qualityInspect.setUnit(saleProduct.getUnit());
         qualityInspect.setQuantity(saleProduct.getQuantity());
         qualityInspectMapper.insert(qualityInspect);
-        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(saleProduct.getProductId(), 0,null);
-        if (qualityTestStandard.size()>0){
+        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(saleProduct.getProductId(), 0, null);
+        if (qualityTestStandard.size() > 0) {
             qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
             qualityInspectMapper.updateById(qualityInspect);
             qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
-                    .eq(QualityTestStandardParam::getTestStandardId,qualityTestStandard.get(0).getId()))
+                            .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))
                     .forEach(qualityTestStandardParam -> {
                         QualityInspectParam param = new QualityInspectParam();
                         com.ruoyi.common.utils.bean.BeanUtils.copyProperties(qualityTestStandardParam, param);
@@ -316,6 +322,7 @@
         if (!updateList.isEmpty()) {
             for (SalesLedgerProduct product : updateList) {
                 product.setType(type);
+                product.fillRemainingQuantity();
                 salesLedgerProductMapper.updateById(product);
             }
         }
@@ -330,6 +337,7 @@
                 salesLedgerProduct.setFutureTickets(salesLedgerProduct.getQuantity());
                 salesLedgerProduct.setFutureTicketsAmount(salesLedgerProduct.getTaxInclusiveTotalPrice());
                 salesLedgerProduct.setPendingTicketsTotal(salesLedgerProduct.getTaxInclusiveTotalPrice());
+                salesLedgerProduct.fillRemainingQuantity();
                 salesLedgerProductMapper.insert(salesLedgerProduct);
             }
         }
@@ -425,12 +433,12 @@
     @Transactional(rollbackFor = Exception.class)
     public int deletePurchaseLedgerByIds(Long[] ids) {
         if (ids == null || ids.length == 0) {
-           throw new BaseException("璇烽�変腑鑷冲皯涓�鏉℃暟鎹�");
+            throw new BaseException("璇烽�変腑鑷冲皯涓�鏉℃暟鎹�");
         }
         for (Long id : ids) {
             PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(id);
             if (purchaseLedger.getApprovalStatus().equals(3)) {
-                throw new BaseException(purchaseLedger.getPurchaseContractNumber()+"宸茬粡瀹℃壒閫氳繃锛屼笉鍏佽鍒犻櫎");
+                throw new BaseException(purchaseLedger.getPurchaseContractNumber() + "宸茬粡瀹℃壒閫氳繃锛屼笉鍏佽鍒犻櫎");
             }
         }
         // 鎵归噺鍒犻櫎鍏宠仈鐨勯噰璐叆搴撹褰�
@@ -453,11 +461,11 @@
         salesLedgerProductMapper.delete(queryWrapper);
         // 鎵归噺鍒犻櫎鍏宠仈鐨勯噰璐彴璐︾殑鏉ョエ鐧昏
         LambdaQueryWrapper<TicketRegistration> ticketRegistrationLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        ticketRegistrationLambdaQueryWrapper.in(TicketRegistration::getPurchaseLedgerId,ids);
+        ticketRegistrationLambdaQueryWrapper.in(TicketRegistration::getPurchaseLedgerId, ids);
         ticketRegistrationMapper.delete(ticketRegistrationLambdaQueryWrapper);
         // 鎵归噺鍒犻櫎鍏宠仈鐨勯噰璐彴璐︾殑鏉ョエ鐧昏璁板綍
         LambdaQueryWrapper<ProductRecord> productRecordLambdaQueryWrapper = new LambdaQueryWrapper<>();
-        productRecordLambdaQueryWrapper.in(ProductRecord::getPurchaseLedgerId,ids);
+        productRecordLambdaQueryWrapper.in(ProductRecord::getPurchaseLedgerId, ids);
         productRecordMapper.delete(productRecordLambdaQueryWrapper);
         // 鎵归噺鍒犻櫎浠樻鐧昏
         LambdaQueryWrapper<PaymentRegistration> paymentRegistrationLambdaQueryWrapper = new LambdaQueryWrapper<>();
@@ -469,7 +477,7 @@
 
         List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(materialInspectLambdaQueryWrapper);
         qualityInspects.stream().forEach(qualityInspect -> {
-            if (ObjectUtils.isNotEmpty(qualityInspect.getInspectState())&&qualityInspect.getInspectState().equals(1)) {
+            if (ObjectUtils.isNotEmpty(qualityInspect.getInspectState()) && qualityInspect.getInspectState().equals(1)) {
                 throw new BaseException("宸叉彁浜ょ殑妫�楠屽崟涓嶈兘鍒犻櫎");
             }
         });
@@ -485,7 +493,7 @@
         // 鍒犻櫎閲囪喘瀹℃壒璁板綍
         for (Long id : ids) {
             PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(id);
-            if(purchaseLedger != null){
+            if (purchaseLedger != null) {
                 ApproveProcess one = approveProcessService.getOne(new LambdaQueryWrapper<ApproveProcess>()
                         .eq(ApproveProcess::getApproveType, 5)
                         .eq(ApproveProcess::getApproveDelete, 0)
@@ -517,11 +525,12 @@
         productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId())
                 .eq(SalesLedgerProduct::getType, purchaseLedgerDto.getType());
         List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(productWrapper);
+        products.forEach(SalesLedgerProduct::fillRemainingQuantity);
 
         // 3.鏌ヨ涓婁紶鏂囦欢
         LambdaQueryWrapper<CommonFile> salesLedgerFileWrapper = new LambdaQueryWrapper<>();
         salesLedgerFileWrapper.eq(CommonFile::getCommonId, purchaseLedger.getId())
-                .eq(CommonFile::getType,FileNameType.PURCHASE.getValue());
+                .eq(CommonFile::getType, FileNameType.PURCHASE.getValue());
         List<CommonFile> salesLedgerFiles = commonFileMapper.selectList(salesLedgerFileWrapper);
 
         // 4. 杞崲 DTO
@@ -577,6 +586,7 @@
             product.setTicketsAmount(null);
             product.setTempFutureTickets(product.getFutureTickets());
             product.setTempFutureTicketsAmount(product.getFutureTicketsAmount());
+            product.fillRemainingQuantity();
         });
         resultDto.setProductData(productList);
         return resultDto;
@@ -672,7 +682,7 @@
             // 渚涘簲鍟嗘暟鎹�
             List<SupplierManage> customers = supplierManageMapper.selectList(new LambdaQueryWrapper<SupplierManage>().in(SupplierManage::getSupplierName,
                     salesLedgerImportDtoList.stream().map(PurchaseLedgerImportDto::getSupplierName).collect(Collectors.toList())));
-            List<Map<String,Object>> list = productModelMapper.getProductAndModelList();
+            List<Map<String, Object>> list = productModelMapper.getProductAndModelList();
             // 褰曞叆浜烘暟鎹�
             List<SysUser> sysUsers = sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>().in(SysUser::getNickName,
                     salesLedgerImportDtoList.stream().map(PurchaseLedgerImportDto::getRecorderName).collect(Collectors.toList())));
@@ -680,7 +690,7 @@
                 PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                         .eq(PurchaseLedger::getPurchaseContractNumber, salesLedgerImportDto.getPurchaseContractNumber())
                         .last("limit 1"));
-                if(purchaseLedger != null){
+                if (purchaseLedger != null) {
                     continue;
                 }
                 PurchaseLedger salesLedger = new PurchaseLedger();
@@ -707,12 +717,12 @@
                     throw new RuntimeException("閲囪喘鍗曞彿:" + salesLedgerImportDto.getPurchaseContractNumber() + ",鏃犲搴斾骇鍝佹暟鎹紒");
                 salesLedger.setContractAmount(salesLedgerProductImportDtos.stream()
                         .map(PurchaseLedgerProductImportDto::getTaxInclusiveTotalPrice)
-                        .reduce(BigDecimal.ZERO,BigDecimal::add));
+                        .reduce(BigDecimal.ZERO, BigDecimal::add));
                 // 閫氳繃閿�鍞崟鍙风粦瀹氶攢鍞�
                 SalesLedger salesLedger1 = salesLedgerMapper.selectOne(new LambdaQueryWrapper<SalesLedger>()
                         .eq(SalesLedger::getSalesContractNo, salesLedger.getSalesContractNo())
                         .last("LIMIT 1"));
-                if(salesLedger1 != null){
+                if (salesLedger1 != null) {
                     salesLedger.setSalesLedgerId(salesLedger1.getId());
                 }
                 // 閲囪喘瀹℃牳
@@ -753,13 +763,14 @@
                     salesLedgerProduct.setPendingTicketsTotal(salesLedgerProductImportDto.getTaxInclusiveTotalPrice());
                     // 鏄惁璐ㄦ鍒ゆ柇
                     salesLedgerProduct.setIsChecked(salesLedgerProductImportDto.getIsChecked() == 1);
-                    if(salesLedgerProductImportDto.getIsChecked() == 1){
+                    if (salesLedgerProductImportDto.getIsChecked() == 1) {
                         addQualityInspect(salesLedger, salesLedgerProduct);
                     }
+                    salesLedgerProduct.fillRemainingQuantity();
                     salesLedgerProductMapper.insert(salesLedgerProduct);
                 }
                 // 閲囪喘瀹℃牳
-                addApproveByPurchase(loginUser,salesLedger);
+                addApproveByPurchase(loginUser, salesLedger);
             }
 
             return AjaxResult.success("瀵煎叆鎴愬姛");
@@ -784,6 +795,7 @@
         productWrapper.eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId())
                 .eq(SalesLedgerProduct::getType, 2);
         List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(productWrapper);
+        products.forEach(SalesLedgerProduct::fillRemainingQuantity);
 
         // 4. 杞崲 DTO
         PurchaseLedgerDto resultDto = new PurchaseLedgerDto();
@@ -795,7 +807,7 @@
         return resultDto;
     }
 
-    public void addApproveByPurchase(LoginUser loginUser,PurchaseLedger purchaseLedger) throws Exception {
+    public void addApproveByPurchase(LoginUser loginUser, PurchaseLedger purchaseLedger) throws Exception {
         ApproveProcessVO approveProcessVO = new ApproveProcessVO();
         approveProcessVO.setApproveType(5);
         approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
@@ -806,6 +818,258 @@
         approveProcessService.addApprove(approveProcessVO);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void scanInbound(PurchaseScanStockDto dto) {
+        if (dto == null || dto.getPurchaseLedgerId() == null) {
+            throw new ServiceException("閲囪喘鍏ュ簱澶辫触,鍏ュ簱鏁版嵁涓嶈兘涓虹┖");
+        }
+        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(dto.getPurchaseLedgerId());
+        if (purchaseLedger == null) {
+            throw new ServiceException("鍏ュ簱澶辫触,閲囪喘鍙拌处涓嶅瓨鍦�");
+        }
+        if (CollectionUtils.isEmpty(dto.getSalesLedgerProductList())) {
+            throw new ServiceException("閲囪喘鍏ュ簱澶辫触,鍏ュ簱浜у搧涓嶈兘涓虹┖");
+        }
+        int purchaseType = PURCHASE.getCode();
+        Map<Long, BigDecimal> inboundQtyByLineId = new LinkedHashMap<>();
+        for (SalesLedgerProduct inbound : dto.getSalesLedgerProductList()) {
+            if (inbound == null || inbound.getId() == null) {
+                throw new ServiceException("鍏ュ簱澶辫触,閲囪喘浜у搧淇℃伅涓嶅畬鏁�");
+            }
+            BigDecimal inboundQty = inbound.getStockedQuantity();
+            if (inboundQty == null) {
+                throw new ServiceException("鍏ュ簱澶辫触,鍏ュ簱鏁伴噺涓嶈兘涓虹┖");
+            }
+            if (inboundQty.compareTo(BigDecimal.ZERO) < 0) {
+                throw new ServiceException("鍏ュ簱澶辫触,鍏ュ簱鏁伴噺涓嶈兘涓鸿礋鏁�");
+            }
+            inboundQtyByLineId.merge(inbound.getId(), inboundQty, BigDecimal::add);
+        }
+        Long purchaseId = purchaseLedger.getId();
+        for (Map.Entry<Long, BigDecimal> entry : inboundQtyByLineId.entrySet()) {
+            Long productLineId = entry.getKey();
+            BigDecimal inboundThisLine = entry.getValue();
+            if (inboundThisLine.compareTo(BigDecimal.ZERO) == 0) {
+                continue;
+            }
+            SalesLedgerProduct dbProduct = salesLedgerProductMapper.selectById(productLineId);
+            if (dbProduct == null) {
+                throw new ServiceException("鍏ュ簱澶辫触,閲囪喘浜у搧涓嶅瓨鍦�");
+            }
+            if (!Objects.equals(dbProduct.getSalesLedgerId(), purchaseId) || !Objects.equals(dbProduct.getType(), purchaseType)) {
+                throw new ServiceException("鍏ュ簱澶辫触,浜у搧涓庨噰璐彴璐︿笉鍖归厤");
+            }
+            if (dbProduct.getProductModelId() == null) {
+                throw new ServiceException("鍏ュ簱澶辫触,浜у搧瑙勬牸鏈淮鎶�,鏃犳硶鍏ュ簱");
+            }
+            BigDecimal oldStocked = dbProduct.getStockedQuantity() == null ? BigDecimal.ZERO : dbProduct.getStockedQuantity();
+            BigDecimal newStocked = oldStocked.add(inboundThisLine);
+
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setRecordId(dbProduct.getId());
+            stockInventoryDto.setRecordType(StockInQualifiedRecordTypeEnum.PURCHASE_SCAN_STOCK_IN.getCode());
+            stockInventoryDto.setQualitity(inboundThisLine);
+            stockInventoryDto.setProductModelId(dbProduct.getProductModelId());
+            stockInventoryDto.setSalesLedgerId(null);
+            stockInventoryDto.setSalesLedgerProductId(dbProduct.getId());
+            stockInventoryService.addstockInventory(stockInventoryDto);
+
+            BigDecimal orderQty = dbProduct.getQuantity() == null ? BigDecimal.ZERO : dbProduct.getQuantity();
+            int lineStockStatus;
+            if (newStocked.compareTo(BigDecimal.ZERO) <= 0) {
+                lineStockStatus = 0;
+            } else if (orderQty.compareTo(BigDecimal.ZERO) > 0 && newStocked.compareTo(orderQty) < 0) {
+                lineStockStatus = 1;
+            } else {
+                lineStockStatus = 2;
+            }
+            dbProduct.setStockedQuantity(newStocked);
+            dbProduct.setProductStockStatus(lineStockStatus);
+            dbProduct.fillRemainingQuantity();
+            salesLedgerProductMapper.updateById(dbProduct);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void scanOutbound(PurchaseScanStockDto dto) {
+        if (dto == null || dto.getPurchaseLedgerId() == null) {
+            throw new ServiceException("閲囪喘鍑哄簱澶辫触,鍑哄簱鏁版嵁涓嶈兘涓虹┖");
+        }
+        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(dto.getPurchaseLedgerId());
+        if (purchaseLedger == null) {
+            throw new ServiceException("鍑哄簱澶辫触,閲囪喘鍙拌处涓嶅瓨鍦�");
+        }
+        if (CollectionUtils.isEmpty(dto.getSalesLedgerProductList())) {
+            throw new ServiceException("閲囪喘鍑哄簱澶辫触,鍑哄簱浜у搧涓嶈兘涓虹┖");
+        }
+        int purchaseType = PURCHASE.getCode();
+        Map<Long, BigDecimal> outboundQtyByLineId = new LinkedHashMap<>();
+        for (SalesLedgerProduct line : dto.getSalesLedgerProductList()) {
+            if (line == null || line.getId() == null) {
+                throw new ServiceException("鍑哄簱澶辫触,閲囪喘浜у搧淇℃伅涓嶅畬鏁�");
+            }
+            BigDecimal outboundQty = line.getStockedQuantity();
+            if (outboundQty == null) {
+                throw new ServiceException("鍑哄簱澶辫触,鍑哄簱鏁伴噺涓嶈兘涓虹┖");
+            }
+            if (outboundQty.compareTo(BigDecimal.ZERO) < 0) {
+                throw new ServiceException("鍑哄簱澶辫触,鍑哄簱鏁伴噺涓嶈兘涓鸿礋鏁�");
+            }
+            outboundQtyByLineId.merge(line.getId(), outboundQty, BigDecimal::add);
+        }
+        Long purchaseId = purchaseLedger.getId();
+        for (Map.Entry<Long, BigDecimal> entry : outboundQtyByLineId.entrySet()) {
+            Long productLineId = entry.getKey();
+            BigDecimal outboundThisLine = entry.getValue();
+            if (outboundThisLine.compareTo(BigDecimal.ZERO) == 0) {
+                continue;
+            }
+            SalesLedgerProduct dbProduct = salesLedgerProductMapper.selectById(productLineId);
+            if (dbProduct == null) {
+                throw new ServiceException("鍑哄簱澶辫触,閲囪喘浜у搧涓嶅瓨鍦�");
+            }
+            if (!Objects.equals(dbProduct.getSalesLedgerId(), purchaseId) || !Objects.equals(dbProduct.getType(), purchaseType)) {
+                throw new ServiceException("鍑哄簱澶辫触,浜у搧涓庨噰璐彴璐︿笉鍖归厤");
+            }
+            if (dbProduct.getProductModelId() == null) {
+                throw new ServiceException("鍑哄簱澶辫触,浜у搧瑙勬牸鏈淮鎶�,鏃犳硶鍑哄簱");
+            }
+            stockUtils.assertQualifiedAvailable(dbProduct.getProductModelId(), outboundThisLine);
+
+            BigDecimal oldStocked = dbProduct.getStockedQuantity() == null ? BigDecimal.ZERO : dbProduct.getStockedQuantity();
+            BigDecimal newStocked = oldStocked.subtract(outboundThisLine);
+            if (newStocked.compareTo(BigDecimal.ZERO) < 0) {
+                newStocked = BigDecimal.ZERO;
+            }
+
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setRecordId(dbProduct.getId());
+            stockInventoryDto.setRecordType(StockOutQualifiedRecordTypeEnum.PURCHASE_SCAN_STOCK_OUT.getCode());
+            stockInventoryDto.setQualitity(outboundThisLine);
+            stockInventoryDto.setProductModelId(dbProduct.getProductModelId());
+            stockInventoryDto.setSalesLedgerId(null);
+            stockInventoryDto.setSalesLedgerProductId(dbProduct.getId());
+            stockInventoryService.subtractStockInventory(stockInventoryDto);
+
+            BigDecimal orderQty = dbProduct.getQuantity() == null ? BigDecimal.ZERO : dbProduct.getQuantity();
+            int lineStockStatus;
+            if (newStocked.compareTo(BigDecimal.ZERO) <= 0) {
+                lineStockStatus = 0;
+            } else if (orderQty.compareTo(BigDecimal.ZERO) > 0 && newStocked.compareTo(orderQty) < 0) {
+                lineStockStatus = 1;
+            } else {
+                lineStockStatus = 2;
+            }
+            dbProduct.setStockedQuantity(newStocked);
+            dbProduct.setProductStockStatus(lineStockStatus);
+            dbProduct.fillRemainingQuantity();
+            salesLedgerProductMapper.updateById(dbProduct);
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void scanInboundUnqualified(PurchaseScanStockDto dto) {
+        if (dto == null || dto.getPurchaseLedgerId() == null) {
+            throw new ServiceException("閲囪喘涓嶅悎鏍煎叆搴撳け璐�,鍏ュ簱鏁版嵁涓嶈兘涓虹┖");
+        }
+        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(dto.getPurchaseLedgerId());
+        if (purchaseLedger == null) {
+            throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,閲囪喘鍙拌处涓嶅瓨鍦�");
+        }
+        if (CollectionUtils.isEmpty(dto.getSalesLedgerProductList())) {
+            throw new ServiceException("閲囪喘涓嶅悎鏍煎叆搴撳け璐�,鍏ュ簱浜у搧涓嶈兘涓虹┖");
+        }
+        int purchaseType = PURCHASE.getCode();
+        Map<Long, BigDecimal> inboundQtyByLineId = new LinkedHashMap<>();
+        for (SalesLedgerProduct inbound : dto.getSalesLedgerProductList()) {
+            if (inbound == null || inbound.getId() == null) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,閲囪喘浜у搧淇℃伅涓嶅畬鏁�");
+            }
+            BigDecimal inboundQty = inbound.getStockedQuantity();
+            if (inboundQty == null) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,鍏ュ簱鏁伴噺涓嶈兘涓虹┖");
+            }
+            if (inboundQty.compareTo(BigDecimal.ZERO) < 0) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,鍏ュ簱鏁伴噺涓嶈兘涓鸿礋鏁�");
+            }
+            inboundQtyByLineId.merge(inbound.getId(), inboundQty, BigDecimal::add);
+        }
+        Long purchaseId = purchaseLedger.getId();
+        for (Map.Entry<Long, BigDecimal> entry : inboundQtyByLineId.entrySet()) {
+            Long productLineId = entry.getKey();
+            BigDecimal inboundThisLine = entry.getValue();
+            if (inboundThisLine.compareTo(BigDecimal.ZERO) == 0) {
+                continue;
+            }
+            SalesLedgerProduct dbProduct = salesLedgerProductMapper.selectById(productLineId);
+            if (dbProduct == null) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,閲囪喘浜у搧涓嶅瓨鍦�");
+            }
+            if (!Objects.equals(dbProduct.getSalesLedgerId(), purchaseId) || !Objects.equals(dbProduct.getType(), purchaseType)) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,浜у搧涓庨噰璐彴璐︿笉鍖归厤");
+            }
+            if (dbProduct.getProductModelId() == null) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,浜у搧瑙勬牸鏈淮鎶�,鏃犳硶鍏ュ簱");
+            }
+            stockUtils.addUnStock(null, null, dbProduct.getProductModelId(), inboundThisLine,
+                    StockInUnQualifiedRecordTypeEnum.PURCHASE_SCAN_UNSTOCK_IN.getCode(), dbProduct.getId());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void scanOutboundUnqualified(PurchaseScanStockDto dto) {
+        if (dto == null || dto.getPurchaseLedgerId() == null) {
+            throw new ServiceException("閲囪喘涓嶅悎鏍煎嚭搴撳け璐�,鍑哄簱鏁版嵁涓嶈兘涓虹┖");
+        }
+        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(dto.getPurchaseLedgerId());
+        if (purchaseLedger == null) {
+            throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,閲囪喘鍙拌处涓嶅瓨鍦�");
+        }
+        if (CollectionUtils.isEmpty(dto.getSalesLedgerProductList())) {
+            throw new ServiceException("閲囪喘涓嶅悎鏍煎嚭搴撳け璐�,鍑哄簱浜у搧涓嶈兘涓虹┖");
+        }
+        int purchaseType = PURCHASE.getCode();
+        Map<Long, BigDecimal> outboundQtyByLineId = new LinkedHashMap<>();
+        for (SalesLedgerProduct line : dto.getSalesLedgerProductList()) {
+            if (line == null || line.getId() == null) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,閲囪喘浜у搧淇℃伅涓嶅畬鏁�");
+            }
+            BigDecimal outboundQty = line.getStockedQuantity();
+            if (outboundQty == null) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,鍑哄簱鏁伴噺涓嶈兘涓虹┖");
+            }
+            if (outboundQty.compareTo(BigDecimal.ZERO) < 0) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,鍑哄簱鏁伴噺涓嶈兘涓鸿礋鏁�");
+            }
+            outboundQtyByLineId.merge(line.getId(), outboundQty, BigDecimal::add);
+        }
+        Long purchaseId = purchaseLedger.getId();
+        for (Map.Entry<Long, BigDecimal> entry : outboundQtyByLineId.entrySet()) {
+            Long productLineId = entry.getKey();
+            BigDecimal outboundThisLine = entry.getValue();
+            if (outboundThisLine.compareTo(BigDecimal.ZERO) == 0) {
+                continue;
+            }
+            SalesLedgerProduct dbProduct = salesLedgerProductMapper.selectById(productLineId);
+            if (dbProduct == null) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,閲囪喘浜у搧涓嶅瓨鍦�");
+            }
+            if (!Objects.equals(dbProduct.getSalesLedgerId(), purchaseId) || !Objects.equals(dbProduct.getType(), purchaseType)) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,浜у搧涓庨噰璐彴璐︿笉鍖归厤");
+            }
+            if (dbProduct.getProductModelId() == null) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,浜у搧瑙勬牸鏈淮鎶�,鏃犳硶鍑哄簱");
+            }
+            stockUtils.assertUnqualifiedAvailable(dbProduct.getProductModelId(), outboundThisLine);
+            stockUtils.subtractUnStock(null, null, dbProduct.getProductModelId(), outboundThisLine,
+                    StockOutUnQualifiedRecordTypeEnum.PURCHASE_SCAN_UNSTOCK_OUT.getCode(), dbProduct.getId());
+        }
+    }
+
     /**
      * 涓嬪垝绾垮懡鍚嶈浆椹煎嘲鍛藉悕
      */
diff --git a/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java b/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
index 8a11486..b8863c1 100644
--- a/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
+++ b/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -411,4 +411,32 @@
         return AjaxResult.success(list);
     }
 
+    @PostMapping("/scanInbound")
+    @ApiOperation("閿�鍞鍗曟壂鐮�-鍚堟牸鍏ュ簱")
+    public AjaxResult scanInbound(@RequestBody SalesScanInboundDto dto) {
+        salesLedgerService.scanInbound(dto);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/scanInboundUnqualified")
+    @ApiOperation("閿�鍞鍗曟壂鐮�-涓嶅悎鏍煎叆搴�")
+    public AjaxResult scanInboundUnqualified(@RequestBody SalesScanInboundDto dto) {
+        salesLedgerService.scanInboundUnqualified(dto);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/scanOutbound")
+    @ApiOperation("閿�鍞鍗曟壂鐮�-鍚堟牸鍑哄簱")
+    public AjaxResult scanOutbound(@RequestBody SalesScanInboundDto dto) {
+        salesLedgerService.scanOutbound(dto);
+        return AjaxResult.success();
+    }
+
+    @PostMapping("/scanOutboundUnqualified")
+    @ApiOperation("閿�鍞鍗曟壂鐮�-涓嶅悎鏍煎嚭搴�")
+    public AjaxResult scanOutboundUnqualified(@RequestBody SalesScanInboundDto dto) {
+        salesLedgerService.scanOutboundUnqualified(dto);
+        return AjaxResult.success();
+    }
+
 }
diff --git a/src/main/java/com/ruoyi/sales/dto/SalesScanInboundDto.java b/src/main/java/com/ruoyi/sales/dto/SalesScanInboundDto.java
new file mode 100644
index 0000000..dd33b4c
--- /dev/null
+++ b/src/main/java/com/ruoyi/sales/dto/SalesScanInboundDto.java
@@ -0,0 +1,23 @@
+package com.ruoyi.sales.dto;
+
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * 閿�鍞鍗曟壂鐮佸叆鍑哄簱(鍚堟牸/涓嶅悎鏍�)Dto
+ */
+@Data
+@ApiModel(value = "SalesScanInboundDto", description = "閿�鍞鍗曟壂鐮佸叆鍑哄簱(鍚堟牸/涓嶅悎鏍�)")
+public class SalesScanInboundDto {
+
+    @ApiModelProperty("閿�鍞鍗旾d")
+    private Long SalesLedgerId;
+
+    @ApiModelProperty("閿�鍞骇鍝佽鏁版嵁")
+    private List<SalesLedgerProduct> salesLedgerProductList;
+
+}
diff --git a/src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java b/src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
index 02d5080..6bb145e 100644
--- a/src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
+++ b/src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java
@@ -72,8 +72,22 @@
      */
     @Excel(name = "鏁伴噺")
     private BigDecimal quantity;
+
+    /**
+     * 宸插叆搴撴暟閲�
+     */
+    private BigDecimal stockedQuantity;
+
     @Excel(name = "鏈�浣庡簱瀛樻暟閲�")
     private BigDecimal minStock;
+
+    /**
+     * 鍓╀綑寰呭叆搴撴暟閲忥紙璁㈠崟鏁伴噺 鈭� 鍚堟牸宸插叆搴撴暟閲忥紝澶氬叆鏃惰涓� 0锛�
+     */
+    @Excel(name = "鍓╀綑鏁伴噺")
+    @ApiModelProperty("鍓╀綑寰呭叆搴撴暟閲�(璁㈠崟鏁伴噺-鍚堟牸宸插叆搴�)")
+    private BigDecimal remainingQuantity;
+
     /**
      * 绋庣巼
      */
@@ -293,9 +307,9 @@
     private String floorCode;
 
     /**
-     * 浜у搧鍏ュ簱鐘舵��   0-鏈叆搴擄紝1-宸插叆搴�
+     * 浜у搧鍏ュ簱鐘舵�侊細0-鏈叆搴擄紝1-閮ㄥ垎鍏ュ簱锛�2-宸插叆搴�
      */
-    @ApiModelProperty("浜у搧鍏ュ簱鐘舵��")
+    @ApiModelProperty("浜у搧鍏ュ簱鐘舵�侊細0-鏈叆搴擄紝1-閮ㄥ垎鍏ュ簱锛�2-宸插叆搴�")
     private Integer productStockStatus;
 
     @TableField(exist = false)
@@ -312,4 +326,12 @@
     // 鍙敤鏁伴噺  quantity - returnQuality
     @TableField(exist = false)
     private BigDecimal availableQuality;
+
+
+    public void fillRemainingQuantity() {
+        BigDecimal q = this.quantity == null ? BigDecimal.ZERO : this.quantity;
+        BigDecimal stocked = this.stockedQuantity == null ? BigDecimal.ZERO : this.stockedQuantity;
+        BigDecimal rem = q.subtract(stocked);
+        this.remainingQuantity = rem.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : rem;
+    }
 }
diff --git a/src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java b/src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
index 807a830..dc93395 100644
--- a/src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
+++ b/src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java
@@ -70,4 +70,11 @@
 
     List<Customer> shippedCustomers();
 
+    void scanInbound(SalesScanInboundDto dto);
+
+    void scanInboundUnqualified(SalesScanInboundDto dto);
+
+    void scanOutbound(SalesScanInboundDto dto);
+
+    void scanOutboundUnqualified(SalesScanInboundDto dto);
 }
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
index ac469e8..ab45874 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -100,7 +100,11 @@
 
     @Override
     public SalesLedgerProduct selectSalesLedgerProductById(Long id) {
-        return salesLedgerProductMapper.selectById(id);
+        SalesLedgerProduct row = salesLedgerProductMapper.selectById(id);
+        if (row != null) {
+            row.fillRemainingQuantity();
+        }
+        return row;
     }
 
     @Override
@@ -201,6 +205,7 @@
             item.setReturnQuality(returnQuality);
             BigDecimal quantity = item.getQuantity() == null ? BigDecimal.ZERO : item.getQuantity();
             item.setAvailableQuality(quantity.subtract(returnQuality));
+            item.fillRemainingQuantity();
             ProductModel productModel = finalProductModelMap.get(item.getProductModelId());
             if (productModel != null) {
                 item.setThickness(productModel.getThickness());
@@ -275,6 +280,7 @@
 
         int result;
         Long salesLedgerId = salesLedgerProduct.getSalesLedgerId();
+        salesLedgerProduct.fillRemainingQuantity();
         if (salesLedgerProduct.getId() == null) {
             salesLedgerProduct.setRegisterDate(LocalDateTime.now());
             result = salesLedgerProductMapper.insert(salesLedgerProduct);
@@ -497,6 +503,7 @@
             } else {
                 item.setStatusName("鏈畬鎴愪粯娆�");
             }
+            item.fillRemainingQuantity();
         });
         return salesLedgerProductDtoIPage;
     }
@@ -511,6 +518,7 @@
             } else {
                 item.setStatusName("鏈畬鎴愪粯娆�");
             }
+            item.fillRemainingQuantity();
         });
         return salesLedgerProductDtoIPage;
     }
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
index 8cbef1d..0a277b1 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -19,6 +19,9 @@
 import com.ruoyi.common.enums.FileNameType;
 import com.ruoyi.common.enums.SaleEnum;
 import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.StockOutUnQualifiedRecordTypeEnum;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.utils.DateUtils;
@@ -37,6 +40,7 @@
 import com.ruoyi.project.system.domain.SysUser;
 import com.ruoyi.project.system.mapper.SysDeptMapper;
 import com.ruoyi.project.system.mapper.SysUserMapper;
+import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.purchase.dto.SimpleReturnOrderGroupDto;
 import com.ruoyi.purchase.mapper.PurchaseReturnOrderProductsMapper;
 import com.ruoyi.quality.mapper.QualityInspectMapper;
@@ -139,6 +143,7 @@
     private final StockOutRecordMapper stockOutRecordMapper;
     private final StockInRecordService stockInRecordService;
     private final StockOutRecordService stockOutRecordService;
+    private final StockUtils stockUtils;
 
     @Autowired
     private SysDeptMapper sysDeptMapper;
@@ -272,6 +277,7 @@
                     product.setActualTotalArea(pieceArea.multiply(quantity).setScale(2, RoundingMode.HALF_UP));
                 }
             }
+            product.fillRemainingQuantity();
         }
 
         // 3.鏌ヨ涓婁紶鏂囦欢
@@ -620,6 +626,7 @@
                 salesLedgerProduct.setNoInvoiceNum(quantity);
                 salesLedgerProduct.setNoInvoiceAmount(salesLedgerProduct.getTaxExclusiveTotalPrice());
                 salesLedgerProduct.setPendingInvoiceTotal(taxInclusiveTotalPrice);
+                salesLedgerProduct.fillRemainingQuantity();
                 salesLedgerProductMapper.insert(salesLedgerProduct);
                 if (!processList.isEmpty()) {
                     salesLedgerProductProcessBindService.updateProductProcessBind(processList, salesLedgerProduct.getId());
@@ -1000,6 +1007,7 @@
             for (SalesLedgerProduct product : updateList) {
                 product.setType(type.getCode());
                 product.setProductStockStatus(0);
+                product.fillRemainingQuantity();
                 salesLedgerProductMapper.updateById(product);
                 //  娓呯┖閿�鍞骇鍝佺粦瀹氱殑鍔犲伐
                 salesLedgerProductProcessBindService.updateProductProcessBind(product.getSalesProductProcessList(), product.getId());
@@ -1013,6 +1021,7 @@
                 salesLedgerProduct.setNoInvoiceAmount(salesLedgerProduct.getTaxInclusiveTotalPrice());
                 salesLedgerProduct.setPendingInvoiceTotal(salesLedgerProduct.getTaxInclusiveTotalPrice());
                 salesLedgerProduct.setProductStockStatus(0);
+                salesLedgerProduct.fillRemainingQuantity();
                 salesLedgerProductMapper.insert(salesLedgerProduct);
                 //  缁戝畾浜у搧棰濆鍔犲伐
                 //  娓呯┖閿�鍞骇鍝佺粦瀹氱殑鍔犲伐
@@ -1717,4 +1726,298 @@
                 .in(Customer::getId, customerIds)
                 .orderByAsc(Customer::getCustomerName));
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void scanInbound(SalesScanInboundDto dto) {
+        if (dto == null || dto.getSalesLedgerId() == null) {
+            throw new ServiceException("閿�鍞鍗曞叆搴撳け璐�,鍏ュ簱鏁版嵁涓嶈兘涓虹┖");
+        }
+
+        SalesLedger salesLedger = baseMapper.selectById(dto.getSalesLedgerId());
+        if (salesLedger == null) {
+            throw new ServiceException("鍏ュ簱澶辫触,閿�鍞鍗曚笉瀛樺湪");
+        }
+        if (salesLedger.getStockStatus() == null) {
+            throw new ServiceException("鍏ュ簱澶辫触,閿�鍞鍗曠姸鎬佸紓甯�");
+        }
+        if (salesLedger.getStockStatus() == 2) {
+            throw new ServiceException("鍏ュ簱澶辫触,璇ラ攢鍞鍗曞凡鍏ㄩ儴鍏ュ簱");
+        }
+        if (salesLedger.getDeliveryStatus() == 5) {
+            throw new ServiceException("鍏ュ簱澶辫触,璇ラ攢鍞鍗曞凡鍙戣揣");
+        }
+        if (CollectionUtils.isEmpty(dto.getSalesLedgerProductList())) {
+            throw new ServiceException("閿�鍞鍗曞叆搴撳け璐�,鍏ュ簱浜у搧涓嶈兘涓虹┖");
+        }
+        //  鏈鍏ュ簱鏁伴噺鍙互澶т簬璁㈠崟鏁伴噺,浣嗕笉鑳戒负璐�
+        Map<Long, BigDecimal> inboundQtyByLineId = new LinkedHashMap<>();
+        for (SalesLedgerProduct inbound : dto.getSalesLedgerProductList()) {
+            if (inbound == null || inbound.getId() == null) {
+                throw new ServiceException("鍏ュ簱澶辫触,閿�鍞骇鍝佷俊鎭笉瀹屾暣");
+            }
+            BigDecimal inboundQty = inbound.getStockedQuantity();
+            if (inboundQty == null) {
+                throw new ServiceException("鍏ュ簱澶辫触,鍏ュ簱鏁伴噺涓嶈兘涓虹┖");
+            }
+            if (inboundQty.compareTo(BigDecimal.ZERO) < 0) {
+                throw new ServiceException("鍏ュ簱澶辫触,鍏ュ簱鏁伴噺涓嶈兘涓鸿礋鏁�");
+            }
+            inboundQtyByLineId.merge(inbound.getId(), inboundQty, BigDecimal::add);
+        }
+        Long ledgerId = salesLedger.getId();
+        for (Map.Entry<Long, BigDecimal> entry : inboundQtyByLineId.entrySet()) {
+            Long salesLedgerProductId = entry.getKey();
+            BigDecimal inboundThisLine = entry.getValue();
+            if (inboundThisLine.compareTo(BigDecimal.ZERO) == 0) {
+                continue;
+            }
+            SalesLedgerProduct dbProduct = salesLedgerProductMapper.selectById(salesLedgerProductId);
+            if (dbProduct == null) {
+                throw new ServiceException("鍏ュ簱澶辫触,閿�鍞骇鍝佷笉瀛樺湪");
+            }
+            if (!Objects.equals(dbProduct.getSalesLedgerId(), ledgerId)) {
+                throw new ServiceException("鍏ュ簱澶辫触,閿�鍞骇鍝佷笌璁㈠崟涓嶅尮閰�");
+            }
+            if (!Objects.equals(dbProduct.getType(), SaleEnum.SALE.getCode())) {
+                throw new ServiceException("鍏ュ簱澶辫触,浠呮敮鎸侀攢鍞鍗曚骇鍝佽");
+            }
+            if (dbProduct.getProductModelId() == null) {
+                throw new ServiceException("鍏ュ簱澶辫触,浜у搧瑙勬牸鏈淮鎶�,鏃犳硶鍏ュ簱");
+            }
+            BigDecimal oldStocked = dbProduct.getStockedQuantity() == null ? BigDecimal.ZERO : dbProduct.getStockedQuantity();
+            BigDecimal newStocked = oldStocked.add(inboundThisLine);
+
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setRecordId(dbProduct.getId());
+            stockInventoryDto.setRecordType(StockInQualifiedRecordTypeEnum.SALE_SCAN_STOCK_IN.getCode());
+            stockInventoryDto.setQualitity(inboundThisLine);
+            stockInventoryDto.setProductModelId(dbProduct.getProductModelId());
+            stockInventoryDto.setSalesLedgerId(ledgerId);
+            stockInventoryDto.setSalesLedgerProductId(dbProduct.getId());
+            stockInventoryService.addstockInventory(stockInventoryDto);
+
+            BigDecimal orderQty = dbProduct.getQuantity() == null ? BigDecimal.ZERO : dbProduct.getQuantity();
+            int lineStockStatus;
+            if (newStocked.compareTo(BigDecimal.ZERO) <= 0) {
+                lineStockStatus = 0;
+            } else if (orderQty.compareTo(BigDecimal.ZERO) > 0 && newStocked.compareTo(orderQty) < 0) {
+                lineStockStatus = 1;
+            } else {
+                lineStockStatus = 2;
+            }
+            dbProduct.setStockedQuantity(newStocked);
+            dbProduct.setProductStockStatus(lineStockStatus);
+            dbProduct.fillRemainingQuantity();
+            salesLedgerProductMapper.updateById(dbProduct);
+        }
+        List<SalesLedgerProduct> ledgerAllProducts = salesLedgerProductMapper.selectList(
+                Wrappers.<SalesLedgerProduct>lambdaQuery().eq(SalesLedgerProduct::getSalesLedgerId, ledgerId));
+        boolean anyInbound = ledgerAllProducts.stream().anyMatch(p -> {
+            BigDecimal sq = p.getStockedQuantity();
+            return sq != null && sq.compareTo(BigDecimal.ZERO) > 0;
+        });
+        boolean allLinesFull = ledgerAllProducts.stream().allMatch(p -> Objects.equals(p.getProductStockStatus(), 2));
+        salesLedger.setStockStatus(allLinesFull ? 2 : (anyInbound ? 1 : 0));
+        baseMapper.updateById(salesLedger);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void scanInboundUnqualified(SalesScanInboundDto dto) {
+        if (dto == null || dto.getSalesLedgerId() == null) {
+            throw new ServiceException("閿�鍞鍗曚笉鍚堟牸鍏ュ簱澶辫触,鍏ュ簱鏁版嵁涓嶈兘涓虹┖");
+        }
+        SalesLedger salesLedger = baseMapper.selectById(dto.getSalesLedgerId());
+        if (salesLedger == null) {
+            throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,閿�鍞鍗曚笉瀛樺湪");
+        }
+        if (salesLedger.getDeliveryStatus() != null && salesLedger.getDeliveryStatus() == 5) {
+            throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,璇ラ攢鍞鍗曞凡鍙戣揣");
+        }
+        if (CollectionUtils.isEmpty(dto.getSalesLedgerProductList())) {
+            throw new ServiceException("閿�鍞鍗曚笉鍚堟牸鍏ュ簱澶辫触,鍏ュ簱浜у搧涓嶈兘涓虹┖");
+        }
+        Map<Long, BigDecimal> inboundQtyByLineId = new LinkedHashMap<>();
+        for (SalesLedgerProduct inbound : dto.getSalesLedgerProductList()) {
+            if (inbound == null || inbound.getId() == null) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,閿�鍞骇鍝佷俊鎭笉瀹屾暣");
+            }
+            BigDecimal inboundQty = inbound.getStockedQuantity();
+            if (inboundQty == null) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,鍏ュ簱鏁伴噺涓嶈兘涓虹┖");
+            }
+            if (inboundQty.compareTo(BigDecimal.ZERO) < 0) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,鍏ュ簱鏁伴噺涓嶈兘涓鸿礋鏁�");
+            }
+            inboundQtyByLineId.merge(inbound.getId(), inboundQty, BigDecimal::add);
+        }
+        Long ledgerId = salesLedger.getId();
+        for (Map.Entry<Long, BigDecimal> entry : inboundQtyByLineId.entrySet()) {
+            Long salesLedgerProductId = entry.getKey();
+            BigDecimal inboundThisLine = entry.getValue();
+            if (inboundThisLine.compareTo(BigDecimal.ZERO) == 0) {
+                continue;
+            }
+            SalesLedgerProduct dbProduct = salesLedgerProductMapper.selectById(salesLedgerProductId);
+            if (dbProduct == null) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,閿�鍞骇鍝佷笉瀛樺湪");
+            }
+            if (!Objects.equals(dbProduct.getSalesLedgerId(), ledgerId)) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,閿�鍞骇鍝佷笌璁㈠崟涓嶅尮閰�");
+            }
+            if (!Objects.equals(dbProduct.getType(), SaleEnum.SALE.getCode())) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,浠呮敮鎸侀攢鍞鍗曚骇鍝佽");
+            }
+            if (dbProduct.getProductModelId() == null) {
+                throw new ServiceException("涓嶅悎鏍煎叆搴撳け璐�,浜у搧瑙勬牸鏈淮鎶�,鏃犳硶鍏ュ簱");
+            }
+            stockUtils.addUnStock(ledgerId, dbProduct.getId(), dbProduct.getProductModelId(), inboundThisLine,
+                    StockInUnQualifiedRecordTypeEnum.SALES_SCAN_UNSTOCK_IN.getCode(), dbProduct.getId());
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void scanOutbound(SalesScanInboundDto dto) {
+        if (dto == null || dto.getSalesLedgerId() == null) {
+            throw new ServiceException("閿�鍞鍗曞嚭搴撳け璐�,鍑哄簱鏁版嵁涓嶈兘涓虹┖");
+        }
+        SalesLedger salesLedger = baseMapper.selectById(dto.getSalesLedgerId());
+        if (salesLedger == null) {
+            throw new ServiceException("鍑哄簱澶辫触,閿�鍞鍗曚笉瀛樺湪");
+        }
+        if (salesLedger.getDeliveryStatus() != null && salesLedger.getDeliveryStatus() == 5) {
+            throw new ServiceException("鍑哄簱澶辫触,璇ラ攢鍞鍗曞凡鍙戣揣");
+        }
+        if (CollectionUtils.isEmpty(dto.getSalesLedgerProductList())) {
+            throw new ServiceException("閿�鍞鍗曞嚭搴撳け璐�,鍑哄簱浜у搧涓嶈兘涓虹┖");
+        }
+        int saleType = SaleEnum.SALE.getCode();
+        Map<Long, BigDecimal> outboundQtyByLineId = new LinkedHashMap<>();
+        for (SalesLedgerProduct line : dto.getSalesLedgerProductList()) {
+            if (line == null || line.getId() == null) {
+                throw new ServiceException("鍑哄簱澶辫触,閿�鍞骇鍝佷俊鎭笉瀹屾暣");
+            }
+            BigDecimal outboundQty = line.getStockedQuantity();
+            if (outboundQty == null) {
+                throw new ServiceException("鍑哄簱澶辫触,鍑哄簱鏁伴噺涓嶈兘涓虹┖");
+            }
+            if (outboundQty.compareTo(BigDecimal.ZERO) < 0) {
+                throw new ServiceException("鍑哄簱澶辫触,鍑哄簱鏁伴噺涓嶈兘涓鸿礋鏁�");
+            }
+            outboundQtyByLineId.merge(line.getId(), outboundQty, BigDecimal::add);
+        }
+        Long ledgerId = salesLedger.getId();
+        for (Map.Entry<Long, BigDecimal> entry : outboundQtyByLineId.entrySet()) {
+            Long salesLedgerProductId = entry.getKey();
+            BigDecimal outboundThisLine = entry.getValue();
+            if (outboundThisLine.compareTo(BigDecimal.ZERO) == 0) {
+                continue;
+            }
+            SalesLedgerProduct dbProduct = salesLedgerProductMapper.selectById(salesLedgerProductId);
+            if (dbProduct == null) {
+                throw new ServiceException("鍑哄簱澶辫触,閿�鍞骇鍝佷笉瀛樺湪");
+            }
+            if (!Objects.equals(dbProduct.getSalesLedgerId(), ledgerId) || !Objects.equals(dbProduct.getType(), saleType)) {
+                throw new ServiceException("鍑哄簱澶辫触,閿�鍞骇鍝佷笌璁㈠崟涓嶅尮閰�");
+            }
+            if (dbProduct.getProductModelId() == null) {
+                throw new ServiceException("鍑哄簱澶辫触,浜у搧瑙勬牸鏈淮鎶�,鏃犳硶鍑哄簱");
+            }
+            stockUtils.assertQualifiedAvailable(dbProduct.getProductModelId(), outboundThisLine);
+
+            BigDecimal oldStocked = dbProduct.getStockedQuantity() == null ? BigDecimal.ZERO : dbProduct.getStockedQuantity();
+            BigDecimal newStocked = oldStocked.subtract(outboundThisLine);
+            if (newStocked.compareTo(BigDecimal.ZERO) < 0) {
+                newStocked = BigDecimal.ZERO;
+            }
+
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setRecordId(dbProduct.getId());
+            stockInventoryDto.setRecordType(StockOutQualifiedRecordTypeEnum.SALE_SCAN_STOCK_OUT.getCode());
+            stockInventoryDto.setQualitity(outboundThisLine);
+            stockInventoryDto.setProductModelId(dbProduct.getProductModelId());
+            stockInventoryDto.setSalesLedgerId(ledgerId);
+            stockInventoryDto.setSalesLedgerProductId(dbProduct.getId());
+            stockInventoryService.subtractStockInventory(stockInventoryDto);
+
+            BigDecimal orderQty = dbProduct.getQuantity() == null ? BigDecimal.ZERO : dbProduct.getQuantity();
+            int lineStockStatus;
+            if (newStocked.compareTo(BigDecimal.ZERO) <= 0) {
+                lineStockStatus = 0;
+            } else if (orderQty.compareTo(BigDecimal.ZERO) > 0 && newStocked.compareTo(orderQty) < 0) {
+                lineStockStatus = 1;
+            } else {
+                lineStockStatus = 2;
+            }
+            dbProduct.setStockedQuantity(newStocked);
+            dbProduct.setProductStockStatus(lineStockStatus);
+            dbProduct.fillRemainingQuantity();
+            salesLedgerProductMapper.updateById(dbProduct);
+        }
+        List<SalesLedgerProduct> ledgerAllProducts = salesLedgerProductMapper.selectList(
+                Wrappers.<SalesLedgerProduct>lambdaQuery().eq(SalesLedgerProduct::getSalesLedgerId, ledgerId));
+        boolean anyInbound = ledgerAllProducts.stream().anyMatch(p -> {
+            BigDecimal sq = p.getStockedQuantity();
+            return sq != null && sq.compareTo(BigDecimal.ZERO) > 0;
+        });
+        boolean allLinesFull = ledgerAllProducts.stream().allMatch(p -> Objects.equals(p.getProductStockStatus(), 2));
+        salesLedger.setStockStatus(allLinesFull ? 2 : (anyInbound ? 1 : 0));
+        baseMapper.updateById(salesLedger);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void scanOutboundUnqualified(SalesScanInboundDto dto) {
+        if (dto == null || dto.getSalesLedgerId() == null) {
+            throw new ServiceException("閿�鍞鍗曚笉鍚堟牸鍑哄簱澶辫触,鍑哄簱鏁版嵁涓嶈兘涓虹┖");
+        }
+        SalesLedger salesLedger = baseMapper.selectById(dto.getSalesLedgerId());
+        if (salesLedger == null) {
+            throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,閿�鍞鍗曚笉瀛樺湪");
+        }
+        if (salesLedger.getDeliveryStatus() != null && salesLedger.getDeliveryStatus() == 5) {
+            throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,璇ラ攢鍞鍗曞凡鍙戣揣");
+        }
+        if (CollectionUtils.isEmpty(dto.getSalesLedgerProductList())) {
+            throw new ServiceException("閿�鍞鍗曚笉鍚堟牸鍑哄簱澶辫触,鍑哄簱浜у搧涓嶈兘涓虹┖");
+        }
+        int saleType = SaleEnum.SALE.getCode();
+        Map<Long, BigDecimal> outboundQtyByLineId = new LinkedHashMap<>();
+        for (SalesLedgerProduct line : dto.getSalesLedgerProductList()) {
+            if (line == null || line.getId() == null) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,閿�鍞骇鍝佷俊鎭笉瀹屾暣");
+            }
+            BigDecimal outboundQty = line.getStockedQuantity();
+            if (outboundQty == null) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,鍑哄簱鏁伴噺涓嶈兘涓虹┖");
+            }
+            if (outboundQty.compareTo(BigDecimal.ZERO) < 0) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,鍑哄簱鏁伴噺涓嶈兘涓鸿礋鏁�");
+            }
+            outboundQtyByLineId.merge(line.getId(), outboundQty, BigDecimal::add);
+        }
+        Long ledgerId = salesLedger.getId();
+        for (Map.Entry<Long, BigDecimal> entry : outboundQtyByLineId.entrySet()) {
+            Long salesLedgerProductId = entry.getKey();
+            BigDecimal outboundThisLine = entry.getValue();
+            if (outboundThisLine.compareTo(BigDecimal.ZERO) == 0) {
+                continue;
+            }
+            SalesLedgerProduct dbProduct = salesLedgerProductMapper.selectById(salesLedgerProductId);
+            if (dbProduct == null) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,閿�鍞骇鍝佷笉瀛樺湪");
+            }
+            if (!Objects.equals(dbProduct.getSalesLedgerId(), ledgerId) || !Objects.equals(dbProduct.getType(), saleType)) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,閿�鍞骇鍝佷笌璁㈠崟涓嶅尮閰�");
+            }
+            if (dbProduct.getProductModelId() == null) {
+                throw new ServiceException("涓嶅悎鏍煎嚭搴撳け璐�,浜у搧瑙勬牸鏈淮鎶�,鏃犳硶鍑哄簱");
+            }
+            stockUtils.assertUnqualifiedAvailable(dbProduct.getProductModelId(), outboundThisLine);
+            stockUtils.subtractUnStock(ledgerId, dbProduct.getId(), dbProduct.getProductModelId(), outboundThisLine,
+                    StockOutUnQualifiedRecordTypeEnum.SALE_SCAN_UNSTOCK_OUT.getCode(), dbProduct.getId());
+        }
+    }
 }
diff --git a/src/main/java/com/ruoyi/stock/dto/StockUninventoryDto.java b/src/main/java/com/ruoyi/stock/dto/StockUninventoryDto.java
index 404c58a..c9263c9 100644
--- a/src/main/java/com/ruoyi/stock/dto/StockUninventoryDto.java
+++ b/src/main/java/com/ruoyi/stock/dto/StockUninventoryDto.java
@@ -6,7 +6,7 @@
 import java.math.BigDecimal;
 
 @Data
-public class StockUninventoryDto  extends StockUninventory {
+public class StockUninventoryDto extends StockUninventory {
     private String productName;
     private String model;
     private String unit;
@@ -19,4 +19,14 @@
     private Long recordId;
 
     private BigDecimal unLockedQuantity;
+
+    /**
+     * 閿�鍞鍗旾D
+     */
+    private Long salesLedgerId;
+
+    /**
+     * 閿�鍞鍗曚骇鍝佽ID
+     */
+    private Long salesLedgerProductId;
 }
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
index d98bc5a..5208f92 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -119,7 +119,10 @@
             throw new RuntimeException("搴撳瓨涓嶈冻鏃犳硶鍑哄簱");
         }
 
-        stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto);
+        int affectRows = stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto);
+        if (affectRows <= 0) {
+            throw new RuntimeException("搴撳瓨涓嶈冻鏃犳硶鍑哄簱");
+        }
         return true;
     }
 
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
index 2d5b8f5..6fb76e6 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
@@ -54,6 +54,8 @@
         stockInRecordDto.setStockInNum(stockUninventoryDto.getQualitity());
         stockInRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
         stockInRecordDto.setType("1");
+        stockInRecordDto.setSalesLedgerId(stockUninventoryDto.getSalesLedgerId());
+        stockInRecordDto.setSalesLedgerProductId(stockUninventoryDto.getSalesLedgerProductId());
         stockInRecordService.add(stockInRecordDto);
         //鍐嶈繘琛屾柊澧炲簱瀛樻暟閲忓簱瀛�
         //鍏堟煡璇㈠簱瀛樿〃涓殑浜у搧鏄惁瀛樺湪锛屼笉瀛樺湪鏂板锛屽瓨鍦ㄦ洿鏂�
@@ -82,12 +84,17 @@
         stockOutRecordDto.setStockOutNum(stockUninventoryDto.getQualitity());
         stockOutRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
         stockOutRecordDto.setType("1");
+        stockOutRecordDto.setSalesLedgerId(stockUninventoryDto.getSalesLedgerId());
+        stockOutRecordDto.setSalesLedgerProductId(stockUninventoryDto.getSalesLedgerProductId());
         stockOutRecordService.add(stockOutRecordDto);
         StockUninventory oldStockInventory = stockUninventoryMapper.selectOne(new QueryWrapper<StockUninventory>().lambda().eq(StockUninventory::getProductModelId, stockUninventoryDto.getProductModelId()));
         if (ObjectUtils.isEmpty(oldStockInventory)) {
             throw new RuntimeException("浜у搧搴撳瓨涓嶅瓨鍦�");
         }else {
-            stockUninventoryMapper.updateSubtractStockUnInventory(stockUninventoryDto);
+            int affectRows = stockUninventoryMapper.updateSubtractStockUnInventory(stockUninventoryDto);
+            if (affectRows <= 0) {
+                throw new RuntimeException("搴撳瓨涓嶈冻鏃犳硶鍑哄簱");
+            }
         }
         return 1;
     }
diff --git a/src/main/resources/mapper/stock/StockInventoryMapper.xml b/src/main/resources/mapper/stock/StockInventoryMapper.xml
index 3931022..fb46594 100644
--- a/src/main/resources/mapper/stock/StockInventoryMapper.xml
+++ b/src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -49,7 +49,8 @@
             </if>
             update_time = now()
         </set>
-        where product_model_id = #{ew.productModelId} and qualitity >= #{ew.qualitity}
+        where product_model_id = #{ew.productModelId}
+          and (qualitity - COALESCE(locked_quantity, 0)) >= #{ew.qualitity}
     </update>
 
     <select id="pagestockInventory" resultType="com.ruoyi.stock.dto.StockInventoryDto">
diff --git a/src/main/resources/mapper/stock/StockUninventoryMapper.xml b/src/main/resources/mapper/stock/StockUninventoryMapper.xml
index e043bd7..d48acfa 100644
--- a/src/main/resources/mapper/stock/StockUninventoryMapper.xml
+++ b/src/main/resources/mapper/stock/StockUninventoryMapper.xml
@@ -25,7 +25,8 @@
             </if>
             update_time = now()
         </set>
-        where product_model_id = #{ew.productModelId} and qualitity >= #{ew.qualitity}
+        where product_model_id = #{ew.productModelId}
+          and (qualitity - COALESCE(locked_quantity, 0)) >= #{ew.qualitity}
     </update>
     <update id="updateAddStockUnInventory">
         update stock_uninventory

--
Gitblit v1.9.3