From 5c5fc86ebb905d5c0d09caef3d80f38b2aba6f18 Mon Sep 17 00:00:00 2001
From: 青城 <1662047068@qq.com>
Date: 星期三, 09 七月 2025 09:16:37 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master' into pim-qiao

---
 src/main/java/com/ruoyi/framework/security/LoginUser.java                                   |    5 
 src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java                |   91 ++
 src/main/java/com/ruoyi/common/config/MinioConfig.java                                      |   27 
 src/main/java/com/ruoyi/common/utils/MinioUtils.java                                        |  309 +++++++
 src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordOutMapper.java            |   21 
 src/main/resources/mapper/purchase/ProductRecordMapper.xml                                  |   23 
 src/main/java/com/ruoyi/procurementrecord/dto/ProcurementDto.java                           |   89 ++
 src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecordOut.java                    |   66 +
 src/main/java/com/ruoyi/basic/pojo/StorageBlob.java                                         |   67 +
 pom.xml                                                                                     |   24 
 src/main/java/com/ruoyi/other/controller/TempFileController.java                            |   19 
 src/main/java/com/ruoyi/purchase/dto/ProductRecordDto.java                                  |    3 
 src/main/resources/mapper/procurementrecord/ProcurementRecordOutMapper.xml                  |   47 +
 src/main/java/com/ruoyi/common/constant/StorageAttachmentConstants.java                     |   17 
 src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordController.java       |   83 ++
 src/main/java/com/ruoyi/purchase/service/IProductRecordService.java                         |    5 
 src/main/resources/mapper/basic/StorageAttachmentMapper.xml                                 |   22 
 src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutAdd.java                  |   22 
 src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordOutService.java          |   26 
 src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDto.java                       |  107 ++
 doc/add.sql                                                                                 |   50 +
 src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutPageDto.java              |   87 ++
 src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java                 |   38 
 src/main/java/com/ruoyi/basic/mapper/StorageAttachmentMapper.java                           |   18 
 src/main/java/com/ruoyi/basic/service/StorageBlobService.java                               |   35 
 src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java                      |  103 ++
 src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordOutController.java    |   60 +
 src/main/java/com/ruoyi/basic/pojo/StorageAttachment.java                                   |   72 +
 src/main/java/com/ruoyi/purchase/mapper/ProductRecordMapper.java                            |    2 
 src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java    |  198 +++++
 src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java               |   17 
 src/main/resources/mapper/basic/StorageBlobMapper.xml                                       |   22 
 src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java                       |   25 
 src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java            |   29 
 src/main/java/com/ruoyi/framework/web/domain/MinioResult.java                               |   16 
 src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java                                       |    9 
 src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java                                 |   18 
 src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java                         |   43 +
 src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.java             |   31 
 src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordOutServiceImpl.java |   92 ++
 src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordMapper.java               |   30 
 src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecord.java                       |   60 +
 src/main/resources/mapper/procurementrecord/ProcurementRecordMapper.xml                     |   79 ++
 src/main/java/com/ruoyi/procurementrecord/dto/ProcurementAddDto.java                        |   18 
 src/main/java/com/ruoyi/procurementrecord/dto/ProcurementUpdateDto.java                     |   21 
 src/main/resources/application.yml                                                          |   14 
 src/main/java/com/ruoyi/procurementrecord/dto/Details.java                                  |   24 
 47 files changed, 2,268 insertions(+), 16 deletions(-)

diff --git a/doc/add.sql b/doc/add.sql
index 35b95ae..8902911 100644
--- a/doc/add.sql
+++ b/doc/add.sql
@@ -81,4 +81,52 @@
 
 ALTER TABLE `product-inventory-management`.`device_maintenance`
     ADD COLUMN `device_name` varchar(255) NULL AFTER `tenant_id`,
-    ADD COLUMN `device_model` varchar(255) NULL AFTER `device_name`;
\ No newline at end of file
+    ADD COLUMN `device_model` varchar(255) NULL AFTER `device_name`;
+
+
+
+DROP TABLE IF EXISTS storage_attachment;
+CREATE TABLE storage_attachment (
+                                    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+                                    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
+                                    update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
+                                    deleted BIGINT DEFAULT 0 NOT NULL,
+                                    record_type SMALLINT DEFAULT 0 NOT NULL,
+                                    record_id BIGINT DEFAULT 0 NOT NULL,
+                                    name VARCHAR(100) DEFAULT '' NOT NULL,
+                                    storage_blob_id BIGINT DEFAULT 0 NOT NULL
+) COMMENT='閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�';
+
+ALTER TABLE storage_attachment
+    COMMENT '閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�';
+
+ALTER TABLE storage_attachment MODIFY record_type SMALLINT COMMENT '鍏宠仈鐨勮褰曠被鍨�';
+ALTER TABLE storage_attachment MODIFY record_id BIGINT COMMENT '鍏宠仈鐨勮褰昳d';
+ALTER TABLE storage_attachment MODIFY name VARCHAR(100) COMMENT '鍚嶇О, 濡�: file, avatar (鍖哄垎鍚屼竴鏉¤褰曚笉鍚岀被鍨嬬殑闄勪欢)';
+ALTER TABLE storage_attachment MODIFY storage_blob_id BIGINT COMMENT '鍏宠仈storage_blob璁板綍id';
+
+CREATE INDEX idx_storage_attachment_on_record
+    ON storage_attachment (record_type, record_id);
+
+
+CREATE TABLE storage_blob (
+                              id BIGINT AUTO_INCREMENT PRIMARY KEY,
+                              create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
+                              key_name VARCHAR(150) DEFAULT '' NOT NULL,
+                              content_type VARCHAR(100) DEFAULT '' NOT NULL,
+                              original_filename VARCHAR(255) DEFAULT '' NOT NULL,
+                              bucket_filename VARCHAR(255) DEFAULT '' NOT NULL,
+                              bucket_name VARCHAR(255) DEFAULT '' NOT NULL,
+                              byte_size BIGINT DEFAULT 0 NOT NULL,
+                              UNIQUE (key_name)
+) COMMENT='閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�';
+
+ALTER TABLE storage_blob
+    COMMENT '閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�';
+
+ALTER TABLE storage_blob MODIFY key_name VARCHAR(150) COMMENT '璧勬簮id';
+ALTER TABLE storage_blob MODIFY content_type VARCHAR(100) COMMENT '璧勬簮绫诲瀷锛屼緥濡侸PG鍥剧墖鐨勮祫婧愮被鍨嬩负image/jpg';
+ALTER TABLE storage_blob MODIFY original_filename VARCHAR(255) COMMENT '鍘熸枃浠跺悕绉�';
+ALTER TABLE storage_blob MODIFY bucket_filename VARCHAR(255) COMMENT '瀛樺偍妗朵腑鏂囦欢鍚�';
+ALTER TABLE storage_blob MODIFY bucket_name VARCHAR(255) COMMENT '瀛樺偍妗跺悕';
+ALTER TABLE storage_blob MODIFY byte_size BIGINT COMMENT '璧勬簮灏哄(瀛楄妭)';
diff --git a/pom.xml b/pom.xml
index ae85289..14851a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,8 @@
         <velocity.version>2.3</velocity.version>
         <!-- override dependency version -->
         <tomcat.version>9.0.102</tomcat.version>
+        <minio.version>8.4.3</minio.version>
+        <okhttp.version>4.9.0</okhttp.version>
         <logback.version>1.2.13</logback.version>
         <spring-security.version>5.7.12</spring-security.version>
         <spring-framework.version>5.3.39</spring-framework.version>
@@ -252,6 +254,28 @@
             <artifactId>lombok</artifactId>
         </dependency>
 
