From 2ec6b477938b95608873feefa819c687d69c4a88 Mon Sep 17 00:00:00 2001
From: chenhj <1263187585@qq.com>
Date: 星期六, 25 四月 2026 15:09:56 +0800
Subject: [PATCH] Merge branch 'dev_New_pro' of http://114.132.189.42:9002/r/product-inventory-management-after into dev_New_pro

---
 src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java         |   52 +--
 src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java             |   16 +
 src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java                             |    5 
 src/main/java/com/ruoyi/stock/service/StockInventoryService.java                     |    4 
 src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java                               |    4 
 src/main/java/com/ruoyi/stock/service/StockInRecordService.java                      |    4 
 src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java             |  104 ++++++++
 src/main/java/com/ruoyi/stock/controller/StockInRecordController.java                |   21 +
 src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java               |   22 +
 src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java |   29 +-
 src/main/resources/mapper/stock/StockUninventoryMapper.xml                           |    9 
 src/main/java/com/ruoyi/stock/service/StockUninventoryService.java                   |    4 
 src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java            |   60 ++++
 src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java                             |   26 +
 src/main/java/com/ruoyi/stock/pojo/StockInRecord.java                                |    4 
 src/main/java/com/ruoyi/basic/controller/StorageAttachmentController.java            |   58 ++++
 src/main/resources/mapper/stock/StockInventoryMapper.xml                             |   37 ++
 src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java                         |   15 +
 src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java            |   89 ++++++
 src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java                     |    3 
 src/main/java/com/ruoyi/stock/controller/StockInventoryController.java               |   16 +
 src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java                              |    5 
 src/main/java/com/ruoyi/stock/service/StockOutRecordService.java                     |    4 
 src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java                  |   27 +
 src/main/java/com/ruoyi/basic/utils/FileUtil.java                                    |   43 +++
 src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java                       |    2 
 src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java          |   61 ++++
 src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java                              |   18 +
 28 files changed, 677 insertions(+), 65 deletions(-)

diff --git a/src/main/java/com/ruoyi/basic/controller/StorageAttachmentController.java b/src/main/java/com/ruoyi/basic/controller/StorageAttachmentController.java
new file mode 100644
index 0000000..730201d
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/controller/StorageAttachmentController.java
@@ -0,0 +1,58 @@
+package com.ruoyi.basic.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.basic.dto.StorageAttachmentDTO;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.dto.SupplierManageDto;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.pojo.StorageAttachment;
+import com.ruoyi.basic.service.StorageAttachmentService;
+import com.ruoyi.common.constant.StorageAttachmentConstants;
+import com.ruoyi.common.enums.StorageAttachmentRecordType;
+import com.ruoyi.framework.web.domain.R;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/basic/storage_attachment")
+public class StorageAttachmentController {
+    private StorageAttachmentService storageAttachmentService;
+
+    /**
+     * 鍒嗛〉鏌ヨ閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param page 鍒嗛〉鍙傛暟
+     * @param storageAttachmentDTO 鍏宠仈璁板綍淇℃伅
+     * @return 鍒嗛〉缁撴灉
+     */
+    @GetMapping("/listPage")
+    public R listPage(Page page, StorageAttachmentDTO storageAttachmentDTO) {
+        return R.ok(storageAttachmentService.listPage(page, storageAttachmentDTO));
+    }
+
+    /**
+     * 鍒犻櫎閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param ids 鏂囦欢id鍒楄〃
+     * @return 鍒犻櫎缁撴灉
+     */
+    @DeleteMapping("/delete")
+    public R batchDelete(@RequestBody List<Long> ids) {
+        return R.ok(storageAttachmentService.batchDeleteStorageAttachment(ids));
+    }
+
+    /**
+     * 淇濆瓨閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param storageBlobs 鏂囦欢淇℃伅鍒楄〃
+     * @param recordId 绠$悊璁板綍id
+     * @param recordType 鍏宠仈璁板綍绫诲瀷
+     * @param fileType 鏂囦欢绫诲瀷
+     */
+    @PostMapping("/add")
+    public R add(@RequestBody List<StorageBlobDTO> storageBlobs, Long recordId, String recordType, String fileType) {
+        storageAttachmentService.saveStorageAttachment(storageBlobs, recordId, recordType, fileType);
+        return R.ok();
+    }
+}
diff --git a/src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java b/src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java
index b0298ff..8cdf321 100644
--- a/src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java
+++ b/src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java
@@ -11,4 +11,19 @@
     private final String type;
     ApplicationTypeEnum(String type) { this.type = type; }
     public String getType() { return type; }
+
+    /**
+     * 鏍规嵁 type 鍊艰幏鍙栧搴旂殑鏋氫妇瀹炰緥
+     * @param type 搴旂敤绫诲瀷瀛楃涓�
+     * @return 瀵瑰簲鐨� ApplicationTypeEnum 鏋氫妇瀹炰緥
+     * @throws RuntimeException 濡傛灉 type 鏃犳晥
+     */
+    public static ApplicationTypeEnum getByType(String type) {
+        for (ApplicationTypeEnum enumValue : ApplicationTypeEnum.values()) {
+            if (enumValue.getType().equals(type)) {
+                return enumValue;
+            }
+        }
+        throw new RuntimeException("鏃犳晥鐨勫簲鐢ㄧ被鍨�: " + type);
+    }
 }
diff --git a/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java b/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
index cca8977..fd8d8c0 100644
--- a/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -6,9 +6,25 @@
     PDA_VERSION("pda_version"),
     SALES_LEDGER("sales_ledger"),
     SUPPLIER_MANAGE("supplier_manage"),
-    APPROVAL_PROCESS("approval_process");
+    APPROVAL_PROCESS("approval_process"),
+    QR_CODE_SCAN_RECORDS("qr_code_scan_records");
 
     private final String type;
     RecordTypeEnum(String type) { this.type = type; }
     public String getType() { return type; }
+
+    /**
+     * 鏍规嵁 type 鍊艰幏鍙栧搴旂殑鏋氫妇瀹炰緥
+     * @param type 璁板綍绫诲瀷瀛楃涓�
+     * @return 瀵瑰簲鐨� RecordTypeEnum 鏋氫妇瀹炰緥
+     * @throws RuntimeException 濡傛灉 type 鏃犳晥
+     */
+    public static RecordTypeEnum getByType(String type) {
+        for (RecordTypeEnum enumValue : RecordTypeEnum.values()) {
+            if (enumValue.getType().equals(type)) {
+                return enumValue;
+            }
+        }
+        throw new RuntimeException("鏃犳晥鐨勮褰曠被鍨�: " + type);
+    }
 }
