From 8beb176e14312b8e7ca43a25f8ec1386157502f9 Mon Sep 17 00:00:00 2001
From: liding <756868258@qq.com>
Date: 星期四, 28 五月 2026 14:40:42 +0800
Subject: [PATCH] feat:采购批量审核-质检-入库

---
 src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java   |   35 +++
 src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java          |    2 
 src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java          |    3 
 src/main/java/com/ruoyi/quality/service/IQualityInspectService.java           |    3 
 src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java                   |    3 
 src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java  |  423 ++++++++++++++++++++++++++++++++++++++
 src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java     |    8 
 src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml                   |    6 
 src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java |  111 +++++++++
 src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java               |   21 +
 10 files changed, 603 insertions(+), 12 deletions(-)

diff --git a/src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java b/src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java
index dfb9517..005795d 100644
--- a/src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java
+++ b/src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java
@@ -28,4 +28,6 @@
     Boolean delete(List<Long> ids);
 
     R approve(ApprovalInstanceDto approvalInstanceDto);
+
+    R autoApprove(Long instanceId);
 }
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
index 2a4f52a..5c5e540 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
@@ -270,6 +270,87 @@
         return approveAndMoveNext(instance, currentNode, approvalInstanceDto, now);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R autoApprove(Long instanceId) {
+        if (instanceId == null) {
+            return R.fail("瀹℃壒瀹炰緥 ID 涓嶈兘涓虹┖");
+        }
+
+        ApprovalInstance instance = getPendingApprovalInstance(instanceId);
+        if (instance == null) {
+            return R.fail("瀹℃壒瀹炰緥涓嶅瓨鍦�");
+        }
+        if ("REJECTED".equals(instance.getStatus())) {
+            return R.fail("瀹℃壒宸查┏鍥烇紝鏃犳硶鑷姩閫氳繃");
+        }
+        if ("APPROVED".equals(instance.getStatus())) {
+            return R.ok("瀹℃壒宸插畬鎴�");
+        }
+
+        ApprovalInstanceDto autoApproveDto = new ApprovalInstanceDto();
+        autoApproveDto.setId(instanceId);
+        autoApproveDto.setApproveComment("绯荤粺鑷姩瀹℃壒");
+
+        int loopCount = 0;
+        while (loopCount++ < 20) {
+            ApprovalInstance currentInstance = getPendingApprovalInstance(instanceId);
+            if (currentInstance == null) {
+                return R.fail("瀹℃壒瀹炰緥涓嶅瓨鍦�");
+            }
+            if ("APPROVED".equals(currentInstance.getStatus())) {
+                return R.ok("瀹℃壒宸插畬鎴�");
+            }
+            if ("REJECTED".equals(currentInstance.getStatus())) {
+                return R.fail("瀹℃壒宸查┏鍥烇紝鏃犳硶鑷姩閫氳繃");
+            }
+
+            ApprovalInstanceNode currentNode = approveProcessConfigNodeUtils.getCurrentNode(currentInstance.getId());
+            if (currentNode == null) {
+                currentInstance.setStatus("APPROVED");
+                currentInstance.setFinishTime(LocalDateTime.now());
+                this.updateById(currentInstance);
+                handleBusinessAfterApprovalFinished(currentInstance);
+                return R.ok("瀹℃壒宸插畬鎴�");
+            }
+
+            List<ApprovalTask> pendingTasks = approvalTaskService.list(
+                    Wrappers.<ApprovalTask>lambdaQuery()
+                            .eq(ApprovalTask::getInstanceId, currentInstance.getId())
+                            .eq(ApprovalTask::getNodeId, currentNode.getId())
+                            .eq(ApprovalTask::getTaskStatus, "PENDING")
+                            .eq(ApprovalTask::getDeleted, 0)
+            );
+
+            LocalDateTime now = LocalDateTime.now();
+            for (ApprovalTask currentTask : pendingTasks) {
+                if (!updateCurrentTask(autoApproveDto, "APPROVED", currentTask, now)) {
+                    return R.fail("褰撳墠浠诲姟宸茶澶勭悊锛岃鍒锋柊鍚庨噸璇�");
+                }
+                saveApprovalRecord(
+                        currentInstance.getId(),
+                        currentNode.getId(),
+                        currentTask.getId(),
+                        0L,
+                        "绯荤粺鑷姩瀹℃壒",
+                        "APPROVED",
+                        autoApproveDto.getApproveComment()
+                );
+            }
+
+            if (!approveProcessConfigNodeUtils.canProceedToNextLevel(currentInstance.getId(), currentNode.getApproveType())) {
+                return R.ok("瀹℃壒鎴愬姛锛岀瓑寰呭叾浠栧鎵逛汉澶勭悊");
+            }
+
+            R moveResult = moveToNextLevel(currentInstance, currentNode, autoApproveDto, now, false);
+            if (!R.isSuccess(moveResult)) {
+                return moveResult;
+            }
+        }
+
+        return R.fail("鑷姩瀹℃壒寰幆娆℃暟瓒呴檺");
+    }
+
     private String normalizeApproveAction(String approveAction) {
         if (!StringUtils.hasText(approveAction)) {
             return null;
@@ -336,6 +417,14 @@
                                  ApprovalInstanceNode currentNode,
                                  ApprovalInstanceDto approvalInstanceDto,
                                  LocalDateTime now) {
+        return moveToNextLevel(instance, currentNode, approvalInstanceDto, now, true);
+    }
+
+    private R moveToNextLevel(ApprovalInstance instance,
+                              ApprovalInstanceNode currentNode,
+                              ApprovalInstanceDto approvalInstanceDto,
+                              LocalDateTime now,
+                              boolean notifyNextNode) {
         if (!updateCurrentNodeStatus(currentNode.getId(), "APPROVED", now)) {
             return R.ok("褰撳墠鑺傜偣宸插鐞嗗畬鎴�");
         }
@@ -359,14 +448,16 @@
             instance.setCurrentLevel(nextLevel);
             instance.setStatus("PENDING");
             this.updateById(instance);
-            List<ApprovalTask> nextTasks = approvalTaskService.list(
-                    Wrappers.<ApprovalTask>lambdaQuery()
-                            .eq(ApprovalTask::getInstanceId, instance.getId())
-                            .eq(ApprovalTask::getNodeId, nextInstanceNode.getId())
-                            .eq(ApprovalTask::getTaskStatus, "PENDING")
-                            .eq(ApprovalTask::getDeleted, 0)
-            );
-            sendApproveNotice(instance, nextTasks);
+            if (notifyNextNode) {
+                List<ApprovalTask> nextTasks = approvalTaskService.list(
+                        Wrappers.<ApprovalTask>lambdaQuery()
+                                .eq(ApprovalTask::getInstanceId, instance.getId())
+                                .eq(ApprovalTask::getNodeId, nextInstanceNode.getId())
+                                .eq(ApprovalTask::getTaskStatus, "PENDING")
+                                .eq(ApprovalTask::getDeleted, 0)
+                );
+                sendApproveNotice(instance, nextTasks);
+            }
             return R.ok("瀹℃壒鎴愬姛锛屽凡娴佽浆鍒颁笅涓�鑺傜偣");
         }
 
@@ -390,7 +481,9 @@
         instance.setStatus("PENDING");
         this.updateById(instance);
         approveProcessConfigNodeUtils.createCurrentNodeAndTasks(instance, false);
-        sendApproveNotice(instance, approveProcessConfigNodeUtils.getCurrentPendingTasks(approvalInstanceDto.getId()));
+        if (notifyNextNode) {
+            sendApproveNotice(instance, approveProcessConfigNodeUtils.getCurrentPendingTasks(approvalInstanceDto.getId()));
+        }
         return R.ok("瀹℃壒鎴愬姛锛屽凡娴佽浆鍒颁笅涓�鑺傜偣");
     }
 
