Răsfoiți Sursa

添加文件夹关键词设置

Huanyi 2 luni în urmă
părinte
comite
1de5813c88
24 a modificat fișierele cu 1305 adăugiri și 51 ștergeri
  1. 2 0
      ruoyi-admin/src/main/resources/application.yml
  2. 2 0
      ruoyi-admin/src/main/resources/i18n/messages_en_US.properties
  3. 2 0
      ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties
  4. 2 0
      ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java
  5. 0 1
      ruoyi-common/yingpaipay-common-document/src/main/java/com/yingpaipay/common/file/util/PdfUtils.java
  6. 5 0
      ruoyi-modules/yingpaipay-business/pom.xml
  7. 2 0
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/constant/DictTypeConst.java
  8. 10 0
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/domain/Document.java
  9. 591 30
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/enumeration/MailTemplateEnum.java
  10. 16 0
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/CommonFolderService.java
  11. 48 10
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/DocumentQcTaskDetailServiceImpl.java
  12. 78 10
      ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/DocumentServiceImpl.java
  13. 109 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/controller/KeywordSettingController.java
  14. 14 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/domain/FolderKeyword.java
  15. 47 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/domain/KeywordSetting.java
  16. 41 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/domain/bo/KeywordSettingBo.java
  17. 50 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/domain/vo/KeywordSettingVo.java
  18. 7 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/mapper/FolderKeywordMapper.java
  19. 15 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/mapper/KeywordSettingMapper.java
  20. 4 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/IFolderKeywordService.java
  21. 70 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/IKeywordSettingService.java
  22. 14 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/impl/FolderKeywordServiceImpl.java
  23. 153 0
      ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/impl/KeywordSettingServiceImpl.java
  24. 23 0
      script/sql/business/create.sql

+ 2 - 0
ruoyi-admin/src/main/resources/application.yml

@@ -136,6 +136,8 @@ tenant:
         - ai_setting
         - textin_setting
         - carousel_setting
+        - keyword_setting
+        - folder_keyword
 
 # MyBatisPlus配置
 # https://baomidou.com/config/

+ 2 - 0
ruoyi-admin/src/main/resources/i18n/messages_en_US.properties

@@ -96,3 +96,5 @@ search.temp=Pending Archiving Area
 
 textin.networkerror=Network error.
 textin.recognitionfail=Recognition failed.
+
+setting.keyword.add.exists=The keyword is existing.

+ 2 - 0
ruoyi-admin/src/main/resources/i18n/messages_zh_CN.properties

@@ -96,3 +96,5 @@ search.temp=待归档区
 
 textin.networkerror=网络异常
 textin.recognitionfail=识别失败
+
+setting.keyword.add.exists=关键词已存在

+ 2 - 0
ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/constant/CacheNames.java

@@ -90,4 +90,6 @@ public interface CacheNames {
 
     String SETTING = "setting";
 
+    String KEYWORD_SETTING = "keyword_setting";
+
 }

+ 0 - 1
ruoyi-common/yingpaipay-common-document/src/main/java/com/yingpaipay/common/file/util/PdfUtils.java

@@ -67,7 +67,6 @@ public class PdfUtils {
             throw new IllegalArgumentException("Base64 PDF list is empty or null");
         }
 
