From 7bf754a7835d06f26240c4ca15bc5f83650de377 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期一, 21 九月 2026 20:52:25 +0800
Subject: [PATCH] docs(api): 更新信托基金与代储台账及油品出库相关接口文档

---
 src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java |  740 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 721 insertions(+), 19 deletions(-)

diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
index 99cc760..08960b9 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
@@ -2,15 +2,38 @@
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.account.mapper.sales.AccountInvoiceApplicationMapper;
+import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
+import com.ruoyi.basic.pojo.Vehicle;
+import com.ruoyi.basic.service.IVehicleService;
+import com.ruoyi.common.enums.ReviewStatusEnum;
+import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
 import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
-import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
 import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.utils.EnumUtil;
 import com.ruoyi.common.utils.OrderUtils;
+import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.outsourcing.enums.OutsourcingOrderStatusEnum;
+import com.ruoyi.outsourcing.mapper.OutsourcingOrderMapper;
+import com.ruoyi.outsourcing.mapper.OutsourcingOrderProductMapper;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrder;
+import com.ruoyi.outsourcing.pojo.OutsourcingOrderProduct;
+import com.ruoyi.sales.dto.SalesLedgerDto;
+import com.ruoyi.sales.mapper.SalesLedgerArrivalMapper;
+import com.ruoyi.sales.mapper.SalesLedgerMapper;
+import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
+import com.ruoyi.sales.mapper.StockOutRecordSalesLedgerMapper;
+import com.ruoyi.sales.pojo.SalesLedger;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import com.ruoyi.sales.pojo.StockOutRecordSalesLedger;
+
+import java.time.LocalDateTime;
+import com.ruoyi.stock.dto.OilOutAvailableDto;
 import com.ruoyi.stock.dto.StockInventoryDto;
 import com.ruoyi.stock.dto.StockOutRecordDto;
 import com.ruoyi.stock.dto.StockUninventoryDto;
@@ -18,16 +41,23 @@
 import com.ruoyi.stock.mapper.StockInventoryMapper;
 import com.ruoyi.stock.mapper.StockOutRecordMapper;
 import com.ruoyi.stock.mapper.StockUninventoryMapper;
-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.StockOutRecordService;
-import lombok.AllArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
-import javax.servlet.http.HttpServletResponse;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 
 /**
  * <p>
@@ -38,11 +68,43 @@
  * @since 2026-01-21 05:27:04
  */
 @Service