diff --git a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
index 0beecd2..9b8735f 100644
--- a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
+++ b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
@@ -18,6 +18,7 @@
 import org.springframework.stereotype.Component;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.Collections;
 
 @Component
@@ -88,11 +89,21 @@
      * @param recordId
      */
     public void addStock(Long productModelId, BigDecimal quantity, String recordType, Long recordId) {
+        addStock(productModelId, quantity, recordType, recordId, null);
+    }
+
+    /**
+     * 鍚堟牸鍏ュ簱
+     * @param recordType
+     * @param recordId
+     */
+    public void addStock(Long productModelId, BigDecimal quantity, String recordType, Long recordId, LocalDateTime createTime) {
         StockInventoryDto stockInventoryDto = new StockInventoryDto();
         stockInventoryDto.setRecordId(recordId);
         stockInventoryDto.setRecordType(String.valueOf(recordType));
         stockInventoryDto.setQualitity(quantity);
         stockInventoryDto.setProductModelId(productModelId);
+        stockInventoryDto.setCreateTime(createTime);
         stockInventoryService.addStockInRecordOnly(stockInventoryDto);
     }
 
@@ -104,12 +115,22 @@
      * @param recordId
      */
     public void addStockWithBatchNo(Long productModelId, BigDecimal quantity, String recordType, Long recordId, String batchNo) {
+        addStockWithBatchNo(productModelId, quantity, recordType, recordId, batchNo, null);
+    }
+
+    /**
+     * 鍚堟牸鍏ュ簱甯︽壒娆″彿
+     * @param recordType
+     * @param recordId
+     */
+    public void addStockWithBatchNo(Long productModelId, BigDecimal quantity, String recordType, Long recordId, String batchNo, LocalDateTime createTime) {
         StockInventoryDto stockInventoryDto = new StockInventoryDto();
         stockInventoryDto.setRecordId(recordId);
         stockInventoryDto.setRecordType(String.valueOf(recordType));
         stockInventoryDto.setQualitity(quantity);
         stockInventoryDto.setProductModelId(productModelId);
         stockInventoryDto.setBatchNo(batchNo);
+        stockInventoryDto.setCreateTime(createTime);
         stockInventoryService.addStockInRecordOnly(stockInventoryDto);
     }
 