-        // 验证每个文件是否存在且可读
         for (File file : files) {
             if (file == null || !file.exists() || !file.isFile()) {
                 throw new IllegalArgumentException("Invalid PDF file: " + (file != null ? file.getAbsolutePath() : "null"));

+ 5 - 0
ruoyi-modules/yingpaipay-business/pom.xml

@@ -108,6 +108,11 @@
             <artifactId>ruoyi-common-sse</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.dromara</groupId>
+            <artifactId>ruoyi-common-mail</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.yingpaipay</groupId>
             <artifactId>yingpaipay-common-document</artifactId>

+ 2 - 0
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/constant/DictTypeConst.java

@@ -21,4 +21,6 @@ public interface DictTypeConst {
 
     String AUDITOR_TYPE = "auditor_type";
 
+    String QC_QUESTION_TYPE = "qc_question_type";
+
 }

+ 10 - 0
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/domain/Document.java

@@ -95,4 +95,14 @@ public class Document extends TenantEntity {
 
     private Date effectiveDate;
 
+    private String language;
+
+    private String version;
+
+    private Date versionDate;
+
+    private Date ethicsSubmissionDate;
+
+    private Date ethicsApprovalDate;
+
 }

+ 591 - 30
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/enumeration/MailTemplateEnum.java

@@ -3,46 +3,607 @@ package com.yingpaipay.business.enumeration;
 import lombok.AccessLevel;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
+import org.springframework.context.i18n.LocaleContextHolder;
 
 @Getter
 @AllArgsConstructor(access = AccessLevel.PRIVATE)
 public enum MailTemplateEnum {
 
-    TO_SUBMIT(
-        """
+    TO_SUBMIT("文件递交提醒", "Document Submission Reminder", """
+            <!DOCTYPE html>
+            <html>
+            <head>
+              <meta charset="UTF-8">
+              <meta name="viewport" content="width=device-width, initial-scale=1.0">
+              <title>文档递交提醒</title>
+            </head>
+            <body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f9f9f9; color: #333333; line-height: 1.6;">
+              <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f9f9f9; padding: 20px 0;">
+                <tr>
+                  <td align="center">
+                    <table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); overflow: hidden; border: 1px solid #eaeaea;">
+                      <tr>
+                        <td style="padding: 30px 40px 20px 40px;">
+                          <h2 style="margin: 0; font-size: 22px; color: #2c3e50;">文档递交提醒</h2>
+                        </td>
+                      </tr>
+                      <tr>
+                        <td style="padding: 0 40px 30px 40px;">
+                          <p style="margin: 20px 0 10px 0; font-size: 16px;">
+                            尊敬的{0}:
+                          </p>
+                          <p style="margin: 10px 0; font-size: 16px;">
+                            您有一份文档需要递交,相关信息如下:
+                          </p>
+                          <ul style="margin: 15px 0 20px 0; padding-left: 20px; font-size: 16px;">
+                            <li><strong>文档名称:</strong>{1}</li>
+                            <li><strong>所属项目:</strong>{2}</li>
+                            <li><strong>存储路径:</strong>{3}</li>
+                            <li><strong>截止时间:</strong>{4}</li>
+                          </ul>
+                          <p style="margin: 20px 0 0 0; font-size: 16px; color: #d35400;">
+                            请您尽快处理,以免影响后续流程。
+                          </p>
+                        </td>
+                      </tr>
+                      <tr>
+                        <td style="padding: 20px 40px; background-color: #f1f1f1; font-size: 14px; color: #777777; border-top: 1px solid #eeeeee;">
+                          此邮件由系统自动发送,请勿直接回复。
+                        </td>
+                      </tr>
+                    </table>
+                  </td>
+                </tr>
+              </table>
+            </body>
+            </html>
+        """, """
+        <!DOCTYPE html>
+        <html>
+        <head>
+          <meta charset="UTF-8">
+          <meta name="viewport" content="width=device-width, initial-scale=1.0">
+          <title>Document Submission Reminder</title>
+        </head>
+        <body style="margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; background-color: #f5f7fa; color: #333333; line-height: 1.6;">
+          <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f5f7fa; padding: 20px 0;">
+            <tr>
+              <td align="center">
+                <table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); overflow: hidden; border: 1px solid #e0e0e0;">
+                  <!-- Header -->
+                  <tr>
+                    <td style="padding: 30px 40px 20px 40px; background-color: #f8f9fa; border-bottom: 1px solid #eeeeee;">
+                      <h2 style="margin: 0; font-size: 22px; color: #2c3e50;">Document Submission Reminder</h2>
+                    </td>
+                  </tr>
+                  <!-- Body -->
+                  <tr>
+                    <td style="padding: 30px 40px;">
+                      <p style="margin: 0 0 16px 0; font-size: 16px;">
+                        Dear {0},
+                      </p>
+                      <p style="margin: 0 0 20px 0; font-size: 16px;">
+                        This is a friendly reminder that you have a document pending submission.
+                      </p>
+                      <table role="presentation" width="100%" style="margin: 20px 0; font-size: 16px; border-collapse: collapse;">
+                        <tr>
+                          <td style="padding: 6px 0; width: 120px; font-weight: bold; color: #555555;">Document:</td>
+                          <td style="padding: 6px 0;">{1}</td>
+                        </tr>
+                        <tr>
+                        <td style="padding: 6px 0; font-weight: bold; color: #555555;">Project:</td>
+                        <td style="padding: 6px 0;">{2}</td>
+                      </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Location:</td>
+                          <td style="padding: 6px 0;">{3}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Deadline:</td>
+                          <td style="padding: 6px 0; color: #d32f2f;"><strong>{4}</strong></td>
+                        </tr>
+                      </table>
+                      <p style="margin: 20px 0 0 0; font-size: 16px;">
+                        Please submit the document at your earliest convenience to avoid any delays.
+                      </p>
+                    </td>
+                  </tr>
+                  <!-- Footer -->
+                  <tr>
+                    <td style="padding: 20px 40px; background-color: #f1f1f1; font-size: 14px; color: #777777; border-top: 1px solid #eeeeee;">
+                      This is an automated message. Please do not reply directly to this email.
+                    </td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+          </table>
+        </body>
+        </html>
+        """), TO_AUDIT("文档待审核提醒", "Document Awaiting Review", """
+            <!DOCTYPE html>
+            <html>
+            <head>
+              <meta charset="UTF-8">
+              <meta name="viewport" content="width=device-width, initial-scale=1.0">
+              <title>文档待审核提醒</title>
+            </head>
+            <body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f9f9f9; color: #333333; line-height: 1.6;">
+              <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f9f9f9; padding: 20px 0;">
+                <tr>
+                  <td align="center">
+                    <table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); overflow: hidden; border: 1px solid #eaeaea;">
+                      <tr>
+                        <td style="padding: 30px 40px 20px 40px;">
+                          <h2 style="margin: 0; font-size: 22px; color: #2c3e50;">文档待审核提醒</h2>
+                        </td>
+                      </tr>
+                      <tr>
+                        <td style="padding: 0 40px 30px 40px;">
+                          <p style="margin: 20px 0 10px 0; font-size: 16px;">
+                            尊敬的{0}:
+                          </p>
+                          <p style="margin: 10px 0; font-size: 16px;">
+                            您有一份文档已提交,等待您的审核,相关信息如下:
+                          </p>
+                          <ul style="margin: 15px 0 20px 0; padding-left: 20px; font-size: 16px;">
+                            <li><strong>文档名称:</strong>{1}</li>
+                            <li><strong>所属项目:</strong>{2}</li>
+                            <li><strong>存储路径:</strong>{3}</li>
+                            <li><strong>提交时间:</strong>{4}</li>
+                          </ul>
+                          <p style="margin: 20px 0 0 0; font-size: 16px; color: #3498db;">
+                            请您及时登录系统进行审核处理。
+                          </p>
+                        </td>
+                      </tr>
+                      <tr>
+                        <td style="padding: 20px 40px; background-color: #f1f1f1; font-size: 14px; color: #777777; border-top: 1px solid #eeeeee;">
+                          此邮件由系统自动发送,请勿直接回复。
+                        </td>
+                      </tr>
+                    </table>
+                  </td>
+                </tr>
+              </table>
+            </body>
+            </html>
+        """, """
+        <!DOCTYPE html>
+        <html>
+        <head>
+          <meta charset="UTF-8">
+          <meta name="viewport" content="width=device-width, initial-scale=1.0">
+          <title>Document Awaiting Review</title>
+        </head>
+        <body style="margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; background-color: #f5f7fa; color: #333333; line-height: 1.6;">
+          <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f5f7fa; padding: 20px 0;">
+            <tr>
+              <td align="center">
+                <table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); overflow: hidden; border: 1px solid #e0e0e0;">
+                  <!-- Header -->
+                  <tr>
+                    <td style="padding: 30px 40px 20px 40px; background-color: #f8f9fa; border-bottom: 1px solid #eeeeee;">
+                      <h2 style="margin: 0; font-size: 22px; color: #2c3e50;">Document Awaiting Review</h2>
+                    </td>
+                  </tr>
+                  <!-- Body -->
+                  <tr>
+                    <td style="padding: 30px 40px;">
+                      <p style="margin: 0 0 16px 0; font-size: 16px;">
+                        Dear {0},
+                      </p>
+                      <p style="margin: 0 0 20px 0; font-size: 16px;">
+                        A document has been submitted and is awaiting your review.
+                      </p>
+                      <table role="presentation" width="100%" style="margin: 20px 0; font-size: 16px; border-collapse: collapse;">
+                        <tr>
+                          <td style="padding: 6px 0; width: 120px; font-weight: bold; color: #555555;">Document:</td>
+                          <td style="padding: 6px 0;">{1}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Project:</td>
+                          <td style="padding: 6px 0;">{2}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Location:</td>
+                          <td style="padding: 6px 0;">{3}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Submitted At:</td>
+                          <td style="padding: 6px 0; color: #1976d2;"><strong>{4}</strong></td>
+                        </tr>
+                      </table>
+                      <p style="margin: 20px 0 0 0; font-size: 16px;">
+                        Please log in to the system at your earliest convenience to complete the review.
+                      </p>
+                    </td>
+                  </tr>
+                  <!-- Footer -->
+                  <tr>
+                    <td style="padding: 20px 40px; background-color: #f1f1f1; font-size: 14px; color: #777777; border-top: 1px solid #eeeeee;">
+                      This is an automated message. Please do not reply directly to this email.
+                    </td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+          </table>
+        </body>
+        </html>
+        """), AUDIT_REJECT("文档审核结果通知", "Document Audit Result", """
+            <!DOCTYPE html>
+            <html>
+            <head>
+              <meta charset="UTF-8">
+              <meta name="viewport" content="width=device-width, initial-scale=1.0">
+              <title>文档审核驳回</title>
+            </head>
+            <body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f9f9f9; color: #333333; line-height: 1.6;">
+              <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f9f9f9; padding: 20px 0;">
+                <tr>
+                  <td align="center">
+                    <table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); overflow: hidden; border: 1px solid #eaeaea;">
+                      <tr>
+                        <td style="padding: 30px 40px 20px 40px; background-color: #ffebee; border-bottom: 1px solid #ffcdd2;">
+                          <h2 style="margin: 0; font-size: 22px; color: #e74c3c;">❌ 文档审核未通过</h2>
+                        </td>
+                      </tr>
+                      <tr>
+                        <td style="padding: 0 40px 30px 40px;">
+                          <p style="margin: 20px 0 10px 0; font-size: 16px;">
+                            尊敬的{0}:
+                          </p>
+                          <p style="margin: 10px 0; font-size: 16px;">
+                            您提交的文档未通过审核,请根据以下意见修改后重新提交:
+                          </p>
+                          <ul style="margin: 15px 0 20px 0; padding-left: 20px; font-size: 16px;">
+                            <li><strong>文档名称:</strong>{1}</li>
+                            <li><strong>所属项目:</strong>{2}</li>
+                            <li><strong>存储路径:</strong>{3}</li>
+                            <li><strong>驳回时间:</strong>{4}</li>
+                          </ul>
+                          <div style="margin: 20px 0; padding: 15px; background-color: #fff8f8; border-left: 4px solid #e74c3c; font-size: 16px; color: #c0392b;">
+                            <strong>驳回理由:</strong><br>
+                            {5}
+                          </div>
+                          <p style="margin: 20px 0 0 0; font-size: 16px; color: #e74c3c;">
+                            请尽快处理,以免影响项目进度。
+                          </p>
+                        </td>
+                      </tr>
+                      <tr>
+                        <td style="padding: 20px 40px; background-color: #f1f1f1; font-size: 14px; color: #777777; border-top: 1px solid #eeeeee;">
+                          此邮件由系统自动发送,请勿直接回复。
+                        </td>
+                      </tr>
+                    </table>
+                  </td>
+                </tr>
+              </table>
+            </body>
+            </html>
+        """, """
+        <!DOCTYPE html>
+        <html>
+        <head>
+          <meta charset="UTF-8">
+          <meta name="viewport" content="width=device-width, initial-scale=1.0">
+          <title>Document Rejected</title>
+        </head>
+        <body style="margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; background-color: #f5f7fa; color: #333333; line-height: 1.6;">
+          <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f5f7fa; padding: 20px 0;">
+            <tr>
+              <td align="center">
+                <table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); overflow: hidden; border: 1px solid #e0e0e0;">
+                  <!-- Header -->
+                  <tr>
+                    <td style="padding: 30px 40px 20px 40px; background-color: #ffebee; border-bottom: 1px solid #ffcdd2;">
+                      <h2 style="margin: 0; font-size: 22px; color: #c62828;">❌ Document Rejected</h2>
+                    </td>
+                  </tr>
+                  <!-- Body -->
+                  <tr>
+                    <td style="padding: 30px 40px;">
+                      <p style="margin: 0 0 16px 0; font-size: 16px;">
+                        Dear {0},
+                      </p>
+                      <p style="margin: 0 0 20px 0; font-size: 16px;">
+                        Your submitted document was not approved. Please revise and resubmit based on the feedback below.
+                      </p>
+                      <table role="presentation" width="100%" style="margin: 20px 0; font-size: 16px; border-collapse: collapse;">
+                        <tr>
+                          <td style="padding: 6px 0; width: 120px; font-weight: bold; color: #555555;">Document:</td>
+                          <td style="padding: 6px 0;">{1}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Project:</td>
+                          <td style="padding: 6px 0;">{2}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Location:</td>
+                          <td style="padding: 6px 0;">{3}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Rejected At:</td>
+                          <td style="padding: 6px 0; color: #c62828;"><strong>{4}</strong></td>
+                        </tr>
+                      </table>
+                      <div style="margin: 25px 0; padding: 15px; background-color: #fdf6f6; border-left: 4px solid #d32f2f; font-size: 16px; color: #b71c1c;">
+                        <strong>Rejection Reason:</strong><br>
+                        {5}
+                      </div>
+                      <p style="margin: 20px 0 0 0; font-size: 16px; color: #c62828;">
+                        Please address this promptly to avoid project delays.
+                      </p>
+                    </td>
+                  </tr>
+                  <!-- Footer -->
+                  <tr>
+                    <td style="padding: 20px 40px; background-color: #f1f1f1; font-size: 14px; color: #777777; border-top: 1px solid #eeeeee;">
+                      This is an automated message. Please do not reply directly to this email.
+                    </td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+          </table>
+        </body>
+        </html>
+        """), AUDIT_PASS("文档审核结果通知", "Document Audit Result", """
+            <!DOCTYPE html>
+            <html>
+            <head>
+              <meta charset="UTF-8">
+              <meta name="viewport" content="width=device-width, initial-scale=1.0">
+              <title>文档审核通过</title>
+            </head>
+            <body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f9f9f9; color: #333333; line-height: 1.6;">
+              <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f9f9f9; padding: 20px 0;">
+                <tr>
+                  <td align="center">
+                    <table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); overflow: hidden; border: 1px solid #eaeaea;">
+                      <tr>
+                        <td style="padding: 30px 40px 20px 40px; background-color: #e8f5e9; border-bottom: 1px solid #c8e6c9;">
+                          <h2 style="margin: 0; font-size: 22px; color: #27ae60;">✅ 文档审核通过</h2>
+                        </td>
+                      </tr>
+                      <tr>
+                        <td style="padding: 0 40px 30px 40px;">
+                          <p style="margin: 20px 0 10px 0; font-size: 16px;">
+                            尊敬的{0}:
+                          </p>
+                          <p style="margin: 10px 0; font-size: 16px;">
+                            您提交的文档已通过审核,相关信息如下:
+                          </p>
+                          <ul style="margin: 15px 0 20px 0; padding-left: 20px; font-size: 16px;">
+                            <li><strong>文档名称:</strong>{1}</li>
+                            <li><strong>所属项目:</strong>{2}</li>
+                            <li><strong>存储路径:</strong>{3}</li>
+                            <li><strong>审核时间:</strong>{4}</li>
+                          </ul>
+                          <p style="margin: 20px 0 0 0; font-size: 16px; color: #27ae60;">
+                            感谢您的配合。
+                          </p>
+                        </td>
+                      </tr>
+                      <tr>
+                        <td style="padding: 20px 40px; background-color: #f1f1f1; font-size: 14px; color: #777777; border-top: 1px solid #eeeeee;">
+                          此邮件由系统自动发送,请勿直接回复。
+                        </td>
+                      </tr>
+                    </table>
+                  </td>
+                </tr>
+              </table>
+            </body>
+            </html>
+        """, """
+        <!DOCTYPE html>
+        <html>
+        <head>
+          <meta charset="UTF-8">
+          <meta name="viewport" content="width=device-width, initial-scale=1.0">
+          <title>Document Approved</title>
+        </head>
+        <body style="margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; background-color: #f5f7fa; color: #333333; line-height: 1.6;">
+          <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f5f7fa; padding: 20px 0;">
+            <tr>
+              <td align="center">
+                <table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); overflow: hidden; border: 1px solid #e0e0e0;">
+                  <!-- Header -->
+                  <tr>
+                    <td style="padding: 30px 40px 20px 40px; background-color: #e8f5e9; border-bottom: 1px solid #c8e6c9;">
+                      <h2 style="margin: 0; font-size: 22px; color: #2e7d32;">✅ Document Approved</h2>
+                    </td>
+                  </tr>
+                  <!-- Body -->
+                  <tr>
+                    <td style="padding: 30px 40px;">
+                      <p style="margin: 0 0 16px 0; font-size: 16px;">
+                        Dear {0},
+                      </p>
+                      <p style="margin: 0 0 20px 0; font-size: 16px;">
+                        Your submitted document has been approved.
+                      </p>
+                      <table role="presentation" width="100%" style="margin: 20px 0; font-size: 16px; border-collapse: collapse;">
+                        <tr>
+                          <td style="padding: 6px 0; width: 120px; font-weight: bold; color: #555555;">Document:</td>
+                          <td style="padding: 6px 0;">{1}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Project:</td>
+                          <td style="padding: 6px 0;">{2}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Location:</td>
+                          <td style="padding: 6px 0;">{3}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Approved At:</td>
+                          <td style="padding: 6px 0; color: #2e7d32;"><strong>{4}</strong></td>
+                        </tr>
+                      </table>
+                      <p style="margin: 20px 0 0 0; font-size: 16px; color: #2e7d32;">
+                        Thank you for your cooperation.
+                      </p>
+                    </td>
+                  </tr>
+                  <!-- Footer -->
+                  <tr>
+                    <td style="padding: 20px 40px; background-color: #f1f1f1; font-size: 14px; color: #777777; border-top: 1px solid #eeeeee;">
+                      This is an automated message. Please do not reply directly to this email.
+                    </td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+          </table>
+        </body>
+        </html>
+        """), QC_REJECT("质控驳回通知", "Quality Control Rejected", """
+            <!DOCTYPE html>
+            <html>
+            <head>
+              <meta charset="UTF-8">
+              <meta name="viewport" content="width=device-width, initial-scale=1.0">
+              <title>质控驳回</title>
+            </head>
+            <body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f9f9f9; color: #333333; line-height: 1.6;">
+              <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f9f9f9; padding: 20px 0;">
+                <tr>
+                  <td align="center">
+                    <table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.1); overflow: hidden; border: 1px solid #eaeaea;">
+                      <tr>
+                        <td style="padding: 30px 40px 20px 40px; background-color: #ffebee; border-bottom: 1px solid #ffcdd2;">
+                          <h2 style="margin: 0; font-size: 22px; color: #e74c3c;">❌ 质控未通过</h2>
+                        </td>
+                      </tr>
+                      <tr>
+                        <td style="padding: 0 40px 30px 40px;">
+                          <p style="margin: 20px 0 10px 0; font-size: 16px;">
+                            尊敬的{0}:
+                          </p>
+                          <p style="margin: 10px 0; font-size: 16px;">
+                            您负责的指控任务在质控环节被驳回,请根据以下反馈修改并重新提交:
+                          </p>
+                          <ul style="margin: 15px 0 20px 0; padding-left: 20px; font-size: 16px;">
+                            <li><strong>指控任务名:</strong>{1}</li>
+                            <li><strong>所属项目:</strong>{2}</li>
+                            <li><strong>文件名称:</strong>{3}</li>
+                            <li><strong>问题类型:</strong>{4}</li>
+                            <li><strong>截止日期:</strong>{6}</li>
+                          </ul>
+                          <div style="margin: 20px 0; padding: 15px; background-color: #fff8f8; border-left: 4px solid #e74c3c; font-size: 16px; color: #c0392b;">
+                            <strong>质控意见:</strong><br>
+                            {5}
+                          </div>
+                          <p style="margin: 20px 0 0 0; font-size: 16px; color: #e74c3c;">
+                            请务必在截止日期前完成修正,以免影响整体进度。
+                          </p>
+                        </td>
+                      </tr>
+                      <tr>
+                        <td style="padding: 20px 40px; background-color: #f1f1f1; font-size: 14px; color: #777777; border-top: 1px solid #eeeeee;">
+                          此邮件由系统自动发送,请勿直接回复。
+                        </td>
+                      </tr>
+                    </table>
+                  </td>
+                </tr>
+              </table>
+            </body>
+            </html>
+        """, """
+        <!DOCTYPE html>
+        <html>
+        <head>
+          <meta charset="UTF-8">
+          <meta name="viewport" content="width=device-width, initial-scale=1.0">
+          <title>Quality Control Rejected</title>
+        </head>
+        <body style="margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; background-color: #f5f7fa; color: #333333; line-height: 1.6;">
+          <table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="background-color: #f5f7fa; padding: 20px 0;">
+            <tr>
+              <td align="center">
+                <table role="presentation" width="600" cellspacing="0" cellpadding="0" style="background-color: #ffffff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); overflow: hidden; border: 1px solid #e0e0e0;">
+                  <!-- Header -->
+                  <tr>
+                    <td style="padding: 30px 40px 20px 40px; background-color: #ffebee; border-bottom: 1px solid #ffcdd2;">
+                      <h2 style="margin: 0; font-size: 22px; color: #c62828;">❌ Quality Control Rejected</h2>
+                    </td>
+                  </tr>
+                  <!-- Body -->
+                  <tr>
+                    <td style="padding: 30px 40px;">
+                      <p style="margin: 0 0 16px 0; font-size: 16px;">
+                        Dear {0},
+                      </p>
+                      <p style="margin: 0 0 20px 0; font-size: 16px;">
+                        Your assigned allegation task has failed quality control. Please revise and resubmit based on the feedback below.
+                      </p>
+                      <table role="presentation" width="100%" style="margin: 20px 0; font-size: 16px; border-collapse: collapse;">
+                        <tr>
+                          <td style="padding: 6px 0; width: 140px; font-weight: bold; color: #555555;">Allegation Task:</td>
+                          <td style="padding: 6px 0;">{1}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Project:</td>
+                          <td style="padding: 6px 0;">{2}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">File Name:</td>
+                          <td style="padding: 6px 0;">{3}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Issue Type:</td>
+                          <td style="padding: 6px 0; color: #d32f2f;">{4}</td>
+                        </tr>
+                        <tr>
+                          <td style="padding: 6px 0; font-weight: bold; color: #555555;">Deadline:</td>
+                          <td style="padding: 6px 0; color: #d32f2f;"><strong>{6}</strong></td>
+                        </tr>
+                      </table>
+                      <div style="margin: 25px 0; padding: 15px; background-color: #fdf6f6; border-left: 4/p>
+                      <div style="margin: 25px 0; padding: 15px; background-color: #fdf6f6; border-left: 4px solid #d32f2f; font-size: 16px; color: #b71c1c;">
+                        <strong>QC Feedback:</strong><br>
+                        {5}
+                      </div>
+                      <p style="margin: 20px 0 0 0; font-size: 16px; color: #c62828;">
+                        Please ensure corrections are completed before the deadline to avoid project delays.
+                      </p>
+                    </td>
+                  </tr>
+                  <!-- Footer -->
+                  <tr>
+                    <td style="padding: 20px 40px; background-color: #f1f1f1; font-size: 14px; color: #777777; border-top: 1px solid #eeeeee;">
+                      This is an automated message. Please do not reply directly to this email.
+                    </td>
+                  </tr>
+                </table>
+              </td>
+            </tr>
+          </table>
+        </body>
+        </html>
+        """), TO_QC("", "", """
 