+
+
+        <!-- minio -->
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>${minio.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.squareup.okhttp3</groupId>
+                    <artifactId>okhttp</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+        <!-- minio渚濊禆okhttp 涓嶇劧鎶ラ敊 -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>${okhttp.version}</version>
+        </dependency>
+
     </dependencies>
 
 	<build>
diff --git a/src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java b/src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java
new file mode 100644
index 0000000..b868048
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java
@@ -0,0 +1,9 @@
+package com.ruoyi.basic.dto;
+
+import com.ruoyi.basic.pojo.StorageBlob;
+import lombok.Data;
+
+@Data
+public class StorageBlobDTO extends StorageBlob {
+    private String url;
+}
diff --git a/src/main/java/com/ruoyi/basic/mapper/StorageAttachmentMapper.java b/src/main/java/com/ruoyi/basic/mapper/StorageAttachmentMapper.java
new file mode 100644
index 0000000..12a34d8
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/mapper/StorageAttachmentMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.basic.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.basic.pojo.StorageAttachment;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+@Mapper
+public interface StorageAttachmentMapper extends BaseMapper<StorageAttachment> {
+
+}
diff --git a/src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java b/src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java
new file mode 100644
index 0000000..756b4b9
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.basic.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.basic.pojo.StorageBlob;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+@Mapper
+public interface StorageBlobMapper extends BaseMapper<StorageBlob> {
+
+}
diff --git a/src/main/java/com/ruoyi/basic/pojo/StorageAttachment.java b/src/main/java/com/ruoyi/basic/pojo/StorageAttachment.java
new file mode 100644
index 0000000..0e30cad
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/pojo/StorageAttachment.java
@@ -0,0 +1,72 @@
+package com.ruoyi.basic.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 瀹炰綋绫�
+ *
+ * @author ruoyi
+ * @date 2025-05-29
+ */
+@Data
+@TableName("storage_attachment")
+public class StorageAttachment implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /** 鍒涘缓鏃堕棿 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 鏇存柊鏃堕棿 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    /**
+     * 閫昏緫鍒犻櫎
+     */
+    @TableField(value = "deleted")
+    private Long deleted;
+    /**
+     * 鍏宠仈鐨勮褰曠被鍨�
+     */
+    @TableField(value = "record_type")
+    private Long recordType;
+    /**
+     * 鍏宠仈鐨勮褰昳d
+     */
+    @TableField(value = "record_id")
+    private Long recordId;
+    /**
+     * 绫诲瀷鍚嶇О, 濡�: file, avatar (鍖哄垎鍚屼竴鏉¤褰曚笉鍚岀被鍨嬬殑闄勪欢)
+     */
+    @TableField(value = "name")
+    private String name;
+    /**
+     * 鍏宠仈storage_blob璁板綍id
+     */
+    @TableField(value = "storage_blob_id")
+    private Long storageBlobId;
+
+    private StorageBlobDTO storageBlobDTO;
+
+    public StorageAttachment(String fileType, Long recordType, Long recordId) {
+        this.name = fileType;
+        this.recordType = recordType;
+        this.recordId = recordId;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/basic/pojo/StorageBlob.java b/src/main/java/com/ruoyi/basic/pojo/StorageBlob.java
new file mode 100644
index 0000000..8e92865
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/pojo/StorageBlob.java
@@ -0,0 +1,67 @@
+package com.ruoyi.basic.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 瀹炰綋绫�
+ *
+ * @author ruoyi
+ * @date 2025-05-29
+ */
+@Data
+@TableName("storage_blob")
+public class StorageBlob implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /** 鍒涘缓鏃堕棿 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+//    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /**
+     * 璧勬簮id
+     */
+    @TableField(value = "key")
+    private String key;
+    /**
+     * 璧勬簮绫诲瀷锛屼緥濡侸PG鍥剧墖鐨勮祫婧愮被鍨嬩负image/jpg
+     */
+    @TableField(value = "content_type")
+    private String contentType;
+    /**
+     * 鍘熸枃浠跺悕
+     */
+    @TableField(value = "original_filename")
+    private String originalFilename;
+
+    /**
+     * 瀛樺偍妗朵腑
+     */
+    @TableField(value = "bucket_filename")
+    private String bucketFilename;
+    /**
+     * 瀛樺偍妗跺悕
+     */
+    @TableField(value = "bucket_name")
+    private String bucketName;
+    /**
+     * 璧勬簮灏哄(瀛楄妭)
+     */
+    @TableField(value = "byte_size")
+    private Long byteSize;
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java b/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
new file mode 100644
index 0000000..e285063
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
@@ -0,0 +1,43 @@
+package com.ruoyi.basic.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.basic.pojo.StorageAttachment;
+import com.ruoyi.common.constant.StorageAttachmentConstants;
+import com.ruoyi.common.enums.StorageAttachmentRecordType;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+public interface StorageAttachmentService extends IService<StorageAttachment> {
+    /**
+     * 鏌ヨ閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param recordId 鍏宠仈璁板綍id
+     * @param recordType 鍏宠仈璁板綍绫诲瀷
+     * @param fileType 鏂囦欢绫诲瀷
+     * @return 鏂囦欢淇℃伅鍒楄〃
+     */
+    List<StorageAttachment> selectStorageAttachments(Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType);
+
+    /**
+     * 淇濆瓨閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param attachments 鏂囦欢淇℃伅鍒楄〃
+     * @param recordId 绠$悊璁板綍id
+     * @param recordType 鍏宠仈璁板綍绫诲瀷
+     * @param fileType 鏂囦欢绫诲瀷
+     */
+    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType);
+
+    /**
+     * 鍒犻櫎閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param storageAttachment 鏂囦欢淇℃伅
+     * @return 鍒犻櫎缁撴灉
+     */
+    public int deleteStorageAttachment(StorageAttachment storageAttachment);
+}
diff --git a/src/main/java/com/ruoyi/basic/service/StorageBlobService.java b/src/main/java/com/ruoyi/basic/service/StorageBlobService.java
new file mode 100644
index 0000000..00d8c3b
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/service/StorageBlobService.java
@@ -0,0 +1,35 @@
+package com.ruoyi.basic.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.pojo.StorageAttachment;
+import com.ruoyi.basic.pojo.StorageBlob;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+public interface StorageBlobService extends IService<StorageBlob> {
+
+    /**
+     * 鏂囦欢涓婁紶鎺ュ彛
+     * @param files 鏂囦欢淇℃伅
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @return 涓婁紶缁撴灉
+     */
+    List<StorageBlobDTO> updateStorageBlobs(List<MultipartFile> files, String bucketName);
+
+    /**
+     * 鎵归噺鍒犻櫎鏂囦欢
+     * @param attachment
+     * @return
+     */
+    public int deleteStorageBlobs(StorageAttachment attachment);
+}
diff --git a/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
new file mode 100644
index 0000000..78c8647
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
@@ -0,0 +1,91 @@
+package com.ruoyi.basic.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.pojo.StorageAttachment;
+import com.ruoyi.basic.mapper.StorageAttachmentMapper;
+import com.ruoyi.basic.mapper.StorageBlobMapper;
+import com.ruoyi.basic.pojo.StorageBlob;
+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.common.utils.MinioUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+@Service
+@RequiredArgsConstructor
+public class StorageAttachmentServiceImpl extends ServiceImpl<StorageAttachmentMapper, StorageAttachment> implements StorageAttachmentService {
+    @Autowired
+    private StorageBlobMapper storageBlobMapper;
+
+    @Autowired
+    private StorageAttachmentMapper storageAttachmentMapper;
+
+    @Autowired
+    private StorageBlobService storageBlobService;
+
+    @Autowired
+    private MinioUtils minioUtils;
+
+    @Override
+    public List<StorageAttachment> selectStorageAttachments(Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType) {
+        List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
+                .eq(StorageAttachment::getRecordId, recordId)
+                .eq(StorageAttachment::getRecordType, recordType.ordinal())
+                .eq(StorageAttachment::getName, fileType.toString()));
+        if (storageAttachments != null) {
+            for (StorageAttachment storageAttachment : storageAttachments) {
+                StorageBlob storageBlob = storageBlobMapper.selectById(storageAttachment.getStorageBlobId());
+                StorageBlobDTO storageBlobDTO = new StorageBlobDTO();
+                BeanUtils.copyProperties(storageBlob, storageBlobDTO);
+                storageBlobDTO.setUrl(minioUtils.getPreviewUrl(storageBlob.getBucketName(), storageBlob.getBucketName(), true));
+                storageAttachment.setStorageBlobDTO(storageBlobDTO);
+            }
+        }
+
+        return storageAttachments;
+    }
+
+    @Override
+    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType) {
+        // 鍒犻櫎鏃у浘
+        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);
+        }
+
+    }
+
+    @Override
+    public int deleteStorageAttachment(StorageAttachment storageAttachment) {
+        // 鍏堝垹闄ゆ槑缁嗚〃
+        storageBlobService.deleteStorageBlobs(storageAttachment);
+
+
+        return storageAttachmentMapper.delete(new LambdaQueryWrapper<StorageAttachment>()
+                .eq(StorageAttachment::getRecordId, storageAttachment.getRecordId())
+                .eq(StorageAttachment::getRecordType, storageAttachment.getRecordType())
+                .eq(StorageAttachment::getName, storageAttachment.getName()));
+    }
+}
diff --git a/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
new file mode 100644
index 0000000..6be4401
--- /dev/null
+++ b/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
@@ -0,0 +1,103 @@
+package com.ruoyi.basic.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+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.StorageBlobService;
+import com.ruoyi.common.exception.file.InvalidExtensionException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.MinioUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.uuid.IdUtils;
+import com.ruoyi.framework.web.domain.MinioResult;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+@Service
+@RequiredArgsConstructor
+public class StorageBlobServiceImpl extends ServiceImpl<StorageBlobMapper, StorageBlob> implements StorageBlobService {
+    @Autowired
+    private StorageAttachmentMapper storageAttachmentMapper;
+
+    @Autowired
+    private StorageBlobMapper storageBlobMapper;
+
+    @Autowired
+    private MinioUtils minioUtils;
+
+    @Override
+    public List<StorageBlobDTO> updateStorageBlobs(List<MultipartFile> files, String bucketName) {
+
+        // 鑻ユ病浼犲叆bucketName锛屽垯浣跨敤榛樿bucketName
+        if (StringUtils.isEmpty(bucketName)) {
+            bucketName  = minioUtils.getDefaultBucket();
+        }
+
+        List<StorageBlobDTO> storageBlobDTOs = new ArrayList<>();
+        for (MultipartFile file : files) {
+            try {
+                MinioResult res = minioUtils.upload(bucketName, file, false);
+                StorageBlobDTO dto = new StorageBlobDTO();
+                dto.setContentType(file.getContentType());
+                dto.setBucketFilename(res.getBucketFileName());
+                dto.setOriginalFilename(res.getOriginalName());
+                dto.setByteSize(file.getSize());
+                dto.setKey(IdUtils.simpleUUID());
+                dto.setBucketName(bucketName);
+                dto.setCreateTime(DateUtils.getNowDate());
+                dto.setUrl(minioUtils.getPreviewUrl(res.getBucketFileName(), bucketName, false));
+                // 鎻掑叆鏁版嵁搴�
+                storageBlobMapper.insert(dto);
+
+                storageBlobDTOs.add(dto);
+            } catch (InvalidExtensionException e) {
+                throw new RuntimeException("minio鏂囦欢涓婁紶寮傚父锛�" +  e);
+            }
+        }
+
+
+        return storageBlobDTOs;
+    }
+
+    @Override
+    public int deleteStorageBlobs(StorageAttachment attachment) {
+        List<StorageAttachment> attachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
+                .eq(StorageAttachment::getRecordId, attachment.getRecordId())
+                .eq(StorageAttachment::getRecordType, attachment.getRecordType())
+                .eq(StorageAttachment::getName, attachment.getName()));
+        List<Long> ids = attachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
+        List<StorageBlob> storageBlobs = storageBlobMapper.selectList(new LambdaQueryWrapper<StorageBlob>()
+                .in(StorageBlob::getId, ids));
+        if (!storageBlobs.isEmpty()) {
+            for (StorageBlob storageBlob : storageBlobs) {
+                // 绉婚櫎妗跺唴鏂囦欢
+                minioUtils.removeObjectsResult(storageBlob.getBucketName(), storageBlob.getBucketName());
+            }
+        }
+
+        if (!ids.isEmpty()) {
+            return storageBlobMapper.delete(new QueryWrapper<StorageBlob>().lambda().in(StorageBlob::getId, ids));
+        }
+
+        return 0;
+    }
+}
diff --git a/src/main/java/com/ruoyi/common/config/MinioConfig.java b/src/main/java/com/ruoyi/common/config/MinioConfig.java
new file mode 100644
index 0000000..28f489f
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/config/MinioConfig.java
@@ -0,0 +1,27 @@
+package com.ruoyi.common.config;
+
+import io.minio.MinioClient;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+@Configuration
+@Component
+@ConfigurationProperties(prefix = "minio")
+@Data
+public class MinioConfig {
+    private String endpoint;
+    private int port;
+    private String accessKey;
+    private String secretKey;
+    private Boolean secure;
+
+    @Bean
+    public MinioClient getMinioClient() {
+        return MinioClient.builder().endpoint(endpoint, port, secure)
+                .credentials(accessKey, secretKey)
+                .build();
+    }
+}
diff --git a/src/main/java/com/ruoyi/common/constant/StorageAttachmentConstants.java b/src/main/java/com/ruoyi/common/constant/StorageAttachmentConstants.java
new file mode 100644
index 0000000..1bc8123
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/constant/StorageAttachmentConstants.java
@@ -0,0 +1,17 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 闄勪欢甯搁噺
+ */
+public class StorageAttachmentConstants {
+
+    /**
+     * 鏂囦欢
+     */
+    public static final String StorageAttachmentFile = "file";
+
+    /**
+     * 鍥剧墖
+     */
+    public static final String StorageAttachmentImage = "image";
+}
diff --git a/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java b/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java
new file mode 100644
index 0000000..8b0597e
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java
@@ -0,0 +1,25 @@
+package com.ruoyi.common.enums;
+
+import lombok.AllArgsConstructor;
+
+/**
+ * 闄勪欢璁板綍绫诲瀷鏋氫妇
+ *
+ */
+@AllArgsConstructor
+public enum StorageAttachmentRecordType {
+    // 渚嬪瓙 瀹為檯寮�鍙戣鍒犻櫎
+    Template("Template","鑼冧緥");
+
+
+    private final String code;
+    private final String info;
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+}
diff --git a/src/main/java/com/ruoyi/common/utils/MinioUtils.java b/src/main/java/com/ruoyi/common/utils/MinioUtils.java
new file mode 100644
index 0000000..55af1dd
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/utils/MinioUtils.java
@@ -0,0 +1,309 @@
+package com.ruoyi.common.utils;
+
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.ruoyi.common.exception.UtilException;
+import com.ruoyi.common.exception.file.InvalidExtensionException;
+import com.ruoyi.common.utils.file.FileUploadUtils;
+import com.ruoyi.common.utils.file.MimeTypeUtils;
+import com.ruoyi.framework.web.domain.MinioResult;
+import io.minio.*;
+import io.minio.http.Method;
+import io.minio.messages.DeleteError;
+import io.minio.messages.DeleteObject;
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.FastByteArrayOutputStream;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Component
+public class MinioUtils {
+
+    @Autowired
+    private MinioClient minioClient;
+
+    @Value("${minio.preview-expiry}")
+    private Integer previewExpiry;
+
+    /**
+     * -- GETTER --
+     *  鑾峰彇榛樿瀛樺偍妗跺悕绉�
+     *
+     * @return
+     */
+    @Getter
+    @Value("${minio.default-bucket}")
+    private String defaultBucket;
+
+    /**
+     * 鍒ゆ柇瀛樺偍妗舵槸鍚﹀瓨鍦紝涓嶅瓨鍦ㄥ垯鍒涘缓
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     */
+    public void existBucket(String bucketName) {
+        try {
+            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
+            if (!exists) {
+                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 鍒涘缓瀛樺偍妗�
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @return 鏄惁鍒涘缓鎴愬姛
+     */
+    public Boolean makeBucket(String bucketName) {
+        try {
+            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 鍒犻櫎瀛樺偍妗�
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    public Boolean removeBucket(String bucketName) {
+        try {
+            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 鍒ゆ柇瀵硅薄鏄惁瀛樺湪
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param originalFileName MinIO涓瓨鍌ㄥ璞″叏璺緞
+     * @return 瀵硅薄鏄惁瀛樺湪
+     */
+    public boolean existObject(String bucketName, String originalFileName) {
+        try {
+            minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(originalFileName).build());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 鏂囦欢涓婁紶
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param file       鏂囦欢
+     * @return 妗朵腑浣嶇疆
+     */
+    public MinioResult upload(String bucketName, MultipartFile file, Boolean isPreviewExpiry) throws InvalidExtensionException {
+        MultipartFile[] fileArr = {file};
+        List<MinioResult> fileNames = upload(bucketName, fileArr, isPreviewExpiry);
+        return fileNames.isEmpty() ? null : fileNames.get(0);
+    }
+
+    /**
+     * 涓婁紶鏂囦欢
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param fileList   鏂囦欢鍒楄〃
+     * @return 妗朵腑浣嶇疆鍒楄〃
+     */
+    public List<MinioResult> upload(String bucketName, List<MultipartFile> fileList, Boolean isPreviewExpiry) throws InvalidExtensionException {
+        MultipartFile[] fileArr = fileList.toArray(new MultipartFile[0]);
+        return upload(bucketName, fileArr, isPreviewExpiry);
+    }
+
+    /**
+     * description: 涓婁紶鏂囦欢
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param fileArr    鏂囦欢鍒楄〃
+     * @return 妗朵腑浣嶇疆鍒楄〃
+     */
+    public List<MinioResult> upload(String bucketName, MultipartFile[] fileArr, Boolean isPreviewExpiry) throws InvalidExtensionException {
+        for (MultipartFile file : fileArr) {
+            FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        }
+        // 淇濊瘉妗朵竴瀹氬瓨鍦�
+        existBucket(bucketName);
+        // 鎵ц姝e父鎿嶄綔
+        List<MinioResult> bucketFileNames = new ArrayList<>(fileArr.length);
+        for (MultipartFile file : fileArr) {
+            // 鑾峰彇鍘熷鏂囦欢鍚嶇О
+            String originalFileName = file.getOriginalFilename();
+            // 鑾峰彇褰撳墠鏃ユ湡锛屾牸寮忎緥濡傦細2020-11
+            String datePath = new SimpleDateFormat("yyyy-MM").format(new Date());
+            // 鏂囦欢鍚嶇О
+            String uuid = IdWorker.get32UUID();
+            // 鑾峰彇鏂囦欢鍚庣紑
+            String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
+            String bucketFilePath = datePath + "/" + uuid + suffix;
+
+            // 鎺ㄩ�佹枃浠跺埌MinIO
+            try (InputStream in = file.getInputStream()) {
+                minioClient.putObject(PutObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(bucketFilePath)
+                        .stream(in, in.available(), -1)
+                        .contentType(file.getContentType())
+                        .build()
+                );
+            } catch (Exception e) {
+                throw new UtilException("MinioUtils锛氫笂浼犳枃浠跺伐鍏风被寮傚父:" + e);
+            }
+            MinioResult minioResult = new MinioResult();
+            minioResult.setBucketFileName(bucketFilePath);
+            // 杩斿洖姘镐箙棰勮鍦板潃
+            if (isPreviewExpiry) {
+                String previewUrl = getPreviewUrl(bucketFilePath, bucketName, isPreviewExpiry);
+                minioResult.setPreviewExpiry(previewUrl);
+            }
+            minioResult.setOriginalName(originalFileName);
+            bucketFileNames.add(minioResult);
+        }
+        return bucketFileNames;
+    }
+
+    /**
+     * 鏂囦欢涓嬭浇
+     *
+     * @param bucketName       瀛樺偍妗跺悕绉�
+     * @param bucketFileName   妗朵腑鏂囦欢鍚嶇О
+     * @param originalFileName 鍘熷鏂囦欢鍚嶇О
+     * @param response         response瀵硅薄
+     */
+    public void download(String bucketName, String bucketFileName, String originalFileName, HttpServletResponse response) {
+        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(bucketFileName).build();
+        try (GetObjectResponse objResponse = minioClient.getObject(objectArgs)) {
+            byte[] buf = new byte[1024];
+            int len;
+            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
+                while ((len = objResponse.read(buf)) != -1) {
+                    os.write(buf, 0, len);
+                }
+                os.flush();
+                byte[] bytes = os.toByteArray();
+                response.setCharacterEncoding("utf-8");
+                //璁剧疆寮哄埗涓嬭浇涓嶆墦寮�
+                response.setContentType("application/force-download");
+                // 璁剧疆闄勪欢鍚嶇О缂栫爜
+                originalFileName = new String(originalFileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
+                // 璁剧疆闄勪欢鍚嶇О
+                response.addHeader("Content-Disposition", "attachment;fileName=" + originalFileName);
+                // 鍐欏叆鏂囦欢
+                try (ServletOutputStream stream = response.getOutputStream()) {
+                    stream.write(bytes);
+                    stream.flush();
+                }
+            }
+        } catch (Exception e) {
+            throw new UtilException("MinioUtils锛氫笂浼犳枃浠跺伐鍏风被寮傚父");
+        }
+    }
+
+    /**
+     * 鑾峰彇宸蹭笂浼犲璞$殑鏂囦欢娴侊紙鍚庣鍥犱负涓氬姟闇�瑕佽幏鍙栨枃浠舵祦鍙互璋冪敤璇ユ柟娉曪級
+     *
+     * @param bucketName     瀛樺偍妗跺悕绉�
+     * @param bucketFileName 妗朵腑鏂囦欢鍚嶇О
+     * @return 鏂囦欢娴�
+     */
+    public InputStream getFileStream(String bucketName, String bucketFileName) throws Exception {
+        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(bucketFileName).build();
+        return minioClient.getObject(objectArgs);
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎鏂囦欢瀵硅薄缁撴灉
+     *
+     * @param bucketName      瀛樺偍妗跺悕绉�
+     * @param bucketFileName 妗朵腑鏂囦欢鍚嶇О
+     * @return 鍒犻櫎缁撴灉
+     */
+    public DeleteError removeObjectsResult(String bucketName, String bucketFileName) {
+        List<DeleteError> results = removeObjectsResult(bucketName, Collections.singletonList(bucketFileName));
+        return !results.isEmpty() ? results.get(0) : null;
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎鏂囦欢瀵硅薄缁撴灉
+     *
+     * @param bucketName      瀛樺偍妗跺悕绉�
+     * @param bucketFileNames 妗朵腑鏂囦欢鍚嶇О闆嗗悎
+     * @return 鍒犻櫎缁撴灉
+     */
+    public List<DeleteError> removeObjectsResult(String bucketName, List<String> bucketFileNames) {
+        Iterable<Result<DeleteError>> results = removeObjects(bucketName, bucketFileNames);
+        List<DeleteError> res = new ArrayList<>();
+        for (Result<DeleteError> result : results) {
+            try {
+                res.add(result.get());
+            } catch (Exception e) {
+                throw new UtilException("MinioUtils锛氫笂浼犳枃浠跺伐鍏风被寮傚父");
+            }
+        }
+        return res;
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎鏂囦欢瀵硅薄
+     *
+     * @param bucketName      瀛樺偍妗跺悕绉�
+     * @param bucketFileNames 妗朵腑鏂囦欢鍚嶇О闆嗗悎
+     */
+    private Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> bucketFileNames) {
+        List<DeleteObject> dos = bucketFileNames.stream().map(DeleteObject::new).collect(Collectors.toList());
+        return minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(dos).build());
+    }
+
+    /**
+     * 鏌ヨ棰勮url
+     * @param bucketFileName minio鏂囦欢鍚嶇О
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param isPreviewExpiry 鏄惁闇�瑕佽繃鏈熸椂闂� 榛樿24灏忔椂
+     * @return
+     */
+    public String getPreviewUrl(String bucketFileName, String bucketName, Boolean isPreviewExpiry) {
+        if (StringUtils.isNotBlank(bucketFileName)) {
+            try {
+                minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(bucketFileName).build());
+                // 涓篺alse鍙敓鎴�24灏忔椂鏈夋晥鏃堕暱鐨剈rl閾炬帴锛屽彲浠ヨ闂鏂囦欢
+                if (isPreviewExpiry){
+                    return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(bucketFileName).build());
+                }else {
+                    return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(bucketFileName).expiry(previewExpiry, TimeUnit.HOURS).build());
+                }
+            } catch (Exception e) {
+                throw new UtilException("MinioUtils锛氫笂浼犳枃浠跺伐鍏风被寮傚父");
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/framework/security/LoginUser.java b/src/main/java/com/ruoyi/framework/security/LoginUser.java
index 3b020b0..bb91be1 100644
--- a/src/main/java/com/ruoyi/framework/security/LoginUser.java
+++ b/src/main/java/com/ruoyi/framework/security/LoginUser.java
@@ -161,6 +161,11 @@
         return user.getUserName();
     }
 
+    public String getNickName()
+    {
+        return user.getNickName();
+    }
+
     /**
      * 璐︽埛鏄惁鏈繃鏈�,杩囨湡鏃犳硶楠岃瘉
      */
diff --git a/src/main/java/com/ruoyi/framework/web/domain/MinioResult.java b/src/main/java/com/ruoyi/framework/web/domain/MinioResult.java
new file mode 100644
index 0000000..8542ec8
--- /dev/null
+++ b/src/main/java/com/ruoyi/framework/web/domain/MinioResult.java
@@ -0,0 +1,16 @@
+package com.ruoyi.framework.web.domain;
+
+
+import lombok.Data;
+
+@Data
+public class MinioResult {
+    // minio涓殑鏂囦欢鍚嶇О
+    private String bucketFileName;
+
+    // 婧愭枃浠跺悕绉�
+    private String originalName;
+
+    // 棰勮璺緞
+    private String previewExpiry;
+}
diff --git a/src/main/java/com/ruoyi/other/controller/TempFileController.java b/src/main/java/com/ruoyi/other/controller/TempFileController.java
index 1ed7875..810c532 100644
--- a/src/main/java/com/ruoyi/other/controller/TempFileController.java
+++ b/src/main/java/com/ruoyi/other/controller/TempFileController.java
@@ -3,8 +3,13 @@
 
 import com.ruoyi.framework.web.domain.AjaxResult;
 import com.ruoyi.other.service.TempFileService;
+import com.ruoyi.purchase.dto.ProductRecordDto;
+import com.ruoyi.purchase.dto.TicketRegistrationDto;
+import com.ruoyi.purchase.service.ITicketRegistrationService;
+import com.ruoyi.purchase.service.impl.TicketRegistrationServiceImpl;
 import lombok.AllArgsConstructor;
 import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.multipart.MultipartFile;
@@ -17,6 +22,8 @@
 
     private TempFileService tempFileService;
 
+    private TicketRegistrationServiceImpl ticketRegistrationServiceImpl;
+
     @PostMapping("/upload")
     public AjaxResult uploadFile(MultipartFile file, Integer type) {
         try {
@@ -26,4 +33,16 @@
         }
     }
 
+    @PostMapping("uploadFile")
+    public AjaxResult uploadFile(@RequestBody ProductRecordDto productRecordDto) {
+        try {
+            if (!productRecordDto.getTempFileIds().isEmpty()&&productRecordDto.getTicketRegistrationId() != null) {
+                ticketRegistrationServiceImpl.migrateTempFilesToFormal(productRecordDto.getTicketRegistrationId(), productRecordDto.getTempFileIds());
+            }
+        } catch (Exception e) {
+            return AjaxResult.error(e.getMessage());
+        }
+        return AjaxResult.success();
+    }
+
 }
diff --git a/src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordController.java b/src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordController.java
new file mode 100644
index 0000000..6778afc
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordController.java
@@ -0,0 +1,83 @@
+package com.ruoyi.procurementrecord.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+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.page.TableDataInfo;
+import com.ruoyi.procurementrecord.dto.ProcurementAddDto;
+import com.ruoyi.procurementrecord.dto.ProcurementDto;
+import com.ruoyi.procurementrecord.dto.ProcurementPageDto;
+import com.ruoyi.procurementrecord.dto.ProcurementUpdateDto;
+import com.ruoyi.procurementrecord.service.ProcurementRecordService;
+import com.ruoyi.purchase.dto.InvoicePurchaseReportDto;
+import com.ruoyi.quality.pojo.QualityInspect;
+import io.swagger.annotations.Api;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * @author :yys
+ * @date : 2025/7/7 14:32
+ */
+@RestController
+@Api(tags = "閲囪喘鍏ュ簱")
+@RequestMapping("/stockin")
+public class ProcurementRecordController extends BaseController {
+
+
+    @Autowired
+    private ProcurementRecordService procurementRecordService;
+
+    @GetMapping("/productlist")
+    @Log(title = "閲囪喘鍏ュ簱-鍏ュ簱绠$悊-鏂板鍏ュ簱鏌ヨ", businessType = BusinessType.OTHER)
+    public AjaxResult list(ProcurementDto procurementDto) {
+        List<ProcurementDto> result =procurementRecordService.listProcurementBySalesLedgerId(procurementDto);
+        return AjaxResult.success(result);
+    }
+
+    @PostMapping("/add")
+    @Log(title = "閲囪喘鍏ュ簱-鍏ュ簱绠$悊-鏂板鍏ュ簱", businessType = BusinessType.INSERT)
+    @Transactional
+    public AjaxResult add(@RequestBody ProcurementAddDto procurementDto) {
+        return AjaxResult.success(procurementRecordService.add(procurementDto));
+    }
+
+    @PostMapping("/update")
+    @Log(title = "閲囪喘鍏ュ簱-鍏ュ簱绠$悊-淇敼鍏ュ簱", businessType = BusinessType.UPDATE)
+    @Transactional
+    public AjaxResult updatePro(@RequestBody ProcurementUpdateDto procurementDto) {
+        return AjaxResult.success(procurementRecordService.updatePro(procurementDto));
+    }
+
+    @PostMapping("/del")
+    @Log(title = "閲囪喘鍏ュ簱-鍏ュ簱绠$悊-鍒犻櫎鍏ュ簱", businessType = BusinessType.DELETE)
+    @Transactional
+    public AjaxResult deletePro(@RequestBody ProcurementUpdateDto procurementDto) {
+        return AjaxResult.success(procurementRecordService.deletePro(procurementDto));
+    }
+
+    @GetMapping("/listPage")
+    @Log(title = "閲囪喘鍏ュ簱-鍏ュ簱绠$悊-鍏ュ簱鏌ヨ", businessType = BusinessType.OTHER)
+    public AjaxResult listPage(Page page, ProcurementPageDto procurementDto) {
+        IPage<ProcurementPageDto> result =procurementRecordService.listPage(page, procurementDto);
+        return AjaxResult.success(result);
+    }
+
+    /**
+     * 瀵煎嚭
+     * @param response
+     */
+    @PostMapping("/export")
+    public void export(HttpServletResponse response) {
+        procurementRecordService.export(response);
+    }
+
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordOutController.java b/src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordOutController.java
new file mode 100644
index 0000000..1a656be
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordOutController.java
@@ -0,0 +1,60 @@
+package com.ruoyi.procurementrecord.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+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.procurementrecord.dto.ProcurementPageDto;
+import com.ruoyi.procurementrecord.dto.ProcurementRecordOutAdd;
+import com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto;
+import com.ruoyi.procurementrecord.dto.ProcurementUpdateDto;
+import com.ruoyi.procurementrecord.service.ProcurementRecordOutService;
+import io.swagger.annotations.Api;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author :yys
+ * @date : 2025/7/8 13:30
+ */
+@RestController
+@Api(tags = "閲囪喘鍑哄簱")
+@RequestMapping("/stockmanagement")
+public class ProcurementRecordOutController extends BaseController {
+
+    @Autowired
+    private ProcurementRecordOutService procurementRecordOutService;
+
+    @PostMapping("/stockout")
+    @Log(title = "閲囪喘鍏ュ簱-鍑哄簱绠$悊-鍑哄簱", businessType = BusinessType.INSERT)
+    public AjaxResult stockout(@RequestBody ProcurementRecordOutAdd procurementRecordOutAdd) {
+        return AjaxResult.success(procurementRecordOutService.stockout(procurementRecordOutAdd));
+    }
+
+    @GetMapping("/listPage")
+    @Log(title = "閲囪喘鍏ュ簱-鍑哄簱绠$悊-鍑哄簱鏌ヨ", businessType = BusinessType.OTHER)
+    public AjaxResult listPage(Page page, ProcurementRecordOutPageDto procurementDto) {
+        IPage<ProcurementRecordOutPageDto> result = procurementRecordOutService.listPage(page, procurementDto);
+        return AjaxResult.success(result);
+    }
+
+    @PostMapping("/del")
+    @Log(title = "閲囪喘鍏ュ簱-鍑哄簱绠$悊-鍒犻櫎鍑哄簱", businessType = BusinessType.DELETE)
+    public AjaxResult deletePro(@RequestBody ProcurementUpdateDto procurementDto) {
+        return AjaxResult.success(procurementRecordOutService.deletePro(procurementDto));
+    }
+
+    /**
+     * 瀵煎嚭
+     * @param response
+     */
+    @PostMapping("/export")
+    public void export(HttpServletResponse response) {
+        procurementRecordOutService.export(response);
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/dto/Details.java b/src/main/java/com/ruoyi/procurementrecord/dto/Details.java
new file mode 100644
index 0000000..510b62d
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/dto/Details.java
@@ -0,0 +1,24 @@
+package com.ruoyi.procurementrecord.dto;
+
+import java.math.BigDecimal;
+
+/**
+ * @author :yys
+ * @date : 2025/7/7 16:17
+ */
+public class Details {
+    private Integer id;
+    private BigDecimal inboundQuantity;
+    public Integer getId() {
+        return id;
+    }
+    public void setId(Integer id) {
+        this.id = id;
+    }
+    public BigDecimal getInboundQuantity() {
+        return inboundQuantity;
+    }
+    public void setInboundQuantity(BigDecimal inboundQuantity) {
+        this.inboundQuantity = inboundQuantity;
+    }
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementAddDto.java b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementAddDto.java
new file mode 100644
index 0000000..73d9895
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementAddDto.java
@@ -0,0 +1,18 @@
+package com.ruoyi.procurementrecord.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @author :yys
+ * @date : 2025/7/7 16:07
+ */
+@Data
+public class ProcurementAddDto {
+
+    private List<Details> details;
+
+    private String nickName;
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementDto.java b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementDto.java
new file mode 100644
index 0000000..63ce258
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementDto.java
@@ -0,0 +1,89 @@
+package com.ruoyi.procurementrecord.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * @author :yys
+ * @date : 2025/7/7 14:46
+ */
+@Data
+public class ProcurementDto {
+
+    private String purchaseContractNumber;
+
+    private Integer id;
+
+    private Integer recordId;
+
+    /**
+     * 鍏ュ簱鏁伴噺
+     */
+    private Integer quantityStock;
+
+    /**
+     * 渚涘簲鍟嗗悕绉�
+     */
+    @Excel(name = "渚涘簲鍟嗗悕绉�")
+    private String supplierName;
+
+
+    /**
+     * 浜у搧澶х被
+     */
+    @Excel(name = "浜у搧澶х被")
+    private String productCategory;
+
+    /**
+     * 瑙勬牸鍨嬪彿
+     */
+    @Excel(name = "瑙勬牸鍨嬪彿")
+    private String specificationModel;
+
+    /**
+     * 鍗曚綅
+     */
+    @Excel(name = "鍗曚綅")
+    private String unit;
+
+    /**
+     * 鏁伴噺
+     */
+    @Excel(name = "鏁伴噺")
+    private BigDecimal quantity;
+
+    /**
+     * 寰呭叆搴撴暟閲�
+     */
+    @Excel(name = "寰呭叆搴撴暟閲�")
+    private BigDecimal quantity0;
+
+    /**
+     * 绋庣巼
+     */
+    @Excel(name = "绋庣巼")
+    private BigDecimal taxRate;
+
+    /**
+     * 鍚◣鍗曚环
+     */
+    @Excel(name = "鍚◣鍗曚环")
+    private BigDecimal taxInclusiveUnitPrice;
+
+    /**
+     * 鍚◣鎬讳环
+     */
+    @Excel(name = "鍚◣鎬讳环")
+    private BigDecimal taxInclusiveTotalPrice;
+
+    /**
+     * 涓嶅惈绋庢�讳环
+     */
+    @Excel(name = "涓嶅惈绋庢�讳环")
+    private BigDecimal taxExclusiveTotalPrice;
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDto.java b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDto.java
new file mode 100644
index 0000000..8009463
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDto.java
@@ -0,0 +1,107 @@
+package com.ruoyi.procurementrecord.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * @author :yys
+ * @date : 2025/7/7 17:00
+ */
+@Data
+public class ProcurementPageDto {
+
+    private Integer id;
+
+    /**
+     * 鍏ュ簱鎵规
+     */
+    @Excel(name = "鍏ュ簱鎵规")
+    private String inboundBatches;
+
+    /**
+     * 鍚堝悓鍙�
+     */
+    private String purchaseContractNumber;
+
+    private String salesLedgerProductId;
+
+    /**
+     * 鍑哄叆搴撴暟閲�
+     */
+    @Excel(name = "鍏ュ簱鏁伴噺")
+    private BigDecimal inboundNum;
+
+    /**
+     * 寰呭嚭搴撴暟閲�
+     */
+    @Excel(name = "寰呭嚭搴撴暟閲�")
+    private BigDecimal inboundNum0;
+
+
+    /**
+     * 鍑哄叆搴撴椂闂�
+     */
+    @Excel(name = "鍏ュ簱鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createDate;
+
+    /**
+     * 鍑哄叆搴撶敤鎴�
+     */
+    @Excel(name = "鍏ュ簱浜�")
+    private String createBy;
+
+    /**
+     * 渚涘簲鍟嗗悕绉�
+     */
+    @Excel(name = "渚涘簲鍟嗗悕绉�")
+    private String supplierName;
+
+
+    /**
+     * 浜у搧澶х被
+     */
+    @Excel(name = "浜у搧澶х被")
+    private String productCategory;
+
+    /**
+     * 瑙勬牸鍨嬪彿
+     */
+    @Excel(name = "瑙勬牸鍨嬪彿")
+    private String specificationModel;
+
+    /**
+     * 鍗曚綅
+     */
+    @Excel(name = "鍗曚綅")
+    private String unit;
+
+    /**
+     * 绋庣巼
+     */
+    @Excel(name = "绋庣巼(%)")
+    private BigDecimal taxRate;
+
+    /**
+     * 鍚◣鍗曚环
+     */
+    @Excel(name = "鍚◣鍗曚环")
+    private BigDecimal taxInclusiveUnitPrice;
+
+    /**
+     * 鍚◣鎬讳环
+     */
+    @Excel(name = "鍚◣鎬讳环")
+    private BigDecimal taxInclusiveTotalPrice;
+
+    /**
+     * 涓嶅惈绋庢�讳环
+     */
+    @Excel(name = "涓嶅惈绋庢�讳环")
+    private BigDecimal taxExclusiveTotalPrice;
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutAdd.java b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutAdd.java
new file mode 100644
index 0000000..6c76cdd
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutAdd.java
@@ -0,0 +1,22 @@
+package com.ruoyi.procurementrecord.dto;
+
+import lombok.Data;
+
+/**
+ * @author :yys
+ * @date : 2025/7/8 13:34
+ */
+@Data
+public class ProcurementRecordOutAdd {
+
+    private String quantity;
+
+    private String time;
+
+    private Integer id;
+
+    private Integer userId;
+
+    private Integer salesLedgerProductId;
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutPageDto.java b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutPageDto.java
new file mode 100644
index 0000000..b0d8cad
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementRecordOutPageDto.java
@@ -0,0 +1,87 @@
+package com.ruoyi.procurementrecord.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * @author :yys
+ * @date : 2025/7/8 14:13
+ */
+@Data
+public class ProcurementRecordOutPageDto {
+
+    private Integer id;
+
+    /**
+     * 鍑哄叆搴撴暟閲�
+     */
+    @Excel(name = "鍑哄簱鏁伴噺")
+    private BigDecimal inboundNum;
+
+    /**
+     * 鍑哄叆搴撴椂闂�
+     */
+    @Excel(name = "鍑哄簱鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private LocalDateTime createDate;
+
+    /**
+     * 鍑哄叆搴撶敤鎴�
+     */
+    @Excel(name = "鍑哄簱浜�")
+    private String createBy;
+
+    /**
+     * 渚涘簲鍟嗗悕绉�
+     */
+    @Excel(name = "渚涘簲鍟嗗悕绉�")
+    private String supplierName;
+
+
+    /**
+     * 浜у搧澶х被
+     */
+    @Excel(name = "浜у搧澶х被")
+    private String productCategory;
+
+    /**
+     * 瑙勬牸鍨嬪彿
+     */
+    @Excel(name = "瑙勬牸鍨嬪彿")
+    private String specificationModel;
+
+    /**
+     * 鍗曚綅
+     */
+    @Excel(name = "鍗曚綅")
+    private String unit;
+
+    /**
+     * 绋庣巼
+     */
+    @Excel(name = "绋庣巼(%)")
+    private BigDecimal taxRate;
+
+    /**
+     * 鍚◣鍗曚环
+     */
+    @Excel(name = "鍚◣鍗曚环")
+    private BigDecimal taxInclusiveUnitPrice;
+
+    /**
+     * 鍚◣鎬讳环
+     */
+    @Excel(name = "鍚◣鎬讳环")
+    private BigDecimal taxInclusiveTotalPrice;
+
+    /**
+     * 涓嶅惈绋庢�讳环
+     */
+    @Excel(name = "涓嶅惈绋庢�讳环")
+    private BigDecimal taxExclusiveTotalPrice;
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementUpdateDto.java b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementUpdateDto.java
new file mode 100644
index 0000000..153884e
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/dto/ProcurementUpdateDto.java
@@ -0,0 +1,21 @@
+package com.ruoyi.procurementrecord.dto;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+/**
+ * @author :yys
+ * @date : 2025/7/8 10:40
+ */
+@Data
+public class ProcurementUpdateDto {
+
+    private Integer id;
+
+    private BigDecimal quantityStock;
+
+    private List<Integer> ids;
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordMapper.java b/src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordMapper.java
new file mode 100644
index 0000000..c472256
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordMapper.java
@@ -0,0 +1,30 @@
+package com.ruoyi.procurementrecord.mapper;
+
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.procurementrecord.dto.ProcurementDto;
+import com.ruoyi.procurementrecord.dto.ProcurementPageDto;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecord;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author :yys
+ * @date : 2025/7/7 14:30
+ */
+public interface ProcurementRecordMapper extends BaseMapper<ProcurementRecord> {
+    /**
+     * 鏍规嵁閲囪喘鍙拌处id鏌ヨ閲囪喘鍏ュ簱淇℃伅
+     *
+     * @param procurementDto
+     * @return
+     */
+    List<ProcurementDto> listProcurementBySalesLedgerId(@Param("req") ProcurementDto procurementDto);
+
+    IPage<ProcurementPageDto> listPage(Page page,@Param("req")  ProcurementPageDto procurementDto);
+
+    List<ProcurementPageDto> list();
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordOutMapper.java b/src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordOutMapper.java
new file mode 100644
index 0000000..4a9390b
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementRecordOutMapper.java
@@ -0,0 +1,21 @@
+package com.ruoyi.procurementrecord.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * @author :yys
+ * @date : 2025/7/8 13:26
+ */
+public interface ProcurementRecordOutMapper extends BaseMapper<ProcurementRecordOut> {
+
+    IPage<ProcurementRecordOutPageDto> listPage(Page page,@Param("req") ProcurementRecordOutPageDto procurementDto);
+
+    List<ProcurementRecordOutPageDto> list();
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecord.java b/src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecord.java
new file mode 100644
index 0000000..562e1fe
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecord.java
@@ -0,0 +1,60 @@
+package com.ruoyi.procurementrecord.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Builder;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * @author :yys
+ * @date : 2025/7/7 14:25
+ */
+@TableName("procurement_record_storage")
+@Data
+@Builder
+public class ProcurementRecord {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 浜у搧淇℃伅琛╥d
+     */
+    private Integer salesLedgerProductId;
+
+    /**
+     * 鍏ュ簱鎵规
+     */
+    private String inboundBatches;
+
+    /**
+     * 鍏ュ簱鏁伴噺
+     */
+    private BigDecimal inboundNum;
+
+    /**
+     * 鍏ュ簱鏃堕棿
+     */
+    private LocalDateTime createDate;
+
+    /**
+     * 鍏ュ簱鐢ㄦ埛
+     */
+    private String createBy;
+
+    /**
+     * 鍏ュ簱鐢ㄦ埛id
+     */
+    private Long userId;
+
+    /**
+     * 绉熸埛ID
+     */
+    private Long tenantId;
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecordOut.java b/src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecordOut.java
new file mode 100644
index 0000000..e4761a2
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecordOut.java
@@ -0,0 +1,66 @@
+package com.ruoyi.procurementrecord.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Builder;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * @author :yys
+ * @date : 2025/7/8 13:24
+ */
+@TableName("procurement_record_out")
+@Data
+@Builder
+public class ProcurementRecordOut {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 浜у搧淇℃伅琛╥d
+     */
+    private Integer salesLedgerProductId;
+
+    /**
+     * 鍏ュ簱id
+     */
+    private Integer procurementRecordStorageId;
+
+    /**
+     * 鍑哄簱鎵规
+     */
+    private String inboundBatches;
+
+    /**
+     * 鍑哄嚭搴撴暟閲�
+     */
+    private BigDecimal inboundNum;
+
+    /**
+     * 鍑哄嚭搴撴椂闂�
+     */
+    private LocalDateTime createDate;
+
+    /**
+     * 鍑哄嚭搴撶敤鎴�
+     */
+    private String createBy;
+
+    /**
+     * 鍑哄嚭搴撶敤鎴穒d
+     */
+    private Integer userId;
+
+    /**
+     * 绉熸埛ID
+     */
+    private Long tenantId;
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordOutService.java b/src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordOutService.java
new file mode 100644
index 0000000..785efc4
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordOutService.java
@@ -0,0 +1,26 @@
+package com.ruoyi.procurementrecord.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.procurementrecord.dto.ProcurementRecordOutAdd;
+import com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto;
+import com.ruoyi.procurementrecord.dto.ProcurementUpdateDto;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author :yys
+ * @date : 2025/7/8 13:28
+ */
+public interface ProcurementRecordOutService extends IService<ProcurementRecordOut> {
+
+    int stockout(ProcurementRecordOutAdd procurementRecordOutAdd);
+
+    IPage<ProcurementRecordOutPageDto> listPage(Page page, ProcurementRecordOutPageDto procurementDto);
+
+    int deletePro(ProcurementUpdateDto procurementDto);
+
+    void export(HttpServletResponse response);
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.java b/src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.java
new file mode 100644
index 0000000..d0806c4
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.java
@@ -0,0 +1,31 @@
+package com.ruoyi.procurementrecord.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.procurementrecord.dto.ProcurementAddDto;
+import com.ruoyi.procurementrecord.dto.ProcurementDto;
+import com.ruoyi.procurementrecord.dto.ProcurementPageDto;
+import com.ruoyi.procurementrecord.dto.ProcurementUpdateDto;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecord;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * @author :yys
+ * @date : 2025/7/7 14:36
+ */
+public interface ProcurementRecordService extends IService<ProcurementRecord> {
+    List<ProcurementDto> listProcurementBySalesLedgerId(ProcurementDto procurementDto);
+
+    int add(ProcurementAddDto procurementDto);
+
+    IPage<ProcurementPageDto> listPage(Page page, ProcurementPageDto procurementDto);
+
+    int updatePro(ProcurementUpdateDto procurementDto);
+
+    int deletePro(ProcurementUpdateDto procurementDto);
+
+    void export(HttpServletResponse response);
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordOutServiceImpl.java b/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordOutServiceImpl.java
new file mode 100644
index 0000000..078d8f9
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordOutServiceImpl.java
@@ -0,0 +1,92 @@
+package com.ruoyi.procurementrecord.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.utils.SecurityUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.procurementrecord.dto.ProcurementPageDto;
+import com.ruoyi.procurementrecord.dto.ProcurementRecordOutAdd;
+import com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto;
+import com.ruoyi.procurementrecord.dto.ProcurementUpdateDto;
+import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecord;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
+import com.ruoyi.procurementrecord.service.ProcurementRecordOutService;
+import com.ruoyi.project.system.domain.SysUser;
+import com.ruoyi.project.system.mapper.SysUserMapper;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.servlet.http.HttpServletResponse;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author :yys
+ * @date : 2025/7/8 13:29
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class ProcurementRecordOutServiceImpl extends ServiceImpl<ProcurementRecordOutMapper, ProcurementRecordOut> implements ProcurementRecordOutService {
+
+    public final ProcurementRecordOutMapper procurementRecordOutMapper;
+
+    public final SysUserMapper sysUserMapper;
+
+    @Override
+    public int stockout(ProcurementRecordOutAdd procurementRecordOutAdd) {
+        SysUser sysUser = sysUserMapper.selectUserById(Long.valueOf(procurementRecordOutAdd.getUserId().toString()));
+        if(sysUser == null){
+            throw new RuntimeException("鍑哄簱浜轰笉瀛樺湪");
+        }
+        // 鏌ヨ閲囪喘鍑哄簱鏁伴噺
+        LambdaQueryWrapper<ProcurementRecordOut> procurementRecordLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        procurementRecordLambdaQueryWrapper.eq(ProcurementRecordOut::getProcurementRecordStorageId, procurementRecordOutAdd.getId());
+        Long aLong = procurementRecordOutMapper.selectCount(procurementRecordLambdaQueryWrapper);
+        ProcurementRecordOut.ProcurementRecordOutBuilder procurementRecordOut = ProcurementRecordOut.builder()
+                .procurementRecordStorageId(procurementRecordOutAdd.getId())
+                .salesLedgerProductId(procurementRecordOutAdd.getSalesLedgerProductId())
+                .inboundBatches(aLong.equals(0L) ? "绗�1鎵规" : "绗�"+ (aLong + 1) + "鎵规")
+                .inboundNum(new BigDecimal(procurementRecordOutAdd.getQuantity()))
+                .createDate(LocalDateTime.now())
+                .userId(procurementRecordOutAdd.getUserId())
+                .createBy(sysUser.getNickName())
+                .tenantId(sysUser.getTenantId());
+        this.save(procurementRecordOut.build());
+        return 0;
+    }
+
+    @Override
+    public IPage<ProcurementRecordOutPageDto> listPage(Page page, ProcurementRecordOutPageDto procurementDto) {
+        return procurementRecordOutMapper.listPage(page, procurementDto);
+    }
+
+    public List<ProcurementRecordOut> getProcurementRecordOutByIds(List<Integer> id) {
+        return procurementRecordOutMapper.selectBatchIds(id);
+    }
+
+    @Override
+    public int deletePro(ProcurementUpdateDto procurementDto) {
+        List<ProcurementRecordOut> procurementRecordOutByIds = getProcurementRecordOutByIds(procurementDto.getIds());
+        if(CollectionUtils.isEmpty(procurementRecordOutByIds)){
+            throw new RuntimeException("鏈煡璇㈠埌閫変腑鐨勪汉");
+        }
+        procurementRecordOutMapper.deleteBatchIds(procurementRecordOutByIds.stream().map(ProcurementRecordOut::getId).collect(Collectors.toList()));
+        return 0;
+    }
+
+    @Override
+    public void export(HttpServletResponse response) {
+        List<ProcurementRecordOutPageDto> list =procurementRecordOutMapper.list();
+        ExcelUtil<ProcurementRecordOutPageDto> util = new ExcelUtil<>(ProcurementRecordOutPageDto.class);
+        util.exportExcel(response, list, "鍑哄簱鍙拌处");
+    }
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java b/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java
new file mode 100644
index 0000000..5590a6c
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java
@@ -0,0 +1,198 @@
+package com.ruoyi.procurementrecord.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.utils.SecurityUtils;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.procurementrecord.dto.*;
+import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
+import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecord;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
+import com.ruoyi.procurementrecord.service.ProcurementRecordService;
+import com.ruoyi.quality.pojo.QualityInspect;
+import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.servlet.http.HttpServletResponse;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author :yys
+ * @date : 2025/7/7 14:38
+ */
+@Service
+@RequiredArgsConstructor
+@Slf4j
+public class ProcurementRecordServiceImpl extends ServiceImpl<ProcurementRecordMapper, ProcurementRecord> implements ProcurementRecordService {
+
+    private final ProcurementRecordMapper procurementRecordMapper;
+
+    private final ProcurementRecordOutMapper procurementRecordOutMapper;
+
+    private final SalesLedgerProductMapper salesLedgerProductMapper;
+
+    @Override
+    public List<ProcurementDto> listProcurementBySalesLedgerId(ProcurementDto procurementDto) {
+        List<ProcurementDto> procurementDtos = procurementRecordMapper.listProcurementBySalesLedgerId(procurementDto);
+        // 璁$畻寰呭叆搴撴暟閲�
+        // 鏌ヨ閲囪喘璁板綍宸插叆搴撴暟閲�
+        List<Integer> collect = procurementDtos.stream().map(ProcurementDto::getId).collect(Collectors.toList());
+        if(CollectionUtils.isEmpty( collect)){
+            return procurementDtos;
+        }
+        LambdaQueryWrapper<ProcurementRecord> procurementRecordLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        procurementRecordLambdaQueryWrapper.in(ProcurementRecord::getSalesLedgerProductId, collect);
+        List<ProcurementRecord> procurementRecords = procurementRecordMapper.selectList(procurementRecordLambdaQueryWrapper);
+        if(CollectionUtils.isEmpty( procurementRecords)){
+            return procurementDtos;
+        }
+        for (ProcurementDto dto : procurementDtos) {
+            // 鏍规嵁閲囪喘鍙拌处ID绛涢�夊搴旂殑鍏ュ簱璁板綍
+            List<ProcurementRecord> collect1 = procurementRecords.stream()
+                    .filter(procurementRecord -> procurementRecord.getSalesLedgerProductId().equals(dto.getId()))
+                    .collect(Collectors.toList());
+            
+            // 濡傛灉娌℃湁鐩稿叧鐨勫叆搴撹褰曪紝璺宠繃璇ユ潯鏁版嵁
+            if(CollectionUtils.isEmpty(collect1)){
+                dto.setQuantity0(dto.getQuantity());
+                continue;
+            }
+            
+            // 璁$畻宸插叆搴撴暟閲忔�诲拰锛屽苟璁剧疆寰呭叆搴撴暟閲�
+            BigDecimal totalInboundNum = collect1.stream()
+                    .map(ProcurementRecord::getInboundNum)
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            
+            // 寰呭叆搴撴暟閲� = 鎬绘暟閲� - 宸插叆搴撴暟閲�
+            dto.setQuantity0(dto.getQuantity().subtract(totalInboundNum));
+        }
+        return procurementDtos;
+    }
+
+    public ProcurementRecord getProcurementRecordById(Integer id){
+        ProcurementRecord procurementRecord = procurementRecordMapper.selectById(id);
+        if(procurementRecord == null) {
+            throw new RuntimeException("鏈壘鍒拌閲囪喘鍏ュ簱璁板綍");
+        }
+        return procurementRecord;
+    }
+
+    public List<ProcurementRecord> getProcurementRecordByIds(List<Integer> id){
+        List<ProcurementRecord> procurementRecord = procurementRecordMapper.selectBatchIds(id);
+        if(procurementRecord == null) {
+            throw new RuntimeException("鏈壘鍒拌閲囪喘鍏ュ簱璁板綍");
+        }
+        return procurementRecord;
+    }
+
+    @Override
+    public int updatePro(ProcurementUpdateDto procurementDto) {
+        ProcurementRecord procurementRecordById = getProcurementRecordById(procurementDto.getId());
+        procurementRecordById.setInboundNum(procurementDto.getQuantityStock());
+        return procurementRecordMapper.updateById(procurementRecordById);
+    }
+
+    @Override
+    public int deletePro(ProcurementUpdateDto procurementDto) {
+        List<ProcurementRecord> procurementRecordById = getProcurementRecordByIds(procurementDto.getIds());
+        procurementRecordMapper.deleteBatchIds(procurementRecordById.stream().map(ProcurementRecord::getId).collect(Collectors.toList()));
+        // 鍒犻櫎鎵�鏈夊搴旂殑鍑哄簱璁板綍
+        LambdaQueryWrapper<ProcurementRecordOut> procurementRecordOutLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        procurementRecordOutLambdaQueryWrapper.in(ProcurementRecordOut::getProcurementRecordStorageId, procurementDto.getIds());
+        List<ProcurementRecordOut> procurementRecordOuts = procurementRecordOutMapper.selectList(procurementRecordOutLambdaQueryWrapper);
+        if(!CollectionUtils.isEmpty(procurementRecordOuts)){
+            procurementRecordOutMapper.deleteBatchIds(procurementRecordOuts.stream().map(ProcurementRecordOut::getId).collect(Collectors.toList()));
+        }
+        return 0;
+    }
+
+    @Override
+    public void export(HttpServletResponse response) {
+        List<ProcurementPageDto> list =procurementRecordMapper.list();
+        ExcelUtil<ProcurementPageDto> util = new ExcelUtil<ProcurementPageDto>(ProcurementPageDto.class);
+        util.exportExcel(response, list, "鍏ュ簱鍙拌处");
+    }
+
+    @Override
+    public int add(ProcurementAddDto procurementDto) {
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        // 鎵归噺鏂板
+        for (Details detail : procurementDto.getDetails()) {
+            // 鏌ヨ閲囪喘鍏ュ簱鏁伴噺
+            LambdaQueryWrapper<ProcurementRecord> procurementRecordLambdaQueryWrapper = new LambdaQueryWrapper<>();
+            procurementRecordLambdaQueryWrapper.eq(ProcurementRecord::getSalesLedgerProductId, detail.getId());
+            Long aLong = procurementRecordMapper.selectCount(procurementRecordLambdaQueryWrapper);
+
+            ProcurementRecord.ProcurementRecordBuilder procurementRecordBuilder = ProcurementRecord.builder()
+                    .salesLedgerProductId(detail.getId())
+                    .inboundBatches(aLong.equals(0L) ? "绗�1鎵规" : "绗�"+ (aLong + 1) + "鎵规")
+                    .inboundNum(detail.getInboundQuantity())
+                    .createDate(LocalDateTime.now())
+                    .userId(loginUser.getUserId())
+                    .tenantId(loginUser.getTenantId())
+                    .createBy(procurementDto.getNickName());
+            this.save(procurementRecordBuilder.build());
+            // 鍏ュ簱鎴愬姛鍑忔帀閲囪喘鏁伴噺
+//            LambdaQueryWrapper<SalesLedgerProduct> salesLedgerProductLambdaQueryWrapper = new LambdaQueryWrapper<>();
+//            salesLedgerProductLambdaQueryWrapper.eq(SalesLedgerProduct::getId, detail.getId());
+//            SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectOne(salesLedgerProductLambdaQueryWrapper);
+//            if(salesLedgerProduct == null){
+//               throw new RuntimeException("鏈壘鍒拌鍟嗗搧");
+//            }
+//            salesLedgerProduct.setQuantity(salesLedgerProduct.getQuantity().subtract(detail.getInboundQuantity()));
+//            salesLedgerProductMapper.updateById(salesLedgerProduct);
+        }
+        return 1;
+    }
+
+    @Override
+    public IPage<ProcurementPageDto> listPage(Page page, ProcurementPageDto procurementDto) {
+        IPage<ProcurementPageDto> procurementPageDtoIPage = procurementRecordMapper.listPage(page, procurementDto);
+        List<ProcurementPageDto> procurementPageDtos = procurementPageDtoIPage.getRecords();
+        // 璁$畻寰呭叆搴撴暟閲�
+        // 鏌ヨ閲囪喘璁板綍宸插叆搴撴暟閲�
+        List<Integer> collect = procurementPageDtos.stream().map(ProcurementPageDto::getId).collect(Collectors.toList());
+        if(CollectionUtils.isEmpty( collect)){
+            return procurementPageDtoIPage;
+        }
+        LambdaQueryWrapper<ProcurementRecordOut> procurementRecordLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        procurementRecordLambdaQueryWrapper.in(ProcurementRecordOut::getProcurementRecordStorageId, collect);
+        List<ProcurementRecordOut> procurementRecords = procurementRecordOutMapper.selectList(procurementRecordLambdaQueryWrapper);
+        if(CollectionUtils.isEmpty( procurementRecords)){
+            return procurementPageDtoIPage;
+        }
+        for (ProcurementPageDto dto : procurementPageDtos) {
+            // 鏍规嵁閲囪喘鍙拌处ID绛涢�夊搴旂殑鍑哄簱璁板綍
+            List<ProcurementRecordOut> collect1 = procurementRecords.stream()
+                    .filter(ProcurementRecordOut -> ProcurementRecordOut.getProcurementRecordStorageId().equals(dto.getId()))
+                    .collect(Collectors.toList());
+
+            // 濡傛灉娌℃湁鐩稿叧鐨勫嚭搴撹褰曪紝璺宠繃璇ユ潯鏁版嵁
+            if(CollectionUtils.isEmpty(collect1)){
+                dto.setInboundNum0(dto.getInboundNum());
+                continue;
+            }
+
+            // 璁$畻宸插嚭搴撴暟閲忔�诲拰锛屽苟璁剧疆寰呭嚭搴撴暟閲�
+            BigDecimal totalInboundNum = collect1.stream()
+                    .map(ProcurementRecordOut::getInboundNum)
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+            // 寰呭嚭搴撴暟閲� = 鎬绘暟閲� - 宸插嚭搴撴暟閲�
+            dto.setInboundNum0(dto.getInboundNum().subtract(totalInboundNum));
+        }
+        return procurementPageDtoIPage;
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java b/src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java
index 7b8ceed..ae4141b 100644
--- a/src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java
+++ b/src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java
@@ -19,6 +19,7 @@
 import io.swagger.annotations.ApiModelProperty;
 import io.swagger.annotations.ApiOperation;
 import lombok.AllArgsConstructor;
+import org.springframework.security.core.parameters.P;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
@@ -82,6 +83,22 @@
         return toAjax(ticketRegistrationService.addOrUpdateRegistration(ticketRegistrationDto));
     }
 
+
+    @GetMapping("/getProductRecordById")
+    public AjaxResult getProductRecordById(Long id) {
+        if (id == null) {
+            return AjaxResult.error("鍙傛暟閿欒");
+        }
+        return AjaxResult.success(productRecordService.getProductRecordById(id));
+    }
+
+    @ApiModelProperty("淇敼鏉ョエ鐧昏")
+    @PostMapping("/updateRegistration")
+    public AjaxResult updateRegistration(@RequestBody ProductRecordDto productRecordDto) {
+
+        return productRecordService.updateRecord(productRecordDto);
+    }
+
     /**
      * 鍒犻櫎鏉ョエ鐧昏
      */
diff --git a/src/main/java/com/ruoyi/purchase/dto/ProductRecordDto.java b/src/main/java/com/ruoyi/purchase/dto/ProductRecordDto.java
index 92c4b86..261159a 100644
--- a/src/main/java/com/ruoyi/purchase/dto/ProductRecordDto.java
+++ b/src/main/java/com/ruoyi/purchase/dto/ProductRecordDto.java
@@ -1,5 +1,6 @@
 package com.ruoyi.purchase.dto;
 
+import com.ruoyi.other.pojo.TempFile;
 import com.ruoyi.purchase.pojo.ProductRecord;
 import com.ruoyi.sales.pojo.CommonFile;
 import lombok.Data;
@@ -30,4 +31,6 @@
     private String unTicketsPrice = "0";
 
     private List<CommonFile> commonFiles;
+
+    private List<String> tempFileIds;
 }
diff --git a/src/main/java/com/ruoyi/purchase/mapper/ProductRecordMapper.java b/src/main/java/com/ruoyi/purchase/mapper/ProductRecordMapper.java
index fb691ed..9d003ba 100644
--- a/src/main/java/com/ruoyi/purchase/mapper/ProductRecordMapper.java
+++ b/src/main/java/com/ruoyi/purchase/mapper/ProductRecordMapper.java
@@ -17,4 +17,6 @@
 public interface ProductRecordMapper extends BaseMapper<ProductRecord> {
 
     IPage<ProductRecordDto> productRecordPage(Page page, @Param("c") TicketRegistrationDto ticketRegistrationDto);
+
+    ProductRecordDto getProductRecordById(Long id);
 }
diff --git a/src/main/java/com/ruoyi/purchase/service/IProductRecordService.java b/src/main/java/com/ruoyi/purchase/service/IProductRecordService.java
index 235f096..2fa21ca 100644
--- a/src/main/java/com/ruoyi/purchase/service/IProductRecordService.java
+++ b/src/main/java/com/ruoyi/purchase/service/IProductRecordService.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.AjaxResult;
 import com.ruoyi.purchase.dto.ProductRecordDto;
 import com.ruoyi.purchase.dto.TicketRegistrationDto;
 import com.ruoyi.purchase.pojo.ProductRecord;
@@ -20,4 +21,8 @@
     List<ProductRecord> selectProductRecordList(TicketRegistrationDto ticketRegistrationDto);
 
     IPage<ProductRecordDto> productRecordPage(Page page, TicketRegistrationDto ticketRegistrationDto);
+
+    AjaxResult updateRecord(ProductRecordDto productRecordDto);
+
+    ProductRecordDto getProductRecordById(Long id);
 }
diff --git a/src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java b/src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java
index ee393d0..fd9752b 100644
--- a/src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java
@@ -5,15 +5,21 @@
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.enums.FileNameType;
+import com.ruoyi.framework.web.domain.AjaxResult;
 import com.ruoyi.purchase.dto.ProductRecordDto;
 import com.ruoyi.purchase.dto.TicketRegistrationDto;
 import com.ruoyi.purchase.mapper.ProductRecordMapper;
+import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
 import com.ruoyi.purchase.pojo.ProductRecord;
+import com.ruoyi.purchase.pojo.PurchaseLedger;
 import com.ruoyi.purchase.service.IProductRecordService;
 import com.ruoyi.sales.mapper.CommonFileMapper;
+import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
 import com.ruoyi.sales.pojo.CommonFile;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
+import org.springframework.beans.BeanUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
@@ -34,8 +40,10 @@
 
     @Autowired
     private CommonFileMapper commonFileMapper;
-
-
+    @Autowired
+    private SalesLedgerProductMapper salesLedgerProductMapper;
+    @Autowired
+    private PurchaseLedgerMapper purchaseLedgerMapper;
 
 
     /**
@@ -61,4 +69,30 @@
         });
         return productRecordDtoIPage;
     }
+
+    @Override
+    public AjaxResult updateRecord(ProductRecordDto productRecordDto) {
+        SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(productRecordDto.getSaleLedgerProjectId());
+        ProductRecord productRecord = productRecordMapper.selectById(productRecordDto.getId());
+        if (salesLedgerProduct != null) {
+            salesLedgerProduct.setFutureTicketsAmount(salesLedgerProduct.getFutureTicketsAmount().add(productRecord.getTicketsAmount()).subtract(productRecordDto.getTicketsAmount()));
+            salesLedgerProduct.setFutureTickets(salesLedgerProduct.getFutureTickets().add(productRecord.getTicketsNum().subtract(productRecordDto.getTicketsNum())));
+            salesLedgerProductMapper.updateById(salesLedgerProduct);
+
+        }
+        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(productRecord.getPurchaseLedgerId());
+        if (purchaseLedger != null) {
+            purchaseLedger.setReceiptPaymentAmount(purchaseLedger.getReceiptPaymentAmount());
+        }
+        BeanUtils.copyProperties(productRecordDto,productRecord);
+        productRecordMapper.updateById(productRecord);
+
+        return AjaxResult.success("淇敼鎴愬姛");
+    }
+
+    @Override
+    public ProductRecordDto getProductRecordById(Long id) {
+        ProductRecordDto productRecordDto = productRecordMapper.getProductRecordById(id);
+        return productRecordDto;
+    }
 }
diff --git a/src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java b/src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java
index 966e361..08bbf4b 100644
--- a/src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java
+++ b/src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java
@@ -128,6 +128,8 @@
             throw new IllegalArgumentException("閲囪喘鍙拌处璁板綍涓嶅瓨鍦紝ID: " + ticketRegistrationDto.getPurchaseLedgerId());
         }
 
+
+
         // 3. 鍒涘缓鎴栨洿鏂扮エ鎹櫥璁板疄浣�
         TicketRegistration ticketRegistration = new TicketRegistration();
         BeanUtils.copyProperties(ticketRegistrationDto, ticketRegistration);
@@ -150,17 +152,24 @@
         // 6. 澧炲姞閲囪喘鍙拌处浜у搧寮�绁ㄨ褰�
         List<SalesLedgerProduct> salesLedgerProducts = ticketRegistrationDto.getProductData();
         if (CollectionUtils.isNotEmpty(salesLedgerProducts)) {
+            int insert = 0 ;
             for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
-                ProductRecord productRecord = new ProductRecord();
+                //鎺掗櫎鎺夊紑绁ㄤ负0鐨勬暟鎹�
+                if (salesLedgerProduct.getTicketsNum() != null && salesLedgerProduct.getTicketsNum().compareTo(BigDecimal.ZERO) > 0) {
+                    ProductRecord productRecord = new ProductRecord();
 
-                productRecord.setTicketRegistrationId(ticketRegistration.getId());
-                productRecord.setPurchaseLedgerId(ticketRegistrationDto.getPurchaseLedgerId());
-                productRecord.setCreatedAt(DateUtils.getNowDate());
-                BeanUtils.copyProperties(salesLedgerProduct, productRecord);
-                productRecord.setSaleLedgerProjectId(salesLedgerProduct.getId());
-                productRecord.setId(null);
-                productRecord.setType("2");
-                productRecordMapper.insert(productRecord);
+                    productRecord.setTicketRegistrationId(ticketRegistration.getId());
+                    productRecord.setPurchaseLedgerId(ticketRegistrationDto.getPurchaseLedgerId());
+                    productRecord.setCreatedAt(DateUtils.getNowDate());
+                    BeanUtils.copyProperties(salesLedgerProduct, productRecord);
+                    productRecord.setSaleLedgerProjectId(salesLedgerProduct.getId());
+                    productRecord.setId(null);
+                    productRecord.setType("2");
+                     insert = productRecordMapper.insert(productRecord);
+                }
+                if (insert <= 0) {
+                    throw new RuntimeException("浜у搧寮�绁ㄦ暟閮戒负0锛岃妫�鏌�");
+                }
             }
         }
         // 杩佺Щ涓存椂鏂囦欢鍒版寮忕洰褰�
@@ -178,7 +187,7 @@
      * @param tempFileIds 涓存椂鏂囦欢ID鍒楄〃
      * @throws IOException 鏂囦欢鎿嶄綔寮傚父
      */
-    private void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException {
+    public void migrateTempFilesToFormal(Long businessId, List<String> tempFileIds) throws IOException {
         if (CollectionUtils.isEmpty(tempFileIds)) {
             return;
         }
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 611744d..a987879 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -16,7 +16,7 @@
 
 # 寮�鍙戠幆澧冮厤缃�
 server:
-  # 鏈嶅姟鍣ㄧ殑HTTP绔彛锛岄粯璁や负80807
+  # 鏈嶅姟鍣ㄧ殑HTTP绔彛锛岄粯璁や负8080
   port: 7003
   servlet:
     # 搴旂敤鐨勮闂矾寰�
@@ -38,6 +38,14 @@
     com.ruoyi: warn
     org.springframework: warn
 
+minio:
+  endpoint: http://114.132.189.42/
+  port: 7019
+  secure: false
+  accessKey: admin
+  secretKey: 12345678
+  preview-expiry: 24 # 棰勮鍦板潃榛樿24灏忔椂
+  default-bucket: uploadPath
 # 鐢ㄦ埛閰嶇疆
 user:
   password:
@@ -73,9 +81,9 @@
     # 绔彛锛岄粯璁や负6379
     port: 6379
     # 鏁版嵁搴撶储寮�
-    database: 1
+    database: 0
     # 瀵嗙爜
-#    password: root2022!
+    password:
 #    password: 123456
 
     # 杩炴帴瓒呮椂鏃堕棿
diff --git a/src/main/resources/mapper/basic/StorageAttachmentMapper.xml b/src/main/resources/mapper/basic/StorageAttachmentMapper.xml
new file mode 100644
index 0000000..a2cc6cf
--- /dev/null
+++ b/src/main/resources/mapper/basic/StorageAttachmentMapper.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.basic.mapper.StorageAttachmentMapper">
+
+        <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+        <resultMap id="BaseResultMap" type="com.ruoyi.basic.pojo.StorageAttachment">
+                    <id column="id" property="id" />
+                    <result column="create_time" property="createTime" />
+                    <result column="update_time" property="updateTime" />
+                    <result column="deleted" property="deleted" />
+                    <result column="record_type" property="recordType" />
+                    <result column="record_id" property="recordId" />
+                    <result column="name" property="name" />
+                    <result column="storage_blob_id" property="storageBlobId" />
+        </resultMap>
+
+        <!-- 閫氱敤鏌ヨ缁撴灉鍒� -->
+        <sql id="Base_Column_List">
+            id, create_time, update_time, deleted, record_type, record_id, name, storage_blob_id
+        </sql>
+
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/basic/StorageBlobMapper.xml b/src/main/resources/mapper/basic/StorageBlobMapper.xml
new file mode 100644
index 0000000..84e3b00
--- /dev/null
+++ b/src/main/resources/mapper/basic/StorageBlobMapper.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.basic.mapper.StorageBlobMapper">
+
+        <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+        <resultMap id="BaseResultMap" type="com.ruoyi.basic.pojo.StorageBlob">
+                    <id column="id" property="id" />
+                    <result column="create_time" property="createTime" />
+                    <result column="key" property="key" />
+                    <result column="content_type" property="contentType" />
+                    <result column="original_filename" property="originalFilename" />
+                    <result column="bucket_filename" property="bucketFilename" />
+                    <result column="bucket_name" property="bucketName" />
+                    <result column="byte_size" property="byteSize" />
+        </resultMap>
+
+        <!-- 閫氱敤鏌ヨ缁撴灉鍒� -->
+        <sql id="Base_Column_List">
+            id, create_time, key, content_type, original_filename,bucket_filename,bucket_name,  byte_size
+        </sql>
+
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/procurementrecord/ProcurementRecordMapper.xml b/src/main/resources/mapper/procurementrecord/ProcurementRecordMapper.xml
new file mode 100644
index 0000000..cf76a56
--- /dev/null
+++ b/src/main/resources/mapper/procurementrecord/ProcurementRecordMapper.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper">
+
+    <select id="listProcurementBySalesLedgerId" resultType="com.ruoyi.procurementrecord.dto.ProcurementDto">
+        select
+            t1.supplier_name,
+            t2.product_category,
+            t2.id,
+            t3.id as recordId,
+            t2.specification_model,
+            t2.unit,
+            t2.quantity,
+            t2.quantity as quantity0,
+            t2.tax_rate,
+            t2.tax_inclusive_unit_price,
+            t2.tax_inclusive_total_price,
+            t2.tax_exclusive_total_price,
+            t3.inbound_num as quantityStock
+        from  purchase_ledger t1
+        left join sales_ledger_product t2 on t1.id = t2.sales_ledger_id
+        left join procurement_record_storage t3 on t2.id = t3.sales_ledger_product_id
+        where t1.purchase_contract_number = #{req.purchaseContractNumber}
+        <if test="req.id != null and req.id != ''">
+            and t3.id = #{req.id}
+        </if>
+        group by t2.id
+    </select>
+    <select id="listPage" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDto">
+        select
+        t3.supplier_name,
+        t3.purchase_contract_number,
+        t2.product_category,
+        t1.id,
+        t1.sales_ledger_product_id,
+        t2.specification_model,
+        t2.unit,
+        t2.tax_rate,
+        t2.tax_inclusive_unit_price,
+        t2.tax_inclusive_total_price,
+        t2.tax_exclusive_total_price,
+        t1.inbound_batches,
+        t1.inbound_num,
+        t1.inbound_num as inboundNum0,
+        t1.create_date,
+        t1.create_by
+        from  procurement_record_storage t1
+                  left join sales_ledger_product t2 on t2.id = t1.sales_ledger_product_id
+                  left join purchase_ledger t3 on t3.id = t2.sales_ledger_id
+        <where>
+            1 = 1
+            <if test="req.supplierName != null and req.supplierName != ''">
+                and t3.supplier_name like  concat('%',#{req.supplierName},'%')
+            </if>
+        </where>
+    </select>
+    <select id="list" resultType="com.ruoyi.procurementrecord.dto.ProcurementPageDto">
+        select
+            t3.supplier_name,
+            t3.purchase_contract_number,
+            t2.product_category,
+            t1.id,
+            t2.specification_model,
+            t2.unit,
+            t2.quantity,
+            t2.quantity as quantity0,
+            t2.tax_rate,
+            t2.tax_inclusive_unit_price,
+            t2.tax_inclusive_total_price,
+            t2.tax_exclusive_total_price,
+            t1.inbound_batches,
+            t1.inbound_num,
+            t1.create_date,
+            t1.create_by
+        from  procurement_record_storage t1
+                  left join sales_ledger_product t2 on t2.id = t1.sales_ledger_product_id
+                  left join purchase_ledger t3 on t3.id = t2.sales_ledger_id
+    </select>
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/procurementrecord/ProcurementRecordOutMapper.xml b/src/main/resources/mapper/procurementrecord/ProcurementRecordOutMapper.xml
new file mode 100644
index 0000000..8c35ffc
--- /dev/null
+++ b/src/main/resources/mapper/procurementrecord/ProcurementRecordOutMapper.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper">
+
+    <select id="listPage" resultType="com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto">
+        select
+        t3.supplier_name,
+        t2.product_category,
+        t1.id,
+        t2.specification_model,
+        t2.unit,
+        t2.tax_rate,
+        t2.tax_inclusive_unit_price,
+        t2.tax_inclusive_total_price,
+        t2.tax_exclusive_total_price,
+        t1.inbound_num,
+        t1.create_date,
+        t1.create_by
+        from  procurement_record_out t1
+        left join sales_ledger_product t2 on t2.id = t1.sales_ledger_product_id
+        left join purchase_ledger t3 on t3.id = t2.sales_ledger_id
+        <where>
+            1 = 1
+            <if test="req.supplierName != null and req.supplierName != ''">
+                and t3.supplier_name like  concat('%',#{req.supplierName},'%')
+            </if>
+        </where>
+    </select>
+    <select id="list" resultType="com.ruoyi.procurementrecord.dto.ProcurementRecordOutPageDto">
+        select
+            t3.supplier_name,
+            t2.product_category,
+            t1.id,
+            t2.specification_model,
+            t2.unit,
+            t2.tax_rate,
+            t2.tax_inclusive_unit_price,
+            t2.tax_inclusive_total_price,
+            t2.tax_exclusive_total_price,
+            t1.inbound_num,
+            t1.create_date,
+            t1.create_by
+        from  procurement_record_out t1
+                  left join sales_ledger_product t2 on t2.id = t1.sales_ledger_product_id
+                  left join purchase_ledger t3 on t3.id = t2.sales_ledger_id
+    </select>
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/purchase/ProductRecordMapper.xml b/src/main/resources/mapper/purchase/ProductRecordMapper.xml
index 35f08ac..dd5aac6 100644
--- a/src/main/resources/mapper/purchase/ProductRecordMapper.xml
+++ b/src/main/resources/mapper/purchase/ProductRecordMapper.xml
@@ -35,5 +35,28 @@
         <if test="c.createdAtEnd != null and c.createdAtEnd != ''">
             and pr.created_at &lt;= date_format(#{c.createdAtEnd},'%Y-%m-%d hh:mm:ss')
         </if>
+        <if test="c.purchaseContractNumber != null and c.purchaseContractNumber != ''">
+            and tr.purchase_contract_number like concat('%',#{c.purchaseContractNumber},'%')
+        </if>
+    </select>
+    <select id="getProductRecordById" resultType="com.ruoyi.purchase.dto.ProductRecordDto">
+        SELECT
+            sl.sales_contract_no,
+            sl.customer_contract_no,
+            sl.customer_name,
+            pm.model AS product_model,
+            pl.purchase_contract_number,
+            pl.supplier_name,
+            pr.*,
+            tr.invoice_number,
+            ROUND(pr.tickets_amount/(1+pr.tax_rate/100),2 ) as un_tickets_price,
+            ROUND(pr.tickets_amount-pr.tickets_amount/(1+pr.tax_rate/100),2 )as invoice_amount
+        FROM product_record pr
+                 left join purchase_ledger pl on pl.id = pr.purchase_ledger_id
+                 left join sales_ledger sl on sl.id = pl.sales_ledger_id
+                 left join ticket_registration tr on tr.id = pr.ticket_registration_id
+                 left join product_model pm on pm.id = pr.product_model_id
+
+        WHERE type = 2 and pr.id = #{id}
     </select>
 </mapper>
\ No newline at end of file

--
Gitblit v1.9.3