diff --git a/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java b/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
index 537c903..58216c1 100644
--- a/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
+++ b/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
@@ -7,6 +7,7 @@
 import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
 import com.ruoyi.framework.web.controller.BaseController;
 import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.framework.web.page.TableDataInfo;
 import com.ruoyi.purchase.dto.PurchaseLedgerDto;
 import com.ruoyi.purchase.mapper.PurchaseLedgerTemplateMapper;
@@ -137,6 +138,13 @@
         return toAjax(purchaseLedgerService.addOrEditPurchase(purchaseLedgerDto));
     }
 
+    @Operation(summary = "鎵归噺鎺ㄨ繘閲囪喘鍙拌处鍒板叆搴�")
+    @Log(title = "鎵归噺鎺ㄨ繘閲囪喘鍙拌处鍒板叆搴�", businessType = BusinessType.OTHER)
+    @PostMapping("/batchInsertPurchaseSteps")
+    public R batchInsertPurchaseSteps(@RequestBody PurchaseLedgerDto purchaseLedgerDto) {
+        return purchaseLedgerService.batchInsertPurchaseSteps(purchaseLedgerDto == null ? null : purchaseLedgerDto.getIds());
+    }
+
     /**
      * 鏌ヨ閲囪喘妯℃澘
      */
diff --git a/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java b/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
index 22faea3..cbca6f7 100644
--- a/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
+++ b/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
@@ -133,6 +133,9 @@
 
     private List<SalesLedgerProduct> productData;
 
+    @Schema(description = "鎵归噺澶勭悊閲囪喘鍙拌处ID鍒楄〃")
+    private List<Long> ids;
+
     private List<String> tempFileIds;
 
     private List<CommonFile> SalesLedgerFiles;
diff --git a/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java b/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
index a1f135e..4b91b89 100644
--- a/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
+++ b/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
@@ -4,6 +4,7 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.purchase.dto.PurchaseLedgerDto;
 import com.ruoyi.purchase.pojo.PurchaseLedger;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
@@ -24,6 +25,8 @@
 
     int addOrEditPurchase(PurchaseLedgerDto purchaseLedgerDto) throws Exception;
 
+    R batchInsertPurchaseSteps(List<Long> ids);
+
     void addQualityInspect(PurchaseLedger purchaseLedger, SalesLedgerProduct saleProduct);
 
     int deletePurchaseLedgerByIds(Long[] ids);
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 6008e69..7a70435 100644
--- a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -8,6 +8,7 @@
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
 import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
+import com.ruoyi.approve.pojo.ApprovalInstance;
 import com.ruoyi.approve.pojo.ApprovalTemplate;
 import com.ruoyi.approve.pojo.ApproveProcess;
 import com.ruoyi.approve.service.ApprovalInstanceService;
@@ -22,14 +23,20 @@
 import com.ruoyi.basic.pojo.SupplierManage;
 import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.common.exception.base.BaseException;
+import com.ruoyi.common.enums.ApprovalStatusEnum;
+import com.ruoyi.common.enums.ReviewStatusEnum;
+import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
+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;
 import com.ruoyi.framework.security.LoginUser;
 import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.other.mapper.TempFileMapper;
 import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
 import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
+import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.project.system.domain.SysUser;
 import com.ruoyi.project.system.mapper.SysUserMapper;
 import com.ruoyi.purchase.dto.PurchaseLedgerDto;
@@ -46,12 +53,15 @@
 import com.ruoyi.quality.pojo.QualityInspectParam;
 import com.ruoyi.quality.pojo.QualityTestStandard;
 import com.ruoyi.quality.pojo.QualityTestStandardParam;
+import com.ruoyi.quality.service.IQualityInspectService;
 import com.ruoyi.sales.mapper.CommonFileMapper;
 import com.ruoyi.sales.mapper.SalesLedgerMapper;
 import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
 import com.ruoyi.sales.pojo.SalesLedger;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
+import com.ruoyi.stock.pojo.StockInRecord;
+import com.ruoyi.stock.service.StockInRecordService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
@@ -102,6 +112,9 @@
     private final ProcurementRecordMapper procurementRecordStorageMapper;
     private final FileUtil fileUtil;
     private final ApprovalInstanceService approvalInstanceService;