-        """,
-        """
+        """, """
 
-        """
-    ),
-    TO_AUDIT(
-        """
-
-        """,
-        """
-
-        """
-    ),
-    AUDIT_REJECT(
-        """
-
-        """,
-        """
-
-        """
-    ),
-    AUDIT_PASS(
-        """
-
-        """,
-        """
-
-        """
-    )
+        """),
     ;
 
+    private final String zhTitle;
+    private final String enTitle;
     private final String zhContent;
     private final String enContent;
 
+    public String getTitle() {
+        return LocaleContextHolder.getLocale().getLanguage().equals("zh") ? this.zhTitle : this.enTitle;
+    }
+
+    public String getContent() {
+        return LocaleContextHolder.getLocale().getLanguage().equals("zh") ? this.zhContent : this.enContent;
+    }
+
 }

+ 16 - 0
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/CommonFolderService.java

@@ -207,4 +207,20 @@ public class CommonFolderService {
                 .eq(Folder::getProjectId, projectId)
         );
     }
+
+    public String getPathById(Long folderId, Long projectId) {
+        Map<Long, Folder> map = new HashMap<>();
+        queryByProjectId(projectId).forEach(e -> map.put(e.getId(), e));
+        StringBuilder sb = new StringBuilder();
+        Folder currentFolder = map.get(folderId);
+
+        while (currentFolder != null) {
+            sb.insert(0, currentFolder.getName()).insert(0, "/");
+            if (currentFolder.getParentId() == null) {
+                break;
+            }
+            currentFolder = map.get(currentFolder.getParentId());
+        }
+        return sb.toString();
+    }
 }