diff --git a/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java b/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
index 7fbaf64..ef095f4 100644
--- a/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
+++ b/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
@@ -1,6 +1,13 @@
 package com.ruoyi.basic.service;
 
+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.basic.dto.StorageAttachmentDTO;
+import com.ruoyi.basic.dto.StorageAttachmentVO;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.pojo.StorageAttachment;
 import com.ruoyi.common.constant.StorageAttachmentConstants;
 import com.ruoyi.common.enums.StorageAttachmentRecordType;
@@ -19,15 +26,20 @@
 
     /**
      * 淇濆瓨閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
-     * @param attachments 鏂囦欢淇℃伅鍒楄〃
+     * @param storageBlobDTOS 鏂囦欢淇℃伅鍒楄〃
      * @param recordId 绠$悊璁板綍id
      * @param recordType 鍏宠仈璁板綍绫诲瀷
      * @param fileType 鏂囦欢绫诲瀷
      */
-    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType);
+    public void saveStorageAttachment(List<StorageBlobDTO> storageBlobDTOS, Long recordId, String recordType, String fileType);
 
-    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, String fileType);
-
+    /**
+     * 鍒嗛〉鏌ヨ閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param page 鍒嗛〉鍙傛暟
+     * @param storageAttachmentDTO 鍏宠仈璁板綍淇℃伅
+     * @return 鍒嗛〉缁撴灉
+     */
+    public IPage<StorageAttachmentVO> listPage(Page page, StorageAttachmentDTO storageAttachmentDTO);
 
     /**
      * 鍒犻櫎閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
@@ -35,4 +47,11 @@
      * @return 鍒犻櫎缁撴灉
      */
     public int deleteStorageAttachment(StorageAttachment storageAttachment);
+
+    /**
+     * 鎵归噺鍒犻櫎閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param ids 鏂囦欢id鍒楄〃
+     * @return 鍒犻櫎缁撴灉
+     */
+    public int batchDeleteStorageAttachment(List<Long> ids);
 }
diff --git a/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
index f3f3ae3..db38d7a 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
@@ -1,15 +1,22 @@
 package com.ruoyi.basic.service.impl;
 
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.dto.StorageAttachmentDTO;
+import com.ruoyi.basic.dto.StorageAttachmentVO;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.mapper.StorageAttachmentMapper;
 import com.ruoyi.basic.mapper.StorageBlobMapper;
 import com.ruoyi.basic.pojo.StorageAttachment;
 import com.ruoyi.basic.service.StorageAttachmentService;
 import com.ruoyi.basic.service.StorageBlobService;
-import com.ruoyi.common.constant.StorageAttachmentConstants;
-import com.ruoyi.common.enums.StorageAttachmentRecordType;
+import com.ruoyi.basic.utils.FileUtil;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.util.List;
 
@@ -29,40 +36,17 @@
     private final StorageAttachmentMapper storageAttachmentMapper;
 
     private final StorageBlobService storageBlobService;
-
+    private final FileUtil fileUtil;
 
     @Override
-    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType) {
-        // 鍒犻櫎鏃у浘
-        // todo fileChange
-//        deleteStorageAttachment(new StorageAttachment(fileType.toString(), (long) recordType.ordinal(), recordId));
-//        for (StorageAttachment attachment : attachments) {
-//            // 鑾峰彇鍏宠仈璁板綍
-//            StorageBlob storageBlob = attachment.getStorageBlobDTO();
-//            attachment.setName(fileType.toString());
-//            attachment.setRecordType((long) recordType.ordinal());
-//            attachment.setRecordId(recordId);
-//            attachment.setStorageBlobId(storageBlob.getId());
-//            storageAttachmentMapper.insert(attachment);
-//        }
-
+    @Transactional(rollbackFor = Exception.class)
+    public void saveStorageAttachment(List<StorageBlobDTO> storageBlobDTOS, Long recordId, String recordType, String fileType) {
+      fileUtil.saveStorageAttachment(ApplicationTypeEnum.getByType(fileType), RecordTypeEnum.getByType(recordType), recordId, storageBlobDTOS);
     }
 
     @Override
-    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, String fileType) {
-        // 鍒犻櫎鏃у浘
-//        deleteStorageAttachment(new StorageAttachment(fileType, (long) recordType.ordinal(), recordId));
-        // todo fileChange
-//        for (StorageAttachment attachment : attachments) {
-//            // 鑾峰彇鍏宠仈璁板綍
-//            StorageBlob storageBlob = attachment.getStorageBlobDTO();
-//            attachment.setName(fileType);
-//            attachment.setRecordType((long) recordType.ordinal());
-//            attachment.setRecordId(recordId);
-//            attachment.setStorageBlobId(storageBlob.getId());
-//            storageAttachmentMapper.insert(attachment);
-//        }
-
+    public IPage<StorageAttachmentVO> listPage(Page page, StorageAttachmentDTO storageAttachmentDTO) {
+        return fileUtil.getStorageAttachmentVosPageListByApplicationAndRecordTypeAndRecordId(page, storageAttachmentDTO);
     }
 
     @Override
@@ -79,4 +63,10 @@
 //    }
         return 0;
     }
+
+    @Override
+    public int batchDeleteStorageAttachment(List<Long> ids) {
+        fileUtil.deleteStorageAttachmentsByStorageAttachmentIds(ids);
+        return 1;
+    }
 }
diff --git a/src/main/java/com/ruoyi/basic/utils/FileUtil.java b/src/main/java/com/ruoyi/basic/utils/FileUtil.java
index 97397f3..fde28b4 100644
--- a/src/main/java/com/ruoyi/basic/utils/FileUtil.java
+++ b/src/main/java/com/ruoyi/basic/utils/FileUtil.java
@@ -1,7 +1,10 @@
 package com.ruoyi.basic.utils;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.basic.dto.StorageAttachmentDTO;
 import com.ruoyi.basic.dto.StorageAttachmentVO;
 import com.ruoyi.basic.dto.StorageBlobDTO;
 import com.ruoyi.basic.dto.StorageBlobVO;