+    private final IQualityInspectService qualityInspectService;
+    private final StockInRecordService stockInRecordService;
+    private final StockUtils stockUtils;
     private final ApprovalTemplateMapper approvalTemplateMapper;
 
     @Override
@@ -177,6 +190,188 @@
         return 1;
     }
 
+    @Override
+    public R batchInsertPurchaseSteps(List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            return R.fail("璇烽�夋嫨閲囪喘鍙拌处");
+        }
+
+        List<Long> distinctIds = ids.stream()
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(distinctIds)) {
+            return R.fail("璇烽�夋嫨閲囪喘鍙拌处");
+        }
+
+        PurchaseLedgerDto queryDto = new PurchaseLedgerDto();
+        queryDto.setIds(distinctIds);
+        IPage<PurchaseLedgerDto> pageResult = this.selectPurchaseLedgerListPage(
+                new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(1, distinctIds.size()),
+                queryDto
+        );
+        List<PurchaseLedgerDto> ledgerDtos = pageResult == null || pageResult.getRecords() == null
+                ? Collections.emptyList()
+                : pageResult.getRecords();
+
+        Map<Long, PurchaseLedgerDto> ledgerDtoMap = ledgerDtos.stream()
+                .filter(item -> item.getId() != null)
+                .collect(Collectors.toMap(PurchaseLedgerDto::getId, item -> item, (left, right) -> left, LinkedHashMap::new));
+
+        List<Map<String, Object>> details = new ArrayList<>();
+        int successCount = 0;
+        int skipCount = 0;
+        int failCount = 0;
+        int autoApprovedCount = 0;
+
+        for (Long id : distinctIds) {
+            Map<String, Object> detail = new LinkedHashMap<>();
+            detail.put("purchaseLedgerId", id);
+            PurchaseLedgerDto purchaseLedgerDto = ledgerDtoMap.get(id);
+            if (purchaseLedgerDto == null) {
+                failCount++;
+                detail.put("status", "FAIL");
+                detail.put("message", "閲囪喘鍙拌处涓嶅瓨鍦ㄦ垨鏈煡璇㈠埌");
+                details.add(detail);
+                continue;
+            }
+
+            detail.put("purchaseContractNumber", purchaseLedgerDto.getPurchaseContractNumber());
+            detail.put("approvalStatus", purchaseLedgerDto.getApprovalStatus());
+            detail.put("stockInStatus", purchaseLedgerDto.getStockInStatus());
+
+            if (ApprovalStatusEnum.REJECTED.getCode().equals(purchaseLedgerDto.getApprovalStatus())) {
+                skipCount++;
+                detail.put("status", "SKIP");
+                detail.put("message", "閲囪喘鍗曞凡椹冲洖");
+                details.add(detail);
+                continue;
+            }
+
+            if ("瀹屽叏鍏ュ簱".equals(purchaseLedgerDto.getStockInStatus())) {
+                skipCount++;
+                detail.put("status", "SKIP");
+                detail.put("message", "閲囪喘鍗曞凡瀹屽叏鍏ュ簱");
+                details.add(detail);
+                continue;
+            }
+
+            try {
+                PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(id);
+                if (purchaseLedger == null) {
+                    failCount++;
+                    detail.put("status", "FAIL");
+                    detail.put("message", "閲囪喘鍙拌处涓嶅瓨鍦�");
+                    details.add(detail);
+                    continue;
+                }
+
+                if (!ApprovalStatusEnum.APPROVED.getCode().equals(purchaseLedger.getApprovalStatus())) {
+                    ApprovalInstance approvalInstance = approvalInstanceService.getOne(
+                            Wrappers.<ApprovalInstance>lambdaQuery()
+                                    .eq(ApprovalInstance::getBusinessId, purchaseLedger.getId())
+                                    .eq(ApprovalInstance::getBusinessType, 5L)
+                                    .eq(ApprovalInstance::getDeleted, 0)
+                                    .orderByDesc(ApprovalInstance::getId)
+                                    .last("limit 1")
+                    );
+                    if (approvalInstance == null) {
+                        failCount++;
+                        detail.put("status", "FAIL");
+                        detail.put("message", "鏈壘鍒板搴旂殑閲囪喘瀹℃壒瀹炰緥");
+                        details.add(detail);
+                        continue;
+                    }
+
+                    if ("APPROVED".equals(approvalInstance.getStatus())
+                            && !ApprovalStatusEnum.APPROVED.getCode().equals(purchaseLedger.getApprovalStatus())) {
+                        purchaseLedger.setApprovalStatus(ApprovalStatusEnum.APPROVED.getCode());
+                        purchaseLedgerMapper.updateById(purchaseLedger);
+                    } else if (!"APPROVED".equals(approvalInstance.getStatus())) {
+                        R autoApproveResult = approvalInstanceService.autoApprove(approvalInstance.getId());
+                        if (autoApproveResult == null || !R.isSuccess(autoApproveResult)) {
+                            failCount++;
+                            detail.put("status", "FAIL");
+                            detail.put("message", autoApproveResult == null ? "閲囪喘瀹℃壒鑷姩閫氳繃澶辫触" : autoApproveResult.getMsg());
+                            details.add(detail);
+                            continue;
+                        }
+                        autoApprovedCount++;
+                    }
+
+                    purchaseLedger = purchaseLedgerMapper.selectById(id);
+                    if (purchaseLedger == null || !ApprovalStatusEnum.APPROVED.getCode().equals(purchaseLedger.getApprovalStatus())) {
+                        failCount++;
+                        detail.put("status", "FAIL");
+                        detail.put("message", "閲囪喘鍗曞鎵圭姸鎬佹湭鏇存柊涓哄凡閫氳繃");
+                        details.add(detail);
+                        continue;
+                    }
+                }
+
+                List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(
+                        Wrappers.<SalesLedgerProduct>lambdaQuery()
+                                .eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId())
+                                .eq(SalesLedgerProduct::getType, 2)
+                                .orderByAsc(SalesLedgerProduct::getId)
+                );
+                if (CollectionUtils.isEmpty(products)) {
+                    skipCount++;
+                    detail.put("status", "SKIP");
+                    detail.put("message", "閲囪喘鍗曟病鏈変骇鍝佹槑缁�");
+                    details.add(detail);
+                    continue;
+                }
+
+                int processedProductCount = 0;
+                int skippedProductCount = 0;
+                int failedProductCount = 0;
+                for (SalesLedgerProduct product : products) {
+                    try {
+                        boolean processed;
+                        if (Boolean.TRUE.equals(product.getIsChecked())) {
+                            processed = processPurchaseQualityProduct(purchaseLedger, product);
+                        } else {
+                            processed = processPurchaseDirectProduct(purchaseLedger, product);
+                        }
+                        if (processed) {
+                            processedProductCount++;
+                        } else {
+                            skippedProductCount++;
+                        }
+                    } catch (Exception ex) {
+                        failedProductCount++;
+                        log.error("鎵归噺鎺ㄨ繘閲囪喘鍙拌处澶辫触, purchaseLedgerId={}, productId={}, productModelId={}",
+                                purchaseLedger.getId(), product.getId(), product.getProductModelId(), ex);
+                    }
+                }
+
+                successCount++;
+                detail.put("status", failedProductCount > 0 ? "PARTIAL" : "SUCCESS");
+                detail.put("processedProductCount", processedProductCount);
+                detail.put("skippedProductCount", skippedProductCount);
+                detail.put("failedProductCount", failedProductCount);
+                detail.put("message", failedProductCount > 0 ? "閮ㄥ垎浜у搧澶勭悊澶辫触" : "澶勭悊瀹屾垚");
+                details.add(detail);
+            } catch (Exception ex) {
+                failCount++;
+                detail.put("status", "FAIL");
+                detail.put("message", ex.getMessage());
+                details.add(detail);
+                log.error("鎵归噺鎺ㄨ繘閲囪喘鍙拌处澶辫触, purchaseLedgerId={}", id, ex);
+            }
+        }
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("totalCount", distinctIds.size());
+        summary.put("successCount", successCount);
+        summary.put("skipCount", skipCount);
+        summary.put("failCount", failCount);
+        summary.put("autoApprovedCount", autoApprovedCount);
+        summary.put("details", details);
+        return R.ok(summary, "鎵归噺鎺ㄨ繘閲囪喘姝ラ瀹屾垚");
+    }
+
 
     public void addQualityInspect(PurchaseLedger purchaseLedger, SalesLedgerProduct saleProduct) {
         QualityInspect qualityInspect = new QualityInspect();
@@ -202,7 +397,219 @@
                         param.setId(null);
                         param.setInspectId(qualityInspect.getId());
                         qualityInspectParamMapper.insert(param);
-                    });
+            });
+        }
+    }
+
+    private boolean processPurchaseQualityProduct(PurchaseLedger purchaseLedger, SalesLedgerProduct product) {
+        if (purchaseLedger == null || product == null || product.getProductModelId() == null) {
+            return false;
+        }
+
+        QualityInspect qualityInspect = findLatestPurchaseQualityInspect(purchaseLedger.getId(), product.getProductModelId());
+        if (qualityInspect == null) {
+            addQualityInspect(purchaseLedger, product);
+            qualityInspect = findLatestPurchaseQualityInspect(purchaseLedger.getId(), product.getProductModelId());
+        }
+        if (qualityInspect == null) {
+            return false;
+        }
+
+        LocalDateTime purchaseInspectTime = toStartOfDayPlusDays(purchaseLedger.getEntryDate(), 1);
+        if (purchaseInspectTime != null && (qualityInspect.getCheckTime() == null
+                || !DateUtils.toLocalDate(qualityInspect.getCheckTime()).equals(purchaseInspectTime.toLocalDate()))) {
+            qualityInspect.setCheckTime(DateUtils.toDate(purchaseInspectTime.toLocalDate()));
+            qualityInspectMapper.updateById(qualityInspect);
+        }
+
+        List<StockInRecord> stockRecords = findQualityStockRecords(qualityInspect.getId(), product.getProductModelId());
+        if (hasApprovedStockRecord(stockRecords)) {
+            return true;
+        }
+
+        if (!Integer.valueOf(1).equals(qualityInspect.getInspectState())) {
+            R autoSubmitResult = qualityInspectService.autoSubmit(qualityInspect.getId());
+            if (autoSubmitResult == null || !R.isSuccess(autoSubmitResult)) {
+                return false;
+            }
+            qualityInspect = qualityInspectMapper.selectById(qualityInspect.getId());
+        }
+
+        stockRecords = findQualityStockRecords(qualityInspect.getId(), product.getProductModelId());
+        if (CollectionUtils.isEmpty(stockRecords)
+                && qualityInspect.getQualifiedQuantity() != null
+                && qualityInspect.getQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
+            stockUtils.addStockWithBatchNo(
+                    product.getProductModelId(),
+                    qualityInspect.getQualifiedQuantity(),
+                    StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode(),
+                    qualityInspect.getId(),
+                    null,
+                    purchaseInspectTime == null ? null : purchaseInspectTime.plusDays(1)
+            );
+            stockRecords = findQualityStockRecords(qualityInspect.getId(), product.getProductModelId());
+        }
+
+        StockInRecord targetStockRecord = findLatestUnapprovedStockRecord(stockRecords);
+        if (targetStockRecord == null) {
+            return false;
+        }
+
+        LocalDateTime qualityStockCreateTime = resolveQualityStockCreateTime(qualityInspect);
+        if (qualityStockCreateTime != null && (targetStockRecord.getCreateTime() == null
+                || !qualityStockCreateTime.equals(targetStockRecord.getCreateTime()))) {
+            targetStockRecord.setCreateTime(qualityStockCreateTime);
+            stockInRecordService.updateById(targetStockRecord);
+        }
+
+        approveStockRecords(Collections.singletonList(targetStockRecord));
+        stockRecords = findQualityStockRecords(qualityInspect.getId(), product.getProductModelId());
+        return hasApprovedStockRecord(stockRecords);
+    }
+
+    private boolean processPurchaseDirectProduct(PurchaseLedger purchaseLedger, SalesLedgerProduct product) {
+        if (purchaseLedger == null || product == null || product.getProductModelId() == null) {
+            return false;
+        }
+        if (product.getQuantity() == null) {
+            return false;
+        }
+        if (!StringUtils.hasText(purchaseLedger.getPurchaseContractNumber())) {
+            return false;
+        }
+
+        LocalDateTime stockCreateTime = toStartOfDayPlusDays(purchaseLedger.getEntryDate(), 1);
+
+        List<StockInRecord> stockRecords = findDirectStockRecords(purchaseLedger.getId(), purchaseLedger.getPurchaseContractNumber(), product.getProductModelId(), product.getId());
+        if (hasApprovedStockRecord(stockRecords)) {
+            return true;
+        }
+
+        if (CollectionUtils.isEmpty(stockRecords)) {
+            stockUtils.addStockWithBatchNo(
+                    product.getProductModelId(),
+                    product.getQuantity(),
+                    StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(),
+                    purchaseLedger.getId(),
+                    purchaseLedger.getPurchaseContractNumber() + "-" + product.getId(),
+                    stockCreateTime
+            );
+            stockRecords = findDirectStockRecords(purchaseLedger.getId(), purchaseLedger.getPurchaseContractNumber(), product.getProductModelId(), product.getId());
+        }
+
+        if (CollectionUtils.isEmpty(stockRecords)) {
+            return false;
+        }
+
+        StockInRecord targetStockRecord = findLatestUnapprovedStockRecord(stockRecords);
+        if (targetStockRecord == null) {
+            return false;
+        }
+
+        if (stockCreateTime != null && (targetStockRecord.getCreateTime() == null
+                || !stockCreateTime.equals(targetStockRecord.getCreateTime()))) {
+            targetStockRecord.setCreateTime(stockCreateTime);
+            stockInRecordService.updateById(targetStockRecord);
+        }
+
+        approveStockRecords(Collections.singletonList(targetStockRecord));
+        stockRecords = findDirectStockRecords(purchaseLedger.getId(), purchaseLedger.getPurchaseContractNumber(), product.getProductModelId(), product.getId());
+        return hasApprovedStockRecord(stockRecords);
+    }
+
+    private LocalDateTime toStartOfDayPlusDays(Date date, int days) {
+        if (date == null) {
+            return null;
+        }
+        return DateUtils.toLocalDate(date).plusDays(days).atStartOfDay();
+    }
+
+    private LocalDateTime resolveQualityStockCreateTime(QualityInspect qualityInspect) {
+        if (qualityInspect == null || qualityInspect.getCheckTime() == null) {
+            return null;
+        }
+        return DateUtils.toLocalDate(qualityInspect.getCheckTime()).plusDays(1).atStartOfDay();
+    }
+
+    private QualityInspect findLatestPurchaseQualityInspect(Long purchaseLedgerId, Long productModelId) {
+        if (purchaseLedgerId == null || productModelId == null) {
+            return null;
+        }
+        return qualityInspectMapper.selectOne(
+                Wrappers.<QualityInspect>lambdaQuery()
+                        .eq(QualityInspect::getInspectType, 0)
+                        .eq(QualityInspect::getPurchaseLedgerId, purchaseLedgerId)
+                        .eq(QualityInspect::getProductModelId, productModelId)
+                        .orderByDesc(QualityInspect::getId)
+                        .last("limit 1")
+        );
+    }
+
+    private List<StockInRecord> findQualityStockRecords(Long qualityInspectId, Long productModelId) {
+        if (qualityInspectId == null || productModelId == null) {
+            return Collections.emptyList();
+        }
+        return stockInRecordService.list(
+                Wrappers.<StockInRecord>lambdaQuery()
+                        .eq(StockInRecord::getRecordType, StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode())
+                        .eq(StockInRecord::getRecordId, qualityInspectId)
+                        .eq(StockInRecord::getProductModelId, productModelId)
+                        .orderByDesc(StockInRecord::getId)
+        );
+    }
+
+    private List<StockInRecord> findDirectStockRecords(Long purchaseLedgerId, String purchaseContractNumber, Long productModelId, Long purchaseProductId) {
+        if (purchaseLedgerId == null || productModelId == null || purchaseProductId == null || !StringUtils.hasText(purchaseContractNumber)) {
+            return Collections.emptyList();
+        }
+        String batchNo = purchaseContractNumber + "-" + purchaseProductId;
+        return stockInRecordService.list(
+                Wrappers.<StockInRecord>lambdaQuery()
+                        .eq(StockInRecord::getRecordType, StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode())
+                        .eq(StockInRecord::getRecordId, purchaseLedgerId)
+                        .eq(StockInRecord::getProductModelId, productModelId)
+                        .eq(StockInRecord::getBatchNo, batchNo)
+                        .orderByDesc(StockInRecord::getId)
+        );
+    }
+
+    private boolean hasApprovedStockRecord(List<StockInRecord> stockRecords) {
+        return stockRecords != null && stockRecords.stream()
+                .anyMatch(item -> ReviewStatusEnum.APPROVED.getCode().equals(item.getApprovalStatus()));
+    }
+
+    private StockInRecord findLatestUnapprovedStockRecord(List<StockInRecord> stockRecords) {
+        if (CollectionUtils.isEmpty(stockRecords)) {
+            return null;
+        }
+        return stockRecords.stream()
+                .filter(item -> !ReviewStatusEnum.APPROVED.getCode().equals(item.getApprovalStatus()))
+                .findFirst()
+                .orElse(null);
+    }
+
+    private void approveStockRecords(List<StockInRecord> stockRecords) {
+        if (CollectionUtils.isEmpty(stockRecords)) {
+            return;
+        }
+        List<Long> rejectedIds = stockRecords.stream()
+                .filter(item -> ReviewStatusEnum.REJECTED.getCode().equals(item.getApprovalStatus()))
+                .map(StockInRecord::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        if (!rejectedIds.isEmpty()) {
+            stockInRecordService.batchReAudit(rejectedIds);
+        }
+
+        List<Long> pendingIds = stockRecords.stream()
+                .filter(item -> item.getApprovalStatus() == null
+                        || ReviewStatusEnum.PENDING_REVIEW.getCode().equals(item.getApprovalStatus())
+                        || ReviewStatusEnum.REJECTED.getCode().equals(item.getApprovalStatus()))
+                .map(StockInRecord::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        if (!pendingIds.isEmpty()) {
+            stockInRecordService.batchApprove(pendingIds, ReviewStatusEnum.APPROVED.getCode());
         }
     }
 
@@ -620,9 +1027,19 @@
         if (loginUser == null) {
             return;
         }
+        ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne(
+                new LambdaQueryWrapper<ApprovalTemplate>()
+                        .eq(ApprovalTemplate::getBusinessType, 5L)
+                        .orderByDesc(ApprovalTemplate::getId)
+                        .last("LIMIT 1")
+        );
+        if (approvalTemplate == null) {
+            throw new BaseException("璇峰厛閰嶇疆閲囪喘瀹℃壒妯℃澘");
+        }
+
         ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto();
-        approvalInstance.setTemplateId(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,5L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getId());
-        approvalInstance.setTemplateName(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,5L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getTemplateName());
+        approvalInstance.setTemplateId(approvalTemplate.getId());
+        approvalInstance.setTemplateName(approvalTemplate.getTemplateName());
         approvalInstance.setBusinessId(purchaseLedger.getId());
         approvalInstance.setBusinessType(5L);
         approvalInstance.setCurrentLevel(1);
diff --git a/src/main/java/com/ruoyi/quality/service/IQualityInspectService.java b/src/main/java/com/ruoyi/quality/service/IQualityInspectService.java
index d78a5ca..8b42207 100644
--- a/src/main/java/com/ruoyi/quality/service/IQualityInspectService.java
+++ b/src/main/java/com/ruoyi/quality/service/IQualityInspectService.java
@@ -3,6 +3,7 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.quality.dto.QualityInspectDto;
 import com.ruoyi.quality.pojo.QualityInspect;
 
@@ -23,5 +24,7 @@
 
     int submit(QualityInspect qualityInspect);
 
+    R autoSubmit(Long id);
+
     void down(HttpServletResponse response, QualityInspect qualityInspect);
 }
diff --git a/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java b/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
index 0707375..328c358 100644
--- a/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
+++ b/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -11,8 +11,10 @@
 import com.deepoove.poi.config.Configure;
 import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
 import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.HackLoopTableRenderPolicy;
 import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.procurementrecord.service.ProcurementRecordService;
 import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.quality.dto.QualityInspectDto;
@@ -39,6 +41,8 @@
 import java.io.OutputStream;
 import java.math.BigDecimal;
 import java.net.URLEncoder;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -127,6 +131,10 @@
             stockInventoryDto.setRecordId(qualityInspect.getId());
             stockInventoryDto.setProductModelId(qualityInspect.getProductModelId());
             stockInventoryDto.setQualitity(qualityInspect.getQualifiedQuantity());
+            if (qualityInspect.getCheckTime() != null) {
+                LocalDate stockCreateDate = DateUtils.toLocalDate(qualityInspect.getCheckTime()).plusDays(1);
+                stockInventoryDto.setCreateTime(LocalDateTime.of(stockCreateDate, java.time.LocalTime.MIDNIGHT));
+            }
             stockInventoryDto.setBatchNo(resolveProductionBatchNo(
                     qualityInspect.getProductMainId(),
                     qualityInspect.getId(),
@@ -151,6 +159,33 @@
         return qualityInspectMapper.updateById(qualityInspect);
     }
 
+    @Override
+    public R autoSubmit(Long id) {
+        if (id == null) {
+            return R.fail("妫�楠屽崟ID涓嶈兘涓虹┖");
+        }
+        QualityInspect qualityInspect = qualityInspectMapper.selectById(id);
+        if (qualityInspect == null) {
+            return R.fail("妫�楠屽崟涓嶅瓨鍦�");
+        }
+        if (Integer.valueOf(1).equals(qualityInspect.getInspectState())) {
+            return R.ok("妫�楠屽崟宸叉彁浜�");
+        }
+
+        if (ObjectUtils.isNull(qualityInspect.getCheckResult())) {
+            qualityInspect.setCheckResult("鍚堟牸");
+        }
+        if (ObjectUtils.isNull(qualityInspect.getQualifiedQuantity())) {
+            qualityInspect.setQualifiedQuantity(qualityInspect.getQuantity() == null ? BigDecimal.ZERO : qualityInspect.getQuantity());
+        }
+        if (ObjectUtils.isNull(qualityInspect.getUnqualifiedQuantity())) {
+            qualityInspect.setUnqualifiedQuantity(BigDecimal.ZERO);
+        }
+        qualityInspectMapper.updateById(qualityInspect);
+        int rows = submit(qualityInspect);
+        return rows > 0 ? R.ok("妫�楠屽崟鎻愪氦鎴愬姛") : R.fail("妫�楠屽崟鎻愪氦澶辫触");
+    }
+
     private String resolveProductionBatchNo(Long productionProductMainId,
                                             Long qualityInspectId,
                                             Long productModelId) {
diff --git a/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml b/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
index a818160..a0a0a2d 100644
--- a/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
+++ b/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
@@ -120,6 +120,12 @@
                 <if test="c.supplierId != null">
                     AND pl.supplier_id = #{c.supplierId}
                 </if>
+                <if test="c.ids != null and c.ids.size() > 0">
+                    AND pl.id IN
+                    <foreach collection="c.ids" item="id" open="(" separator="," close=")">
+                        #{id}
+                    </foreach>
+                </if>
                 <if test="c.approvalStatus != null">
                     AND pl.approval_status = #{c.approvalStatus}
                 </if>

--
Gitblit v1.9.3