+ 48 - 10
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/DocumentQcTaskDetailServiceImpl.java

@@ -1,10 +1,10 @@
 package com.yingpaipay.business.service.impl;
 
-import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import cn.hutool.core.lang.Dict;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yingpaipay.business.constant.DictTypeConst;
 import com.yingpaipay.business.constant.QcTaskStatusConst;
 import com.yingpaipay.business.domain.*;
 import com.yingpaipay.business.domain.bo.DocumentQcAuditBo;
@@ -16,25 +16,31 @@ import com.yingpaipay.business.domain.vo.DocumentQcTaskDetailListVo;
 import com.yingpaipay.business.domain.vo.TaskCenterQcListVo;
 import com.yingpaipay.business.enumeration.DocumentQcStatusEnum;
 import com.yingpaipay.business.enumeration.DocumentStatusEnum;
+import com.yingpaipay.business.enumeration.MailTemplateEnum;
 import com.yingpaipay.business.mapper.DocumentQcTaskLogMapper;
 import com.yingpaipay.business.mapper.DocumentQcTaskMapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.dromara.common.core.exception.BusinessException;
-import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.DateUtils;
 import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mail.utils.MailUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.system.domain.SysUser;
 import org.dromara.system.domain.vo.SysUserVo;
+import org.dromara.system.service.ISysDictTypeService;
 import org.dromara.system.service.ISysUserService;