@@ -198,6 +201,46 @@
     }
 
     /**
+     * 閫氳繃璁板綍绫诲瀷鑾峰彇鏂囦欢淇℃伅 attachment锛堝垎椤碉級
+     *
+     * @param page       鍒嗛〉鍙傛暟
+     * @param storageAttachmentDTO 鍏宠仈璁板綍淇℃伅
+     */
+    public IPage<StorageAttachmentVO> getStorageAttachmentVosPageListByApplicationAndRecordTypeAndRecordId(Page page, StorageAttachmentDTO storageAttachmentDTO) {
+        // 鍒嗛〉鏌ヨ绗﹀悎鏉′欢鐨� StorageAttachment 璁板綍
+        LambdaQueryWrapper<StorageAttachment> queryWrapper = new LambdaQueryWrapper<StorageAttachment>()
+                .eq(StorageAttachment::getRecordType, storageAttachmentDTO.getRecordType())
+                .eq(StorageAttachment::getRecordId, storageAttachmentDTO.getRecordId());
+        if (storageAttachmentDTO.getApplication() != null) {
+            queryWrapper.eq(StorageAttachment::getApplication, storageAttachmentDTO.getApplication());
+        }
+        IPage<StorageAttachmentVO> storageAttachmentIPage = storageAttachmentMapper.selectPage(page, queryWrapper);
+
+        // 杞崲涓� StorageAttachmentVO 骞惰幏鍙栧搴旂殑 StorageBlobVO
+        List<StorageAttachmentVO> storageAttachmentVOS = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(storageAttachmentIPage.getRecords())) {
+            for (StorageAttachment storageAttachment : storageAttachmentIPage.getRecords()) {
+                StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO();
+                BeanUtils.copyProperties(storageAttachment, storageAttachmentVO);
+                List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(Collections.singletonList(storageAttachment.getId()));
+                if (CollectionUtils.isEmpty(storageBlobVOS)) {
+                    storageAttachmentVO.setStorageBlobVOS(new ArrayList<>());
+                } else {
+                    storageAttachmentVO.setStorageBlobVOS(storageBlobVOS);
+                }
+                storageAttachmentVOS.add(storageAttachmentVO);
+            }
+        }
+
+        // 鏋勫缓鍒嗛〉缁撴灉
+        IPage<StorageAttachmentVO> resultPage = new Page<>();
+        BeanUtils.copyProperties(storageAttachmentIPage, resultPage);
+        resultPage.setRecords(storageAttachmentVOS);
+
+        return resultPage;
+    }
+
+    /**
      * 閫氳繃鏂囦欢鐢ㄩ�斻�佸叧鑱旇褰曠被鍨嬨�佸叧鑱旇褰昳d鑾峰彇鏂囦欢鍏宠仈淇℃伅 attachment
      *
      * @param application 鏂囦欢鐢ㄩ��
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java
index 85831e9..acc30a7 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java
@@ -5,11 +5,16 @@
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.dto.StorageBlobVO;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.mapper.StorageAttachmentMapper;
 import com.ruoyi.basic.mapper.StorageBlobMapper;
 import com.ruoyi.basic.pojo.StorageAttachment;
 import com.ruoyi.basic.pojo.StorageBlob;
 import com.ruoyi.basic.service.StorageAttachmentService;
+import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.inspectiontask.dto.QrCodeScanRecordDto;
 import com.ruoyi.inspectiontask.mapper.QrCodeMapper;
@@ -51,6 +56,8 @@
 
 
     private final SysUserMapper sysUserMapper;
+
+    private final FileUtil fileUtil;
 
     @Override
     public IPage<QrCodeScanRecordDto> selectQrCodeScanRecordList(Page<QrCodeScanRecord> page, QrCodeScanRecordDto qrCodeScanRecordDto) {
@@ -159,19 +166,15 @@
         }
 
         if (qrCodeScanRecordDto.getStorageBlobVO() != null && !qrCodeScanRecordDto.getStorageBlobVO().isEmpty()) {
-            List<StorageAttachment> attachments = new ArrayList<>();
+            // vo 杞� dto
+            List<StorageBlobDTO> storageBlobDTOs = new ArrayList<>();
+            for (StorageBlobVO storageBlobVO : qrCodeScanRecordDto.getStorageBlobVO()) {
+                StorageBlobDTO storageBlobDTO = new StorageBlobDTO();
+                BeanUtils.copyProperties(storageBlobVO, storageBlobDTO);
+                storageBlobDTOs.add(storageBlobDTO);
+            }
 
-            // todo fileChange
-//            for (StorageBlobDTO storageBlobDTO : qrCodeScanRecordDto.getStorageBlobDTO()) {
-//                StorageAttachment storageAttachment = new StorageAttachment(
-//                        StorageAttachmentFile,
-//                        (long) QrCodeScanRecords.ordinal(),
-//                        qrCodeScanRecord.getId()
-//                );
-//                storageAttachment.setStorageBlobDTO(storageBlobDTO);
-//                attachments.add(storageAttachment);
-//            }
-            storageAttachmentService.saveStorageAttachment(attachments, qrCodeScanRecord.getId(), QrCodeScanRecords, StorageAttachmentFile);
+            fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.QR_CODE_SCAN_RECORDS, qrCodeScanRecord.getId(), storageBlobDTOs);
         }
         return i;
     }
@@ -181,4 +184,4 @@
         return qrCodeScanRecordMapper.deleteBatchIds(Arrays.asList(ids));
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/ruoyi/stock/controller/StockInRecordController.java b/src/main/java/com/ruoyi/stock/controller/StockInRecordController.java
index ac89cc9..1887b27 100644
--- a/src/main/java/com/ruoyi/stock/controller/StockInRecordController.java
+++ b/src/main/java/com/ruoyi/stock/controller/StockInRecordController.java
@@ -43,10 +43,31 @@
         return AjaxResult.success(stockInRecordService.batchDelete(ids));
     }
 
+    @DeleteMapping("/pending")
+    @Log(title = "鍏ュ簱绠$悊-鍒犻櫎寰呭鎵瑰叆搴�", businessType = BusinessType.DELETE)
+    @Operation(summary = "鍒犻櫎寰呭鎵圭殑鍏ュ簱璁板綍")
+    public AjaxResult deletePending(@RequestBody List<Long> ids) {
+        if(CollectionUtils.isEmpty(ids)){
+            return AjaxResult.error("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        return AjaxResult.success(stockInRecordService.batchDeletePending(ids));
+    }
+
     @PostMapping("/exportStockInRecord")
     @Operation(summary = "瀵煎嚭鍏ュ簱璁板綍")
     public void exportStockInRecord(HttpServletResponse response, StockInRecordDto stockInRecordDto) {
         stockInRecordService.exportStockInRecord(response,stockInRecordDto);
     }
 
+    @PostMapping("/approve")
+    @Log(title = "鍏ュ簱绠$悊-瀹℃壒鍏ュ簱", businessType = BusinessType.UPDATE)
+    @Operation(summary = "鎵归噺瀹℃壒鍏ュ簱璁板綍")
+    public AjaxResult approve(@RequestBody StockInRecordDto approveDto) {
+        if(CollectionUtils.isEmpty(approveDto.getIds())){
+            return AjaxResult.error("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        stockInRecordService.batchApprove(approveDto.getIds(), approveDto.getApprovalStatus());
+        return AjaxResult.success();
+    }
+
 }
diff --git a/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java b/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
index 02a25fd..2096b6d 100644
--- a/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
+++ b/src/main/java/com/ruoyi/stock/controller/StockInventoryController.java
@@ -68,6 +68,22 @@
         return R.ok(stockInventoryService.subtractStockInventory(stockInventoryDto));
     }
 
+    @PostMapping("/addStockInRecordOnly")
+    @Operation(summary = "鏂板鍏ュ簱璁板綍锛堜粎鍒涘缓璁板綍锛屼笉璋冩暣搴撳瓨锛�")
+    public R addStockInRecordOnly(@RequestBody StockInventoryDto stockInventoryDto) {
+        stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode()));
+        stockInventoryDto.setRecordId(0L);
+        return R.ok(stockInventoryService.addStockInRecordOnly(stockInventoryDto));
+    }
+
+    @PostMapping("/addStockOutRecordOnly")
+    @Operation(summary = "鏂板鍑哄簱璁板綍锛堜粎鍒涘缓璁板綍锛屼笉璋冩暣搴撳瓨锛�")
+    public R addStockOutRecordOnly(@RequestBody StockInventoryDto stockInventoryDto) {
+        stockInventoryDto.setRecordType(String.valueOf(StockOutQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_OUT.getCode()));
+        stockInventoryDto.setRecordId(0L);
+        return R.ok(stockInventoryService.addStockOutRecordOnly(stockInventoryDto));
+    }
+
 
     @PostMapping("importStockInventory")
     @Operation(summary = "瀵煎叆搴撳瓨")
diff --git a/src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java b/src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java
index c4d74c2..7351890 100644
--- a/src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java
+++ b/src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java
@@ -10,7 +10,6 @@
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
 import lombok.AllArgsConstructor;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.bind.annotation.*;
 
@@ -61,10 +60,31 @@
         return AjaxResult.success(stockOutRecordService.batchDelete(ids));
     }
 
+    @DeleteMapping("/pending")
+    @Log(title = "鍑哄簱绠$悊-鍒犻櫎寰呭鎵瑰嚭搴�", businessType = BusinessType.DELETE)
+    @Operation(summary = "鍒犻櫎寰呭鎵圭殑鍑哄簱璁板綍")
+    public AjaxResult deletePending(@RequestBody List<Long> ids) {
+        if(CollectionUtils.isEmpty(ids)){
+            return AjaxResult.error("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        return AjaxResult.success(stockOutRecordService.batchDeletePending(ids));
+    }
+
     @PostMapping("/exportStockOutRecord")
     @Operation(summary = "瀵煎嚭鍑哄簱璁板綍")
     public void exportStockOutRecord(HttpServletResponse response, StockOutRecordDto stockOutRecordDto) {
         stockOutRecordService.exportStockOutRecord(response,stockOutRecordDto);
     }
 
+    @PostMapping("/approve")
+    @Log(title = "鍑哄簱绠$悊-瀹℃壒鍑哄簱", businessType = BusinessType.UPDATE)
+    @Operation(summary = "鎵归噺瀹℃壒鍑哄簱璁板綍")
+    public AjaxResult approve(@RequestBody StockOutRecordDto approveDto) {
+        if(CollectionUtils.isEmpty(approveDto.getIds())){
+            return AjaxResult.error("璇烽�夋嫨鑷冲皯涓�鏉℃暟鎹�");
+        }
+        stockOutRecordService.batchApprove(approveDto.getIds(), approveDto.getApprovalStatus());
+        return AjaxResult.success();
+    }
+
 }
diff --git a/src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java b/src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java
index ebdbd07..477a68d 100644
--- a/src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java
+++ b/src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java
@@ -51,6 +51,22 @@
         return R.ok(stockUninventoryService.subtractStockUninventory(stockUninventoryDto));
     }
 
+    @PostMapping("/addStockInRecordOnly")
+    @Operation(summary = "鏂板鍏ュ簱璁板綍锛堜粎鍒涘缓璁板綍锛屼笉璋冩暣搴撳瓨锛�")
+    public R addStockInRecordOnly(@RequestBody StockUninventoryDto stockUninventoryDto) {
+        stockUninventoryDto.setRecordType(String.valueOf(StockInUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode()));
+        stockUninventoryDto.setRecordId(0L);
+        return R.ok(stockUninventoryService.addStockInRecordOnly(stockUninventoryDto));
+    }
+
+    @PostMapping("/addStockOutRecordOnly")
+    @Operation(summary = "鏂板鍑哄簱璁板綍锛堜粎鍒涘缓璁板綍锛屼笉璋冩暣搴撳瓨锛�")
+    public R addStockOutRecordOnly(@RequestBody StockUninventoryDto stockUninventoryDto) {
+        stockUninventoryDto.setRecordType(String.valueOf(StockOutUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode()));
+        stockUninventoryDto.setRecordId(0L);
+        return R.ok(stockUninventoryService.addStockOutRecordOnly(stockUninventoryDto));
+    }
+
     @PostMapping("/exportStockUninventory")
     @Operation(summary = "瀵煎嚭搴撳瓨")
     public void exportStockUninventory(HttpServletResponse response, StockUninventoryDto stockUninventoryDto) {
diff --git a/src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java b/src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java
index 05c1960..56ad762 100644
--- a/src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java
+++ b/src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java
@@ -4,6 +4,8 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.util.List;
+
 
 @Data
 public class StockInRecordDto extends StockInRecord {
@@ -30,4 +32,7 @@
     @Schema(description = "椤堕儴鐖朵骇鍝乮d")
     private Long topParentProductId;
 
+    @Schema(description = "璁板綍ID鍒楄〃")
+    private List<Long> ids;
+
 }
diff --git a/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java b/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
index 9cc1fe6..2be6512 100644
--- a/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
+++ b/src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java
@@ -43,31 +43,37 @@
 
     @Schema(description = "椤堕儴鐖朵骇鍝乮d")
     private Long topParentProductId;
-    
+
     @Schema(description = "搴撳瓨绫诲瀷锛歲ualified(鍚堟牸)銆乽nqualified(涓嶅悎鏍�)")
     private String stockType;
-    
+
     @Schema(description = "鍚堟牸搴撳瓨鏁伴噺")
     private BigDecimal qualifiedQuantity;
-    
+
     @Schema(description = "涓嶅悎鏍煎簱瀛樻暟閲�")
     private BigDecimal unQualifiedQuantity;
-    
+
     @Schema(description = "鍚堟牸搴撳瓨鍐荤粨鏁伴噺")
     private BigDecimal qualifiedLockedQuantity;
-    
+
     @Schema(description = "涓嶅悎鏍煎簱瀛樺喕缁撴暟閲�")
     private BigDecimal unQualifiedLockedQuantity;
-    
+
     @Schema(description = "鍚堟牸搴撳瓨鏈喕缁撴暟閲�")
     private BigDecimal qualifiedUnLockedQuantity;
-    
+
     @Schema(description = "涓嶅悎鏍煎簱瀛樻湭鍐荤粨鏁伴噺")
     private BigDecimal unQualifiedUnLockedQuantity;
-    
+
+    @Schema(description = "鍚堟牸搴撳瓨寰呭鏍稿嚭搴撴暟閲忥紙涓嶅崰鐢ㄥ簱瀛樹絾宸茬敵璇峰嚭搴擄級")
+    private BigDecimal qualifiedPendingOutQuantity;
+
+    @Schema(description = "涓嶅悎鏍煎簱瀛樺緟瀹℃牳鍑哄簱鏁伴噺")
+    private BigDecimal unQualifiedPendingOutQuantity;
+
     @Schema(description = "鍚堟牸搴撳瓨ID")
     private Long qualifiedId;
-    
+
     @Schema(description = "涓嶅悎鏍煎簱瀛業D")
     private Long unQualifiedId;
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java b/src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java
index f09d78d..022be78 100644
--- a/src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java
+++ b/src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java
@@ -6,6 +6,8 @@
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import java.util.List;
+
 @Data
 @AllArgsConstructor
 @NoArgsConstructor
@@ -29,4 +31,7 @@
 
     @Schema(description = "椤堕儴鐖朵骇鍝乮d")
     private Long topParentProductId;
+
+    @Schema(description = "璁板綍ID鍒楄〃")
+    private List<Long> ids;
 }
diff --git a/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java b/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
index 9f493d2..f17065a 100644
--- a/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
+++ b/src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java
@@ -50,4 +50,6 @@
     List<Map<String, Object>> selectDailyStockOutCounts(@Param("rootCategoryId") Long rootCategoryId, @Param("startDate") String startDate, @Param("endDate") String endDate);
 
     BigDecimal selectTotalByDate(@Param("now") LocalDate now);
+
+    BigDecimal selectPendingOutQuantity(@Param("productModelId") Long productModelId, @Param("batchNo") String batchNo, @Param("type") String type);
 }
diff --git a/src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java b/src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java
index 89e9b17..d6abdff 100644
--- a/src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java
+++ b/src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java
@@ -9,6 +9,7 @@
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
+import java.math.BigDecimal;
 import java.util.List;
 
 /**
@@ -26,6 +27,8 @@
 
     int updateSubtractStockUnInventory(@Param("ew") StockUninventoryDto stockUninventoryDto);
 
+    BigDecimal selectPendingOutQuantity(@Param("productModelId") Long productModelId, @Param("batchNo") String batchNo, @Param("type") String type);
+
     int updateAddStockUnInventory(@Param("ew") StockUninventoryDto stockUninventoryDto);
 
     List<StockUnInventoryExportData> listStockInventoryExportData(@Param("ew") StockUninventoryDto stockUninventoryDto);
diff --git a/src/main/java/com/ruoyi/stock/pojo/StockInRecord.java b/src/main/java/com/ruoyi/stock/pojo/StockInRecord.java
index 370eeed..6b8a807 100644
--- a/src/main/java/com/ruoyi/stock/pojo/StockInRecord.java
+++ b/src/main/java/com/ruoyi/stock/pojo/StockInRecord.java
@@ -2,6 +2,7 @@
 
 import com.baomidou.mybatisplus.annotation.*;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.enums.ReviewStatusEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import org.springframework.format.annotation.DateTimeFormat;
@@ -65,6 +66,9 @@
     @TableField(fill = FieldFill.INSERT_UPDATE)
     private Integer updateUser;
 
+    @Schema(description = "瀹℃壒鐘舵��  0-寰呭鎵� 1-閫氳繃 2-椹冲洖", implementation = ReviewStatusEnum.class)
+    private Integer approvalStatus;
+
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
 }
diff --git a/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java b/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
index 9d25895..20ea4a2 100644
--- a/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
+++ b/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
@@ -2,6 +2,7 @@
 
 import com.baomidou.mybatisplus.annotation.*;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.enums.ReviewStatusEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Getter;
 import lombok.Setter;
@@ -74,6 +75,9 @@
     @Schema(description = "绫诲瀷  0鍚堟牸鍏ュ簱 1涓嶅悎鏍煎叆搴�")
     private String type;
 
+    @Schema(description = "瀹℃壒鐘舵��  0-寰呭鎵� 1-閫氳繃 2-椹冲洖", implementation = ReviewStatusEnum.class)
+    private Integer approvalStatus;
+
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
 }
diff --git a/src/main/java/com/ruoyi/stock/service/StockInRecordService.java b/src/main/java/com/ruoyi/stock/service/StockInRecordService.java
index 12be000..249f261 100644
--- a/src/main/java/com/ruoyi/stock/service/StockInRecordService.java
+++ b/src/main/java/com/ruoyi/stock/service/StockInRecordService.java
@@ -18,5 +18,9 @@
 
     int batchDelete(List<Long> ids);
 
+    int batchDeletePending(List<Long> ids);
+
     void exportStockInRecord(HttpServletResponse response, StockInRecordDto stockInRecordDto);
+
+    int batchApprove(List<Long> ids, Integer approvalStatus);
 }
diff --git a/src/main/java/com/ruoyi/stock/service/StockInventoryService.java b/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
index 986af11..008ec6a 100644
--- a/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
+++ b/src/main/java/com/ruoyi/stock/service/StockInventoryService.java
@@ -29,6 +29,10 @@
 
     Boolean subtractStockInventory(StockInventoryDto stockInventoryDto);
 
+    Boolean addStockInRecordOnly(StockInventoryDto stockInventoryDto);
+
+    Boolean addStockOutRecordOnly(StockInventoryDto stockInventoryDto);
+
     R importStockInventory(MultipartFile file);
 
     void exportStockInventory(HttpServletResponse response, StockInventoryDto stockInventoryDto);
diff --git a/src/main/java/com/ruoyi/stock/service/StockOutRecordService.java b/src/main/java/com/ruoyi/stock/service/StockOutRecordService.java
index bf00410..bbad1f6 100644
--- a/src/main/java/com/ruoyi/stock/service/StockOutRecordService.java
+++ b/src/main/java/com/ruoyi/stock/service/StockOutRecordService.java
@@ -26,5 +26,9 @@
 
     int batchDelete(List<Long> ids);
 
+    int batchDeletePending(List<Long> ids);
+
     void exportStockOutRecord(HttpServletResponse response, StockOutRecordDto stockOutRecordDto);
+
+    int batchApprove(List<Long> ids, Integer approvalStatus);
 }
diff --git a/src/main/java/com/ruoyi/stock/service/StockUninventoryService.java b/src/main/java/com/ruoyi/stock/service/StockUninventoryService.java
index e31da8b..ffc1ebe 100644
--- a/src/main/java/com/ruoyi/stock/service/StockUninventoryService.java
+++ b/src/main/java/com/ruoyi/stock/service/StockUninventoryService.java
@@ -25,6 +25,10 @@
 
     Integer subtractStockUninventory(StockUninventoryDto stockUninventoryDto);
 
+    Integer addStockInRecordOnly(StockUninventoryDto stockUninventoryDto);
+
+    Integer addStockOutRecordOnly(StockUninventoryDto stockUninventoryDto);
+
     void exportStockUninventory(HttpServletResponse response, StockUninventoryDto stockUninventoryDto);
 
     Boolean frozenStock(StockInventoryDto stockInventoryDto);
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
index ef5e170..f84a9d8 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
@@ -4,6 +4,7 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.enums.ReviewStatusEnum;
 import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
 import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
 import com.ruoyi.common.exception.base.BaseException;
@@ -26,6 +27,7 @@
 import lombok.AllArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 import jakarta.servlet.http.HttpServletResponse;
 import java.util.List;
@@ -127,4 +129,106 @@
         ExcelUtil<StockInRecordExportData> util = new ExcelUtil<>(StockInRecordExportData.class);
         util.exportExcel(response,list, "鍏ュ簱璁板綍淇℃伅");
     }
+
+    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);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int batchDeletePending(List<Long> ids) {
+        for (Long id : ids) {
+            StockInRecord stockInRecord = stockInRecordMapper.selectById(id);
+            if (stockInRecord == null) {
+                throw new BaseException("鍏ュ簱璁板綍涓嶅瓨鍦�,鏃犳硶鍒犻櫎!!!");
+            }
+            if (stockInRecord.getApprovalStatus() != null && !ReviewStatusEnum.PENDING_REVIEW.getCode().equals(stockInRecord.getApprovalStatus())) {
+                throw new BaseException("鍙湁寰呭鎵圭姸鎬佺殑璁板綍鎵嶈兘鍒犻櫎,鍏ュ簱鎵规:" + stockInRecord.getInboundBatches());
+            }
+        }
+        return stockInRecordMapper.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) {
+            StockInRecord stockInRecord = stockInRecordMapper.selectById(id);
+            if (stockInRecord == null) {
+                throw new BaseException("鍏ュ簱璁板綍涓嶅瓨鍦�,鏃犳硶瀹℃壒!!!");
+            }
+            if (stockInRecord.getApprovalStatus() != null && !ReviewStatusEnum.PENDING_REVIEW.getCode().equals(stockInRecord.getApprovalStatus())) {
+                throw new BaseException("鍙湁寰呭鎵圭姸鎬佺殑璁板綍鎵嶈兘瀹℃壒,鍏ュ簱鎵规:" + stockInRecord.getInboundBatches());
+            }
+            stockInRecord.setApprovalStatus(approvalStatus);
+            stockInRecordMapper.updateById(stockInRecord);
+            // 瀹℃壒閫氳繃鏃讹紝搴撳瓨澧炲姞
+            if (ReviewStatusEnum.APPROVED.getCode().equals(approvalStatus)) {
+                if ("0".equals(stockInRecord.getType())) {
+                    // 鍚堟牸鍏ュ簱 -> 鍏堟煡搴撳瓨锛屽瓨鍦ㄥ垯鏇存柊锛屼笉瀛樺湪鍒欐柊澧�
+                    StockInventory stockInventory = getStockInventory(stockInRecord.getProductModelId(), stockInRecord.getBatchNo());
+                    StockInventoryDto stockInventoryDto = new StockInventoryDto();
+                    stockInventoryDto.setProductModelId(stockInRecord.getProductModelId());
+                    stockInventoryDto.setBatchNo(stockInRecord.getBatchNo());
+                    stockInventoryDto.setQualitity(stockInRecord.getStockInNum());
+                    stockInventoryDto.setRemark(stockInRecord.getRemark());
+                    if (stockInventory == null) {
+                        stockInventoryMapper.insert(new StockInventory() {{
+                            setProductModelId(stockInRecord.getProductModelId());
+                            setQualitity(stockInRecord.getStockInNum());
+                            setBatchNo(stockInRecord.getBatchNo());
+                            setRemark(stockInRecord.getRemark());
+                            setVersion(1);
+                        }});
+                    } else {
+                        stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
+                    }
+                } else if ("1".equals(stockInRecord.getType())) {
+                    // 涓嶅悎鏍煎叆搴� -> 鍏堟煡搴撳瓨锛屽瓨鍦ㄥ垯鏇存柊锛屼笉瀛樺湪鍒欐柊澧�
+                    StockUninventory stockUninventory = getStockUninventory(stockInRecord.getProductModelId(), stockInRecord.getBatchNo());
+                    StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
+                    stockUninventoryDto.setProductModelId(stockInRecord.getProductModelId());
+                    stockUninventoryDto.setBatchNo(stockInRecord.getBatchNo());
+                    stockUninventoryDto.setQualitity(stockInRecord.getStockInNum());
+                    stockUninventoryDto.setRemark(stockInRecord.getRemark());
+                    if (stockUninventory == null) {
+                        stockUninventoryMapper.insert(new StockUninventory() {{
+                            setProductModelId(stockInRecord.getProductModelId());
+                            setQualitity(stockInRecord.getStockInNum());
+                            setBatchNo(stockInRecord.getBatchNo());
+                            setRemark(stockInRecord.getRemark());
+                            setVersion(1);
+                        }});
+                    } else {
+                        stockUninventoryMapper.updateAddStockUnInventory(stockUninventoryDto);
+                    }
+                }
+            }
+        }
+        return ids.size();
+    }
 }
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 e3c88e5..4d525c3 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -8,7 +8,8 @@
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
 import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
-import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.framework.web.domain.R;
@@ -145,6 +146,63 @@
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    public Boolean addStockInRecordOnly(StockInventoryDto stockInventoryDto) {
+        StockInRecordDto stockInRecordDto = new StockInRecordDto();
+        stockInRecordDto.setRecordId(stockInventoryDto.getRecordId());
+        stockInRecordDto.setRecordType(stockInventoryDto.getRecordType());
+        stockInRecordDto.setStockInNum(stockInventoryDto.getQualitity());
+        stockInRecordDto.setBatchNo(stockInventoryDto.getBatchNo());
+        stockInRecordDto.setProductModelId(stockInventoryDto.getProductModelId());
+        stockInRecordDto.setType("0");
+        stockInRecordDto.setRemark(stockInventoryDto.getRemark());
+        stockInRecordService.add(stockInRecordDto);
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean addStockOutRecordOnly(StockInventoryDto stockInventoryDto) {
+        LambdaQueryWrapper<StockInventory> eq = new LambdaQueryWrapper<>();
+        eq.eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId());
+        if (StringUtils.isEmpty(stockInventoryDto.getBatchNo())) {
+            eq.isNull(StockInventory::getBatchNo);
+        } else {
+            eq.eq(StockInventory::getBatchNo, stockInventoryDto.getBatchNo());
+        }
+        StockInventory stockInventory = stockInventoryMapper.selectOne(eq);
+        if (stockInventory == null) {
+            throw new ServiceException("搴撳瓨璁板綍涓嶅瓨鍦�");
+        }
+        BigDecimal lockedQty = stockInventory.getLockedQuantity();
+        if (lockedQty == null) {
+            lockedQty = BigDecimal.ZERO;
+        }
+        BigDecimal pendingOut = stockInventoryMapper.selectPendingOutQuantity(
+                stockInventoryDto.getProductModelId(),
+                stockInventoryDto.getBatchNo(),
+                "0"
+        );
+        if (pendingOut == null) {
+            pendingOut = BigDecimal.ZERO;
+        }
+        BigDecimal availableQty = stockInventory.getQualitity().subtract(lockedQty).subtract(pendingOut);
+        if (stockInventoryDto.getQualitity().compareTo(availableQty) > 0) {
+            throw new ServiceException("鐢宠鏁伴噺瓒呰繃鍙敤搴撳瓨锛屽綋鍓嶅彲鐢ㄥ簱瀛樹负锛�" + availableQty);
+        }
+        StockOutRecordDto stockOutRecordDto = new StockOutRecordDto();
+        stockOutRecordDto.setRecordId(stockInventoryDto.getRecordId());
+        stockOutRecordDto.setRecordType(stockInventoryDto.getRecordType());
+        stockOutRecordDto.setStockOutNum(stockInventoryDto.getQualitity());
+        stockOutRecordDto.setBatchNo(stockInventoryDto.getBatchNo());
+        stockOutRecordDto.setProductModelId(stockInventoryDto.getProductModelId());
+        stockOutRecordDto.setType("0");
+        stockOutRecordDto.setRemark(stockInventoryDto.getRemark());
+        stockOutRecordService.add(stockOutRecordDto);
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
     public R importStockInventory(MultipartFile file) {
         try {
             // 鏌ヨ鎵�鏈夌殑浜у搧骞舵瀯寤烘槧灏勶紝鎻愰珮鏌ユ壘鏁堢巼
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 a28969c..8919523 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
@@ -4,6 +4,7 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.enums.ReviewStatusEnum;
 import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
 import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
 import com.ruoyi.common.exception.base.BaseException;
@@ -26,6 +27,8 @@
 import com.ruoyi.stock.service.StockOutRecordService;
 import lombok.AllArgsConstructor;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 import jakarta.servlet.http.HttpServletResponse;
 import java.util.List;
@@ -130,4 +133,90 @@
         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());
+            }
+            stockOutRecord.setApprovalStatus(approvalStatus);
+            stockOutRecordMapper.updateById(stockOutRecord);
+            // 瀹℃壒閫氳繃鏃讹紝鎵e噺搴撳瓨
+            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);
+                }
+            }
+        }
+        return ids.size();
+    }
+
+    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);
+    }
 }
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 f7bd4f2..4c92e39 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java
@@ -6,6 +6,7 @@
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.stock.dto.StockInRecordDto;
@@ -24,6 +25,8 @@
 import org.springframework.transaction.annotation.Transactional;
 
 import jakarta.servlet.http.HttpServletResponse;
+
+import java.math.BigDecimal;
 import java.util.List;
 
 /**
@@ -95,6 +98,7 @@
         stockOutRecordDto.setRecordType(stockUninventoryDto.getRecordType());
         stockOutRecordDto.setStockOutNum(stockUninventoryDto.getQualitity());
         stockOutRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
+        stockOutRecordDto.setBatchNo(stockUninventoryDto.getBatchNo());
         stockOutRecordDto.setType("1");
         stockOutRecordService.add(stockOutRecordDto);
         StockUninventory oldStockInventory = stockUninventoryMapper.selectOne(new QueryWrapper<StockUninventory>().lambda().eq(StockUninventory::getProductModelId, stockUninventoryDto.getProductModelId()));
@@ -107,6 +111,63 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Integer addStockInRecordOnly(StockUninventoryDto stockUninventoryDto) {
+        StockInRecordDto stockInRecordDto = new StockInRecordDto();
+        stockInRecordDto.setRecordId(stockUninventoryDto.getRecordId());
+        stockInRecordDto.setRecordType(stockUninventoryDto.getRecordType());
+        stockInRecordDto.setStockInNum(stockUninventoryDto.getQualitity());
+        stockInRecordDto.setBatchNo(stockUninventoryDto.getBatchNo());
+        stockInRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
+        stockInRecordDto.setType("1");
+        stockInRecordDto.setRemark(stockUninventoryDto.getRemark());
+        stockInRecordService.add(stockInRecordDto);
+        return 1;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Integer addStockOutRecordOnly(StockUninventoryDto stockUninventoryDto) {
+        LambdaQueryWrapper<StockUninventory> eq = new LambdaQueryWrapper<>();
+        eq.eq(StockUninventory::getProductModelId, stockUninventoryDto.getProductModelId());
+        if (StringUtils.isEmpty(stockUninventoryDto.getBatchNo())) {
+            eq.isNull(StockUninventory::getBatchNo);
+        } else {
+            eq.eq(StockUninventory::getBatchNo, stockUninventoryDto.getBatchNo());
+        }
+        StockUninventory stockUninventory = stockUninventoryMapper.selectOne(eq);
+        if (stockUninventory == null) {
+            throw new BaseException("搴撳瓨璁板綍涓嶅瓨鍦�");
+        }
+        BigDecimal lockedQty = stockUninventory.getLockedQuantity();
+        if (lockedQty == null) {
+            lockedQty = BigDecimal.ZERO;
+        }
+        BigDecimal pendingOut = stockUninventoryMapper.selectPendingOutQuantity(
+                stockUninventoryDto.getProductModelId(),
+                stockUninventoryDto.getBatchNo(),
+                "1"
+        );
+        if (pendingOut == null) {
+            pendingOut = BigDecimal.ZERO;
+        }
+        BigDecimal availableQty = stockUninventory.getQualitity().subtract(lockedQty).subtract(pendingOut);
+        if (stockUninventoryDto.getQualitity().compareTo(availableQty) > 0) {
+            throw new BaseException("鐢宠鏁伴噺瓒呰繃鍙敤搴撳瓨锛屽綋鍓嶅彲鐢ㄥ簱瀛樹负锛�" + availableQty);
+        }
+        StockOutRecordDto stockOutRecordDto = new StockOutRecordDto();
+        stockOutRecordDto.setRecordId(stockUninventoryDto.getRecordId());
+        stockOutRecordDto.setRecordType(stockUninventoryDto.getRecordType());
+        stockOutRecordDto.setStockOutNum(stockUninventoryDto.getQualitity());
+        stockOutRecordDto.setBatchNo(stockUninventoryDto.getBatchNo());
+        stockOutRecordDto.setProductModelId(stockUninventoryDto.getProductModelId());
+        stockOutRecordDto.setType("1");
+        stockOutRecordDto.setRemark(stockUninventoryDto.getRemark());
+        stockOutRecordService.add(stockOutRecordDto);
+        return 1;
+    }
+
+    @Override
     public void exportStockUninventory(HttpServletResponse response, StockUninventoryDto stockUninventoryDto) {
         List<StockUnInventoryExportData> list = stockUninventoryMapper.listStockInventoryExportData(stockUninventoryDto);
         ExcelUtil<StockUnInventoryExportData> util = new ExcelUtil<>(StockUnInventoryExportData.class);
diff --git a/src/main/resources/mapper/stock/StockInventoryMapper.xml b/src/main/resources/mapper/stock/StockInventoryMapper.xml
index a7dab00..6e8a3bc 100644
--- a/src/main/resources/mapper/stock/StockInventoryMapper.xml
+++ b/src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -106,8 +106,10 @@
             SUM(unQualifiedQuantity) as unQualifiedQuantity,
             SUM(qualifiedLockedQuantity) as qualifiedLockedQuantity,
             SUM(unQualifiedLockedQuantity) as unQualifiedLockedQuantity,
-            SUM(qualifiedQuantity - qualifiedLockedQuantity) as qualifiedUnLockedQuantity,
-            SUM(unQualifiedQuantity - unQualifiedLockedQuantity) as unQualifiedUnLockedQuantity,
+            SUM(qualifiedQuantity - qualifiedLockedQuantity - IFNULL(qualifiedPendingOut, 0)) as qualifiedUnLockedQuantity,
+            SUM(unQualifiedQuantity - unQualifiedLockedQuantity - IFNULL(unQualifiedPendingOut, 0)) as unQualifiedUnLockedQuantity,
+            SUM(IFNULL(qualifiedPendingOut, 0)) as qualifiedPendingOutQuantity,
+            SUM(IFNULL(unQualifiedPendingOut, 0)) as unQualifiedPendingOutQuantity,
             product_model_id,
             MAX(create_time) as create_time,
             MAX(update_time) as update_time,
@@ -139,7 +141,16 @@
             si.remark,
             pm.unit,
             p.product_name,
-            p.id as product_id
+            p.id as product_id,
+            (
+                select IFNULL(SUM(sor.stock_out_num), 0)
+                from stock_out_record sor
+                where sor.product_model_id = si.product_model_id
+                  and (si.batch_no is null and sor.batch_no is null or si.batch_no = sor.batch_no)
+                  and sor.type = '0'
+                  and sor.approval_status = 0
+            ) as qualifiedPendingOut,
+            0 as unqualifiedPendingOut
             from stock_inventory si
             left join product_model pm on si.product_model_id = pm.id
             left join product p on pm.product_id = p.id
@@ -165,7 +176,16 @@
             su.remark,
             pm.unit,
             p.product_name,
-            p.id as product_id
+            p.id as product_id,
+            0 as qualifiedPendingOut,
+            (
+                select IFNULL(SUM(sor.stock_out_num), 0)
+                from stock_out_record sor
+                where sor.product_model_id = su.product_model_id
+                  and (su.batch_no is null and sor.batch_no is null or su.batch_no = sor.batch_no)
+                  and sor.type = '1'
+                  and sor.approval_status = 0
+            ) as unqualifiedPendingOut
             from stock_uninventory su
             left join product_model pm on su.product_model_id = pm.id
             left join product p on pm.product_id = p.id
@@ -410,4 +430,13 @@
         ORDER BY DATE(sor.create_time) ASC
     </select>
 
+    <select id="selectPendingOutQuantity" resultType="java.math.BigDecimal">
+        SELECT IFNULL(SUM(sor.stock_out_num), 0)
+        FROM stock_out_record sor
+        WHERE sor.product_model_id = #{productModelId}
+          AND (sor.batch_no = #{batchNo} OR (#{batchNo} IS NULL AND sor.batch_no IS NULL))
+          AND sor.type = #{type}
+          AND sor.approval_status = 0
+    </select>
+
 </mapper>
diff --git a/src/main/resources/mapper/stock/StockUninventoryMapper.xml b/src/main/resources/mapper/stock/StockUninventoryMapper.xml
index 70943e7..582542c 100644
--- a/src/main/resources/mapper/stock/StockUninventoryMapper.xml
+++ b/src/main/resources/mapper/stock/StockUninventoryMapper.xml
@@ -95,4 +95,13 @@
         </if>
     </select>
 
+    <select id="selectPendingOutQuantity" resultType="java.math.BigDecimal">
+        SELECT IFNULL(SUM(sor.stock_out_num), 0)
+        FROM stock_out_record sor
+        WHERE sor.product_model_id = #{productModelId}
+          AND (sor.batch_no = #{batchNo} OR (#{batchNo} IS NULL AND sor.batch_no IS NULL))
+          AND sor.type = #{type}
+          AND sor.approval_status = 0
+    </select>
+
 </mapper>

--
Gitblit v1.9.3