-@AllArgsConstructor
+@RequiredArgsConstructor
 public class StockOutRecordServiceImpl extends ServiceImpl<StockOutRecordMapper, StockOutRecord> implements StockOutRecordService {
-    private StockOutRecordMapper stockOutRecordMapper;
-    private StockInventoryMapper stockInventoryMapper;
-    private StockUninventoryMapper stockUninventoryMapper;
+
+    /**
+     * 鍑哄簱绫诲埆锛氶攢鍞�傚敮涓�鐨勫尯鍒槸閿�鍞被鍑哄簱蹇呴』閫夎溅杈嗐�佹寜杞﹁締绠椾笂娆″墿浣欐补閲忥紱
+     * 鎵e簱瀛樸�佽繘鍙戣揣鍙拌处杩欎簺瀵规墍鏈夊嚭搴撶被鍒竴瑙嗗悓浠�
+     */
+    private static final String OUT_CATEGORY_SALE = "閿�鍞�";
+
+    /**
+     * 鍑哄簱绫诲埆锛氫唬鍌ㄣ�傚鎴锋妸鑷繁鐨勬补瀵勫瓨鍦ㄦ垜浠殑缃愰噷銆�
+     * 鍑哄簱瑕佹眰涓庨攢鍞竴鑷达紙閫夋壒娆°�佸鎵规墸搴撳瓨銆佸垹闄ゅ綊杩樸�佽繘鍙戣揣鍙拌处锛夛紝鍙槸涓嶈姹傞�夎溅杈�
+     */
+    private static final String OUT_CATEGORY_DEPOSIT = "浠e偍";
+
+    /**
+     * 閿�鍞彴璐︾殑閿�鍞被鍨嬶細浠e偍锛堝崠瀛樺偍浣嶇疆锛夈�傝繖绫诲彴璐︽病鏈変骇鍝佹槑缁嗭紝鍙湁娌瑰簱+鍌ㄧ綈锛岃 SalesLedger.ledgerType
+     */
+    private static final String LEDGER_TYPE_DEPOSIT = "浠e偍";
+
+    /**
+     * 閿�鍞彴璐︿骇鍝佹槑缁嗙殑琛岀被鍨嬶細閿�鍞�傝 sales_ledger_product.type
+     */
+    private static final Integer LEDGER_PRODUCT_TYPE_SALE = 1;
+
+    private final StockOutRecordMapper stockOutRecordMapper;
+    private final IVehicleService vehicleService;
+    private final StockInventoryMapper stockInventoryMapper;
+    private final StockUninventoryMapper stockUninventoryMapper;
+    private final AccountSalesCollectionMapper accountSalesCollectionMapper;
+    private final AccountInvoiceApplicationMapper accountInvoiceApplicationMapper;
+    private final OutsourcingOrderProductMapper outsourcingOrderProductMapper;
+    private final OutsourcingOrderMapper outsourcingOrderMapper;
+    private final SalesLedgerMapper salesLedgerMapper;
+    private final SalesLedgerProductMapper salesLedgerProductMapper;
+    private final StockOutRecordSalesLedgerMapper stockOutRecordSalesLedgerMapper;
+    private final SalesLedgerArrivalMapper salesLedgerArrivalMapper;
 
     @Override
     public IPage<StockOutRecordDto> listPage(Page page, StockOutRecordDto stockOutRecordDto) {
@@ -51,11 +113,34 @@
 
     @Override
     public int add(StockOutRecordDto stockOutRecordDto) {
-        String no = OrderUtils.countTodayByCreateTime(stockOutRecordMapper, "CK","outboundBatches");
+        List<StockOutRecordSalesLedger> ledgerBindings = resolveSalesLedgerBinding(stockOutRecordDto);
+        LocalDateTime createTime = stockOutRecordDto.getCreateTime();
+        if (createTime == null) {
+            createTime = LocalDateTime.now();
+        }
+        // 鍑哄簱鍗曞彿鍏堜簬鏍¢獙鐢熸垚锛屽ソ璁╂牎楠屽け璐ユ椂鐨勬姤閿欏甫涓婂嚭搴撴壒娆�
+        String no = OrderUtils.countTodayByCreateTime(stockOutRecordMapper, "CK","outbound_batches", createTime);
         stockOutRecordDto.setOutboundBatches(no);
-        StockInRecord stockInRecord = new StockInRecord();
-        BeanUtils.copyProperties(stockOutRecordDto, stockInRecord);
-        return stockOutRecordMapper.insert(stockOutRecordDto);
+        stockOutRecordDto.setCreateTime(createTime);
+        // 娌瑰搧鍑哄簱锛坱ype 涓虹┖锛夊鎵归�氳繃鏃朵竴寰嬫寜鎵规鎵e噺搴撳瓨锛屾壒娆′笉鑳戒负绌恒��
+        // type=0/1 鐨勫悎鏍笺�佷笉鍚堟牸鍑哄簱璧板悇鑷垎鏀紝涓嶅彈杩欓噷绾︽潫
+        if (StringUtils.isEmpty(stockOutRecordDto.getType())) {
+            if (StringUtils.isEmpty(stockOutRecordDto.getBatchNo())) {
+                throw new BaseException("娌瑰搧鍑哄簱蹇呴』閫夋嫨鍑哄簱鎵规");
+            }
+            if (OUT_CATEGORY_SALE.equals(stockOutRecordDto.getOutCategory())) {
+                // 鍙湁閿�鍞被鎸夎溅杈嗙畻娈嬫补锛屼唬鍌�/瀹㈠瓨涓嶈姹傞�夎溅杈�
+                resolveVehicle(stockOutRecordDto);
+            }
+            checkOilOutAvailable(stockOutRecordDto);
+        }
+        if (StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode().equals(stockOutRecordDto.getRecordType())){
+            stockOutRecordDto.setApprovalStatus(3);
+        }
+        int rows = stockOutRecordMapper.insert(stockOutRecordDto);
+        // 鍑哄簱鍗� id 鐢辫嚜澧炰富閿洖濉紝鎷垮埌鍚庢墠鑳借惤缁戝畾琛�
+        saveLedgerBindings(stockOutRecordDto.getId(), ledgerBindings);
+        return rows;
     }
 
     @Override
@@ -66,38 +151,287 @@
             throw new BaseException("璇ュ嚭搴撹褰曚笉瀛樺湪,鏃犳硶鏇存柊!!!");
         }
 
-        String[] ignoreProperties = {"id", "outbound_batches"};//鎺掗櫎id灞炴��
+        List<StockOutRecordSalesLedger> ledgerBindings = resolveSalesLedgerBinding(stockOutRecordDto);
+        // 鎺掗櫎 id 灞炴�э紱recordType/recordId 鐢卞悗绔淮鎶わ紙娌瑰搧鍑哄簱鎭掍负 '1' / 0锛屾墜宸ュ彂璐у崟鍑哄簱鐢卞彂璐ф祦绋嬪啓鍏ワ級锛�
+        // 鍓嶇缂栬緫琛ㄥ崟涓嶅洖浼犱細鎶婂畠浠啿鎴� null锛岀牬鍧忓嚭搴撹褰曠殑鏉ユ簮鏍囪瘑
+        String[] ignoreProperties = {"id", "outbound_batches", "recordType", "recordId"};
         BeanUtils.copyProperties(stockOutRecordDto, stockOutRecord, ignoreProperties);
-        return stockOutRecordMapper.updateById(stockOutRecord);
+        int rows = stockOutRecordMapper.updateById(stockOutRecord);
+        rebindLedger(id, ledgerBindings);
+        return rows;
+    }
+
+    /**
+     * 缁戝畾閿�鍞彴璐︼細涓�娆″嚭搴撳彲浠ュ垎閫佸涓鎴凤紝鎵�浠ュ彴璐︽槸涓垪琛紝姣忎釜鍙拌处鎵嬪伐濉湰娆″垎鎽婂埌鐨勬暟閲忥紝
+     * 鍚勫彴璐︽暟閲忎箣鍜屽繀椤荤瓑浜庢湰娆″嚭搴撴�婚噺銆傚鎴蜂竴寰嬩互鍙拌处涓哄噯瑕嗙洊锛屽嚭搴撶被鍒寜鍙拌处鐨勯攢鍞被鍨嬭嚜鍔ㄥ畾锛�
+     * 鍏嶅緱鍑虹幇銆岀粦浜嗕唬鍌ㄥ彴璐﹀嵈鎸夐攢鍞姹傞�夎溅杈嗐�嶈繖绫荤煕鐩炬暟鎹��
+     * <ul>
+     *     <li>鍗栨补锛堝惈 ledgerType 涓虹┖锛夛細鍑哄簱绫诲埆缃�岄攢鍞�嶏紝閫愪釜鏍¢獙鎵�閫夎鏍肩‘瀹炲湪璇ュ彴璐︾殑閿�鍞槑缁嗛噷锛�
+     *         骞舵妸鍛戒腑鐨勬槑缁� id 钀借繘缁戝畾琛岋紙鍚屼竴鍙拌处鍚岃鏍煎琛屾椂鍥哄畾鍙� id 鏈�灏忕殑閭f潯锛�</li>
+     *     <li>浠e偍锛氬嚭搴撶被鍒疆銆屼唬鍌ㄣ�嶏紝甯﹀叆鍙拌处鐨勬补搴�/鍌ㄧ綈锛岃烦杩囥�岃鏍兼槸鍚﹀湪鍙拌处鏄庣粏閲屻�嶇殑鏍¢獙 鈥斺��
+     *         浠e偍鍙拌处鏈韩灏辨槸鍗栧瓨鍌ㄤ綅缃�佹病鏈変骇鍝佹槑缁嗭紝浣嗘墸搴撳瓨瑕佹寜瑙勬牸瀹氫綅锛岃鏍间粛蹇呴』閫夈��
+     *         浠e偍涓嶈兘涓庡叾浠栧彴璐︽贩缁戯紝涔熶笉浜х粦瀹氳锛堝畠鐨勬补鏄鎴疯嚜宸辩殑锛屼笉璧板埌璐э級</li>
+     * </ul>
+     *
+     * @return 寰呰惤搴撶殑缁戝畾琛岋紱null 琛ㄧず鏈璇锋眰娌″甫鍙拌处淇℃伅锛岃皟鐢ㄦ柟搴斾繚鎸佸凡鏈夌粦瀹氫笉鍙�
+     */
+    private List<StockOutRecordSalesLedger> resolveSalesLedgerBinding(StockOutRecordDto stockOutRecordDto) {
+        List<StockOutRecordSalesLedger> ledgerList = stockOutRecordDto.getLedgerList();
+        if (ledgerList == null && stockOutRecordDto.getSalesLedgerId() != null) {
+            // 鍏煎鍙紶 salesLedgerId 鐨勬棫鍏ュ弬锛氳浣溿�岀粦涓�涓彴璐︺�佹暟閲忓氨鏄湰娆″嚭搴撴�婚噺銆�
+            StockOutRecordSalesLedger single = new StockOutRecordSalesLedger();
+            single.setSalesLedgerId(stockOutRecordDto.getSalesLedgerId());
+            single.setQuantity(stockOutRecordDto.getStockOutNum());
+            ledgerList = new ArrayList<>();
+            ledgerList.add(single);
+        } else if (ledgerList == null) {
+            return null;
+        }
+        if (ledgerList.isEmpty()) {
+            // 鏄庣‘鐨勩�屼笉缁戝彴璐︺��
+            stockOutRecordDto.setSalesLedgerId(null);
+            return ledgerList;
+        }
+        List<SalesLedger> ledgers = new ArrayList<>(ledgerList.size());
+        for (StockOutRecordSalesLedger item : ledgerList) {
+            if (item == null || item.getSalesLedgerId() == null) {
+                throw new BaseException("缁戝畾鐨勯攢鍞彴璐︿笉鑳戒负绌�");
+            }
+            SalesLedger salesLedger = salesLedgerMapper.selectById(item.getSalesLedgerId());
+            if (salesLedger == null) {
+                throw new BaseException("鎵�閫夐攢鍞彴璐︿笉瀛樺湪,閿�鍞彴璐d:" + item.getSalesLedgerId());
+            }
+            ledgers.add(salesLedger);
+        }
+        // 棣栭」鍙拌处缁х画鍐欒繘鍑哄簱璁板綍鐨勫崟鍊煎垪锛屽嚭搴撶鐞嗗垪琛ㄣ�佸簱瀛樻姤琛ㄩ兘鎸夊畠灞曠ず
+        SalesLedger first = ledgers.get(0);
+        stockOutRecordDto.setSalesLedgerId(first.getId());
+        stockOutRecordDto.setCustomerId(first.getCustomerId());
+        stockOutRecordDto.setCustomerName(first.getCustomerName());
+
+        boolean hasDeposit = ledgers.stream().anyMatch(item -> LEDGER_TYPE_DEPOSIT.equals(item.getLedgerType()));
+        if (hasDeposit) {
+            if (ledgers.size() > 1) {
+                throw new BaseException("浠e偍鍙拌处涓嶈兘涓庡叾浠栭攢鍞彴璐︿竴璧风粦瀹�,涓�娆″嚭搴撳彧鑳界粦涓�涓唬鍌ㄥ彴璐�");
+            }
+            applyDepositLedger(stockOutRecordDto, first);
+            return new ArrayList<>();
+        }
+        stockOutRecordDto.setOutCategory(OUT_CATEGORY_SALE);
+        if (stockOutRecordDto.getProductModelId() == null) {
+            throw new BaseException("缁戝畾閿�鍞彴璐︽椂蹇呴』閫夋嫨浜у搧瑙勬牸,閿�鍞悎鍚屽彿:" + first.getSalesContractNo());
+        }
+        BigDecimal stockOutNum = stockOutRecordDto.getStockOutNum();
+        if (stockOutNum == null || stockOutNum.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new BaseException("璇峰~鍐欐湰娆″嚭搴撴暟閲�(鍚�)");
+        }
+        BigDecimal total = BigDecimal.ZERO;
+        for (int index = 0; index < ledgerList.size(); index++) {
+            StockOutRecordSalesLedger item = ledgerList.get(index);
+            SalesLedger salesLedger = ledgers.get(index);
+            BigDecimal quantity = item.getQuantity();
+            if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
+                throw new BaseException("姣忎釜閿�鍞彴璐︾殑缁戝畾鏁伴噺蹇呴』澶т簬0,閿�鍞悎鍚屽彿:" + salesLedger.getSalesContractNo());
+            }
+            SalesLedgerProduct detail = findLedgerSaleDetail(salesLedger.getId(), stockOutRecordDto.getProductModelId());
+            if (detail == null) {
+                throw new BaseException("鎵�閫変骇鍝佷笉鍦ㄨ閿�鍞彴璐︽槑缁嗕腑,閿�鍞悎鍚屽彿:" + salesLedger.getSalesContractNo());
+            }
+            item.setSalesLedgerProductId(detail.getId());
+            item.setCustomerId(salesLedger.getCustomerId());
+            item.setCustomerName(salesLedger.getCustomerName());
+            total = total.add(quantity);
+        }
+        if (total.compareTo(stockOutNum) != 0) {
+            throw new BaseException("鍚勯攢鍞彴璐︾粦瀹氭暟閲忎箣鍜屽繀椤荤瓑浜庢湰娆″嚭搴撴暟閲�,鍑哄簱鏁伴噺:" + toPlain(stockOutNum)
+                    + ",缁戝畾鏁伴噺涔嬪拰:" + toPlain(total));
+        }
+        return ledgerList;
+    }
+
+    /**
+     * 鍚屼竴鍙拌处鍙兘鏈夊琛屽悓瑙勬牸鐨勯攢鍞槑缁嗭紝鍥哄畾鍙� id 鏈�灏忕殑閭f潯锛屼繚璇佹瘡娆$粦瀹氱殑鏄庣粏 id 绋冲畾
+     */
+    private SalesLedgerProduct findLedgerSaleDetail(Long salesLedgerId, Long productModelId) {
+        List<SalesLedgerProduct> details = salesLedgerProductMapper.selectList(
+                Wrappers.<SalesLedgerProduct>lambdaQuery()
+                        .eq(SalesLedgerProduct::getSalesLedgerId, salesLedgerId)
+                        .eq(SalesLedgerProduct::getType, LEDGER_PRODUCT_TYPE_SALE)
+                        .eq(SalesLedgerProduct::getProductModelId, productModelId)
+                        .orderByAsc(SalesLedgerProduct::getId));
+        return details.isEmpty() ? null : details.get(0);
+    }
+
+    /**
+     * 浠e偍鍙拌处锛氬崠鐨勬槸瀛樺偍浣嶇疆銆佹病鏈変骇鍝佹槑缁嗭紝浣嗘墸搴撳瓨瑕佹寜瑙勬牸瀹氫綅锛岃鏍间粛蹇呴』閫�
+     */
+    private void applyDepositLedger(StockOutRecordDto stockOutRecordDto, SalesLedger salesLedger) {
+        stockOutRecordDto.setOutCategory(OUT_CATEGORY_DEPOSIT);
+        stockOutRecordDto.setOilDepotId(salesLedger.getOilDepotId());
+        stockOutRecordDto.setOilDepotName(salesLedger.getOilDepotName());
+        stockOutRecordDto.setTankId(salesLedger.getTankId());
+        stockOutRecordDto.setTankNo(salesLedger.getTankNo());
+        if (stockOutRecordDto.getProductModelId() == null) {
+            throw new BaseException("浠e偍鍑哄簱闇�瑕佹墸鍑忓簱瀛�,蹇呴』閫夋嫨浜у搧瑙勬牸,閿�鍞悎鍚屽彿:" + salesLedger.getSalesContractNo());
+        }
+    }
+
+    /**
+     * 钀藉簱缁戝畾琛屻�傛槑缁� id / 瀹㈡埛 / 鍑哄簱鍗� id 涓�寰嬬敤鍚庣鏍¢獙鏃剁畻鍑虹殑鍊硷紝鍓嶇浼犵殑琚鐩栥��
+     * 閫愭潯 insert锛氭湰琛ㄤ竴娆″嚭搴撳彧鏈夊嚑琛岋紝涓� MyBaseMapper 鐨� insertBatchSomeColumn
+     * 椤圭洰閲屾病閰嶈嚜瀹氫箟 SqlInjector锛屾柟娉曟牴鏈病娉ㄥ唽锛堣皟鐢ㄤ細鎶� Invalid bound statement锛�
+     */
+    private void saveLedgerBindings(Long stockOutRecordId, List<StockOutRecordSalesLedger> bindings) {
+        if (stockOutRecordId == null || CollectionUtils.isEmpty(bindings)) {
+            return;
+        }
+        for (StockOutRecordSalesLedger binding : bindings) {
+            binding.setId(null);
+            binding.setStockOutRecordId(stockOutRecordId);
+            stockOutRecordSalesLedgerMapper.insert(binding);
+        }
+    }
+
+    /**
+     * 閲嶆柊缁戝畾鍙拌处锛氱粦瀹氭病鍙樺氨鍘熸牱涓嶅姩锛堜繚浣忕粦瀹氳 id锛夛紱鍙樹簡鍒欏厛鎸夊嚭搴撳崟鐗╃悊鍒犳棫鍐嶉噸鎻掋��
+     * 宸茬櫥璁板埌璐ц褰曟椂涓嶅厑璁告敼 鈥斺�� 鍒拌揣璁板綍鎸傚湪銆屽彴璐γ楀嚭搴撳崟銆嶄笂锛屾敼缁戜細璁╁畠鎸囧悜涓嶅瓨鍦ㄧ殑鍒嗘憡鏁伴噺
+     */
+    private void rebindLedger(Long stockOutRecordId, List<StockOutRecordSalesLedger> bindings) {
+        if (bindings == null) {
+            return;
+        }
+        List<StockOutRecordSalesLedger> existing = stockOutRecordSalesLedgerMapper.selectList(
+                Wrappers.<StockOutRecordSalesLedger>lambdaQuery()
+                        .eq(StockOutRecordSalesLedger::getStockOutRecordId, stockOutRecordId));
+        if (sameBinding(existing, bindings)) {
+            return;
+        }
+        if (salesLedgerArrivalMapper.countByStockOutRecordId(stockOutRecordId) > 0) {
+            throw new BaseException("璇ュ嚭搴撳崟宸茬櫥璁板埌璐ц褰�,鏃犳硶淇敼缁戝畾鐨勯攢鍞彴璐�,鍑哄簱鍗昳d:" + stockOutRecordId);
+        }
+        stockOutRecordSalesLedgerMapper.delete(Wrappers.<StockOutRecordSalesLedger>lambdaQuery()
+                .eq(StockOutRecordSalesLedger::getStockOutRecordId, stockOutRecordId));
+        saveLedgerBindings(stockOutRecordId, bindings);
+    }
+
+    /**
+     * 鍙拌处涓庡垎鎽婃暟閲忔槸鍚﹀畬鍏ㄤ竴鑷达紙椤哄簭鏃犲叧锛�
+     */
+    private boolean sameBinding(List<StockOutRecordSalesLedger> existing, List<StockOutRecordSalesLedger> incoming) {
+        if (existing.size() != incoming.size()) {
+            return false;
+        }
+        Map<Long, BigDecimal> existingQuantity = new HashMap<>(existing.size());
+        for (StockOutRecordSalesLedger item : existing) {
+            existingQuantity.put(item.getSalesLedgerId(), item.getQuantity());
+        }
+        for (StockOutRecordSalesLedger item : incoming) {
+            BigDecimal quantity = existingQuantity.get(item.getSalesLedgerId());
+            if (quantity == null || quantity.compareTo(item.getQuantity()) != 0) {
+                return false;
+            }
+        }
+        return true;
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public int batchDelete(List<Long> ids) {
+        //todo 濡傛灉鍑哄簱涓庡紑绁ㄦ敹娆炬湁鍏宠仈鍒欐棤娉曞垹闄�
+        if (accountSalesCollectionMapper.existsByStockOutRecordId(ids) ||
+                accountInvoiceApplicationMapper.existsByStockOutRecordId(ids)) {
+            throw new BaseException("鍑哄簱璁板綍瀛樺湪寮�绁ㄦ敹娆惧叧鑱旀暟鎹�,鏃犳硶鍒犻櫎!!!");
+        }
+        // 娌瑰搧鍑哄簱鐨勫紑绁�/鏀舵鍏宠仈瀛樼殑鏄粦瀹氳 id锛堜笌鍑哄簱鍗� id 鏄袱涓嫭绔� id 绌洪棿锛夛紝蹇呴』鍗曠嫭鍒わ紝
+        // 鍚﹀垯缁戜簡鍙拌处鐨勬补鍝佸嚭搴撹寮�绁ㄥ悗杩樿兘鍒犳帀锛岀暀涓嬫寚鍚戜笉瀛樺湪缁戝畾琛岀殑鍏宠仈鏁版嵁
+        if (!CollectionUtils.isEmpty(ids)) {
+            List<Long> bindingIds = stockOutRecordSalesLedgerMapper.selectList(
+                            Wrappers.<StockOutRecordSalesLedger>lambdaQuery()
+                                    .in(StockOutRecordSalesLedger::getStockOutRecordId, ids))
+                    .stream().map(StockOutRecordSalesLedger::getId).toList();
+            if (!CollectionUtils.isEmpty(bindingIds)
+                    && (accountSalesCollectionMapper.existsByStockOutBindingId(bindingIds)
+                    || accountInvoiceApplicationMapper.existsByStockOutBindingId(bindingIds))) {
+                throw new BaseException("鍑哄簱璁板綍瀛樺湪寮�绁ㄦ敹娆惧叧鑱旀暟鎹�,鏃犳硶鍒犻櫎!!!");
+            }
+        }
         for (Long id : ids) {
             StockOutRecord stockOutRecord = stockOutRecordMapper.selectById(id);
-            if (stockOutRecord.getType().equals("0")) {
-                StockInventory stockInventory = stockInventoryMapper.selectOne(new LambdaQueryWrapper<StockInventory>().eq(StockInventory::getProductModelId, stockOutRecord.getProductModelId()));
+            // 鍒拌揣璁板綍鎸傚湪鍑哄簱鍗曚笅锛屽垹浜嗕細鍙樻垚瀛ゅ効锛岄攢鍞彴璐︾殑宸插彂璐ф暟閲忎篃灏辫涓嶆竻浜�
+            if (salesLedgerArrivalMapper.countByStockOutRecordId(id) > 0) {
+                throw new BaseException("鍑哄簱鍗曞凡鐧昏鍒拌揣璁板綍,鏃犳硶鍒犻櫎,鍑哄簱鎵规:" + stockOutRecord.getOutboundBatches());
+            }
+            // 缁戝畾琛屾槸鍑哄簱鍗曠殑闄勫睘鏁版嵁锛屼竴骞舵竻鎺夛紝涓嶇暀鎸囧悜宸插垹鍑哄簱鍗曠殑瀛ゅ効琛�
+            stockOutRecordSalesLedgerMapper.delete(Wrappers.<StockOutRecordSalesLedger>lambdaQuery()
+                    .eq(StockOutRecordSalesLedger::getStockOutRecordId, id));
+            // type 瀵规补鍝佸嚭搴撴槸 null锛屽父閲忓啓鍦ㄥ墠闈㈤伩鍏� NPE
+            if ("0".equals(stockOutRecord.getType())) {
+                LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<StockInventory>()
+                    .eq(StockInventory::getProductModelId, stockOutRecord.getProductModelId());
+                if (StringUtils.isEmpty(stockOutRecord.getBatchNo())) {
+                    wrapper.isNull(StockInventory::getBatchNo);
+                } else {
+                    wrapper.eq(StockInventory::getBatchNo, stockOutRecord.getBatchNo());
+                }
+                StockInventory stockInventory = stockInventoryMapper.selectOne(wrapper);
                 if (stockInventory == null) {
                     throw new BaseException("搴撳瓨璁板綍涓病鏈夊搴旂殑浜у搧,鏃犳硶鍒犻櫎!!!");
                 }else {
                     StockInventoryDto stockInRecordDto = new StockInventoryDto();
                     stockInRecordDto.setProductModelId(stockInventory.getProductModelId());
                     stockInRecordDto.setQualitity(stockOutRecord.getStockOutNum());
+                    stockInRecordDto.setBatchNo(stockInventory.getBatchNo());
                     stockInventoryMapper.updateAddStockInventory(stockInRecordDto);
                 }
-            }else if (stockOutRecord.getType().equals("1")) {
-                StockUninventory stockUninventory = stockUninventoryMapper.selectOne(new LambdaQueryWrapper<StockUninventory>().eq(StockUninventory::getProductModelId, stockOutRecord.getProductModelId()));
+            }
+            else if ("1".equals(stockOutRecord.getType())) {
+                LambdaQueryWrapper<StockUninventory> wrapper = new LambdaQueryWrapper<StockUninventory>()
+                        .eq(StockUninventory::getProductModelId, stockOutRecord.getProductModelId());
+                if (StringUtils.isEmpty(stockOutRecord.getBatchNo())) {
+                    wrapper.isNull(StockUninventory::getBatchNo);
+                } else {
+                    wrapper.eq(StockUninventory::getBatchNo, stockOutRecord.getBatchNo());
+                }
+                StockUninventory stockUninventory = stockUninventoryMapper.selectOne(wrapper);
                 if (stockUninventory == null) {
                     throw new BaseException("搴撳瓨璁板綍涓病鏈夊搴旂殑浜у搧,鏃犳硶鍒犻櫎!!!");
                 }else {
                     StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
                     stockUninventoryDto.setProductModelId(stockUninventory.getProductModelId());
                     stockUninventoryDto.setQualitity(stockOutRecord.getStockOutNum());
+                    stockUninventoryDto.setBatchNo(stockUninventory.getBatchNo());
                     stockUninventoryMapper.updateAddStockUnInventory(stockUninventoryDto);
                 }
             }
+            else {
+                // 娌瑰搧鍑哄簱鍙湁瀹℃壒閫氳繃鏃舵墠鎵h繃搴撳瓨锛屽垹闄ゅ凡閫氳繃鐨勮褰曢渶鎸夊悓鎵规褰掕繕
+                returnOilOutStock(stockOutRecord);
+            }
         }
         return stockOutRecordMapper.deleteBatchIds(ids);
+    }
+
+    /**
+     * 娌瑰搧鍑哄簱褰掕繕搴撳瓨锛屼粎瀵瑰鎵归�氳繃锛堝嵆鎵h繃搴撳瓨锛夌殑璁板綍鐢熸晥銆�
+     * 褰掕繕閲忓彇 deduct_quantity锛堝疄闄呮墸鍑忛噺锛夛紱鏀归�犲墠瀹℃壒閫氳繃鐨勮褰曟病鏈夎鍊硷紝鎸夊叏棰濆綊杩�
+     */
+    private void returnOilOutStock(StockOutRecord stockOutRecord) {
+        if (!ReviewStatusEnum.APPROVED.getCode().equals(stockOutRecord.getApprovalStatus())
+                || StringUtils.isEmpty(stockOutRecord.getBatchNo())
+                || stockOutRecord.getStockOutNum() == null) {
+            return;
+        }
+        BigDecimal returnQuantity = stockOutRecord.getDeductQuantity() != null
+                ? stockOutRecord.getDeductQuantity()
+                : stockOutRecord.getStockOutNum();
+        if (returnQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        StockInventoryDto stockInventoryDto = new StockInventoryDto();
+        stockInventoryDto.setProductModelId(stockOutRecord.getProductModelId());
+        stockInventoryDto.setBatchNo(stockOutRecord.getBatchNo());
+        stockInventoryDto.setQualitity(returnQuantity);
+        stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
     }
 
     @Override
@@ -107,10 +441,378 @@
             if (stockInRecordExportData.getType().equals("0")) {
                 stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockOutQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
             }else {
-                stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockInUnQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
+                stockInRecordExportData.setRecordType(EnumUtil.fromCode(StockInQualifiedRecordTypeEnum.class, Integer.parseInt(stockInRecordExportData.getRecordType())).getValue());
             }
         }
         ExcelUtil<StockOutRecordExportData> util = new ExcelUtil<>(StockOutRecordExportData.class);
         util.exportExcel(response,list, "鍑哄簱璁板綍淇℃伅");
     }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int batchDeletePending(List<Long> ids) {
+        for (Long id : ids) {
+            StockOutRecord stockOutRecord = stockOutRecordMapper.selectById(id);
+            if (stockOutRecord == null) {
+                throw new BaseException("鍑哄簱璁板綍涓嶅瓨鍦�,鏃犳硶鍒犻櫎!!!");
+            }
+            if (stockOutRecord.getApprovalStatus() != null && !ReviewStatusEnum.PENDING_REVIEW.getCode().equals(stockOutRecord.getApprovalStatus())) {
+                throw new BaseException("鍙湁寰呭鎵圭姸鎬佺殑璁板綍鎵嶈兘鍒犻櫎,鍑哄簱鎵规:" + stockOutRecord.getOutboundBatches());
+            }
+        }
+        return stockOutRecordMapper.deleteBatchIds(ids);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int batchApprove(List<Long> ids, Integer approvalStatus) {
+        if (CollectionUtils.isEmpty(ids)) {
+            throw new BaseException("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        if (approvalStatus == null || (!ReviewStatusEnum.APPROVED.getCode().equals(approvalStatus) && !ReviewStatusEnum.REJECTED.getCode().equals(approvalStatus))) {
+            throw new BaseException("瀹℃壒鐘舵�佸�兼棤鏁�");
+        }
+        for (Long id : ids) {
+            StockOutRecord stockOutRecord = stockOutRecordMapper.selectById(id);
+            if (stockOutRecord == null) {
+                throw new BaseException("鍑哄簱璁板綍涓嶅瓨鍦�,鏃犳硶瀹℃壒!!!");
+            }
+            if (stockOutRecord.getApprovalStatus() != null && !ReviewStatusEnum.PENDING_REVIEW.getCode().equals(stockOutRecord.getApprovalStatus())) {
+                throw new BaseException("鍙湁寰呭鎵圭姸鎬佺殑璁板綍鎵嶈兘瀹℃壒,鍑哄簱鎵规:" + stockOutRecord.getOutboundBatches());
+            }
+            // 瀹℃壒閫氳繃鏃跺厛鎵e噺搴撳瓨锛屾墸鍑忛噺锛坉educt_quantity锛夎闅忓鎵圭姸鎬佷竴璧疯惤搴擄紝鎵�浠ユ斁鍦ㄦ洿鏂颁箣鍓�
+            if (ReviewStatusEnum.APPROVED.getCode().equals(approvalStatus)) {
+                if ("0".equals(stockOutRecord.getType())) {
+                    // 鍚堟牸鍑哄簱 -> 鍏堟煡搴撳瓨鏄惁瀛樺湪锛屽瓨鍦ㄦ墠鎵e噺
+                    StockInventory stockInventory = getStockInventory(stockOutRecord.getProductModelId(), stockOutRecord.getBatchNo());
+                    if (stockInventory == null) {
+                        throw new BaseException("鍚堟牸搴撳瓨璁板綍涓嶅瓨鍦�,鍑哄簱鎵规:" + stockOutRecord.getOutboundBatches());
+                    }
+                    StockInventoryDto stockInventoryDto = new StockInventoryDto();
+                    stockInventoryDto.setProductModelId(stockOutRecord.getProductModelId());
+                    stockInventoryDto.setBatchNo(stockOutRecord.getBatchNo());
+                    stockInventoryDto.setQualitity(stockOutRecord.getStockOutNum());
+                    stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto);
+                } else if ("1".equals(stockOutRecord.getType())) {
+                    // 涓嶅悎鏍煎嚭搴� -> 鍏堟煡搴撳瓨鏄惁瀛樺湪锛屽瓨鍦ㄦ墠鎵e噺
+                    StockUninventory stockUninventory = getStockUninventory(stockOutRecord.getProductModelId(), stockOutRecord.getBatchNo());
+                    if (stockUninventory == null) {
+                        throw new BaseException("涓嶅悎鏍煎簱瀛樿褰曚笉瀛樺湪,鍑哄簱鎵规:" + stockOutRecord.getOutboundBatches());
+                    }
+                    StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
+                    stockUninventoryDto.setProductModelId(stockOutRecord.getProductModelId());
+                    stockUninventoryDto.setBatchNo(stockOutRecord.getBatchNo());
+                    stockUninventoryDto.setQualitity(stockOutRecord.getStockOutNum());
+                    stockUninventoryMapper.updateSubtractStockUnInventory(stockUninventoryDto);
+                } else {
+                    // 娌瑰搧鍑哄簱锛坱ype 涓虹┖锛夊鎵归�氳繃鏃舵寜鎵规鎵e噺搴撳瓨銆�
+                    // 涓嶅啀鐢熸垚鍙戣揣鍗曪紝涔熶笉鏀� record_type锛氭补鍝佸嚭搴撶殑 record_type 涓�鐩存槸銆�1銆嶏紝
+                    // 鏀规垚銆�13銆嶄細璁╁嚭搴撶鐞嗗垪琛ㄦ寜鍑哄簱绫诲瀷绛涢�夋椂鐪嬩笉鍒板畠銆傚垽鏂�屾槸涓嶆槸閿�鍞被娌瑰搧鍑哄簱銆�
+                    // 涓�寰嬩互 binding 琛紙stock_out_record_sales_ledger锛変负鍑�
+                    deductOilOutStock(stockOutRecord);
+                }
+            }
+            stockOutRecord.setApprovalStatus(approvalStatus);
+            stockOutRecordMapper.updateById(stockOutRecord);
+            if (ReviewStatusEnum.APPROVED.getCode().equals(approvalStatus)
+                    && StockOutQualifiedRecordTypeEnum.OUTSOURCING_SCRAP_OUT.getCode()
+                            .equals(stockOutRecord.getRecordType())) {
+                tryCompleteOutsourcingOrder(stockOutRecord);
+            }
+        }
+        return ids.size();
+    }
+
+    /**
+     * 濮斿鎶ュ簾鍝佸嚭搴撳鎵归�氳繃鍚庯紝鑻ヨ濮斿璁㈠崟鐨勬姤搴熷搧閫夋枡琛屽凡<b>鍏ㄩ儴</b>鍑哄簱瀹℃壒閫氳繃锛�
+     * 鑷姩鎶婅鍗曚粠銆屽凡纭銆嶆帹鍒般�屽凡瀹屾垚銆嶏紝鐪佹帀浜哄伐鍐嶇偣涓�娆″畬鎴愩��
+     *
+     * <p>鐩存帴鐢� mapper 鑰屼笉鏄� {@code IOutsourcingOrderService#complete}锛氬悗鑰呬緷璧栨湰鏈嶅姟锛�
+     * 涓よ竟閮借蛋鏋勯�犲櫒娉ㄥ叆浼氬舰鎴愬惊鐜緷璧栥�傞┏鍥�/鍙嶅涓嶅洖閫�璁㈠崟鐘舵�侊紙澶勭悊閿欒瀹℃壒璧颁汉宸ラ噸鏂板鎵癸級銆�</p>
+     */
+    private void tryCompleteOutsourcingOrder(StockOutRecord record) {
+        if (record.getRecordId() == null) {
+            return;
+        }
+        OutsourcingOrderProduct line = outsourcingOrderProductMapper.selectById(record.getRecordId());
+        if (line == null || line.getOutsourcingOrderId() == null) {
+            return;
+        }
+        Long orderId = line.getOutsourcingOrderId();
+        // isScrap 鍦� Java 渚ц繃婊わ紝涓庡澶栨ā鍧楄嚜宸辩殑 scrapLineIds 涓�鑷达紝涓嶄緷璧� Boolean 瀛楁鐨勫垪鍚嶆帹鏂�
+        List<Long> lineIds = outsourcingOrderProductMapper.selectList(
+                        Wrappers.<OutsourcingOrderProduct>lambdaQuery()
+                                .eq(OutsourcingOrderProduct::getOutsourcingOrderId, orderId))
+                .stream()
+                .filter(item -> Boolean.TRUE.equals(item.getIsScrap()))
+                .map(OutsourcingOrderProduct::getId)
+                .toList();
+        if (lineIds.isEmpty()) {
+            return;
+        }
+        // 鍑哄簱璁板綍涓�鏉″搴斾竴琛屾槑缁嗭紱缂鸿褰曪紙琚弽瀹″垹闄ょ瓑锛夊氨涓嶅畬鎴愶紝涓庝汉宸ュ畬鎴愮殑鏍¢獙鍙e緞涓�鑷�
+        long approvedCount = listByRecordIds(lineIds, record.getRecordType()).stream()
+                .filter(item -> ReviewStatusEnum.APPROVED.getCode().equals(item.getApprovalStatus()))
+                .count();
+        if (approvedCount < lineIds.size()) {
+            return;
+        }
+        // 鏉′欢鏇存柊锛屽彧鏈夈�屽凡纭銆嶈兘鎺ㄨ繘锛涘凡浣滃簾/宸插畬鎴愮殑璁㈠崟涓嶅姩
+        outsourcingOrderMapper.update(null, Wrappers.<OutsourcingOrder>lambdaUpdate()
+                .eq(OutsourcingOrder::getId, orderId)
+                .eq(OutsourcingOrder::getStatus, OutsourcingOrderStatusEnum.CONFIRMED.getCode())
+                .set(OutsourcingOrder::getStatus, OutsourcingOrderStatusEnum.COMPLETED.getCode()));
+    }
+
+    /**
+     * 娌瑰搧鍑哄簱鎵e噺搴撳瓨銆傚嚭搴撴暟閲忔槸鏈瑁呰溅鎬婚噺锛岃溅涓婁笂娆″墿鐨勬补鏈缁х画鐢紝
+     * 鎵�浠ュ彧浠庡簱瀛樿ˉ瓒炽�屽嚭搴撴暟閲� 鈭� 涓婃鍓╀綑娌归噺銆嶇殑宸锛涗唬鍌�/瀹㈠瓨娌¢�夎溅杈嗭紝
+     * 涓婃鍓╀綑娌归噺鎸� 0 绠楋紝鍗冲叏棰濇墸鍑忋��
+     * updateSubtractStockInventory 鑷甫 qualitity &gt;= 鏁伴噺 鐨勫畧鍗紝褰卞搷琛屾暟涓� 0 鍗冲簱瀛樹笉瓒筹紝
+     * 鎶涢敊鐢卞灞備簨鍔″洖婊�
+     */
+    private void deductOilOutStock(StockOutRecord stockOutRecord) {
+        String suffix = ",鍑哄簱鎵规:" + stockOutRecord.getOutboundBatches();
+        if (StringUtils.isEmpty(stockOutRecord.getBatchNo())) {
+            throw new BaseException("娌瑰搧鍑哄簱蹇呴』閫夋嫨鍑哄簱鎵规" + suffix);
+        }
+        if (stockOutRecord.getProductModelId() == null) {
+            throw new BaseException("娌瑰搧鍑哄簱蹇呴』閫夋嫨浜у搧瑙勬牸" + suffix);
+        }
+        BigDecimal quantity = stockOutRecord.getStockOutNum();
+        if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new BaseException("娌瑰搧鍑哄簱鏁伴噺蹇呴』澶т簬0" + suffix);
+        }
+        BigDecimal lastResidual = getLastResidualOil(stockOutRecord.getVehicleId(), stockOutRecord.getProductModelId(), stockOutRecord.getId());
+        if (lastResidual.compareTo(quantity) > 0) {
+            throw new BaseException("鍑哄簱鏁伴噺涓嶈兘灏忎簬璇ヨ溅涓婃鍓╀綑娌归噺" + toPlain(lastResidual) + suffix);
+        }
+        BigDecimal deduct = quantity.subtract(lastResidual);
+        if (deduct.compareTo(BigDecimal.ZERO) > 0) {
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setProductModelId(stockOutRecord.getProductModelId());
+            stockInventoryDto.setBatchNo(stockOutRecord.getBatchNo());
+            stockInventoryDto.setQualitity(deduct);
+            if (stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto) == 0) {
+                throw new BaseException("搴撳瓨涓嶈冻鏃犳硶鍑哄簱" + suffix);
+            }
+        }
+        // 杞︿笂鍓╂补鍒氬ソ澶燂紙deduct=0锛夋椂涓嶆秹鍙婂簱瀛樻洿鏂帮紝鍚屾牱钀藉簱渚夸簬鍒犻櫎鏃舵寜瀹為檯鎵e噺閲忓綊杩�
+        stockOutRecord.setDeductQuantity(deduct);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int batchReAudit(List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            throw new BaseException("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        for (Long id : ids) {
+            StockOutRecord stockOutRecord = stockOutRecordMapper.selectById(id);
+            if (stockOutRecord == null) {
+                throw new BaseException("鍑哄簱璁板綍涓嶅瓨鍦�,鏃犳硶閲嶆柊瀹℃牳!!!");
+            }
+            if (!ReviewStatusEnum.REJECTED.getCode().equals(stockOutRecord.getApprovalStatus())) {
+                throw new BaseException("鍙湁椹冲洖鐘舵�佺殑璁板綍鎵嶈兘閲嶆柊瀹℃牳,鍑哄簱鎵规:" + stockOutRecord.getOutboundBatches());
+            }
+            stockOutRecord.setApprovalStatus(ReviewStatusEnum.PENDING_REVIEW.getCode());
+            stockOutRecordMapper.updateById(stockOutRecord);
+        }
+        return ids.size();
+    }
+
+    @Override
+    public int updateResidualOil(Long id, BigDecimal residualOil) {
+        StockOutRecord stockOutRecord = stockOutRecordMapper.selectById(id);
+        if (stockOutRecord == null) {
+            throw new BaseException("璇ュ嚭搴撹褰曚笉瀛樺湪,鏃犳硶鏇存柊!!!");
+        }
+        if (residualOil == null || residualOil.compareTo(BigDecimal.ZERO) < 0) {
+            throw new BaseException("鍓╀綑娌归噺涓嶈兘涓虹┖涓斾笉鑳藉皬浜�0");
+        }
+        if (stockOutRecord.getStockOutNum() != null && residualOil.compareTo(stockOutRecord.getStockOutNum()) > 0) {
+            throw new BaseException("鍓╀綑娌归噺涓嶈兘澶т簬鍑哄簱鏁伴噺" + toPlain(stockOutRecord.getStockOutNum()));
+        }
+        StockOutRecord update = new StockOutRecord();
+        update.setId(id);
+        update.setResidualOil(residualOil);
+        return stockOutRecordMapper.updateById(update);
+    }
+
+    private StockInventory getStockInventory(Long productModelId, String batchNo) {
+        LambdaQueryWrapper<StockInventory> eq = new LambdaQueryWrapper<>();
+        eq.eq(StockInventory::getProductModelId, productModelId);
+        if (StringUtils.isEmpty(batchNo)) {
+            eq.isNull(StockInventory::getBatchNo);
+        } else {
+            eq.eq(StockInventory::getBatchNo, batchNo);
+        }
+        return stockInventoryMapper.selectOne(eq);
+    }
+
+    private StockUninventory getStockUninventory(Long productModelId, String batchNo) {
+        LambdaQueryWrapper<StockUninventory> eq = new LambdaQueryWrapper<>();
+        eq.eq(StockUninventory::getProductModelId, productModelId);
+        if (StringUtils.isEmpty(batchNo)) {
+            eq.isNull(StockUninventory::getBatchNo);
+        } else {
+            eq.eq(StockUninventory::getBatchNo, batchNo);
+        }
+        return stockUninventoryMapper.selectOne(eq);
+    }
+
+    /**
+     * 杞﹁締鍥炲~锛氬墠绔彲浠ョ洿鎺ヤ紶 vehicleId锛屼篃鍙互鍙紶杞︾墝鍙凤紝
+     * 鍚庤�呮寜杞﹁締涓绘暟鎹弽鏌ュ洖濉�備袱鑰呴兘鎷夸笉鍒板氨鏃犳硶瀹氫綅銆屼笂娆″墿浣欐补閲忋�嶏紝鐩存帴鎶ラ敊
+     */
+    private void resolveVehicle(StockOutRecordDto stockOutRecordDto) {
+        if (stockOutRecordDto.getVehicleId() != null) {
+            return;
+        }
+        if (StringUtils.isEmpty(stockOutRecordDto.getTruckPlateNo())) {
+            throw new BaseException("閿�鍞被鍑哄簱蹇呴』閫夋嫨杞﹁締");
+        }
+        LambdaQueryWrapper<Vehicle> wrapper = new LambdaQueryWrapper<Vehicle>()
+                .eq(Vehicle::getPlateNumber, stockOutRecordDto.getTruckPlateNo());
+        Vehicle vehicle = vehicleService.getOne(wrapper, false);
+        if (vehicle == null) {
+            throw new BaseException("杞︾墝鍙� " + stockOutRecordDto.getTruckPlateNo() + " 涓嶅湪杞﹁締绠$悊涓紝璇峰厛鍒拌溅杈嗙鐞嗘坊鍔�");
+        }
+        stockOutRecordDto.setVehicleId(vehicle.getId());
+    }
+
+    /**
+     * 鍑哄簱鏁伴噺鏄湰娆¤杞︽�婚噺锛屽繀椤昏惤鍦ㄣ�屼笂娆″墿浣欐补閲� ~ 涓婃鍓╀綑娌归噺 + 搴撳瓨銆嶅尯闂村唴銆�
+     * 浠e偍/瀹㈠瓨涓嶉�夎溅杈嗭紝涓婃鍓╀綑娌归噺鎸� 0 绠楋紝閫�鍖栨垚銆屼笉瓒呰繃褰撳墠搴撳瓨銆�
+     */
+    private void checkOilOutAvailable(StockOutRecordDto stockOutRecordDto) {
+        String suffix = ",鍑哄簱鎵规:" + stockOutRecordDto.getOutboundBatches();
+        if (stockOutRecordDto.getProductModelId() == null) {
+            throw new BaseException("娌瑰搧鍑哄簱蹇呴』閫夋嫨浜у搧瑙勬牸" + suffix);
+        }
+        BigDecimal quantity = stockOutRecordDto.getStockOutNum();
+        if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
+            throw new BaseException("娌瑰搧鍑哄簱鏁伴噺蹇呴』澶т簬0" + suffix);
+        }
+        BigDecimal lastResidual = getLastResidualOil(stockOutRecordDto.getVehicleId(), stockOutRecordDto.getProductModelId(), null);
+        if (lastResidual.compareTo(quantity) > 0) {
+            throw new BaseException("鍑哄簱鏁伴噺涓嶈兘灏忎簬璇ヨ溅涓婃鍓╀綑娌归噺" + toPlain(lastResidual) + suffix);
+        }
+        BigDecimal stockQuantity = getStockQuantity(stockOutRecordDto.getProductModelId(), stockOutRecordDto.getBatchNo());
+        BigDecimal availableQty = lastResidual.add(stockQuantity);
+        if (quantity.compareTo(availableQty) > 0) {
+            throw new BaseException("鍑哄簱鏁伴噺瓒呭嚭鍙敤閲�(涓婃鍓╀綑" + toPlain(lastResidual)
+                    + ",搴撳瓨" + toPlain(stockQuantity) + ",鍙敤" + toPlain(availableQty) + ")" + suffix);
+        }
+    }
+
+    /**
+     * 璇ヨ溅璇ヨ鏍间笂娆¢�佸畬璐у悗鍓╀綑鐨勬补閲忥紝鍙栨渶杩戜竴鏉″~杩囨畫娌逛笖宸插鎵圭殑璁板綍銆�
+     * excludeId 鐢ㄤ簬鎺掗櫎銆屾湰娆¤瀹℃壒鐨勮繖鏉¤嚜宸便�嶁�斺�斿畠鑷繁鐨� residual_oil 鏄�佸畬涔嬪悗鐨勫�硷紝涓嶈兘鐢ㄦ潵绠楁湰娆℃墸鍑�
+     */
+    private BigDecimal getLastResidualOil(Long vehicleId, Long productModelId, Long excludeId) {
+        if (vehicleId == null || productModelId == null) {
+            return BigDecimal.ZERO;
+        }
+        LambdaQueryWrapper<StockOutRecord> wrapper = new LambdaQueryWrapper<StockOutRecord>()
+                .eq(StockOutRecord::getVehicleId, vehicleId)
+                .eq(StockOutRecord::getProductModelId, productModelId)
+                .isNotNull(StockOutRecord::getResidualOil)
+                .eq(StockOutRecord::getApprovalStatus, ReviewStatusEnum.APPROVED.getCode());
+        if (excludeId != null) {
+            wrapper.lt(StockOutRecord::getId, excludeId);
+        }
+        wrapper.orderByDesc(StockOutRecord::getId).last("limit 1");
+        StockOutRecord last = stockOutRecordMapper.selectOne(wrapper);
+        return last == null || last.getResidualOil() == null ? BigDecimal.ZERO : last.getResidualOil();
+    }
+
+    /**
+     * 褰撳墠搴撳瓨閲忥紝鎸夎鏍煎悎璁★紱浼犱簡鎵规鍙峰啀鍙犲姞鎵规鏉′欢
+     */
+    private BigDecimal getStockQuantity(Long productModelId, String batchNo) {
+        if (productModelId == null) {
+            return BigDecimal.ZERO;
+        }
+        LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<StockInventory>()
+                .eq(StockInventory::getProductModelId, productModelId);
+        if (!StringUtils.isEmpty(batchNo)) {
+            wrapper.eq(StockInventory::getBatchNo, batchNo);
+        }
+        return stockInventoryMapper.selectList(wrapper).stream()
+                .map(StockInventory::getQualitity)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+    }
+
+    @Override
+    public OilOutAvailableDto getOilOutAvailableQty(StockOutRecordDto query) {
+        if (query == null || query.getProductModelId() == null) {
+            throw new BaseException("璇峰厛閫夋嫨浜у搧瑙勬牸");
+        }
+        // 浠e偍/瀹㈠瓨涓嶉�夎溅杈嗭紝涓婃鍓╀綑鍙栦笉鍒帮紝鎸� 0 绠楋紝鍙敤閲忓氨绛変簬褰撳墠搴撳瓨
+        BigDecimal lastResidualOil = getLastResidualOil(query.getVehicleId(), query.getProductModelId(), query.getId());
+        BigDecimal stockQuantity = getStockQuantity(query.getProductModelId(), query.getBatchNo());
+        OilOutAvailableDto result = new OilOutAvailableDto();
+        result.setLastResidualOil(lastResidualOil);
+        result.setStockQuantity(stockQuantity);
+        result.setAvailableQty(lastResidualOil.add(stockQuantity));
+        return result;
+    }
+
+    private String toPlain(BigDecimal value) {
+        return value == null ? "0" : value.stripTrailingZeros().toPlainString();
+    }
+
+    @Override
+    public List<StockOutRecord> listByRecordIds(List<Long> recordIds, String recordType) {
+        if (CollectionUtils.isEmpty(recordIds) || StringUtils.isEmpty(recordType)) {
+            return Collections.emptyList();
+        }
+        return stockOutRecordMapper.selectByRecordIds(recordIds, recordType);
+    }
+
+    @Override
+    public IPage<SalesLedger> listBindableSalesLedger(Page page, SalesLedgerDto salesLedgerDto) {
+        // 璇ユ煡璇㈢敤 /*data_scope*/ 璺宠繃鏁版嵁鏉冮檺锛岃�� MP 榛樿浼氭妸 count 璇彞浼樺寲鎴愩�孲ELECT COUNT(*) FROM sales_ledger
+        // WHERE ...銆嶏紝浼樺寲鍚庢爣璁颁涪澶憋紝count 鍙嶈�岃杩藉姞鏉冮檺鏉′欢锛宼otal 浼氭瘮瀹為檯璁板綍灏戙�傚叧鎺� count 浼樺寲鍚�
+        // count 閫�鍖栨垚銆孲ELECT COUNT(*) FROM (鍘熷彞) TOTAL銆嶏紝鏍囪鍘熸牱淇濈暀
+        page.setOptimizeCountSql(false);
+        return stockOutRecordMapper.bindableSalesLedgerPage(page, salesLedgerDto);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int releaseByRecordIds(List<Long> recordIds, String recordType) {
+        List<StockOutRecord> records = listByRecordIds(recordIds, recordType);
+        if (records.isEmpty()) {
+            return 0;
+        }
+        for (StockOutRecord record : records) {
+            if (!ReviewStatusEnum.APPROVED.getCode().equals(record.getApprovalStatus())) {
+                continue;
+            }
+            // type=0 褰掕繕鍚堟牸搴撳瓨锛坰tock_inventory锛夛紝type=1 褰掕繕涓嶅悎鏍煎簱瀛橈紙stock_uninventory锛�
+            if ("0".equals(record.getType())) {
+                StockInventoryDto stockInventoryDto = new StockInventoryDto();
+                stockInventoryDto.setProductModelId(record.getProductModelId());
+                stockInventoryDto.setBatchNo(record.getBatchNo());
+                stockInventoryDto.setQualitity(record.getStockOutNum());
+                if (stockInventoryMapper.updateAddStockInventory(stockInventoryDto) == 0) {
+                    throw new BaseException("搴撳瓨璁板綍涓病鏈夊搴旂殑浜у搧,鏃犳硶褰掕繕,鍑哄簱鎵规:" + record.getOutboundBatches());
+                }
+            } else if ("1".equals(record.getType())) {
+                StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
+                stockUninventoryDto.setProductModelId(record.getProductModelId());
+                stockUninventoryDto.setBatchNo(record.getBatchNo());
+                stockUninventoryDto.setQualitity(record.getStockOutNum());
+                if (stockUninventoryMapper.updateAddStockUnInventory(stockUninventoryDto) == 0) {
+                    throw new BaseException("搴撳瓨璁板綍涓病鏈夊搴旂殑浜у搧,鏃犳硶褰掕繕,鍑哄簱鎵规:" + record.getOutboundBatches());
+                }
+            } else {
+                throw new BaseException("鍑哄簱璁板綍绫诲瀷寮傚父锛屾棤娉曞綊杩樺簱瀛�,鍑哄簱鎵规:" + record.getOutboundBatches());
+            }
+        }
+        return stockOutRecordMapper.deleteBatchIds(records.stream().map(StockOutRecord::getId).toList());
+    }
 }

--
Gitblit v1.9.3