+import org.springframework.context.i18n.LocaleContextHolder;
 import org.springframework.stereotype.Service;
 import com.yingpaipay.business.mapper.DocumentQcTaskDetailMapper;
 import com.yingpaipay.business.service.IDocumentQcTaskDetailService;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.text.MessageFormat;
 import java.util.*;
 
 /**
@@ -55,12 +61,9 @@ public class DocumentQcTaskDetailServiceImpl implements IDocumentQcTaskDetailSer
     private final CommonDocumentService documentService;
     private final CommonProjectService projectService;
     private final ISysUserService userService;
+    private final ISysDictTypeService dictTypeService;
 
-    /**
-     * FIXME 需要考虑到并发场景下重复生成同一个文件的质控任务
-     * 以目前的场景,可暂时不考虑
-     */
-    @Transactional(rollbackFor = Exception.class)
+    @Deprecated
     @Override
     public List<DocumentQcGenerateVo> generate(Long projectId) {
 
@@ -141,7 +144,34 @@ public class DocumentQcTaskDetailServiceImpl implements IDocumentQcTaskDetailSer
     public boolean audit(DocumentQcAuditBo bo) {
 
         DocumentQcTaskDetail detail = baseMapper.selectById(bo.getDetailId());
+        DocumentQcTask task = taskMapper.selectById(bo.getTaskId());
         Document document = documentService.selectById(detail.getDocumentId());
+
+        if (bo.getResult().equals(DocumentStatusEnum.QC_REJECT.getValue())) {
+            Map<String, String> questionTypeMap = new HashMap<>();
+            dictTypeService.selectDictDataByType(DictTypeConst.QC_QUESTION_TYPE)
+                .forEach(e -> questionTypeMap.put(e.getDictValue(), e.getDictLabel()));
+            MailTemplateEnum template = MailTemplateEnum.QC_REJECT;
+            SysUserVo designatedDealer = userService.getById(bo.getDesignatedDealer());
+            String typeContext = Optional.ofNullable(questionTypeMap.get(bo.getRejectionType()))
+                .orElse("{\"zh_CN\":\"未知问题\",\"en_US\":\"Unknown Question\"}");
+            Dict type = JsonUtils.parseMap(typeContext);
+            MailUtils.sendHtml(
+                designatedDealer.getEmail(),
+                template.getTitle(),
+                MessageFormat.format(
+                    template.getContent(),
+                    designatedDealer.getNickName(),
+                    task.getName(),
+                    projectService.queryById(detail.getProjectId()).getName(),
+                    document.getName(),
+                    LocaleContextHolder.getLocale().getLanguage().equals("zh") ? type.getStr("zh_CN") : type.getStr("en_US"),
+                    bo.getOpinion(),
+                    DateUtils.formatDate(bo.getDeadline())
+                )
+            );
+        }
+
         DocumentQcTaskLog log = new DocumentQcTaskLog();
         log.setTaskId(detail.getTaskId());
         log.setDetailId(detail.getId());
@@ -181,9 +211,17 @@ public class DocumentQcTaskDetailServiceImpl implements IDocumentQcTaskDetailSer
             throw new RuntimeException("新增日志失败");
         }
 
-        long count = baseMapper.selectCount(Wrappers.lambdaQuery(DocumentQcTaskDetail.class).eq(DocumentQcTaskDetail::getTaskId, detail.getTaskId()).ne(DocumentQcTaskDetail::getStatus, DocumentQcStatusEnum.PASS.getCode()));
+        long count = baseMapper.selectCount(
+            Wrappers.lambdaQuery(DocumentQcTaskDetail.class)
+                .eq(DocumentQcTaskDetail::getTaskId, detail.getTaskId())
+                .ne(DocumentQcTaskDetail::getStatus, DocumentQcStatusEnum.PASS.getCode())
+        );
         if (count == 0) {
-            boolean taskFlag = taskMapper.update(Wrappers.lambdaUpdate(DocumentQcTask.class).eq(DocumentQcTask::getId, detail.getTaskId()).set(DocumentQcTask::getStatus, QcTaskStatusConst.FINISHED)) == 0;
+            boolean taskFlag = taskMapper.update(
+                Wrappers.lambdaUpdate(DocumentQcTask.class)
+                    .eq(DocumentQcTask::getId, detail.getTaskId())
+                    .set(DocumentQcTask::getStatus, QcTaskStatusConst.FINISHED)
+            ) == 0;
             if (taskFlag) {
                 throw new RuntimeException("更新质控任务状态失败");
             }

+ 78 - 10
ruoyi-modules/yingpaipay-business/src/main/java/com/yingpaipay/business/service/impl/DocumentServiceImpl.java

@@ -11,19 +11,21 @@ import com.yingpaipay.business.domain.bo.*;
 import com.yingpaipay.business.domain.excel.ProjectExcel;
 import com.yingpaipay.business.domain.vo.*;
 import com.yingpaipay.business.enumeration.DocumentStatusEnum;
+import com.yingpaipay.business.enumeration.MailTemplateEnum;
 import com.yingpaipay.business.mapper.DocumentAuditLogMapper;
 import com.yingpaipay.common.file.util.PdfUtils;
-import com.yingpaipay.system.domain.SysUserFolders;
 import com.yingpaipay.system.mapper.SysUserFoldersMapper;
 import jakarta.servlet.http.HttpServletResponse;
 import org.apache.commons.io.FilenameUtils;
 import org.dromara.common.core.constant.GlobalConstants;
 import org.dromara.common.core.exception.BusinessException;
+import org.dromara.common.core.utils.DateUtils;
 import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.common.core.utils.MessageUtils;
 import org.dromara.common.core.utils.StringUtils;
 import org.dromara.common.core.utils.file.FileUtils;
 import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.mail.utils.MailUtils;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -38,6 +40,7 @@ import org.dromara.common.satoken.utils.LoginHelper;
 import org.dromara.common.tenant.helper.TenantHelper;
 import org.dromara.system.domain.SysUser;
 import org.dromara.system.domain.vo.SysOssVo;
+import org.dromara.system.domain.vo.SysUserVo;
 import org.dromara.system.service.ISysDictTypeService;
 import org.dromara.system.service.ISysOssService;
 import org.dromara.system.service.ISysUserService;
@@ -52,6 +55,7 @@ import org.springframework.transaction.annotation.Transactional;
 import java.io.*;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.text.MessageFormat;
 import java.time.LocalDate;
 import java.time.ZoneId;
 import java.time.temporal.ChronoUnit;
@@ -186,6 +190,24 @@ public class DocumentServiceImpl implements IDocumentService {
     public Boolean insertByBo(DocumentBo bo) {
         Document add = MapstructUtils.convert(bo, Document.class);
 
+        if (bo.getType().equals(DocumentTypeConst.PLAN)) {
+
+            SysUserVo user = userService.getById(bo.getPlanSubmitter());
+            MailTemplateEnum template = MailTemplateEnum.TO_SUBMIT;
+            MailUtils.sendHtml(
+                user.getEmail(),
+                template.getTitle(),
+                MessageFormat.format(
+                    template.getContent(),
+                    user.getNickName(),
+                    bo.getName(),
+                    projectService.queryById(bo.getProjectId()).getName(),
+                    folderService.getPathById(bo.getFolderId(), bo.getProjectId()),
+                    DateUtils.formatDateTime(bo.getSubmitDeadline())
+                )
+            );
+        }
+
         add.setSpecificationType(folderService.getSpecificationType(bo.getFolderId(), bo.getProjectId()));
 
         validEntityBeforeSave(add);
@@ -242,15 +264,28 @@ public class DocumentServiceImpl implements IDocumentService {
 
     @Override
     public boolean submit(DocumentSubmitBo bo) {
-        return baseMapper.update(
-            Wrappers.lambdaUpdate(Document.class)
-                .eq(Document::getId, bo.getDocumentId())
-                .set(Document::getActualDocument, bo.getOssId())
-                .set(Document::getSubmitTime, new Date())
-                .set(Document::getSubmitter, LoginHelper.getUserId())
-                .set(Document::getEffectiveDate, bo.getEffectiveDate())
-                .set(Document::getStatus, DocumentStatusEnum.UN_AUDIT.getValue())
-        ) > 0;
+
+        MailTemplateEnum template = MailTemplateEnum.TO_AUDIT;
+        Document document = baseMapper.selectById(bo.getDocumentId());
+        SysUserVo auditor = userService.getById(document.getCreateBy());
+        MailUtils.sendHtml(
+            auditor.getEmail(),
+            template.getTitle(),
+            MessageFormat.format(
+                template.getContent(),
+                auditor.getNickName(),
+                document.getName(),
+                projectService.queryById(document.getProjectId()).getName(),
+                folderService.getPathById(document.getFolderId(), document.getProjectId()),
+                DateUtils.formatDateTime(new Date())
+                )
+        );
+
+        document.setActualDocument(bo.getOssId());
+        document.setSubmitTime(new Date());
+        document.setEffectiveDate(bo.getEffectiveDate());
+        document.setStatus(DocumentStatusEnum.UN_AUDIT.getValue());
+        return baseMapper.updateById(document) > 0;
     }
 
     @Override
@@ -262,6 +297,39 @@ public class DocumentServiceImpl implements IDocumentService {
             throw new BusinessException(MessageUtils.message("document.document.audit.documentnotfound"));
         }
 
+        SysUserVo user = userService.getById(document.getPlanSubmitter() != null ? document.getPlanSubmitter() : document.getSubmitter());
+
+        if (bo.getResult().equals(DocumentStatusEnum.AUDIT_REJECT.getValue())) {
+            MailTemplateEnum template = MailTemplateEnum.AUDIT_REJECT;
+            MailUtils.sendHtml(
+                user.getEmail(),
+                template.getTitle(),
+                MessageFormat.format(
+                    template.getContent(),
+                    user.getNickName(),
+                    document.getName(),
+                    projectService.queryById(document.getProjectId()).getName(),
+                    folderService.getPathById(document.getFolderId(), document.getProjectId()),
+                    DateUtils.formatDateTime(new Date()),
+                    bo.getRejectReason()
+                )
+            );
+        } else {
+            MailTemplateEnum template = MailTemplateEnum.AUDIT_PASS;
+            MailUtils.sendHtml(
+                user.getEmail(),
+                template.getTitle(),
+                MessageFormat.format(
+                    template.getContent(),
+                    user.getNickName(),
+                    document.getName(),
+                    projectService.queryById(document.getProjectId()).getName(),
+                    folderService.getPathById(document.getFolderId(), document.getProjectId()),
+                    DateUtils.formatDateTime(new Date())
+                )
+            );
+        }
+
         if (Objects.equals(bo.getResult(), DocumentStatusEnum.UN_ARCHIEVED.getValue())) {
             document.setPassTime(new Date());
         }

+ 109 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/controller/KeywordSettingController.java

@@ -0,0 +1,109 @@
+package com.yingpaipay.setting.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.excel.utils.ExcelUtil;
+import com.yingpaipay.setting.domain.vo.KeywordSettingVo;
+import com.yingpaipay.setting.domain.bo.KeywordSettingBo;
+import com.yingpaipay.setting.service.IKeywordSettingService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 文件夹关键词设置
+ *
+ * @author Huanyi
+ * @date 2026-01-20
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/setting/keyword")
+public class KeywordSettingController extends BaseController {
+
+    private final IKeywordSettingService keywordSettingService;
+
+    /**
+     * 查询文件夹关键词设置列表
+     */
+    @SaCheckPermission("setting:keyword:list")
+    @GetMapping("/list")
+    public TableDataInfo<KeywordSettingVo> list(KeywordSettingBo bo, PageQuery pageQuery) {
+        return keywordSettingService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出文件夹关键词设置列表
+     */
+    @SaCheckPermission("setting:keyword:export")
+    @Log(title = "文件夹关键词设置", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(KeywordSettingBo bo, HttpServletResponse response) {
+        List<KeywordSettingVo> list = keywordSettingService.queryList(bo);
+        ExcelUtil.exportExcel(list, "文件夹关键词设置", KeywordSettingVo.class, response);
+    }
+
+    /**
+     * 获取文件夹关键词设置详细信息
+     *
+     * @param id 主键
+     */
+    @GetMapping("/{id}")
+    public R<KeywordSettingVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable Long id) {
+        return R.ok(keywordSettingService.queryById(id));
+    }
+
+    /**
+     * 新增文件夹关键词设置
+     */
+    @SaCheckPermission("setting:keyword:add")
+    @Log(title = "文件夹关键词设置", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody KeywordSettingBo bo) {
+        return toAjax(keywordSettingService.insertByBo(bo));
+    }
+
+    /**
+     * 修改文件夹关键词设置
+     */
+    @SaCheckPermission("setting:keyword:edit")
+    @Log(title = "文件夹关键词设置", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody KeywordSettingBo bo) {
+        return toAjax(keywordSettingService.updateByBo(bo));
+    }
+
+    /**
+     * 删除文件夹关键词设置
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("setting:keyword:remove")
+    @Log(title = "文件夹关键词设置", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable Long[] ids) {
+        return toAjax(keywordSettingService.deleteWithValidByIds(List.of(ids), true));
+    }
+
+    @GetMapping("/listOnFolder")
+    public R<List<KeywordSettingVo>> listOnFolder() {
+        return R.ok(keywordSettingService.list());
+    }
+}

+ 14 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/domain/FolderKeyword.java

@@ -0,0 +1,14 @@
+package com.yingpaipay.setting.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+@Data
+@TableName("folder_keyword")
+public class FolderKeyword {
+
+    private Long folderId;
+
+    private Long keywordId;
+
+}

+ 47 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/domain/KeywordSetting.java

@@ -0,0 +1,47 @@
+package com.yingpaipay.setting.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 文件夹关键词设置对象 keyword_setting
+ *
+ * @author Huanyi
+ * @date 2026-01-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("keyword_setting")
+public class KeywordSetting extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 序号
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 文本
+     */
+    private String content;
+
+    /**
+     * 备注
+     */
+    private String note;
+
+    /**
+     * 删除标志(0代表存在 1代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+
+}

+ 41 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/domain/bo/KeywordSettingBo.java

@@ -0,0 +1,41 @@
+package com.yingpaipay.setting.domain.bo;
+
+import com.yingpaipay.setting.domain.KeywordSetting;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 文件夹关键词设置业务对象 keyword_setting
+ *
+ * @author Huanyi
+ * @date 2026-01-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = KeywordSetting.class, reverseConvertGenerate = false)
+public class KeywordSettingBo extends BaseEntity {
+
+    /**
+     * 序号
+     */
+    @NotNull(message = "序号不能为空", groups = { EditGroup.class })
+    private Long id;
+
+    /**
+     * 文本
+     */
+    @NotBlank(message = "文本不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String content;
+
+    /**
+     * 备注
+     */
+    private String note;
+
+
+}

+ 50 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/domain/vo/KeywordSettingVo.java

@@ -0,0 +1,50 @@
+package com.yingpaipay.setting.domain.vo;
+
+import com.yingpaipay.setting.domain.KeywordSetting;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 文件夹关键词设置视图对象 keyword_setting
+ *
+ * @author Huanyi
+ * @date 2026-01-20
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = KeywordSetting.class)
+public class KeywordSettingVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 序号
+     */
+    @ExcelProperty(value = "序号")
+    private Long id;
+
+    /**
+     * 文本
+     */
+    @ExcelProperty(value = "文本")
+    private String content;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String note;
+
+
+}

+ 7 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/mapper/FolderKeywordMapper.java

@@ -0,0 +1,7 @@
+package com.yingpaipay.setting.mapper;
+
+import com.yingpaipay.setting.domain.FolderKeyword;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+public interface FolderKeywordMapper extends BaseMapperPlus<FolderKeyword, FolderKeyword> {
+}

+ 15 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/mapper/KeywordSettingMapper.java

@@ -0,0 +1,15 @@
+package com.yingpaipay.setting.mapper;
+
+import com.yingpaipay.setting.domain.KeywordSetting;
+import com.yingpaipay.setting.domain.vo.KeywordSettingVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 文件夹关键词设置Mapper接口
+ *
+ * @author Huanyi
+ * @date 2026-01-20
+ */
+public interface KeywordSettingMapper extends BaseMapperPlus<KeywordSetting, KeywordSettingVo> {
+
+}

+ 4 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/IFolderKeywordService.java

@@ -0,0 +1,4 @@
+package com.yingpaipay.setting.service;
+
+public interface IFolderKeywordService {
+}

+ 70 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/IKeywordSettingService.java

@@ -0,0 +1,70 @@
+package com.yingpaipay.setting.service;
+
+import com.yingpaipay.setting.domain.vo.KeywordSettingVo;
+import com.yingpaipay.setting.domain.bo.KeywordSettingBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 文件夹关键词设置Service接口
+ *
+ * @author Huanyi
+ * @date 2026-01-20
+ */
+public interface IKeywordSettingService {
+
+    /**
+     * 查询文件夹关键词设置
+     *
+     * @param id 主键
+     * @return 文件夹关键词设置
+     */
+    KeywordSettingVo queryById(Long id);
+
+    /**
+     * 分页查询文件夹关键词设置列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 文件夹关键词设置分页列表
+     */
+    TableDataInfo<KeywordSettingVo> queryPageList(KeywordSettingBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的文件夹关键词设置列表
+     *
+     * @param bo 查询条件
+     * @return 文件夹关键词设置列表
+     */
+    List<KeywordSettingVo> queryList(KeywordSettingBo bo);
+
+    /**
+     * 新增文件夹关键词设置
+     *
+     * @param bo 文件夹关键词设置
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(KeywordSettingBo bo);
+
+    /**
+     * 修改文件夹关键词设置
+     *
+     * @param bo 文件夹关键词设置
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(KeywordSettingBo bo);
+
+    /**
+     * 校验并批量删除文件夹关键词设置信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    List<KeywordSettingVo> list();
+}

+ 14 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/impl/FolderKeywordServiceImpl.java

@@ -0,0 +1,14 @@
+package com.yingpaipay.setting.service.impl;
+
+import com.yingpaipay.setting.mapper.FolderKeywordMapper;
+import com.yingpaipay.setting.service.IFolderKeywordService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class FolderKeywordServiceImpl implements IFolderKeywordService {
+
+    private final FolderKeywordMapper baseMapper;
+
+}

+ 153 - 0
ruoyi-modules/yingpaipay-setting/src/main/java/com/yingpaipay/setting/service/impl/KeywordSettingServiceImpl.java

@@ -0,0 +1,153 @@
+package com.yingpaipay.setting.service.impl;
+
+import org.dromara.common.core.constant.CacheNames;
+import org.dromara.common.core.exception.BusinessException;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.MessageUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.redis.utils.CacheUtils;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import com.yingpaipay.setting.domain.bo.KeywordSettingBo;
+import com.yingpaipay.setting.domain.vo.KeywordSettingVo;
+import com.yingpaipay.setting.domain.KeywordSetting;
+import com.yingpaipay.setting.mapper.KeywordSettingMapper;
+import com.yingpaipay.setting.service.IKeywordSettingService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 文件夹关键词设置Service业务层处理
+ *
+ * @author Huanyi
+ * @date 2026-01-20
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class KeywordSettingServiceImpl implements IKeywordSettingService {
+
+    private final KeywordSettingMapper baseMapper;
+
+    /**
+     * 查询文件夹关键词设置
+     *
+     * @param id 主键
+     * @return 文件夹关键词设置
+     */
+    @Cacheable(cacheNames = CacheNames.KEYWORD_SETTING, key = "#id")
+    @Override
+    public KeywordSettingVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询文件夹关键词设置列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 文件夹关键词设置分页列表
+     */
+    @Override
+    public TableDataInfo<KeywordSettingVo> queryPageList(KeywordSettingBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<KeywordSetting> lqw = buildQueryWrapper(bo);
+        Page<KeywordSettingVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的文件夹关键词设置列表
+     *
+     * @param bo 查询条件
+     * @return 文件夹关键词设置列表
+     */
+    @Override
+    public List<KeywordSettingVo> queryList(KeywordSettingBo bo) {
+        LambdaQueryWrapper<KeywordSetting> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<KeywordSetting> buildQueryWrapper(KeywordSettingBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<KeywordSetting> lqw = Wrappers.lambdaQuery();
+        lqw.orderByDesc(KeywordSetting::getId);
+        lqw.eq(StringUtils.isNotBlank(bo.getContent()), KeywordSetting::getContent, bo.getContent());
+        return lqw;
+    }
+
+    /**
+     * 新增文件夹关键词设置
+     *
+     * @param bo 文件夹关键词设置
+     * @return 是否新增成功
+     */
+    @CachePut(cacheNames = CacheNames.KEYWORD_SETTING, key = "#bo.id")
+    @Override
+    public Boolean insertByBo(KeywordSettingBo bo) {
+        KeywordSetting add = MapstructUtils.convert(bo, KeywordSetting.class);
+        validEntityBeforeSave(add);
+
+        if (baseMapper.selectCount(Wrappers.lambdaQuery(KeywordSetting.class).eq(KeywordSetting::getContent, bo.getContent())) > 0L) {
+            throw new BusinessException(MessageUtils.message("setting.keyword.add.exists"));
+        }
+
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改文件夹关键词设置
+     *
+     * @param bo 文件夹关键词设置
+     * @return 是否修改成功
+     */
+    @CachePut(cacheNames = CacheNames.KEYWORD_SETTING, key = "#bo.id")
+    @Override
+    public Boolean updateByBo(KeywordSettingBo bo) {
+        KeywordSetting update = MapstructUtils.convert(bo, KeywordSetting.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(KeywordSetting entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除文件夹关键词设置信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        ids.forEach(id -> CacheUtils.evict(CacheNames.KEYWORD_SETTING, id));
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    @Cacheable(cacheNames = CacheNames.KEYWORD_SETTING)
+    @Override
+    public List<KeywordSettingVo> list() {
+        return baseMapper.selectVoList();
+    }
+}

+ 23 - 0
script/sql/business/create.sql

@@ -221,3 +221,26 @@ CREATE TABLE `textin_setting`
     `tenant_id`   varchar(40) COMMENT '租户id'
 ) ENGINE = InnoDB
   DEFAULT CHARSET = utf8mb4 COMMENT ='合合信息科技设置';
+
+CREATE TABLE `keyword_setting`
+(
+    `id`          bigint unsigned PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '序号',
+    `content`     varchar(255)                NOT NULL COMMENT '文本',
+    `note`        varchar(255) COMMENT '备注',
+    `create_dept` bigint(20) COMMENT '创建部门',
+    `create_by`   bigint(20) COMMENT '创建者',
+    `create_time` datetime COMMENT '创建时间',
+    `update_by`   bigint(20) COMMENT '更新者',
+    `update_time` datetime COMMENT '更新时间',
+    `del_flag`    char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
+    `tenant_id`   varchar(40) COMMENT '租户id'
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4 COMMENT ='文件夹关键词设置';
+
+CREATE TABLE `folder_keyword`
+(
+    `folder_id`  bigint NOT NULL COMMENT '文件夹ID',
+    `keyword_id` bigint NOT NULL COMMENT '关键词ID',
+    PRIMARY KEY (`folder_id`, `keyword_id`)
+) ENGINE = InnoDB
+  DEFAULT CHARSET = utf8mb4 COMMENT ='文件夹关键词关系表设置';