From 10b88a7ff17caf92f3d4e8a550c1085a70c2517a Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期四, 28 五月 2026 17:43:26 +0800
Subject: [PATCH] Merge dev_New_pro into dev_山西_晋和园_pro

---
 src/main/java/com/ruoyi/approve/controller/ApprovalTaskController.java                             |   18 
 src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceMapper.java                                 |   24 
 src/main/java/com/ruoyi/sales/service/ShippingInfoService.java                                     |    2 
 src/main/java/com/ruoyi/common/utils/OrderUtils.java                                               |   19 
 src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeController.java                     |   18 
 src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java                                    |   21 
 src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java                           |    5 
 src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java                              |    1 
 src/main/resources/mapper/measuringinstrumentledger/SparePartsMapper.xml                           |    3 
 src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeService.java                           |   20 
 src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeDeptMapper.xml                  |   13 
 src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java                   |    8 
 src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeDept.java                    |   59 
 src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java                                             | 1012 ++++
 src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java                                    |    1 
 src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml                             |    1 
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java                     |    1 
 src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java           |    2 
 src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java                                             |    4 
 src/main/java/com/ruoyi/approve/mapper/ApprovalTaskMapper.java                                     |   18 
 src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java                      |  250 +
 src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java            |    4 
 src/main/resources/mapper/system/SysPostMapper.xml                                                 |   31 
 src/main/resources/mapper/system/SysDictTypeMapper.xml                                             |   25 
 src/main/resources/mapper/aftersalesservice/AfterSalesNearExpiryMapper.xml                         |   23 
 src/main/java/com/ruoyi/approve/pojo/ApprovalInstance.java                                         |  151 
 src/main/java/com/ruoyi/approve/mapper/FinReimbursementMapper.java                                 |   24 
 src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java                 |    1 
 src/main/java/com/ruoyi/approve/mapper/FinReimbursementDetailMapper.java                           |   18 
 src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateDto.java                                  |   12 
 src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml                                     |   35 
 src/main/resources/mapper/approve/ApprovalInstanceMapper.xml                                       |   58 
 src/main/java/com/ruoyi/approve/pojo/ApprovalTask.java                                             |  128 
 src/main/resources/mapper/sales/SalesQuotationMapper.xml                                           |   27 
 src/main/java/com/ruoyi/approve/controller/FinReimbursementController.java                         |   60 
 src/main/java/com/ruoyi/approve/service/ApprovalRecordService.java                                 |   16 
 src/main/resources/mapper/collaborativeApproval/NoticeMapper.xml                                   |    1 
 src/main/resources/mapper/collaborativeApproval/RulesRegulationsManagementMapper.xml               |    3 
 src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml                     |    1 
 src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeVo.java                                |   13 
 src/main/java/com/ruoyi/approve/controller/ApprovalInstanceNodeController.java                     |   19 
 src/main/java/com/ruoyi/sales/controller/PaymentShippingController.java                            |    3 
 src/main/resources/financial-agent-prompt.txt                                                      |   12 
 src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeUserMapper.xml                  |   13 
 src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java                          |   92 
 src/main/java/com/ruoyi/approve/service/FinReimbursementTravelService.java                         |   16 
 src/main/resources/mapper/staff/StaffLeaveMapper.xml                                               |   32 
 src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java                               |   22 
 src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java                        |  131 
 src/main/resources/mapper/account/sales/AccountSalesInvoiceMapper.xml                              |    1 
 src/main/java/com/ruoyi/approve/service/impl/FinReimbursementDetailServiceImpl.java                |   20 
 src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java                                               |    1 
 src/main/resources/mapper/approve/ApproveProcessMapper.xml                                         |    2 
 src/main/resources/mapper/approve/ApprovalTemplateNodeMapper.xml                                   |   15 
 src/main/java/com/ruoyi/approve/controller/ApprovalRecordController.java                           |   18 
 src/main/resources/mapper/approve/ApprovalRecordMapper.xml                                         |   20 
 src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java                         |    1 
 src/main/resources/mapper/system/SysUserMapper.xml                                                 |  419 
 src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java                                            |    5 
 src/main/java/com/ruoyi/project/system/mapper/SysUserDeptMapper.java                               |   25 
 src/main/java/com/ruoyi/common/enums/TypeEnums.java                                                |   70 
 src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java                              |   31 
 src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeApproverController.java             |   18 
 src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java                      |  544 ++
 src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java                            |   34 
 src/main/resources/mapper/staff/StaffOnJobMapper.xml                                               |  108 
 src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java                                          | 2311 ++++++++++
 src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingController.java                |    2 
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java                    |    8 
 src/main/java/com/ruoyi/approve/bean/dto/ApprovalInstanceDto.java                                  |   21 
 src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeUser.java                    |   59 
 src/main/java/com/ruoyi/approve/controller/FinReimbursementTravelController.java                   |   18 
 src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java                                    |   28 
 src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeUserController.java    |   18 
 src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java                                         |    4 
 src/main/java/com/ruoyi/quality/service/IQualityInspectService.java                                |    3 
 src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java                          |    4 
 src/main/resources/mapper/account/sales/AccountInvoiceApplicationMapper.xml                        |    1 
 src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNodeApprover.java                             |   76 
 src/main/java/com/ruoyi/projectManagement/controller/RolesController.java                          |    4 
 src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNews.java                             |   88 
 src/main/resources/mapper/warehouse/DocumentationBorrowManagementMapper.xml                        |    1 
 src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesService.java                              |    1 
 src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerRecordMapper.xml      |    4 
 src/main/java/com/ruoyi/approve/controller/ApprovalInstanceController.java                         |   67 
 src/main/resources/mapper/device/DeviceMaintenanceMapper.xml                                       |   16 
 src/main/java/com/ruoyi/approve/controller/FinReimbursementDetailController.java                   |   18 
 src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml                |    1 
 src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java                    |    4 
 src/main/java/com/ruoyi/procurementrecord/pojo/CustomStorage.java                                  |    1 
 src/main/java/com/ruoyi/approve/service/FinReimbursementService.java                               |   31 
 src/main/java/com/ruoyi/approve/utils/ApproveProcessConfigNodeUtils.java                           |  363 +
 src/main/java/com/ruoyi/collaborativeApproval/vo/EnterpriseNewsVo.java                             |   15 
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java                             |   59 
 src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsServiceImpl.java          |  409 +
 src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java                          |   13 
 src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java                                        |    3 
 src/main/resources/mapper/stock/StockInventoryMapper.xml                                           |    2 
 src/main/resources/mapper/approve/ApprovalInstanceNodeMapper.xml                                   |   21 
 src/main/java/com/ruoyi/approve/service/impl/FinReimbursementTravelServiceImpl.java                |   20 
 src/main/resources/application-hqjc.yml                                                            |  268 +
 src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationService.java             |    1 
 src/main/java/com/ruoyi/ai/schedule/AiSessionCleanupTask.java                                      |   24 
 src/main/resources/application-ckgm.yml                                                            |  268 +
 src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsController.java             |   61 
 src/main/java/com/ruoyi/approve/pojo/FinReimbursementTravel.java                                   |  162 
 src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeDeptMapper.java            |   18 
 src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml                                        |    6 
 src/main/resources/mapper/measuringinstrumentledger/SparePartsRequisitionRecordMapper.xml          |    1 
 src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsMapper.java                     |   24 
 src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java                                |   26 
 src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeServiceImpl.java                  |   62 
 src/main/java/com/ruoyi/approve/pojo/FinReimbursementDetail.java                                   |  157 
 src/main/resources/mapper/approve/FinReimbursementTravelMapper.xml                                 |   29 
 src/main/java/com/ruoyi/technology/pojo/TechnologyRouting.java                                     |    1 
 src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java              |    2 
 src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeUserMapper.java            |   18 
 src/main/java/com/ruoyi/staff/service/IStaffOnJobService.java                                      |    2 
 src/main/java/com/ruoyi/approve/service/impl/ApprovalTaskServiceImpl.java                          |   20 
 src/main/resources/mapper/aftersalesservice/AfterSalesServiceMapper.xml                            |    4 
 src/main/java/com/ruoyi/common/enums/ApprovalStatusEnum.java                                       |   49 
 src/main/java/com/ruoyi/approve/bean/vo/FinReimbursementVo.java                                    |   34 
 src/main/java/com/ruoyi/projectManagement/pojo/Roles.java                                          |    1 
 src/main/java/com/ruoyi/approve/service/ApprovalInstanceNodeService.java                           |   16 
 src/main/resources/mapper/warehouse/DocumentationMapper.xml                                        |    1 
 src/main/java/com/ruoyi/ai/controller/FinancialAiController.java                                   |  144 
 src/main/resources/mapper/approve/FinReimbursementMapper.xml                                       |   62 
 src/main/java/com/ruoyi/approve/pojo/FinReimbursement.java                                         |  209 
 src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml            |    2 
 src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceNodeServiceImpl.java                  |   20 
 src/main/resources/mapper/sales/SalesLedgerMapper.xml                                              |   10 
 src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml                            |    2 
 src/main/resources/mapper/basic/SupplierManageMapper.xml                                           |   83 
 src/main/java/com/ruoyi/quality/utils/QualityInspectHelper.java                                    |   73 
 src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java                      |  846 +++
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java                      |   15 
 src/main/java/com/ruoyi/approve/pojo/ApprovalRecord.java                                           |   98 
 src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceNodeMapper.java                             |   18 
 src/main/resources/mapper/account/purchase/AccountPurchaseInvoiceMapper.xml                        |    1 
 src/main/resources/mapper/quality/QualityTestStandardMapper.xml                                    |    1 
 src/main/java/com/ruoyi/approve/service/impl/ApprovalRecordServiceImpl.java                        |   20 
 src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNode.java                                     |   66 
 src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateMapper.java                                 |   24 
 src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeMapper.java                             |   18 
 src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java                                  |  284 +
 src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeUserServiceImpl.java |   20 
 src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeApproverServiceImpl.java          |   20 
 src/main/java/com/ruoyi/approve/pojo/ApprovalInstanceNode.java                                     |  106 
 src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java           |    2 
 src/main/resources/mapper/quality/QualityInspectMapper.xml                                         |    9 
 src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java                        |    3 
 src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeDeptServiceImpl.java |   20 
 src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java                               |    1 
 src/main/resources/mapper/warehouse/DocumentationReturnManagementMapper.xml                        |    1 
 src/main/resources/mapper/approve/ApprovalTemplateMapper.xml                                       |   33 
 src/main/java/com/ruoyi/technology/pojo/TechnologyParam.java                                       |    6 
 src/main/resources/mapper/approve/ApprovalTemplateNodeApproverMapper.xml                           |   16 
 src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeApproverMapper.java                     |   18 
 src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java                        |   35 
 src/main/java/com/ruoyi/staff/dto/StaffOnJobDto.java                                               |    2 
 src/main/resources/mapper/basic/CustomerMapper.xml                                                 |    4 
 src/main/java/com/ruoyi/approve/bean/dto/FinReimbursementDto.java                                  |   21 
 src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeDeptController.java    |   18 
 src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java                               |    6 
 src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeApproverService.java                   |   16 
 src/main/resources/mapper/approve/ApprovalTaskMapper.xml                                           |   24 
 src/main/resources/mapper/system/SysNoticeMapper.xml                                               |    5 
 src/main/java/com/ruoyi/common/enums/ShippingStatusEnum.java                                       |   42 
 src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java                       |  455 ++
 src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java                                             |    1 
 src/main/java/com/ruoyi/approve/service/ApprovalTaskService.java                                   |   16 
 src/main/java/com/ruoyi/approve/mapper/ApprovalRecordMapper.java                                   |   18 
 src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsService.java                   |   29 
 src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java                  |    4 
 src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeUserService.java          |   16 
 src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java                   |    1 
 src/main/resources/mapper/technology/TechnologyOperationMapper.xml                                 |    2 
 src/main/java/com/ruoyi/approve/pojo/ApprovalTemplate.java                                         |  107 
 src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java                          |    1 
 src/main/resources/mapper/collaborativeApproval/EnterpriseNewsMapper.xml                           |   47 
 src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java                      |   92 
 src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeDto.java                              |   12 
 src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java                 |    2 
 src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java                                 |    2 
 src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java                           |  128 
 src/main/resources/mapper/production/ProductionOperationTaskMapper.xml                             |    2 
 src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java           |    4 
 src/main/java/com/ruoyi/common/enums/EnterpriseNewsStatusEnum.java                                 |  143 
 src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java                                       |   65 
 src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeApproverVo.java                        |    9 
 src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml                                |    1 
 src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java                                             |    1 
 src/main/java/com/ruoyi/account/service/impl/financial/AccountSubjectServiceImpl.java              |    6 
 src/main/java/com/ruoyi/collaborativeApproval/dto/EnterpriseNewsDto.java                           |   26 
 src/main/java/com/ruoyi/approve/controller/ApprovalTemplateController.java                         |   74 
 src/main/java/com/ruoyi/approve/service/FinReimbursementDetailService.java                         |   16 
 src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java                                |    2 
 src/main/java/com/ruoyi/common/enums/SalesQuotationStatusEnum.java                                 |   43 
 src/main/resources/mapper/approve/FinReimbursementDetailMapper.xml                                 |   29 
 src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java                                           |    2 
 src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java                                          |    1 
 src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeDeptService.java          |   16 
 src/main/resources/mapper/account/purchase/AccountPurchasePaymentMapper.xml                        |    1 
 src/main/java/com/ruoyi/stock/pojo/StockInRecord.java                                              |    2 
 src/main/resources/mapper/account/sales/AccountSalesCollectionMapper.xml                           |    2 
 src/main/java/com/ruoyi/approve/service/ApprovalTemplateService.java                               |   34 
 src/main/resources/mapper/staff/PersonalShiftMapper.xml                                            |    4 
 src/main/resources/mapper/approve/KnowledgeBaseMapper.xml                                          |    4 
 src/main/java/com/ruoyi/approve/mapper/FinReimbursementTravelMapper.java                           |   18 
 src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateVo.java                                    |   14 
 src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java                               |   33 
 src/main/java/com/ruoyi/sales/pojo/PaymentShipping.java                                            |    1 
 src/main/java/com/ruoyi/quality/controller/QualityTestStandardParamController.java                 |    4 
 src/main/resources/mapper/device/DeviceRepairMapper.xml                                            |    2 
 src/main/resources/mapper/warehouse/WarehouseGoodsShelvesRowcolMapper.xml                          |    1 
 src/main/java/com/ruoyi/approve/service/impl/ApproveBusinessStatusService.java                     |  175 
 src/main/java/com/ruoyi/production/pojo/ProductionOrder.java                                       |    1 
 src/main/resources/mapper/system/SysDeptMapper.xml                                                 |   38 
 src/main/resources/mapper/system/SysRoleMapper.xml                                                 |   32 
 src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeApproverDto.java                      |    8 
 220 files changed, 12,161 insertions(+), 762 deletions(-)

diff --git a/src/main/java/com/ruoyi/account/service/impl/financial/AccountSubjectServiceImpl.java b/src/main/java/com/ruoyi/account/service/impl/financial/AccountSubjectServiceImpl.java
index a6c4149..aa0c858 100644
--- a/src/main/java/com/ruoyi/account/service/impl/financial/AccountSubjectServiceImpl.java
+++ b/src/main/java/com/ruoyi/account/service/impl/financial/AccountSubjectServiceImpl.java
@@ -174,7 +174,7 @@
         if (accountSubjectDto != null && accountSubjectDto.getStatus() != null) {
             queryWrapper.eq(AccountSubject::getStatus, accountSubjectDto.getStatus());
         }
-        queryWrapper.orderByAsc(AccountSubject::getSubjectCode).orderByAsc(AccountSubject::getId);
+        queryWrapper.orderByDesc(AccountSubject::getSubjectCode).orderByDesc(AccountSubject::getId);
         return queryWrapper;
     }
 
@@ -296,8 +296,8 @@
         }
         List<AccountSubject> sortedSubjects = new ArrayList<>(subjects);
         sortedSubjects.sort(Comparator
-                .comparing(AccountSubject::getSubjectCode, Comparator.nullsLast(String::compareTo))
-                .thenComparing(AccountSubject::getId, Comparator.nullsLast(Long::compareTo)));
+                .comparing(AccountSubject::getSubjectCode, Comparator.nullsFirst(String::compareTo)).reversed()
+                .thenComparing(AccountSubject::getId, Comparator.nullsFirst(Long::compareTo)).reversed());
 
         Map<Long, AccountSubjectVo> subjectVoMap = new LinkedHashMap<>();
         for (AccountSubject subject : sortedSubjects) {
diff --git a/src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesService.java b/src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesService.java
index 4b53fba..ee0f5f7 100644
--- a/src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesService.java
+++ b/src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesService.java
@@ -115,7 +115,6 @@
     /**
      * 鍒涘缓鏃堕棿
      */
-    @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createTime;
 
     /**
diff --git a/src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java b/src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java
index 0feab41..b1c7490 100644
--- a/src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java
+++ b/src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java
@@ -11,6 +11,8 @@
 import com.ruoyi.aftersalesservice.service.AfterSalesServiceService;
 import com.ruoyi.common.utils.OrderUtils;
 import com.ruoyi.common.utils.SecurityUtils;
+
+import java.time.LocalDateTime;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.project.system.domain.SysDept;
 import com.ruoyi.project.system.domain.SysUser;
@@ -70,7 +72,7 @@
         if(sysUser == null) throw new RuntimeException("瀹℃牳浜轰笉瀛樺湪");
         afterSalesServiceNewDto.setCheckNickName(sysUser.getNickName());
         if (StringUtils.isEmpty(afterSalesServiceNewDto.getAfterSalesServiceNo())) {
-            String string = OrderUtils.countAfterServiceTodayByCreateTime(afterSalesServiceMapper, "SH_");
+            String string = OrderUtils.countAfterServiceTodayByCreateTime(afterSalesServiceMapper, "SH_", afterSalesServiceNewDto.getCreateTime() != null ? afterSalesServiceNewDto.getCreateTime() : LocalDateTime.now());
             afterSalesServiceNewDto.setAfterSalesServiceNo(string);
         }
         return this.save(afterSalesServiceNewDto);
diff --git a/src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java b/src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java
new file mode 100644
index 0000000..f402fc2
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java
@@ -0,0 +1,284 @@
+package com.ruoyi.ai.assistant;
+
+import com.ruoyi.ai.tools.FinancialAgentTools;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Component
+public class FinancialIntentExecutor {
+
+    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final Pattern LIMIT_PATTERN = Pattern.compile("(?:鍓峾鏈�杩憒灞曠ず|杩斿洖)?\\s*(\\d{1,2})\\s*(?:鏉涓獆鍚�)");
+    private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
+    private static final Pattern RELATIVE_DAY_PATTERN = Pattern.compile("(?:杩憒鏈�杩�)\\s*(\\d{1,3})\\s*澶�");
+    private static final Pattern FUTURE_MONTH_PATTERN = Pattern.compile("(?:鏈潵|鍚庣画|鎺ヤ笅鏉�)\\s*(\\d{1,2})\\s*(?:涓�)?鏈�");
+
+    private final FinancialAgentTools financialAgentTools;
+
+    public FinancialIntentExecutor(FinancialAgentTools financialAgentTools) {
+        this.financialAgentTools = financialAgentTools;
+    }
+
+    public String tryExecute(String memoryId, String message) {
+        if (!StringUtils.hasText(message)) {
+            return null;
+        }
+        String text = message.trim();
+
+        String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text);
+        if (StringUtils.hasText(quickPromptResponse)) {
+            return quickPromptResponse;
+        }
+
+        DateRange dateRange = extractDateRange(text);
+        Integer limit = extractLimit(text);
+        String keyword = extractKeyword(text);
+        String startDate = dateRange.startDate();
+        String endDate = dateRange.endDate();
+        String timeRange = dateRange.label();
+
+        if (containsAny(text, "鎴愭湰鏍哥畻", "浜у搧鎴愭湰", "宸ュ簭鎴愭湰", "浜哄伐鎴愭湰", "鎶樻棫", "鏉愭枡鎹熻��", "鎴愭湰鏈�楂�")) {
+            return financialAgentTools.calculateIntelligentCost(memoryId, startDate, endDate, timeRange, keyword, limit);
+        }
+        if (containsAny(text, "鍒╂鼎鍒嗘瀽", "璁㈠崟鍒╂鼎", "浜忔崯璁㈠崟", "浣庡埄娑�", "鏈�璧氶挶瀹㈡埛", "鍝釜瀹㈡埛鏈�璧氶挶",
+                "瀹㈡埛鏈�璧氶挶", "鍒╂鼎鏈�楂樺鎴�", "鍒╂鼎璐$尞鏈�楂�", "鍒╂鼎涓嬮檷")) {
+            return financialAgentTools.analyzeOrderProfit(memoryId, startDate, endDate, timeRange, keyword, limit);
+        }
+        if (containsAny(text, "搴撳瓨璧勯噾", "搴撳瓨绉帇", "鍛嗘粸搴撳瓨", "璧勯噾鍗犵敤", "鍛ㄨ浆鐜�", "搴撳瓨鍛ㄨ浆")) {
+            return financialAgentTools.analyzeInventoryCapital(memoryId, startDate, endDate, timeRange, keyword, limit);
+        }
+        if (containsAny(text, "鐜伴噾娴�", "鍥炴椋庨櫓", "浠樻鍘嬪姏", "璧勯噾缂哄彛", "搴旀敹", "搴斾粯", "鍥炴棰勬祴")) {
+            return financialAgentTools.forecastCashFlow(memoryId, startDate, endDate, timeRange, extractForecastMonths(text));
+        }
+        if (containsAny(text, "寮傚父棰勮", "缁忚惀寮傚父", "椋庨櫓棰勮", "鎴愭湰寮傚父", "鍒╂鼎寮傚父", "鍥炴寮傚父", "璁㈠崟椋庨櫓")) {
+            return financialAgentTools.detectBusinessAnomalies(memoryId, startDate, endDate, timeRange, limit);
+        }
+        if (containsAny(text, "椹鹃┒鑸�", "缁忚惀鐪嬫澘", "缁忚惀鎬昏", "缁忚惀浠〃鐩�", "缁忚惀澶х洏")) {
+            return financialAgentTools.getBusinessCockpit(memoryId, startDate, endDate, timeRange);
+        }
+        if (containsAny(text, "鏃ユ姤", "鍛ㄦ姤", "缁忚惀鎶ュ憡", "鍒嗘瀽鎶ュ憡")) {
+            return financialAgentTools.generateOperationReport(memoryId, startDate, endDate, timeRange,
+                    containsAny(text, "鍛ㄦ姤") ? "weekly" : "daily");
+        }
+        if (containsAny(text, "涓氳储铻嶅悎", "涓氳储鑱斿姩", "鍙e緞", "鎸囨爣瑙i噴", "涓轰粈涔�")) {
+            return financialAgentTools.retrieveFinancialKnowledge(memoryId, text);
+        }
+        return null;
+    }
+
+    private String tryExecuteQuickPrompt(String memoryId, String text) {
+        String normalized = normalizeForMatch(text);
+        if ("鐢熸垚鏈懆缁忚惀鍛ㄦ姤鍒╂鼎涓庣幇閲戞祦".equals(normalized) || "鐢熸垚鏈懆缁忚惀鍛ㄦ姤".equals(normalized) || "鐢熸垚鍛ㄦ姤".equals(normalized)) {
+            DateRange range = weekRange();
+            return financialAgentTools.generateOperationReport(memoryId, range.startDate(), range.endDate(), range.label(), "weekly");
+        }
+        if ("鍒嗘瀽鏈湀鍒╂鼎涓嬮檷鍘熷洜".equals(normalized)) {
+            DateRange range = monthRange();
+            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
+        }
+        if ("杩�30澶╁摢涓鎴峰埄娑﹁础鐚渶楂�".equals(normalized)) {
+            DateRange range = recentDaysRange(30);
+            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
+        }
+        if ("鏌ョ湅鏈湀缁忚惀椹鹃┒鑸�".equals(normalized) || "鏌ョ湅缁忚惀椹鹃┒鑸�".equals(normalized)) {
+            DateRange range = monthRange();
+            return financialAgentTools.getBusinessCockpit(memoryId, range.startDate(), range.endDate(), range.label());
+        }
+        if ("鏌ヨ杩�30澶╀簭鎹熻鍗�".equals(normalized)) {
+            DateRange range = recentDaysRange(30);
+            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
+        }
+        if ("鍒嗘瀽杩�30澶╁簱瀛樿祫閲戝崰鐢�".equals(normalized)) {
+            DateRange range = recentDaysRange(30);
+            return financialAgentTools.analyzeInventoryCapital(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
+        }
+        if ("棰勬祴鏈潵3涓湀鐜伴噾娴�".equals(normalized)) {
+            return financialAgentTools.forecastCashFlow(memoryId, null, null, null, 3);
+        }
+        if ("鍝釜宸ュ簭鎴愭湰鏈�楂�".equals(normalized)) {
+            return financialAgentTools.calculateIntelligentCost(memoryId, null, null, null, null, null);
+        }
+        return null;
+    }
+
+    private boolean containsAny(String text, String... keywords) {
+        for (String keyword : keywords) {
+            if (text.contains(keyword)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private Integer extractLimit(String text) {
+        Matcher matcher = LIMIT_PATTERN.matcher(text);
+        return matcher.find() ? Integer.parseInt(matcher.group(1)) : null;
+    }
+
+    private Integer extractForecastMonths(String text) {
+        Matcher matcher = FUTURE_MONTH_PATTERN.matcher(text);
+        return matcher.find() ? Integer.parseInt(matcher.group(1)) : null;
+    }
+
+    private DateRange extractDateRange(String text) {
+        Matcher matcher = DATE_PATTERN.matcher(text);
+        if (matcher.find()) {
+            String first = matcher.group(1);
+            String second = matcher.find() ? matcher.group(1) : first;
+            return buildDateRange(first, second, first + "鑷�" + second);
+        }
+        if (text.contains("鏈湀")) {
+            return monthRange();
+        }
+        if (text.contains("涓婃湀")) {
+            return lastMonthRange();
+        }
+        if (text.contains("浠婂勾") || text.contains("鏈勾")) {
+            return yearRange();
+        }
+        if (text.contains("鏈懆")) {
+            return weekRange();
+        }
+        Matcher relativeDayMatcher = RELATIVE_DAY_PATTERN.matcher(text);
+        if (relativeDayMatcher.find()) {
+            int days = Integer.parseInt(relativeDayMatcher.group(1));
+            return recentDaysRange(days);
+        }
+        return new DateRange(null, null, null);
+    }
+
+    private DateRange buildDateRange(String start, String end, String label) {
+        LocalDate startDate = parseDate(start);
+        LocalDate endDate = parseDate(end);
+        if (startDate == null || endDate == null) {
+            return new DateRange(null, null, null);
+        }
+        if (startDate.isAfter(endDate)) {
+            LocalDate temp = startDate;
+            startDate = endDate;
+            endDate = temp;
+        }
+        return new DateRange(formatDate(startDate), formatDate(endDate), label);
+    }
+
+    private DateRange recentDaysRange(int days) {
+        LocalDate end = LocalDate.now();
+        int safeDays = Math.max(days, 1);
+        LocalDate start = end.minusDays(safeDays - 1L);
+        return new DateRange(formatDate(start), formatDate(end), "杩�" + safeDays + "澶�");
+    }
+
+    private DateRange monthRange() {
+        LocalDate today = LocalDate.now();
+        return new DateRange(formatDate(today.withDayOfMonth(1)), formatDate(today), "鏈湀");
+    }
+
+    private DateRange weekRange() {
+        LocalDate today = LocalDate.now();
+        LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L);
+        return new DateRange(formatDate(start), formatDate(today), "鏈懆");
+    }
+
+    private DateRange lastMonthRange() {
+        YearMonth lastMonth = YearMonth.now().minusMonths(1);
+        return new DateRange(formatDate(lastMonth.atDay(1)), formatDate(lastMonth.atEndOfMonth()), "涓婃湀");
+    }
+
+    private DateRange yearRange() {
+        LocalDate today = LocalDate.now();
+        return new DateRange(formatDate(today.withDayOfYear(1)), formatDate(today), "浠婂勾");
+    }
+
+    private LocalDate parseDate(String text) {
+        try {
+            return LocalDate.parse(text, DATE_FMT);
+        } catch (Exception ignored) {
+            return null;
+        }
+    }
+
+    private String formatDate(LocalDate date) {
+        return date == null ? null : date.format(DATE_FMT);
+    }
+
+    private String normalizeForMatch(String text) {
+        if (!StringUtils.hasText(text)) {
+            return "";
+        }
+        return text.replace("锛�", "")
+                .replace("锛�", "")
+                .replace("(", "")
+                .replace(")", "")
+                .replace("锛�", "")
+                .replace(",", "")
+                .replace("銆�", "")
+                .replace(".", "")
+                .replace("锛�", "")
+                .replace("!", "")
+                .replace("锛�", "")
+                .replace("?", "")
+                .replace("锛�", "")
+                .replace(":", "")
+                .replace("锛�", "")
+                .replace(";", "")
+                .replace(" ", "")
+                .trim();
+    }
+
+    private String extractKeyword(String text) {
+        String cleaned = text
+                .replaceAll("\\d{4}-\\d{2}-\\d{2}", "")
+                .replaceAll("(?:杩憒鏈�杩�)\\s*\\d{1,3}\\s*澶�", "")
+                .replaceAll("(?:鍓峾鏈�杩憒灞曠ず|杩斿洖)?\\s*\\d{1,2}\\s*(?:鏉涓獆鍚�)", "")
+                .replace("鏌ヨ", "")
+                .replace("鏌ョ湅", "")
+                .replace("鐪嬩笅", "")
+                .replace("鐪嬬湅", "")
+                .replace("甯垜", "")
+                .replace("璇�", "")
+                .replace("涓�涓�", "")
+                .replace("涓轰粈涔�", "")
+                .replace("鍝釜瀹㈡埛鏈�璧氶挶", "")
+                .replace("鏈�杩戝摢涓鎴锋渶璧氶挶", "")
+                .replace("鏈湀鍝釜瀹㈡埛鏈�璧氶挶", "")
+                .replace("杩�30澶╁摢涓鎴锋渶璧氶挶", "")
+                .replace("鏈�璧氶挶瀹㈡埛", "")
+                .replace("瀹㈡埛鏈�璧氶挶", "")
+                .replace("鍝釜瀹㈡埛鍒╂鼎鏈�楂�", "")
+                .replace("鍒╂鼎鏈�楂樺鎴�", "")
+                .replace("鍝釜瀹㈡埛鍒╂鼎璐$尞鏈�楂�", "")
+                .replace("鍒╂鼎璐$尞鏈�楂樺鎴�", "")
+                .replace("鏈湀", "")
+                .replace("鏈懆", "")
+                .replace("鏈勾", "")
+                .replace("浠婂勾", "")
+                .replace("涓婃湀", "")
+                .replace("杩�30澶�", "")
+                .replace("杩�7澶�", "")
+                .replace("杩�90澶�", "")
+                .replace("鍓�10鏉�", "")
+                .replace("鏈�杩�10鏉�", "")
+                .replace("鍓�20鏉�", "")
+                .replace("鏈�杩�20鏉�", "")
+                .replace("璁㈠崟鍒╂鼎鍒嗘瀽", "")
+                .replace("鍒╂鼎鍒嗘瀽", "")
+                .replace("搴撳瓨璧勯噾鍒嗘瀽", "")
+                .replace("鐜伴噾娴侀娴�", "")
+                .replace("缁忚惀椹鹃┒鑸�", "")
+                .replace("鏃ユ姤", "")
+                .replace("鍛ㄦ姤", "")
+                .replace("寮傚父棰勮", "")
+                .replace("鏉�", "")
+                .trim();
+        return cleaned.length() >= 2 ? cleaned : null;
+    }
+
+    private record DateRange(String startDate, String endDate, String label) {
+    }
+}
diff --git a/src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java b/src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java
new file mode 100644
index 0000000..5251561
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java
@@ -0,0 +1,65 @@
+package com.ruoyi.ai.context;
+
+import com.ruoyi.framework.security.LoginUser;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Component
+public class AiSessionUserContext {
+
+    private final Map<String, LoginUser> loginUserByMemoryId = new ConcurrentHashMap<>();
+    private final Map<String, Instant> lastAccessTimeByMemoryId = new ConcurrentHashMap<>();
+    private static final Duration SESSION_TIMEOUT = Duration.ofHours(24);
+
+    public void bind(String memoryId, LoginUser loginUser) {
+        if (!StringUtils.hasText(memoryId) || loginUser == null) {
+            return;
+        }
+        loginUserByMemoryId.put(memoryId, loginUser);
+        lastAccessTimeByMemoryId.put(memoryId, Instant.now());
+    }
+
+    public LoginUser get(String memoryId) {
+        if (!StringUtils.hasText(memoryId)) {
+            return null;
+        }
+        if (isExpired(memoryId)) {
+            remove(memoryId);
+            return null;
+        }
+        lastAccessTimeByMemoryId.put(memoryId, Instant.now());
+        return loginUserByMemoryId.get(memoryId);
+    }
+
+    public void remove(String memoryId) {
+        if (!StringUtils.hasText(memoryId)) {
+            return;
+        }
+        loginUserByMemoryId.remove(memoryId);
+        lastAccessTimeByMemoryId.remove(memoryId);
+    }
+
+    public void cleanExpiredSessions() {
+        Instant now = Instant.now();
+        lastAccessTimeByMemoryId.entrySet().removeIf(entry -> {
+            boolean expired = Duration.between(entry.getValue(), now).compareTo(SESSION_TIMEOUT) > 0;
+            if (expired) {
+                loginUserByMemoryId.remove(entry.getKey());
+            }
+            return expired;
+        });
+    }
+
+    private boolean isExpired(String memoryId) {
+        Instant lastAccess = lastAccessTimeByMemoryId.get(memoryId);
+        if (lastAccess == null) {
+            return true;
+        }
+        return Duration.between(lastAccess, Instant.now()).compareTo(SESSION_TIMEOUT) > 0;
+    }
+}
diff --git a/src/main/java/com/ruoyi/ai/controller/FinancialAiController.java b/src/main/java/com/ruoyi/ai/controller/FinancialAiController.java
new file mode 100644
index 0000000..9465d5a
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/controller/FinancialAiController.java
@@ -0,0 +1,144 @@
+package com.ruoyi.ai.controller;
+
+import com.ruoyi.ai.assistant.FinancialAgent;
+import com.ruoyi.ai.assistant.FinancialIntentExecutor;
+import com.ruoyi.ai.bean.ChatForm;
+import com.ruoyi.ai.context.AiSessionUserContext;
+import com.ruoyi.ai.service.AiChatSessionService;
+import com.ruoyi.ai.store.MongoChatMemoryStore;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.framework.web.controller.BaseController;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import dev.langchain4j.data.message.AiMessage;
+import dev.langchain4j.data.message.UserMessage;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+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 reactor.core.publisher.Flux;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+
+@Tag(name = "璐㈠姟鏅鸿兘浣�")
+@RestController
+@RequestMapping("/financial-ai")
+public class FinancialAiController extends BaseController {
+
+    private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai");
+
+    private final FinancialAgent financialAgent;
+    private final FinancialIntentExecutor financialIntentExecutor;
+    private final AiSessionUserContext aiSessionUserContext;
+    private final MongoChatMemoryStore mongoChatMemoryStore;
+    private final AiChatSessionService aiChatSessionService;
+
+    public FinancialAiController(FinancialAgent financialAgent,
+                                 FinancialIntentExecutor financialIntentExecutor,
+                                 AiSessionUserContext aiSessionUserContext,
+                                 MongoChatMemoryStore mongoChatMemoryStore,
+                                 AiChatSessionService aiChatSessionService) {
+        this.financialAgent = financialAgent;
+        this.financialIntentExecutor = financialIntentExecutor;
+        this.aiSessionUserContext = aiSessionUserContext;
+        this.mongoChatMemoryStore = mongoChatMemoryStore;
+        this.aiChatSessionService = aiChatSessionService;
+    }
+
+    @Operation(summary = "璐㈠姟鏅鸿兘浣撳璇�")
+    @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
+    public Flux<String> chat(@RequestBody ChatForm chatForm) {
+        if (!StringUtils.hasText(chatForm.getMemoryId())) {
+            return Flux.just("memoryId涓嶈兘涓虹┖");
+        }
+        if (!StringUtils.hasText(chatForm.getMessage())) {
+            return Flux.just("message涓嶈兘涓虹┖");
+        }
+
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        String memoryId = chatForm.getMemoryId();
+        String userMessage = chatForm.getMessage();
+
+        aiSessionUserContext.bind(memoryId, loginUser);
+        aiChatSessionService.touchSession(memoryId, loginUser, userMessage);
+
+        String directResponse = financialIntentExecutor.tryExecute(memoryId, userMessage);
+        if (StringUtils.isNotEmpty(directResponse)) {
+            mongoChatMemoryStore.appendMessages(
+                    memoryId,
+                    List.of(UserMessage.from(userMessage), AiMessage.from(directResponse))
+            );
+            aiChatSessionService.refreshSessionStats(memoryId, loginUser);
+            return Flux.just(directResponse);
+        }
+
+        if (isBusinessDataIntent(userMessage)) {
+            String noGuessResponse = "鏈瘑鍒埌鍙墽琛岀殑鏁版嵁鏌ヨ鏉′欢銆備负淇濊瘉缁撴灉鍑嗙‘锛屽綋鍓嶄笉浼氭帹娴嬫垨缂栭�犳暟鎹紝璇疯ˉ鍏呮槑纭椂闂磋寖鍥淬�佸鎴枫�佷緵搴斿晢鎴栧崟鍙峰悗鍐嶆煡璇€��";
+            mongoChatMemoryStore.appendMessages(
+                    memoryId,
+                    List.of(UserMessage.from(userMessage), AiMessage.from(noGuessResponse))
+            );
+            aiChatSessionService.refreshSessionStats(memoryId, loginUser);
+            return Flux.just(noGuessResponse);
+        }
+
+        return financialAgent.chat(memoryId, userMessage, currentDateForPrompt())
+                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
+                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
+    }
+
+    @Operation(summary = "璐㈠姟鏅鸿兘浣撲細璇濆垪琛�")
+    @GetMapping("/history/sessions")
+    public AjaxResult listSessions() {
+        return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
+    }
+
+    @Operation(summary = "璐㈠姟鏅鸿兘浣撲細璇濇秷鎭�")
+    @GetMapping("/history/messages/{memoryId}")
+    public AjaxResult listMessages(@PathVariable String memoryId) {
+        return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
+    }
+
+    @Operation(summary = "鍒犻櫎璐㈠姟鏅鸿兘浣撲細璇�")
+    @DeleteMapping("/history/{memoryId}")
+    public AjaxResult deleteSession(@PathVariable String memoryId) {
+        aiSessionUserContext.remove(memoryId);
+        return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
+    }
+
+    private String currentDateForPrompt() {
+        return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT);
+    }
+
+    private boolean isBusinessDataIntent(String message) {
+        if (!StringUtils.hasText(message)) {
+            return false;
+        }
+        String text = message.trim();
+        return containsAny(text,
+                "鏌ヨ", "鏌ョ湅", "缁熻", "鍒嗘瀽", "寤鸿", "鎴愭湰鏍哥畻", "浜у搧鎴愭湰", "宸ュ簭鎴愭湰",
+                "璁㈠崟鍒╂鼎", "浜忔崯璁㈠崟", "浣庡埄娑�", "搴撳瓨璧勯噾", "搴撳瓨绉帇", "鍛嗘粸搴撳瓨",
+                "鐜伴噾娴�", "鍥炴椋庨櫓", "浠樻鍘嬪姏", "璧勯噾缂哄彛", "搴旀敹", "搴斾粯",
+                "寮傚父棰勮", "缁忚惀寮傚父", "椋庨櫓棰勮", "椹鹃┒鑸�", "缁忚惀鐪嬫澘", "缁忚惀鎬昏",
+                "鏃ユ姤", "鍛ㄦ姤", "缁忚惀鎶ュ憡", "鍒嗘瀽鎶ュ憡", "涓氳储铻嶅悎", "鍙e緞", "鎸囨爣瑙i噴");
+    }
+
+    private boolean containsAny(String text, String... keywords) {
+        for (String keyword : keywords) {
+            if (text.contains(keyword)) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/main/java/com/ruoyi/ai/schedule/AiSessionCleanupTask.java b/src/main/java/com/ruoyi/ai/schedule/AiSessionCleanupTask.java
new file mode 100644
index 0000000..cecdba8
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/schedule/AiSessionCleanupTask.java
@@ -0,0 +1,24 @@
+package com.ruoyi.ai.schedule;
+
+import com.ruoyi.ai.context.AiSessionUserContext;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+@Component
+public class AiSessionCleanupTask {
+
+    private final AiSessionUserContext aiSessionUserContext;
+
+    public AiSessionCleanupTask(AiSessionUserContext aiSessionUserContext) {
+        this.aiSessionUserContext = aiSessionUserContext;
+    }
+
+    @Scheduled(cron = "0 0 2 * * ?")
+    public void cleanupExpiredSessions() {
+        try {
+            aiSessionUserContext.cleanExpiredSessions();
+        } catch (Exception e) {
+            System.err.println("娓呯悊杩囨湡AI浼氳瘽澶辫触: " + e.getMessage());
+        }
+    }
+}
diff --git a/src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java b/src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
new file mode 100644
index 0000000..e947ec1
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
@@ -0,0 +1,1012 @@
+package com.ruoyi.ai.tools;
+
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ruoyi.ai.context.AiSessionUserContext;
+import com.ruoyi.approve.mapper.ApproveLogMapper;
+import com.ruoyi.approve.mapper.ApproveNodeMapper;
+import com.ruoyi.approve.mapper.ApproveProcessMapper;
+import com.ruoyi.approve.pojo.ApproveLog;
+import com.ruoyi.approve.pojo.ApproveNode;
+import com.ruoyi.approve.pojo.ApproveProcess;
+import com.ruoyi.approve.service.IApproveNodeService;
+import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.framework.security.LoginUser;
+import dev.langchain4j.agent.tool.P;
+import dev.langchain4j.agent.tool.Tool;
+import dev.langchain4j.agent.tool.ToolMemoryId;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+import java.util.regex.Matcher;
+import java.util.stream.Collectors;
+
+@Component
+public class ApproveTodoTools {
+
+    private static final int DEFAULT_LIMIT = 10;
+    private static final int MAX_LIMIT = 20;
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    private final ApproveProcessMapper approveProcessMapper;
+    private final ApproveNodeMapper approveNodeMapper;
+    private final ApproveLogMapper approveLogMapper;
+    private final IApproveNodeService approveNodeService;
+    private final ApproveProcessServiceImpl approveProcessService;
+    private final AiSessionUserContext aiSessionUserContext;
+
+    public ApproveTodoTools(ApproveProcessMapper approveProcessMapper,
+                            ApproveNodeMapper approveNodeMapper,
+                            ApproveLogMapper approveLogMapper,
+                            IApproveNodeService approveNodeService,
+                            ApproveProcessServiceImpl approveProcessService,
+                            AiSessionUserContext aiSessionUserContext) {
+        this.approveProcessMapper = approveProcessMapper;
+        this.approveNodeMapper = approveNodeMapper;
+        this.approveLogMapper = approveLogMapper;
+        this.approveNodeService = approveNodeService;
+        this.approveProcessService = approveProcessService;
+        this.aiSessionUserContext = aiSessionUserContext;
+    }
+
+    @Tool(name = "鏌ヨ瀹℃壒寰呭姙鍒楄〃", value = "鏌ヨ褰撳墠鐧诲綍浜虹浉鍏崇殑瀹℃壒寰呭姙锛屼紭鍏堣繑鍥炶嚜宸卞緟澶勭悊鐨勫鎵癸紝鏀寔鎸夌姸鎬併�佺被鍨嬨�佸叧閿瓧鍜岃寖鍥磋繃婊ゃ��")
+    public String listTodos(@ToolMemoryId String memoryId,
+                            @P(value = "瀹℃壒鐘舵�侊紝鍙�夊�硷細all銆乸ending銆乸rocessing銆乤pproved銆乺ejected銆乺esubmitted", required = false) String status,
+                            @P(value = "瀹℃壒绫诲瀷缂栧彿锛屽彲涓嶄紶", required = false) Integer approveType,
+                            @P(value = "鍏抽敭瀛楋紝鍙尮閰嶆祦绋嬬紪鍙枫�佹爣棰樸�佺敵璇蜂汉銆佸綋鍓嶅鎵逛汉", required = false) String keyword,
+                            @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�20", required = false) Integer limit,
+                            @P(value = "鏌ヨ鑼冨洿锛屽彲閫夊�硷細related銆乤pplicant銆乤pprover锛況elated 琛ㄧず褰撳墠鐢ㄦ埛鐩稿叧锛宎pplicant 琛ㄧず鎴戝彂璧风殑锛宎pprover 琛ㄧず寰呮垜澶勭悊鐨�", required = false) String scope,
+                            @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
+                            @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
+                            @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡� 浠婂ぉ銆佹湰鏈堛�佽繎30澶╋紝鍙笉浼�", required = false) String timeRange) {
+
+        LoginUser loginUser = currentLoginUser(memoryId);
+        Long userId = loginUser.getUserId();
+        Integer statusCode = parseStatus(status);
+        String normalizedScope = normalizeScope(scope);
+        boolean hasDateFilter = StringUtils.hasText(startDate) || StringUtils.hasText(endDate) || StringUtils.hasText(timeRange);
+        DateRange dateRange = hasDateFilter ? resolveDateRange(startDate, endDate, timeRange) : null;
+
+        LambdaQueryWrapper<ApproveProcess> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(ApproveProcess::getApproveDelete, 0);
+        if (statusCode == null) {
+            wrapper.ne(ApproveProcess::getApproveStatus, 2);
+        }
+
+        if (approveType != null) {
+            wrapper.eq(ApproveProcess::getApproveType, approveType);
+        }
+        if (StringUtils.hasText(keyword)) {
+            wrapper.and(w -> w.like(ApproveProcess::getApproveId, keyword)
+                    .or().like(ApproveProcess::getApproveReason, keyword)
+                    .or().like(ApproveProcess::getApproveUserName, keyword)
+                    .or().like(ApproveProcess::getApproveUserCurrentName, keyword));
+        }
+        if ("applicant".equals(normalizedScope)) {
+            wrapper.eq(ApproveProcess::getApproveUser, userId);
+            if (statusCode != null) {
+                wrapper.eq(ApproveProcess::getApproveStatus, statusCode);
+            }
+        } else if ("approver".equals(normalizedScope)) {
+            wrapper.eq(ApproveProcess::getApproveUserCurrentId, userId);
+            if (statusCode != null) {
+                wrapper.eq(ApproveProcess::getApproveStatus, statusCode);
+            }
+        } else if (statusCode != null && (statusCode == 0 || statusCode == 1)) {
+            wrapper.eq(ApproveProcess::getApproveUserCurrentId, userId);
+            wrapper.eq(ApproveProcess::getApproveStatus, statusCode);
+        } else {
+            wrapper.and(w -> w.eq(ApproveProcess::getApproveUser, userId)
+                    .or().eq(ApproveProcess::getApproveUserCurrentId, userId)
+                    .or().apply("FIND_IN_SET({0}, approve_user_ids)", userId));
+            if (statusCode != null) {
+                wrapper.eq(ApproveProcess::getApproveStatus, statusCode);
+            }
+        }
+
+        if (dateRange != null) {
+            wrapper.ge(ApproveProcess::getCreateTime, dateRange.start().atStartOfDay())
+                    .lt(ApproveProcess::getCreateTime, dateRange.end().plusDays(1).atStartOfDay());
+        }
+
+        wrapper.orderByDesc(ApproveProcess::getCreateTime)
+                .last("limit " + normalizeLimit(limit));
+
+        List<ApproveProcess> processes = defaultList(approveProcessMapper.selectList(wrapper));
+        if (processes.isEmpty()) {
+            return jsonResponse(true, "todo_list", "鏈煡璇㈠埌褰撳墠鐢ㄦ埛绗﹀悎鏉′欢鐨勫鎵瑰緟鍔炪��",
+                    Map.of("count", 0),
+                    Map.of("columns", todoColumns(), "items", List.of()),
+                    Map.of());
+        }
+
+        List<Map<String, Object>> items = processes.stream()
+                .filter(process -> canView(process, userId))
+                .sorted(Comparator
+                        .comparing((ApproveProcess process) -> !Objects.equals(process.getApproveUserCurrentId(), userId))
+                        .thenComparing(ApproveProcess::getCreateTime, Comparator.nullsLast(Comparator.reverseOrder())))
+                .map(process -> {
+                    Map<String, Object> item = new LinkedHashMap<>();
+                    item.put("approveId", process.getApproveId());
+                    item.put("approveType", approveTypeName(process.getApproveType()));
+                    item.put("approveUserName", safe(process.getApproveUserName()));
+                    item.put("approveUserCurrentName", safe(process.getApproveUserCurrentName()));
+                    item.put("approveReason", safe(process.getApproveReason()));
+                    item.put("approveStatus", approveStatusName(process.getApproveStatus()));
+                    item.put("createTime", formatDateTime(process.getCreateTime()));
+                    item.put("relation", relationName(process, userId));
+                    return item;
+                })
+                .collect(Collectors.toList());
+
+        return jsonResponse(true, "todo_list", "宸茶繑鍥炲綋鍓嶇敤鎴风浉鍏冲鎵瑰垪琛ㄣ��",
+                Map.of(
+                        "count", items.size(),
+                        "statusFilter", StringUtils.hasText(status) ? status : "all",
+                        "approveType", approveType == null ? "" : approveType,
+                        "keyword", keyword == null ? "" : keyword,
+                        "scope", normalizedScope,
+                        "timeRange", dateRange == null ? "all" : dateRange.label(),
+                        "startDate", dateRange == null ? "" : dateRange.start().toString(),
+                        "endDate", dateRange == null ? "" : dateRange.end().toString()
+                ),
+                Map.of("columns", todoColumns(), "items", items),
+                Map.of());
+    }
+
+    @Tool(name = "鏌ヨ瀹℃壒寰呭姙璇︽儏", value = "鏍规嵁娴佺▼缂栧彿鏌ヨ褰撳墠鐧诲綍浜哄彲瑙佺殑瀹℃壒璇︽儏銆�")
+    public String getTodoDetail(@ToolMemoryId String memoryId,
+                                @P("娴佺▼缂栧彿 approveId") String approveId) {
+        ApproveProcess process = getAccessibleProcess(memoryId, approveId);
+        if (process == null) {
+            return "鏈壘鍒板搴斿鎵癸紝鎴栧綋鍓嶇敤鎴锋棤鏉冩煡鐪嬭娴佺▼銆�";
+        }
+
+        StringJoiner detail = new StringJoiner("\n");
+        detail.add("瀹℃壒璇︽儏");
+        detail.add("娴佺▼缂栧彿: " + safe(process.getApproveId()));
+        detail.add("瀹℃壒绫诲瀷: " + approveTypeName(process.getApproveType()));
+        detail.add("鐢宠浜�: " + safe(process.getApproveUserName()));
+        detail.add("鐢宠閮ㄩ棬: " + safe(process.getApproveDeptName()));
+        detail.add("褰撳墠瀹℃壒浜�: " + safe(process.getApproveUserCurrentName()));
+        detail.add("鏍囬: " + safe(process.getApproveReason()));
+        detail.add("鐘舵��: " + approveStatusName(process.getApproveStatus()));
+        detail.add("鐢宠鏃ユ湡: " + formatDate(process.getApproveTime()));
+        detail.add("寮�濮嬫棩鏈�: " + formatDate(process.getStartDate()));
+        detail.add("缁撴潫鏃ユ湡: " + formatDate(process.getEndDate()));
+        detail.add("鍦扮偣: " + safe(process.getLocation()));
+        detail.add("閲戦: " + (process.getPrice() == null ? "" : process.getPrice().toPlainString()));
+        detail.add("澶囨敞: " + safe(process.getApproveRemark()));
+        detail.add("鍒涘缓鏃堕棿: " + formatDateTime(process.getCreateTime()));
+        detail.add("涓庡綋鍓嶇敤鎴峰叧绯�: " + relationName(process, currentUserId(memoryId)));
+        return detail.toString();
+    }
+
+    @Tool(name = "鏌ヨ瀹℃壒娴佽浆璁板綍", value = "鏍规嵁娴佺▼缂栧彿鏌ヨ瀹℃壒鑺傜偣鍜屽鎵规棩蹇楋紝鐢ㄤ簬鍥炵瓟杩涘害銆佸綋鍓嶅崱鐐瑰拰鍘嗗彶澶勭悊璁板綍銆�")
+    public String getTodoProgress(@ToolMemoryId String memoryId,
+                                  @P("娴佺▼缂栧彿 approveId") String approveId) {
+        ApproveProcess process = getAccessibleProcess(memoryId, approveId);
+        if (process == null) {
+            return jsonResponse(false, "todo_progress", "鏈壘鍒板搴斿鎵癸紝鎴栧綋鍓嶇敤鎴锋棤鏉冩煡鐪嬭娴佺▼銆�",
+                    Map.of("approveId", safe(approveId)),
+                    Map.of(),
+                    Map.of());
+        }
+
+        List<ApproveNode> nodes = listNodes(process);
+        List<ApproveLog> logs = listLogs(process.getId());
+        ApproveNode currentNode = findCurrentNode(nodes);
+
+        List<Map<String, Object>> nodeItems = nodes.stream().map(node -> {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("approveNodeOrder", node.getApproveNodeOrder());
+            item.put("approveNodeUser", safe(node.getApproveNodeUser()));
+            item.put("approveNodeUserId", node.getApproveNodeUserId());
+            item.put("approveNodeStatus", approveNodeStatusName(node.getApproveNodeStatus()));
+            item.put("approveNodeTime", formatDate(node.getApproveNodeTime()));
+            item.put("approveNodeReason", safe(node.getApproveNodeReason()));
+            item.put("approveNodeRemark", safe(node.getApproveNodeRemark()));
+            item.put("isCurrent", currentNode != null && Objects.equals(currentNode.getId(), node.getId()));
+            return item;
+        }).collect(Collectors.toList());
+
+        List<Map<String, Object>> logItems = logs.stream().map(log -> {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("approveNodeOrder", log.getApproveNodeOrder());
+            item.put("approveUser", log.getApproveUser());
+            item.put("approveStatus", approveStatusName(log.getApproveStatus()));
+            item.put("approveTime", formatDate(log.getApproveTime()));
+            item.put("approveRemark", safe(log.getApproveRemark()));
+            return item;
+        }).collect(Collectors.toList());
+
+        return jsonResponse(true, "todo_progress", "宸茶繑鍥炲鎵规祦杞褰曘��",
+                Map.of(
+                        "approveId", safe(process.getApproveId()),
+                        "currentStatus", approveStatusName(process.getApproveStatus()),
+                        "currentApprover", safe(process.getApproveUserCurrentName()),
+                        "currentNodeOrder", currentNode == null ? "" : currentNode.getApproveNodeOrder(),
+                        "nodeCount", nodeItems.size(),
+                        "logCount", logItems.size()
+                ),
+                Map.of("nodes", nodeItems, "logs", logItems),
+                Map.of());
+    }
+
+    @Tool(name = "缁熻瀹℃壒寰呭姙鏁版嵁", value = "鎸夌敤鎴锋寚瀹氱殑鏃堕棿鑼冨洿缁熻褰撳墠鐧诲綍浜虹浉鍏冲鎵圭殑鐘舵�佸垎甯冦�佺被鍨嬪垎甯冨拰瓒嬪娍锛涙湭鎸囧畾鏃堕粯璁よ繎7澶┿��")
+    public String getTodoStats(@ToolMemoryId String memoryId,
+                               @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
+                               @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
+                               @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屼緥濡� 浠婂ぉ銆佹湰鏈堛�佽繎30澶┿��2026-04-01鍒�2026-04-27", required = false) String timeRange) {
+        Long userId = currentUserId(memoryId);
+        List<ApproveProcess> processes = defaultList(approveProcessMapper.selectList(new LambdaQueryWrapper<ApproveProcess>()
+                .eq(ApproveProcess::getApproveDelete, 0)
+                .and(w -> w.eq(ApproveProcess::getApproveUser, userId)
+                        .or().eq(ApproveProcess::getApproveUserCurrentId, userId)
+                        .or().apply("FIND_IN_SET({0}, approve_user_ids)", userId))));
+
+        DateRange dateRange = resolveDateRange(startDate, endDate, timeRange);
+        List<ApproveProcess> filteredProcesses = processes.stream()
+                .filter(process -> withinDateRange(process.getCreateTime(), dateRange))
+                .collect(Collectors.toList());
+
+        if (filteredProcesses.isEmpty()) {
+            return jsonResponse(true, "todo_stats", "褰撳墠鐢ㄦ埛娌℃湁鐩稿叧瀹℃壒鏁版嵁銆�",
+                    Map.of(
+                            "total", 0,
+                            "startDate", dateRange.start().toString(),
+                            "endDate", dateRange.end().toString(),
+                            "timeRange", dateRange.label()
+                    ),
+                    Map.of(
+                            "statusDistribution", Map.of(),
+                            "typeDistribution", Map.of(),
+                            "trend", List.of()
+                    ),
+                    Map.of());
+        }
+
+        Map<String, Long> statusStats = filteredProcesses.stream()
+                .collect(Collectors.groupingBy(p -> approveStatusName(p.getApproveStatus()), LinkedHashMap::new, Collectors.counting()));
+        Map<String, Long> typeStats = filteredProcesses.stream()
+                .collect(Collectors.groupingBy(p -> approveTypeName(p.getApproveType()), LinkedHashMap::new, Collectors.counting()));
+
+        long pendingCount = countByStatus(filteredProcesses, 0);
+        long processingCount = countByStatus(filteredProcesses, 1);
+        long approvedCount = countByStatus(filteredProcesses, 2);
+        long rejectedCount = countByStatus(filteredProcesses, 3);
+        long resubmittedCount = countByStatus(filteredProcesses, 4);
+
+        TrendRange trendRange = buildTrendRange(dateRange.start(), dateRange.end(), filteredProcesses);
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("total", filteredProcesses.size());
+        summary.put("pending", pendingCount);
+        summary.put("processing", processingCount);
+        summary.put("approved", approvedCount);
+        summary.put("rejected", rejectedCount);
+        summary.put("resubmitted", resubmittedCount);
+        summary.put("approvalCompletionRate", calculateRate(approvedCount, filteredProcesses.size()));
+        summary.put("rejectionRate", calculateRate(rejectedCount, filteredProcesses.size()));
+        summary.put("startDate", dateRange.start().toString());
+        summary.put("endDate", dateRange.end().toString());
+        summary.put("timeRange", dateRange.label());
+        summary.put("trendGranularity", trendRange.granularity());
+
+        Map<String, Object> charts = new LinkedHashMap<>();
+        charts.put("statusBarOption", buildStatusBarOption(statusStats));
+        charts.put("typePieOption", buildTypePieOption(typeStats));
+        charts.put("trendLineOption", buildTrendLineOption(trendRange.labels(), trendRange.values(), trendRange.label()));
+
+        return jsonResponse(true, "todo_stats", "宸茶繑鍥炲綋鍓嶇敤鎴风浉鍏冲鎵圭粺璁°��",
+                summary,
+                Map.of(
+                        "statusDistribution", statusStats,
+                        "typeDistribution", typeStats,
+                        "trend", toTrendItems(trendRange.labels(), trendRange.values())
+                ),
+                charts);
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Tool(name = "瀹℃壒寰呭姙", value = "鎵ц瀹℃壒鍔ㄤ綔锛宎ction 浠呮敮鎸� approve 鎴� reject锛屼笖鍙兘澶勭悊褰撳墠鐧诲綍浜鸿嚜宸辩殑寰呭鑺傜偣銆�")
+    public String reviewTodo(@ToolMemoryId String memoryId,
+                             @P("娴佺▼缂栧彿 approveId") String approveId,
+                             @P("鍔ㄤ綔锛宎pprove=閫氳繃锛宺eject=椹冲洖") String action,
+                             @P(value = "瀹℃壒澶囨敞锛屽彲涓嶄紶", required = false) String remark) {
+
+        ApproveProcess process = getAccessibleProcess(memoryId, approveId);
+        if (process == null) {
+            return actionResult(false, "review_action", "鏈壘鍒板搴斿鎵癸紝鎴栧綋鍓嶇敤鎴锋棤鏉冭闂娴佺▼銆�", approveId, null);
+        }
+        if (!canOperate(process, currentUserId(memoryId))) {
+            return actionResult(false, "review_action", "褰撳墠鐧诲綍浜轰笉鏄瀹℃壒鐨勫綋鍓嶅鐞嗕汉銆�", approveId, null);
+        }
+        if (process.getApproveStatus() != null && (process.getApproveStatus() == 2 || process.getApproveStatus() == 3)) {
+            return actionResult(false, "review_action", "璇ュ鎵瑰凡缁撴潫锛屼笉鑳介噸澶嶅鐞嗐��", approveId, null);
+        }
+
+        List<ApproveNode> nodes = listNodes(process);
+        ApproveNode currentNode = findCurrentNode(nodes);
+        if (currentNode == null || !Objects.equals(currentNode.getApproveNodeUserId(), currentUserId(memoryId))) {
+            return actionResult(false, "review_action", "鏈壘鍒板綋鍓嶇敤鎴峰彲澶勭悊鐨勫鎵硅妭鐐广��", approveId, null);
+        }
+
+        String normalizedAction = action == null ? "" : action.trim().toLowerCase();
+        currentNode.setApproveNodeRemark(remark);
+        currentNode.setApproveNodeReason("reject".equals(normalizedAction) ? remark : null);
+        currentNode.setUpdateUser(currentUserId(memoryId));
+        currentNode.setUpdateTime(LocalDateTime.now());
+        currentNode.setIsLast(isLastNode(nodes, currentNode));
+
+        try {
+            switch (normalizedAction) {
+                case "approve" -> currentNode.setApproveNodeStatus(1);
+                case "reject" -> currentNode.setApproveNodeStatus(2);
+                default -> {
+                    return actionResult(false, "review_action", "action 鍙敮鎸� approve 鎴� reject銆�", approveId, null);
+                }
+            }
+            approveNodeService.updateApproveNode(currentNode);
+        } catch (IOException e) {
+            throw new RuntimeException("瀹℃壒澶勭悊澶辫触", e);
+        }
+
+        ApproveProcess refreshed = getProcessByApproveId(approveId);
+        writeApproveLog(memoryId, refreshed, currentNode, remark);
+        ApproveNode nextNode = refreshed == null ? null : findCurrentNode(listNodes(refreshed));
+
+        return actionResult(true, "review_action",
+                "approve".equals(normalizedAction) ? "瀹℃壒宸查�氳繃銆�" : "瀹℃壒宸查┏鍥炪��",
+                approveId,
+                Map.of(
+                        "action", normalizedAction,
+                        "currentStatus", refreshed == null ? "" : approveStatusName(refreshed.getApproveStatus()),
+                        "nextApprover", nextNode == null ? "" : safe(nextNode.getApproveNodeUser()),
+                        "remark", safe(remark)
+                ));
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Tool(name = "鍙栨秷瀹℃壒寰呭姙瀹℃牳", value = "鎾ら攢鏈�杩戜竴娆″鏍哥粨鏋滐紝浠呭厑璁告渶杩戜竴娆″鏍镐汉鎴栫敵璇蜂汉鎿嶄綔銆�")
+    public String cancelReviewTodo(@ToolMemoryId String memoryId,
+                                   @P("娴佺▼缂栧彿 approveId") String approveId,
+                                   @P(value = "鍙栨秷鍘熷洜锛屽彲涓嶄紶", required = false) String reason) {
+
+        ApproveProcess process = getAccessibleProcess(memoryId, approveId);
+        if (process == null) {
+            return actionResult(false, "cancel_review_action", "鏈壘鍒板搴斿鎵癸紝鎴栧綋鍓嶇敤鎴锋棤鏉冭闂娴佺▼銆�", approveId, null);
+        }
+
+        List<ApproveNode> nodes = listNodes(process);
+        ApproveNode lastReviewedNode = nodes.stream()
+                .filter(node -> node.getApproveNodeStatus() != null && node.getApproveNodeStatus() != 0)
+                .max(Comparator.comparing(ApproveNode::getApproveNodeOrder))
+                .orElse(null);
+        if (lastReviewedNode == null) {
+            return actionResult(false, "cancel_review_action", "褰撳墠娴佺▼娌℃湁鍙挙閿�鐨勫鏍歌褰曘��", approveId, null);
+        }
+
+        Long userId = currentUserId(memoryId);
+        if (!isAdmin(userId)
+                && !Objects.equals(process.getApproveUser(), userId)
+                && !Objects.equals(lastReviewedNode.getApproveNodeUserId(), userId)) {
+            return actionResult(false, "cancel_review_action", "鍙湁鐢宠浜恒�佹渶杩戜竴娆″鏍镐汉鎴栫鐞嗗憳鍙互鎾ら攢銆�", approveId, null);
+        }
+
+        lastReviewedNode.setApproveNodeStatus(0);
+        lastReviewedNode.setApproveNodeTime(null);
+        lastReviewedNode.setApproveNodeReason(null);
+        lastReviewedNode.setApproveNodeRemark(reason);
+        lastReviewedNode.setUpdateUser(userId);
+        lastReviewedNode.setUpdateTime(LocalDateTime.now());
+        approveNodeMapper.updateById(lastReviewedNode);
+
+        ApproveLog latestLog = listLogs(process.getId()).stream()
+                .max(Comparator.comparing(ApproveLog::getApproveNodeOrder)
+                        .thenComparing(ApproveLog::getApproveTime, Comparator.nullsLast(Date::compareTo)))
+                .orElse(null);
+        if (latestLog != null) {
+            approveLogMapper.deleteById(latestLog.getId());
+        }
+
+        process.setApproveOverTime(null);
+        process.setApproveRemark(reason);
+        process.setApproveStatus(lastReviewedNode.getApproveNodeOrder() == null || lastReviewedNode.getApproveNodeOrder() <= 1 ? 0 : 1);
+        process.setApproveUserCurrentId(lastReviewedNode.getApproveNodeUserId());
+        process.setApproveUserCurrentName(lastReviewedNode.getApproveNodeUser());
+        approveProcessMapper.updateById(process);
+
+        return actionResult(true, "cancel_review_action", "鏈�杩戜竴娆″鏍稿凡鎾ら攢銆�", approveId, Map.of(
+                "rollbackNodeOrder", lastReviewedNode.getApproveNodeOrder(),
+                "currentStatus", approveStatusName(process.getApproveStatus()),
+                "currentApprover", safe(process.getApproveUserCurrentName()),
+                "reason", safe(reason)
+        ));
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Tool(name = "淇敼瀹℃壒寰呭姙", value = "淇敼瀹℃壒鍗曞熀纭�淇℃伅锛屼粎鍏佽鐢宠浜轰慨鏀癸紱涓嶆敮鎸侀�氳繃 AI 鍙樻洿瀹℃壒绫诲瀷銆�")
+    public String updateTodo(@ToolMemoryId String memoryId,
+                             @P("娴佺▼缂栧彿 approveId") String approveId,
+                             @P(value = "鏂扮殑鏍囬锛屽彲涓嶄紶", required = false) String approveReason,
+                             @P(value = "鏂扮殑寮�濮嬫棩鏈� yyyy-MM-dd锛屽彲涓嶄紶", required = false) String startDate,
+                             @P(value = "鏂扮殑缁撴潫鏃ユ湡 yyyy-MM-dd锛屽彲涓嶄紶", required = false) String endDate,
+                             @P(value = "鏂扮殑閲戦锛屽彲涓嶄紶", required = false) BigDecimal price,
+                             @P(value = "鏂扮殑鍦扮偣锛屽彲涓嶄紶", required = false) String location,
+                             @P(value = "鏂扮殑瀹℃壒绫诲瀷锛屽彲涓嶄紶", required = false) Integer approveType,
+                             @P(value = "鏂扮殑澶囨敞锛屽彲涓嶄紶", required = false) String approveRemark) {
+
+        ApproveProcess process = getAccessibleProcess(memoryId, approveId);
+        if (process == null) {
+            return actionResult(false, "update_action", "鏈壘鍒板搴斿鎵癸紝鎴栧綋鍓嶇敤鎴锋棤鏉冭闂娴佺▼銆�", approveId, null);
+        }
+        if (!Objects.equals(process.getApproveUser(), currentUserId(memoryId)) && !isAdmin(currentUserId(memoryId))) {
+            return actionResult(false, "update_action", "鍙湁鐢宠浜烘垨绠$悊鍛樺彲浠ヤ慨鏀瑰鎵瑰崟銆�", approveId, null);
+        }
+        if (process.getApproveStatus() != null && (process.getApproveStatus() == 1 || process.getApproveStatus() == 2)) {
+            return actionResult(false, "update_action", "瀹℃壒澶勭悊涓垨宸插畬鎴愭椂锛屼笉鍏佽閫氳繃 AI 淇敼銆�", approveId, null);
+        }
+        if (approveType != null && !Objects.equals(approveType, process.getApproveType())) {
+            return actionResult(false, "update_action", "AI 鍔╂墜鏆備笉鏀寔鐩存帴鍙樻洿瀹℃壒绫诲瀷锛岄伩鍏嶈妭鐐归厤缃け鐪熴��", approveId, null);
+        }
+        if (!StringUtils.hasText(approveReason)
+                && !StringUtils.hasText(startDate)
+                && !StringUtils.hasText(endDate)
+                && price == null
+                && !StringUtils.hasText(location)
+                && !StringUtils.hasText(approveRemark)) {
+            return actionResult(false, "update_action", "娌℃湁妫�娴嬪埌鍙洿鏂扮殑瀛楁銆�", approveId, null);
+        }
+
+        if (StringUtils.hasText(approveReason)) {
+            process.setApproveReason(approveReason);
+        }
+        if (StringUtils.hasText(startDate)) {
+            process.setStartDate(parseDate(startDate));
+        }
+        if (StringUtils.hasText(endDate)) {
+            process.setEndDate(parseDate(endDate));
+        }
+        if (price != null) {
+            process.setPrice(price);
+        }
+        if (StringUtils.hasText(location)) {
+            process.setLocation(location);
+        }
+        if (StringUtils.hasText(approveRemark)) {
+            process.setApproveRemark(approveRemark);
+        }
+
+        approveProcessMapper.updateById(process);
+        return actionResult(true, "update_action", "瀹℃壒鍗曞凡鏇存柊銆�", approveId, Map.of(
+                "approveReason", safe(process.getApproveReason()),
+                "startDate", formatDate(process.getStartDate()),
+                "endDate", formatDate(process.getEndDate()),
+                "price", process.getPrice() == null ? "" : process.getPrice(),
+                "location", safe(process.getLocation()),
+                "approveType", approveTypeName(process.getApproveType()),
+                "approveRemark", safe(process.getApproveRemark())
+        ));
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    @Tool(name = "鍒犻櫎瀹℃壒寰呭姙", value = "鍒犻櫎瀹℃壒娴佺▼锛屼粎鍏佽鐢宠浜哄垹闄ゆ湭瀹屾垚鐨勬祦绋嬨��")
+    public String deleteTodo(@ToolMemoryId String memoryId,
+                             @P("娴佺▼缂栧彿 approveId") String approveId) {
+        ApproveProcess process = getAccessibleProcess(memoryId, approveId);
+        if (process == null) {
+            return actionResult(false, "delete_action", "鏈壘鍒板搴斿鎵癸紝鎴栧綋鍓嶇敤鎴锋棤鏉冭闂娴佺▼銆�", approveId, null);
+        }
+        if (!Objects.equals(process.getApproveUser(), currentUserId(memoryId)) && !isAdmin(currentUserId(memoryId))) {
+            return actionResult(false, "delete_action", "鍙湁鐢宠浜烘垨绠$悊鍛樺彲浠ュ垹闄ゅ鎵瑰崟銆�", approveId, null);
+        }
+        if (process.getApproveStatus() != null && (process.getApproveStatus() == 1 || process.getApproveStatus() == 2)) {
+            return actionResult(false, "delete_action", "瀹℃壒澶勭悊涓垨宸插畬鎴愮殑娴佺▼涓嶅厑璁搁�氳繃 AI 鍒犻櫎銆�", approveId, null);
+        }
+
+        approveProcessService.delByIds(Collections.singletonList(process.getId()));
+        return actionResult(true, "delete_action", "瀹℃壒娴佺▼宸插垹闄ゃ��", approveId, Map.of(
+                "deletedProcessId", process.getId(),
+                "approveStatus", approveStatusName(process.getApproveStatus())
+        ));
+    }
+
+    private ApproveProcess getAccessibleProcess(String memoryId, String approveId) {
+        ApproveProcess process = getProcessByApproveId(approveId);
+        if (process == null) {
+            return null;
+        }
+        return canView(process, currentUserId(memoryId)) ? process : null;
+    }
+
+    private ApproveProcess getProcessByApproveId(String approveId) {
+        if (!StringUtils.hasText(approveId)) {
+            return null;
+        }
+        return approveProcessMapper.selectOne(new LambdaQueryWrapper<ApproveProcess>()
+                .eq(ApproveProcess::getApproveId, approveId)
+                .eq(ApproveProcess::getApproveDelete, 0)
+                .last("limit 1"));
+    }
+
+    private List<ApproveNode> listNodes(ApproveProcess process) {
+        if (process == null) {
+            return List.of();
+        }
+        List<ApproveNode> nodes = defaultList(approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>()
+                .eq(ApproveNode::getDeleteFlag, 0)
+                .eq(ApproveNode::getApproveProcessId, process.getApproveId())
+                .orderByAsc(ApproveNode::getApproveNodeOrder)));
+        if (!nodes.isEmpty()) {
+            return nodes;
+        }
+        return defaultList(approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>()
+                .eq(ApproveNode::getDeleteFlag, 0)
+                .eq(ApproveNode::getApproveProcessId, String.valueOf(process.getId()))
+                .orderByAsc(ApproveNode::getApproveNodeOrder)));
+    }
+
+    private List<ApproveLog> listLogs(Long processId) {
+        return defaultList(approveLogMapper.selectList(new LambdaQueryWrapper<ApproveLog>()
+                .eq(ApproveLog::getApproveId, processId)
+                .orderByAsc(ApproveLog::getApproveNodeOrder, ApproveLog::getApproveTime)));
+    }
+
+    private ApproveNode findCurrentNode(List<ApproveNode> nodes) {
+        return nodes.stream()
+                .filter(node -> node.getApproveNodeStatus() != null && node.getApproveNodeStatus() == 0)
+                .min(Comparator.comparing(ApproveNode::getApproveNodeOrder))
+                .orElse(null);
+    }
+
+    private boolean isLastNode(List<ApproveNode> nodes, ApproveNode currentNode) {
+        Integer maxOrder = nodes.stream()
+                .map(ApproveNode::getApproveNodeOrder)
+                .filter(Objects::nonNull)
+                .max(Integer::compareTo)
+                .orElse(null);
+        return maxOrder != null && Objects.equals(maxOrder, currentNode.getApproveNodeOrder());
+    }
+
+    private void writeApproveLog(String memoryId, ApproveProcess process, ApproveNode currentNode, String remark) {
+        if (process == null || currentNode == null) {
+            return;
+        }
+        ApproveLog log = new ApproveLog();
+        log.setApproveId(process.getId());
+        log.setApproveNodeOrder(currentNode.getApproveNodeOrder());
+        log.setApproveUser(currentUserId(memoryId));
+        log.setApproveTime(new Date());
+        log.setApproveStatus(process.getApproveStatus());
+        log.setApproveRemark(remark);
+        approveLogMapper.insert(log);
+    }
+
+    private boolean canView(ApproveProcess process, Long userId) {
+        if (process == null || userId == null) {
+            return false;
+        }
+        return isAdmin(userId)
+                || Objects.equals(process.getApproveUser(), userId)
+                || Objects.equals(process.getApproveUserCurrentId(), userId)
+                || containsUserId(process.getApproveUserIds(), userId);
+    }
+
+    private boolean canOperate(ApproveProcess process, Long userId) {
+        return process != null && userId != null && Objects.equals(process.getApproveUserCurrentId(), userId);
+    }
+
+    private boolean containsUserId(String csv, Long userId) {
+        if (!StringUtils.hasText(csv) || userId == null) {
+            return false;
+        }
+        String target = String.valueOf(userId);
+        for (String item : csv.split(",")) {
+            if (target.equals(item.trim())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private String relationName(ApproveProcess process, Long userId) {
+        if (Objects.equals(process.getApproveUserCurrentId(), userId)) {
+            return "褰撳墠瀹℃壒浜�";
+        }
+        if (Objects.equals(process.getApproveUser(), userId)) {
+            return "鐢宠浜�";
+        }
+        if (containsUserId(process.getApproveUserIds(), userId)) {
+            return "瀹℃壒閾炬垚鍛�";
+        }
+        return "鍙";
+    }
+
+    private List<String> todoColumns() {
+        return List.of("approveId", "approveType", "approveUserName", "approveUserCurrentName",
+                "approveReason", "approveStatus", "createTime", "relation");
+    }
+
+    private int normalizeLimit(Integer limit) {
+        if (limit == null || limit <= 0) {
+            return DEFAULT_LIMIT;
+        }
+        return Math.min(limit, MAX_LIMIT);
+    }
+
+    private Integer parseStatus(String status) {
+        if (!StringUtils.hasText(status) || "all".equalsIgnoreCase(status)) {
+            return null;
+        }
+        return switch (status.trim().toLowerCase()) {
+            case "pending" -> 0;
+            case "processing" -> 1;
+            case "approved" -> 2;
+            case "rejected" -> 3;
+            case "resubmitted" -> 4;
+            default -> null;
+        };
+    }
+
+    private String normalizeScope(String scope) {
+        if (!StringUtils.hasText(scope)) {
+            return "related";
+        }
+        return switch (scope.trim().toLowerCase()) {
+            case "applicant", "mine", "created", "initiated" -> "applicant";
+            case "approver", "handler", "todo", "pending" -> "approver";
+            default -> "related";
+        };
+    }
+
+    private String approveStatusName(Integer status) {
+        if (status == null) {
+            return "鏈煡";
+        }
+        return switch (status) {
+            case 0 -> "寰呭鏍�";
+            case 1 -> "瀹℃牳涓�";
+            case 2 -> "瀹℃牳瀹屾垚";
+            case 3 -> "瀹℃牳鏈�氳繃";
+            case 4 -> "宸查噸鏂版彁浜�";
+            default -> "鏈煡";
+        };
+    }
+
+    private String approveNodeStatusName(Integer status) {
+        if (status == null) {
+            return "鏈煡";
+        }
+        return switch (status) {
+            case 0 -> "鏈鏍�";
+            case 1 -> "鍚屾剰";
+            case 2 -> "鎷掔粷";
+            default -> "鏈煡";
+        };
+    }
+
+    private String approveTypeName(Integer type) {
+        if (type == null) {
+            return "鏈煡";
+        }
+        return switch (type) {
+            case 1 -> "鍏嚭绠$悊";
+            case 2 -> "璇峰亣绠$悊";
+            case 3 -> "鍑哄樊绠$悊";
+            case 4 -> "鎶ラ攢绠$悊";
+            case 5 -> "閲囪喘瀹℃壒";
+            case 6 -> "鎶ヤ环瀹℃壒";
+            case 7 -> "鍙戣揣瀹℃壒";
+            case 8 -> "鍗遍櫓浣滀笟瀹℃壒";
+            case 9 -> "鍔炲叕鐢ㄥ搧瀹℃壒";
+            default -> "绫诲瀷" + type;
+        };
+    }
+
+    private String safe(Object value) {
+        return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ');
+    }
+
+    private String formatDateTime(Object value) {
+        if (value == null) {
+            return "";
+        }
+        if (value instanceof LocalDateTime localDateTime) {
+            return localDateTime.format(DATE_TIME_FORMATTER);
+        }
+        return safe(value);
+    }
+
+    private String formatDate(Date value) {
+        if (value == null) {
+            return "";
+        }
+        return value.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DATE_FORMATTER);
+    }
+
+    private long countByStatus(List<ApproveProcess> processes, int status) {
+        return processes.stream()
+                .filter(process -> process.getApproveStatus() != null)
+                .filter(process -> process.getApproveStatus() == status)
+                .count();
+    }
+
+    private String calculateRate(long part, int total) {
+        if (total <= 0) {
+            return "0.00%";
+        }
+        return String.format("%.2f%%", part * 100.0 / total);
+    }
+
+    private List<Map<String, Object>> toTrendItems(List<String> dates, List<Long> values) {
+        List<Map<String, Object>> items = new ArrayList<>();
+        for (int i = 0; i < dates.size(); i++) {
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("date", dates.get(i));
+            item.put("count", values.get(i));
+            items.add(item);
+        }
+        return items;
+    }
+
+    private Map<String, Object> buildStatusBarOption(Map<String, Long> statusStats) {
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "瀹℃壒鐘舵�佸垎甯�", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", new ArrayList<>(statusStats.keySet())));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(Map.of(
+                "name", "鏁伴噺",
+                "type", "bar",
+                "data", new ArrayList<>(statusStats.values()),
+                "barWidth", "40%"
+        )));
+        return option;
+    }
+
+    private Map<String, Object> buildTypePieOption(Map<String, Long> typeStats) {
+        List<Map<String, Object>> data = typeStats.entrySet().stream()
+                .map(entry -> {
+                    Map<String, Object> item = new LinkedHashMap<>();
+                    item.put("name", entry.getKey());
+                    item.put("value", entry.getValue());
+                    return item;
+                })
+                .collect(Collectors.toList());
+
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "瀹℃壒绫诲瀷鍗犳瘮", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "item"));
+        option.put("legend", Map.of("orient", "vertical", "left", "left"));
+        option.put("series", List.of(Map.of(
+                "name", "瀹℃壒绫诲瀷",
+                "type", "pie",
+                "radius", List.of("35%", "65%"),
+                "data", data
+        )));
+        return option;
+    }
+
+    private Map<String, Object> buildTrendLineOption(List<String> dates, List<Long> values, String label) {
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", label + "瀹℃壒鏂板瓒嬪娍", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", dates));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(Map.of(
+                "name", "鏂板瀹℃壒",
+                "type", "line",
+                "smooth", true,
+                "data", values,
+                "areaStyle", Map.of()
+        )));
+        return option;
+    }
+
+    private Date parseDate(String dateText) {
+        try {
+            LocalDate localDate = LocalDate.parse(dateText, DATE_FORMATTER);
+            return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+        } catch (Exception e) {
+            throw new IllegalArgumentException("鏃ユ湡鏍煎紡蹇呴』鏄� yyyy-MM-dd");
+        }
+    }
+
+    private DateRange resolveDateRange(String startDateText, String endDateText, String timeRange) {
+        LocalDate today = LocalDate.now();
+        LocalDate explicitStart = parseLocalDate(startDateText);
+        LocalDate explicitEnd = parseLocalDate(endDateText);
+        if (explicitStart != null || explicitEnd != null) {
+            LocalDate start = explicitStart != null ? explicitStart : explicitEnd;
+            LocalDate end = explicitEnd != null ? explicitEnd : explicitStart;
+            if (start.isAfter(end)) {
+                LocalDate temp = start;
+                start = end;
+                end = temp;
+            }
+            return new DateRange(start, end, start + "鑷�" + end);
+        }
+        if (!StringUtils.hasText(timeRange)) {
+            return new DateRange(today.minusDays(6), today, "杩�7澶�");
+        }
+
+        String text = timeRange.trim();
+        if (text.contains("浠婂ぉ")) {
+            return new DateRange(today, today, "浠婂ぉ");
+        }
+        if (text.contains("鏄ㄥぉ") || text.contains("鏄ㄦ棩")) {
+            LocalDate day = today.minusDays(1);
+            return new DateRange(day, day, "鏄ㄥぉ");
+        }
+        if (text.contains("鏈懆")) {
+            LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L);
+            return new DateRange(start, today, "鏈懆");
+        }
+        if (text.contains("涓婂懆")) {
+            LocalDate thisWeekStart = today.minusDays(today.getDayOfWeek().getValue() - 1L);
+            LocalDate start = thisWeekStart.minusWeeks(1);
+            LocalDate end = thisWeekStart.minusDays(1);
+            return new DateRange(start, end, "涓婂懆");
+        }
+        if (text.contains("鏈湀")) {
+            LocalDate start = today.withDayOfMonth(1);
+            return new DateRange(start, today, "鏈湀");
+        }
+        if (text.contains("涓婃湀")) {
+            YearMonth lastMonth = YearMonth.from(today).minusMonths(1);
+            return new DateRange(lastMonth.atDay(1), lastMonth.atEndOfMonth(), "涓婃湀");
+        }
+        if (text.contains("鏈勾") || text.contains("浠婂勾")) {
+            LocalDate start = today.withDayOfYear(1);
+            return new DateRange(start, today, "鏈勾");
+        }
+        if (text.contains("鍘诲勾")) {
+            LocalDate start = today.minusYears(1).withDayOfYear(1);
+            LocalDate end = today.minusYears(1).withMonth(12).withDayOfMonth(31);
+            return new DateRange(start, end, "鍘诲勾");
+        }
+
+        Matcher relativeMatcher = java.util.regex.Pattern.compile("(杩憒鏈�杩�)(\\d+)(澶﹟鍛▅涓湀|鏈坾骞�)").matcher(text);
+        if (relativeMatcher.find()) {
+            int amount = Integer.parseInt(relativeMatcher.group(2));
+            String unit = relativeMatcher.group(3);
+            LocalDate start = switch (unit) {
+                case "澶�" -> today.minusDays(Math.max(amount - 1L, 0));
+                case "鍛�" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1);
+                case "涓湀", "鏈�" -> today.minusMonths(Math.max(amount, 1)).plusDays(1);
+                case "骞�" -> today.minusYears(Math.max(amount, 1)).plusDays(1);
+                default -> today.minusDays(6);
+            };
+            return new DateRange(start, today, "杩�" + amount + unit);
+        }
+
+        Matcher dateMatcher = java.util.regex.Pattern.compile("(\\d{4}-\\d{2}-\\d{2})").matcher(text);
+        if (dateMatcher.find()) {
+            LocalDate start = LocalDate.parse(dateMatcher.group(1));
+            LocalDate end = dateMatcher.find() ? LocalDate.parse(dateMatcher.group(1)) : start;
+            if (start.isAfter(end)) {
+                LocalDate temp = start;
+                start = end;
+                end = temp;
+            }
+            return new DateRange(start, end, start + "鑷�" + end);
+        }
+
+        return new DateRange(today.minusDays(6), today, "杩�7澶�");
+    }
+
+    private boolean withinDateRange(LocalDateTime createTime, DateRange dateRange) {
+        if (createTime == null) {
+            return false;
+        }
+        LocalDate date = createTime.toLocalDate();
+        return !date.isBefore(dateRange.start()) && !date.isAfter(dateRange.end());
+    }
+
+    private TrendRange buildTrendRange(LocalDate start, LocalDate end, List<ApproveProcess> processes) {
+        long days = ChronoUnit.DAYS.between(start, end) + 1;
+        if (days <= 31) {
+            List<String> labels = new ArrayList<>();
+            List<Long> values = new ArrayList<>();
+            for (LocalDate cursor = start; !cursor.isAfter(end); cursor = cursor.plusDays(1)) {
+                LocalDate current = cursor;
+                labels.add(current.toString());
+                values.add(processes.stream()
+                        .filter(process -> process.getCreateTime() != null)
+                        .filter(process -> process.getCreateTime().toLocalDate().equals(current))
+                        .count());
+            }
+            return new TrendRange(labels, values, "day", start + "鑷�" + end);
+        }
+
+        List<String> labels = new ArrayList<>();
+        List<Long> values = new ArrayList<>();
+        YearMonth startMonth = YearMonth.from(start);
+        YearMonth endMonth = YearMonth.from(end);
+        for (YearMonth cursor = startMonth; !cursor.isAfter(endMonth); cursor = cursor.plusMonths(1)) {
+            YearMonth current = cursor;
+            labels.add(current.toString());
+            values.add(processes.stream()
+                    .filter(process -> process.getCreateTime() != null)
+                    .filter(process -> YearMonth.from(process.getCreateTime()).equals(current))
+                    .count());
+        }
+        return new TrendRange(labels, values, "month", start + "鑷�" + end);
+    }
+
+    private LocalDate parseLocalDate(String text) {
+        if (!StringUtils.hasText(text)) {
+            return null;
+        }
+        return LocalDate.parse(text.trim());
+    }
+
+    private String actionResult(boolean success, String type, String description, String approveId, Map<String, Object> data) {
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("approveId", safe(approveId));
+        return jsonResponse(success, type, description, summary, data == null ? Map.of() : data, Map.of());
+    }
+
+    private String jsonResponse(boolean success,
+                                String type,
+                                String description,
+                                Map<String, Object> summary,
+                                Map<String, Object> data,
+                                Map<String, Object> charts) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("success", success);
+        result.put("type", type);
+        result.put("description", description);
+        result.put("summary", summary == null ? Map.of() : summary);
+        result.put("data", data == null ? Map.of() : data);
+        result.put("charts", charts == null ? Map.of() : charts);
+        return JSON.toJSONString(result);
+    }
+
+    private LoginUser currentLoginUser(String memoryId) {
+        LoginUser loginUser = aiSessionUserContext.get(memoryId);
+        if (loginUser != null) {
+            return loginUser;
+        }
+        return SecurityUtils.getLoginUser();
+    }
+
+    private Long currentUserId(String memoryId) {
+        return currentLoginUser(memoryId).getUserId();
+    }
+
+    private boolean isAdmin(Long userId) {
+        return SecurityUtils.isAdmin(userId);
+    }
+
+    private <T> List<T> defaultList(List<T> list) {
+        return list == null ? List.of() : list;
+    }
+
+    private record DateRange(LocalDate start, LocalDate end, String label) {
+    }
+
+    private record TrendRange(List<String> labels, List<Long> values, String granularity, String label) {
+    }
+}
diff --git a/src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java b/src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java
new file mode 100644
index 0000000..e907aff
--- /dev/null
+++ b/src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java
@@ -0,0 +1,2311 @@
+package com.ruoyi.ai.tools;
+
+import com.alibaba.fastjson2.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.ruoyi.account.mapper.AccountStatementMapper;
+import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper;
+import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
+import com.ruoyi.account.pojo.AccountStatement;
+import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
+import com.ruoyi.account.pojo.sales.AccountSalesCollection;
+import com.ruoyi.account.service.impl.AccountingServiceImpl;
+import com.ruoyi.ai.context.AiSessionUserContext;
+import com.ruoyi.basic.mapper.CustomerMapper;
+import com.ruoyi.basic.mapper.ProductMapper;
+import com.ruoyi.basic.mapper.ProductModelMapper;
+import com.ruoyi.basic.mapper.SupplierManageMapper;
+import com.ruoyi.basic.pojo.Customer;
+import com.ruoyi.basic.pojo.Product;
+import com.ruoyi.basic.pojo.ProductModel;
+import com.ruoyi.basic.pojo.SupplierManage;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.device.mapper.DeviceLedgerMapper;
+import com.ruoyi.device.mapper.DeviceRepairMapper;
+import com.ruoyi.device.pojo.DeviceLedger;
+import com.ruoyi.device.pojo.DeviceRepair;
+import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
+import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
+import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
+import com.ruoyi.production.mapper.ProductionAccountMapper;
+import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
+import com.ruoyi.production.mapper.ProductionOrderMapper;
+import com.ruoyi.production.mapper.ProductionPlanMapper;
+import com.ruoyi.production.mapper.ProductionProductMainMapper;
+import com.ruoyi.production.mapper.ProductionProductOutputMapper;
+import com.ruoyi.production.pojo.ProductionAccount;
+import com.ruoyi.production.pojo.ProductionOperationTask;
+import com.ruoyi.production.pojo.ProductionOrder;
+import com.ruoyi.production.pojo.ProductionPlan;
+import com.ruoyi.production.pojo.ProductionProductMain;
+import com.ruoyi.production.pojo.ProductionProductOutput;
+import com.ruoyi.sales.mapper.SalesLedgerMapper;
+import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
+import com.ruoyi.sales.pojo.SalesLedger;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import com.ruoyi.stock.mapper.StockInventoryMapper;
+import com.ruoyi.stock.pojo.StockInventory;
+import com.ruoyi.technology.mapper.TechnologyOperationMapper;
+import com.ruoyi.technology.pojo.TechnologyOperation;
+import dev.langchain4j.agent.tool.P;
+import dev.langchain4j.agent.tool.Tool;
+import dev.langchain4j.agent.tool.ToolMemoryId;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.YearMonth;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+@Component
+public class FinancialAgentTools {
+
+    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final Pattern RELATIVE_PATTERN = Pattern.compile("(杩憒鏈�杩�)?\\s*(\\d+)\\s*(澶﹟鍛▅涓湀|鏈坾骞�)");
+    private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
+    private static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
+    private static final int DEFAULT_LIMIT = 10;
+    private static final int MAX_LIMIT = 50;
+
+    private static final BigDecimal DEFAULT_FALLBACK_MATERIAL_COST_RATE = new BigDecimal("0.60");
+    private static final BigDecimal DEFAULT_LABOR_COST_RATE = new BigDecimal("0.15");
+    private static final BigDecimal DEFAULT_OVERHEAD_COST_RATE = new BigDecimal("0.10");
+
+    private static final BigDecimal SME_RECEIVABLE_RISK_THRESHOLD = new BigDecimal("500000");
+    private static final BigDecimal SME_INVENTORY_RISK_THRESHOLD = new BigDecimal("1000000");
+    private static final BigDecimal SME_PROFIT_WARNING_RATE = new BigDecimal("0.08");
+
+    private final SalesLedgerMapper salesLedgerMapper;
+    private final SalesLedgerProductMapper salesLedgerProductMapper;
+    private final ProductionAccountMapper productionAccountMapper;
+    private final ProductionProductMainMapper productionProductMainMapper;
+    private final ProductionOperationTaskMapper productionOperationTaskMapper;
+    private final ProductionOrderMapper productionOrderMapper;
+    private final ProductionPlanMapper productionPlanMapper;
+    private final ProductionProductOutputMapper productionProductOutputMapper;
+    private final TechnologyOperationMapper technologyOperationMapper;
+    private final DeviceLedgerMapper deviceLedgerMapper;
+    private final DeviceRepairMapper deviceRepairMapper;
+    private final ProcurementRecordMapper procurementRecordMapper;
+    private final ProcurementRecordOutMapper procurementRecordOutMapper;
+    private final StockInventoryMapper stockInventoryMapper;
+    private final AccountSalesCollectionMapper accountSalesCollectionMapper;
+    private final AccountPurchasePaymentMapper accountPurchasePaymentMapper;
+    private final AccountStatementMapper accountStatementMapper;
+    private final CustomerMapper customerMapper;
+    private final SupplierManageMapper supplierManageMapper;
+    private final ProductModelMapper productModelMapper;
+    private final ProductMapper productMapper;
+    private final AiSessionUserContext aiSessionUserContext;
+
+    public FinancialAgentTools(SalesLedgerMapper salesLedgerMapper,
+                               SalesLedgerProductMapper salesLedgerProductMapper,
+                               ProductionAccountMapper productionAccountMapper,
+                               ProductionProductMainMapper productionProductMainMapper,
+                               ProductionOperationTaskMapper productionOperationTaskMapper,
+                               ProductionOrderMapper productionOrderMapper,
+                               ProductionPlanMapper productionPlanMapper,
+                               ProductionProductOutputMapper productionProductOutputMapper,
+                               TechnologyOperationMapper technologyOperationMapper,
+                               DeviceLedgerMapper deviceLedgerMapper,
+                               DeviceRepairMapper deviceRepairMapper,
+                               ProcurementRecordMapper procurementRecordMapper,
+                               ProcurementRecordOutMapper procurementRecordOutMapper,
+                               StockInventoryMapper stockInventoryMapper,
+                               AccountSalesCollectionMapper accountSalesCollectionMapper,
+                               AccountPurchasePaymentMapper accountPurchasePaymentMapper,
+                               AccountStatementMapper accountStatementMapper,
+                               CustomerMapper customerMapper,
+                               SupplierManageMapper supplierManageMapper,
+                               ProductModelMapper productModelMapper,
+                               ProductMapper productMapper,
+                               AiSessionUserContext aiSessionUserContext) {
+        this.salesLedgerMapper = salesLedgerMapper;
+        this.salesLedgerProductMapper = salesLedgerProductMapper;
+        this.productionAccountMapper = productionAccountMapper;
+        this.productionProductMainMapper = productionProductMainMapper;
+        this.productionOperationTaskMapper = productionOperationTaskMapper;
+        this.productionOrderMapper = productionOrderMapper;
+        this.productionPlanMapper = productionPlanMapper;
+        this.productionProductOutputMapper = productionProductOutputMapper;
+        this.technologyOperationMapper = technologyOperationMapper;
+        this.deviceLedgerMapper = deviceLedgerMapper;
+        this.deviceRepairMapper = deviceRepairMapper;
+        this.procurementRecordMapper = procurementRecordMapper;
+        this.procurementRecordOutMapper = procurementRecordOutMapper;
+        this.stockInventoryMapper = stockInventoryMapper;
+        this.accountSalesCollectionMapper = accountSalesCollectionMapper;
+        this.accountPurchasePaymentMapper = accountPurchasePaymentMapper;
+        this.accountStatementMapper = accountStatementMapper;
+        this.customerMapper = customerMapper;
+        this.supplierManageMapper = supplierManageMapper;
+        this.productModelMapper = productModelMapper;
+        this.productMapper = productMapper;
+        this.aiSessionUserContext = aiSessionUserContext;
+    }
+
+    @Tool(name = "璐㈠姟鐭ヨ瘑妫�绱�", value = "鎸夎储鍔$粡钀ラ棶棰樻绱笟璐㈣瀺鍚堢煡璇嗙墖娈典笌鎸囨爣鍙e緞锛屼綔涓篟AG涓婁笅鏂囥��")
+    public String retrieveFinancialKnowledge(@ToolMemoryId String memoryId,
+                                             @P(value = "闂鎴栧叧閿瘝锛屼緥濡傚埄娑︿笅闄嶃�佸簱瀛樺懆杞�佽祫閲戠己鍙�") String question) {
+        List<KnowledgeDoc> knowledgeDocs = financeKnowledgeBase();
+        String normalized = normalizeForMatch(question);
+        List<KnowledgeDoc> ranked = knowledgeDocs.stream()
+                .sorted(Comparator.comparingInt((KnowledgeDoc doc) -> keywordHitCount(doc.keywords(), normalized)).reversed())
+                .filter(doc -> keywordHitCount(doc.keywords(), normalized) > 0 || !StringUtils.hasText(normalized))
+                .limit(5)
+                .toList();
+
+        List<Map<String, Object>> items = ranked.stream().map(doc -> {
+            Map<String, Object> map = new LinkedHashMap<>();
+            map.put("topic", doc.topic());
+            map.put("knowledge", doc.knowledge());
+            map.put("relatedTables", doc.relatedTables());
+            map.put("suggestedQuestions", doc.suggestedQuestions());
+            return map;
+        }).toList();
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("question", safe(question));
+        summary.put("hitCount", items.size());
+        summary.put("retrievalMode", "keyword_rag");
+        return jsonResponse(true, "financial_rag_knowledge", "宸茶繑鍥炶储鍔$煡璇嗘绱㈢粨鏋�", summary, Map.of("items", items), Map.of());
+    }
+
+    @Tool(name = "鏅鸿兘鎴愭湰鏍哥畻", value = "鑷姩鏍哥畻浜у搧鎴愭湰銆佸伐搴忔垚鏈�佷汉宸ユ垚鏈�佽澶囨姌鏃с�佹潗鏂欐崯鑰椾笌璁㈠崟鍒╂鼎銆�")
+    public String calculateIntelligentCost(@ToolMemoryId String memoryId,
+                                           @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
+                                           @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
+                                           @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屽鏈湀銆佽繎30澶�", required = false) String timeRange,
+                                           @P(value = "鍏抽敭璇嶏紝鍙尮閰嶅悎鍚屽彿/瀹㈡埛/椤圭洰", required = false) String keyword,
+                                           @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�50", required = false) Integer limit) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        if (loginUser == null) {
+            return jsonResponse(false, "financial_cost_accounting", "鐢ㄦ埛淇℃伅鑾峰彇澶辫触", Map.of(), Map.of(), Map.of());
+        }
+
+        DateRange range = resolveDateRange(startDate, endDate, timeRange, "杩�30澶�");
+        AnalysisBundle bundle = buildOrderProfitBundle(loginUser, range, keyword, limit);
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("timeRange", range.label());
+        summary.put("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
+        summary.put("orderCount", bundle.orderMetrics().size());
+        summary.put("totalRevenue", bundle.totalRevenue());
+        summary.put("totalMaterialCost", bundle.totalMaterialCost());
+        summary.put("totalLaborCost", bundle.totalLaborCost());
+        summary.put("totalDepreciationCost", bundle.totalDepreciationCost());
+        summary.put("totalScrapCost", bundle.totalScrapCost());
+        summary.put("totalCost", bundle.totalCost());
+        summary.put("totalProfit", bundle.totalProfit());
+        summary.put("profitRate", toPercent(rate(bundle.totalProfit(), bundle.totalRevenue())));
+
+        List<Map<String, Object>> orderItems = bundle.orderMetrics().stream()
+                .map(this::toOrderCostItem)
+                .toList();
+        List<Map<String, Object>> processItems = bundle.processCostRanking().entrySet().stream()
+                .map(entry -> {
+                    Map<String, Object> map = new LinkedHashMap<>();
+                    map.put("processName", entry.getKey());
+                    map.put("cost", entry.getValue());
+                    return map;
+                }).toList();
+
+        List<Map<String, Object>> topCustomerItems = buildCustomerProfitTop(bundle.orderMetrics(), 5);
+
+        Map<String, Object> charts = new LinkedHashMap<>();
+        charts.put("costCompositionPieOption",
+                buildCostCompositionPie(bundle.totalMaterialCost(), bundle.totalLaborCost(), bundle.totalDepreciationCost(), bundle.totalScrapCost()));
+        charts.put("orderProfitBarOption", buildOrderProfitBar(bundle.orderMetrics()));
+        charts.put("processCostBarOption", buildProcessCostBar(bundle.processCostRanking()));
+
+        return jsonResponse(true, "financial_cost_accounting", "宸插畬鎴愭櫤鑳芥垚鏈牳绠�", summary,
+                Map.of(
+                        "orders", orderItems,
+                        "processCostRanking", processItems,
+                        "topCustomers", topCustomerItems
+                ),
+                charts
+        );
+    }
+
+    @Tool(name = "璁㈠崟鍒╂鼎鍒嗘瀽", value = "璇嗗埆浣庡埄娑�/浜忔崯璁㈠崟锛岃緭鍑哄師鍥犲垎鏋愬拰浼樺寲寤鸿銆�")
+    public String analyzeOrderProfit(@ToolMemoryId String memoryId,
+                                     @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
+                                     @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
+                                     @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屽鏈湀銆佽繎30澶�", required = false) String timeRange,
+                                     @P(value = "鍏抽敭璇嶏紝鍙尮閰嶅悎鍚屽彿/瀹㈡埛/椤圭洰", required = false) String keyword,
+                                     @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�50", required = false) Integer limit) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        if (loginUser == null) {
+            return jsonResponse(false, "financial_order_profit_analysis", "鐢ㄦ埛淇℃伅鑾峰彇澶辫触", Map.of(), Map.of(), Map.of());
+        }
+
+        DateRange range = resolveDateRange(startDate, endDate, timeRange, "杩�30澶�");
+        AnalysisBundle bundle = buildOrderProfitBundle(loginUser, range, keyword, limit);
+        List<OrderProfitMetric> metrics = bundle.orderMetrics();
+
+        List<OrderProfitMetric> riskyOrders = metrics.stream()
+                .filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0 || item.profitRate().compareTo(SME_PROFIT_WARNING_RATE) < 0)
+                .sorted(Comparator.comparing(OrderProfitMetric::profitRate)
+                        .thenComparing(OrderProfitMetric::profit))
+                .toList();
+
+        Map<String, BigDecimal> customerProfitMap = new LinkedHashMap<>();
+        for (OrderProfitMetric metric : metrics) {
+            customerProfitMap.merge(metric.customerName(), metric.profit(), BigDecimal::add);
+        }
+        Map.Entry<String, BigDecimal> topCustomer = customerProfitMap.entrySet().stream()
+                .max(Map.Entry.comparingByValue())
+                .orElse(Map.entry("鏆傛棤鏁版嵁", BigDecimal.ZERO));
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("timeRange", range.label());
+        summary.put("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
+        summary.put("orderCount", metrics.size());
+        summary.put("lossOrderCount", metrics.stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count());
+        summary.put("lowProfitOrderCount", riskyOrders.size());
+        summary.put("avgProfitRate", toPercent(avgRate(metrics)));
+        summary.put("topCustomerByProfit", topCustomer.getKey());
+        summary.put("topCustomerProfit", topCustomer.getValue());
+
+        List<Map<String, Object>> riskyItems = riskyOrders.stream()
+                .limit(normalizeLimit(limit))
+                .map(this::toRiskOrderItem)
+                .toList();
+
+        Map<String, Object> charts = new LinkedHashMap<>();
+        charts.put("profitDistributionOption", buildProfitDistributionBar(metrics));
+        charts.put("lossOrderTrendOption", buildLossOrderTrendLine(metrics));
+        charts.put("customerProfitTopOption", buildCustomerProfitBar(customerProfitMap));
+
+        return jsonResponse(true, "financial_order_profit_analysis", "宸插畬鎴愯鍗曞埄娑﹀垎鏋�", summary,
+                Map.of(
+                        "riskOrders", riskyItems,
+                        "allOrders", metrics.stream().map(this::toOrderCostItem).toList(),
+                        "customerProfitTop", buildCustomerProfitTop(metrics, 10)
+                ),
+                charts
+        );
+    }
+
+    @Tool(name = "搴撳瓨璧勯噾鍒嗘瀽", value = "鍒嗘瀽搴撳瓨绉帇銆佸憜婊炲簱瀛樸�佽祫閲戝崰鐢ㄤ笌鍛ㄨ浆鐜囥��")
+    public String analyzeInventoryCapital(@ToolMemoryId String memoryId,
+                                          @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
+                                          @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
+                                          @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屽鏈湀銆佽繎30澶�", required = false) String timeRange,
+                                          @P(value = "鍏抽敭璇嶏紝鍙尮閰嶄骇鍝佸悕绉�/鍨嬪彿", required = false) String keyword,
+                                          @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�50", required = false) Integer limit) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        DateRange range = resolveDateRange(startDate, endDate, timeRange, "杩�30澶�");
+        int finalLimit = normalizeLimit(limit);
+
+        List<StockInventory> inventoryRows = queryStockInventory(loginUser);
+        if (inventoryRows.isEmpty()) {
+            return jsonResponse(true, "financial_inventory_capital_analysis", "褰撳墠鏃犲簱瀛樻暟鎹�",
+                    rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of());
+        }
+
+        Set<Long> modelIds = inventoryRows.stream()
+                .map(StockInventory::getProductModelId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Long, ProductModel> productModelMap = queryProductModels(modelIds);
+        Map<Long, Product> productMap = queryProducts(productModelMap.values());
+        Map<Long, BigDecimal> avgUnitCostByModelId = queryAverageUnitCostByModel(loginUser, modelIds);
+        OutboundStats outboundStats = queryOutboundStats(loginUser, modelIds, range);
+
+        List<InventoryMetric> metrics = buildInventoryMetrics(inventoryRows, productModelMap, productMap, avgUnitCostByModelId, outboundStats)
+                .stream()
+                .filter(metric -> matchInventoryKeyword(metric, keyword))
+                .sorted(Comparator.comparing(InventoryMetric::inventoryValue).reversed())
+                .toList();
+
+        BigDecimal totalInventoryValue = metrics.stream().map(InventoryMetric::inventoryValue).reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal stagnantValue = metrics.stream()
+                .filter(metric -> metric.stagnantDays() >= 90)
+                .map(InventoryMetric::inventoryValue)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        long stagnantCount = metrics.stream().filter(metric -> metric.stagnantDays() >= 90).count();
+        long overstockCount = metrics.stream().filter(InventoryMetric::overstock).count();
+        BigDecimal totalOutboundCost = outboundStats.totalOutboundCost();
+        BigDecimal turnoverDays = totalOutboundCost.compareTo(BigDecimal.ZERO) > 0
+                ? totalInventoryValue.multiply(BigDecimal.valueOf(daysBetween(range.start(), range.end()) + 1L))
+                .divide(totalOutboundCost, 2, RoundingMode.HALF_UP)
+                : BigDecimal.ZERO;
+
+        List<Map<String, Object>> items = metrics.stream()
+                .limit(finalLimit)
+                .map(this::toInventoryItem)
+                .toList();
+
+        Map<String, Object> summary = rangeSummary(range, metrics.size(), keyword);
+        summary.put("totalInventoryValue", totalInventoryValue);
+        summary.put("stagnantValue", stagnantValue);
+        summary.put("stagnantCount", stagnantCount);
+        summary.put("overstockCount", overstockCount);
+        summary.put("turnoverDays", turnoverDays);
+        summary.put("capitalOccupation", totalInventoryValue);
+        summary.put("totalOutboundCost", totalOutboundCost);
+
+        Map<String, Object> charts = new LinkedHashMap<>();
+        charts.put("inventoryValueTopOption", buildInventoryTopBar(metrics));
+        charts.put("inventoryAgingPieOption", buildInventoryAgingPie(metrics));
+        charts.put("inventoryTurnoverGauge", buildTurnoverGauge(turnoverDays));
+
+        return jsonResponse(true, "financial_inventory_capital_analysis", "宸插畬鎴愬簱瀛樿祫閲戝垎鏋�", summary, Map.of("items", items), charts);
+    }
+
+    @Tool(name = "搴旀敹搴斾粯涓庣幇閲戞祦棰勬祴", value = "棰勬祴鏈潵鐜伴噾娴併�佸洖娆鹃闄┿�佷粯娆惧帇鍔涗笌璧勯噾缂哄彛銆�")
+    public String forecastCashFlow(@ToolMemoryId String memoryId,
+                                   @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
+                                   @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
+                                   @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屽杩�90澶┿�佹湰骞�", required = false) String timeRange,
+                                   @P(value = "棰勬祴鏈堜唤鏁帮紝榛樿3锛屾渶澶�6", required = false) Integer forecastMonths) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        DateRange range = resolveDateRange(startDate, endDate, timeRange, "杩�90澶�");
+        int months = forecastMonths == null || forecastMonths <= 0 ? 3 : Math.min(forecastMonths, 6);
+
+        List<AccountSalesCollection> collections = queryCollections(loginUser, range);
+        List<AccountPurchasePayment> payments = queryPayments(loginUser, range);
+        List<MonthlyCashFlow> monthlyActual = buildMonthlyCashFlow(range, collections, payments);
+        List<MonthlyCashFlow> monthlyForecast = forecastMonthlyCashFlow(monthlyActual, months);
+
+        StatementSnapshot snapshot = buildStatementSnapshot(loginUser);
+        BigDecimal receivableTotal = snapshot.receivableTotal();
+        BigDecimal payableTotal = snapshot.payableTotal();
+        BigDecimal forecastNetSum = monthlyForecast.stream().map(MonthlyCashFlow::netFlow).reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal coverage = receivableTotal.add(maxZero(forecastNetSum));
+        BigDecimal fundGap = maxZero(payableTotal.subtract(coverage));
+
+        Map<String, String> customerNameMap = queryCustomerNameMap(snapshot.receivableTop().stream().map(StatementMetric::entityId).collect(Collectors.toSet()));
+        Map<String, String> supplierNameMap = querySupplierNameMap(snapshot.payableTop().stream().map(StatementMetric::entityId).collect(Collectors.toSet()));
+
+        List<Map<String, Object>> receivableRiskItems = snapshot.receivableTop().stream().map(item -> toStatementRiskItem(item, customerNameMap, "customer")).toList();
+        List<Map<String, Object>> payablePressureItems = snapshot.payableTop().stream().map(item -> toStatementRiskItem(item, supplierNameMap, "supplier")).toList();
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("timeRange", range.label());
+        summary.put("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
+        summary.put("actualIncomeTotal", collections.stream().map(AccountSalesCollection::getCollectionAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
+        summary.put("actualExpenseTotal", payments.stream().map(AccountPurchasePayment::getPaymentAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
+        summary.put("receivableBalance", receivableTotal);
+        summary.put("payableBalance", payableTotal);
+        summary.put("forecastNetSum", forecastNetSum);
+        summary.put("fundGap", fundGap);
+        summary.put("forecastMonths", months);
+        summary.put("collectionRiskLevel", riskLevelByAmount(receivableTotal));
+        summary.put("paymentPressureLevel", riskLevelByAmount(payableTotal));
+
+        Map<String, Object> charts = new LinkedHashMap<>();
+        charts.put("cashFlowTrendOption", buildCashflowTrend(monthlyActual, monthlyForecast));
+        charts.put("receivablePayableBarOption", buildReceivablePayableBar(receivableTotal, payableTotal));
+        charts.put("fundGapGaugeOption", buildFundGapGauge(fundGap));
+
+        return jsonResponse(true, "financial_cashflow_forecast", "宸插畬鎴愬簲鏀跺簲浠樹笌鐜伴噾娴侀娴�", summary,
+                Map.of(
+                        "actualMonthly", monthlyActual.stream().map(this::toMonthlyCashFlowItem).toList(),
+                        "forecastMonthly", monthlyForecast.stream().map(this::toMonthlyCashFlowItem).toList(),
+                        "receivableRiskTop", receivableRiskItems,
+                        "payablePressureTop", payablePressureItems
+                ),
+                charts
+        );
+    }
+
+    @Tool(name = "缁忚惀寮傚父棰勮", value = "璇嗗埆鎴愭湰寮傚父銆佸埄娑﹀紓甯搞�佸洖娆惧紓甯搞�佽鍗曢闄┿�佸簱瀛樺紓甯搞��")
+    public String detectBusinessAnomalies(@ToolMemoryId String memoryId,
+                                          @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
+                                          @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
+                                          @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屽杩�30澶�", required = false) String timeRange,
+                                          @P(value = "杩斿洖鏉℃暟锛岄粯璁�10锛屾渶澶�50", required = false) Integer limit) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        DateRange range = resolveDateRange(startDate, endDate, timeRange, "杩�30澶�");
+        int finalLimit = normalizeLimit(limit);
+
+        AnalysisBundle currentBundle = buildOrderProfitBundle(loginUser, range, null, Math.max(finalLimit, 30));
+        DateRange prevRange = previousSameLengthRange(range);
+        AnalysisBundle prevBundle = buildOrderProfitBundle(loginUser, prevRange, null, 50);
+
+        BigDecimal currentCostRate = rate(currentBundle.totalCost(), currentBundle.totalRevenue());
+        BigDecimal prevCostRate = rate(prevBundle.totalCost(), prevBundle.totalRevenue());
+        BigDecimal costRateDiff = currentCostRate.subtract(prevCostRate);
+
+        StatementSnapshot snapshot = buildStatementSnapshot(loginUser);
+        List<InventoryMetric> inventoryMetrics = buildInventoryMetrics(
+                queryStockInventory(loginUser),
+                queryProductModels(Collections.emptySet()),
+                Map.of(),
+                queryAverageUnitCostByModel(loginUser, Collections.emptySet()),
+                queryOutboundStats(loginUser, Collections.emptySet(), range)
+        );
+
+        List<Map<String, Object>> anomalyItems = new ArrayList<>();
+        if (costRateDiff.compareTo(new BigDecimal("0.10")) > 0) {
+            anomalyItems.add(anomalyItem("high", "鎴愭湰寮傚父", "鍗曚綅鏀跺叆鎴愭湰鐜囪緝涓婂懆鏈熶笂鍗囪秴杩�10%", Map.of(
+                    "currentCostRate", toPercent(currentCostRate),
+                    "previousCostRate", toPercent(prevCostRate),
+                    "delta", toPercent(costRateDiff)
+            )));
+        }
+        long lossCount = currentBundle.orderMetrics().stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count();
+        if (lossCount > 0) {
+            anomalyItems.add(anomalyItem("high", "鍒╂鼎寮傚父", "妫�娴嬪埌浜忔崯璁㈠崟", Map.of("lossOrderCount", lossCount)));
+        }
+        if (snapshot.receivableTotal().compareTo(snapshot.payableTotal().multiply(new BigDecimal("1.2"))) > 0) {
+            anomalyItems.add(anomalyItem("medium", "鍥炴寮傚父", "搴旀敹浣欓鏄捐憲楂樹簬搴斾粯锛屽洖娆惧帇鍔涘亸澶�", Map.of(
+                    "receivableBalance", snapshot.receivableTotal(),
+                    "payableBalance", snapshot.payableTotal()
+            )));
+        }
+        long overdueOrderCount = currentBundle.orderMetrics().stream()
+                .filter(item -> item.deliveryDate() != null && item.deliveryDate().isBefore(LocalDate.now()) && item.profitRate().compareTo(new BigDecimal("0.10")) < 0)
+                .count();
+        if (overdueOrderCount > 0) {
+            anomalyItems.add(anomalyItem("medium", "璁㈠崟椋庨櫓", "瀛樺湪浣庡埄娑︿笖浜や粯宸查�炬湡璁㈠崟", Map.of("overdueRiskOrderCount", overdueOrderCount)));
+        }
+        long stagnantCount = inventoryMetrics.stream().filter(item -> item.stagnantDays() >= 90).count();
+        if (stagnantCount > 0) {
+            anomalyItems.add(anomalyItem("medium", "搴撳瓨寮傚父", "瀛樺湪瓒呰繃90澶╂湭鍛ㄨ浆搴撳瓨", Map.of("stagnantCount", stagnantCount)));
+        }
+
+        List<Map<String, Object>> topAnomalies = anomalyItems.stream().limit(finalLimit).toList();
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("timeRange", range.label());
+        summary.put("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
+        summary.put("anomalyCount", topAnomalies.size());
+        summary.put("highRiskCount", topAnomalies.stream().filter(item -> "high".equals(item.get("riskLevel"))).count());
+        summary.put("mediumRiskCount", topAnomalies.stream().filter(item -> "medium".equals(item.get("riskLevel"))).count());
+
+        Map<String, Object> charts = new LinkedHashMap<>();
+        charts.put("anomalyLevelPieOption", buildAnomalyLevelPie(topAnomalies));
+        charts.put("anomalyTypeBarOption", buildAnomalyTypeBar(topAnomalies));
+        return jsonResponse(true, "financial_business_anomaly_warning", "宸插畬鎴愮粡钀ュ紓甯搁璀﹀垎鏋�", summary,
+                Map.of("items", topAnomalies), charts);
+    }
+
+    @Tool(name = "AI缁忚惀椹鹃┒鑸�", value = "瀹炴椂灞曠ず浜у�笺�佸埄娑︺�佸簱瀛樸�佸洖娆俱�佽澶囧埄鐢ㄧ巼銆佽鍗曞埄娑︾巼绛夋牳蹇冪粡钀ユ寚鏍囥��")
+    public String getBusinessCockpit(@ToolMemoryId String memoryId,
+                                     @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
+                                     @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
+                                     @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屽鏈湀銆佽繎30澶�", required = false) String timeRange) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        DateRange range = resolveDateRange(startDate, endDate, timeRange, "鏈湀");
+
+        AnalysisBundle profitBundle = buildOrderProfitBundle(loginUser, range, null, 30);
+        StatementSnapshot snapshot = buildStatementSnapshot(loginUser);
+        List<StockInventory> inventories = queryStockInventory(loginUser);
+        BigDecimal inventoryValue = estimateInventoryValue(loginUser, inventories);
+
+        long deviceTotal = countDevices(loginUser);
+        long repairingCount = countRepairingDevices(loginUser);
+        BigDecimal deviceUtilization = deviceTotal > 0
+                ? new BigDecimal(deviceTotal - repairingCount).multiply(ONE_HUNDRED).divide(new BigDecimal(deviceTotal), 2, RoundingMode.HALF_UP)
+                : BigDecimal.ZERO;
+
+        BigDecimal outputValue = profitBundle.totalRevenue();
+        BigDecimal profitRate = rate(profitBundle.totalProfit(), profitBundle.totalRevenue());
+        BigDecimal collectionRate = snapshot.receivableTotal().compareTo(BigDecimal.ZERO) > 0
+                ? ONE_HUNDRED.subtract(rate(snapshot.receivableTotal(), snapshot.receivableTotal().add(snapshot.payableTotal())).multiply(ONE_HUNDRED))
+                : BigDecimal.ZERO;
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("timeRange", range.label());
+        summary.put("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
+        summary.put("outputValue", outputValue);
+        summary.put("profit", profitBundle.totalProfit());
+        summary.put("profitRate", toPercent(profitRate));
+        summary.put("inventoryValue", inventoryValue);
+        summary.put("receivableBalance", snapshot.receivableTotal());
+        summary.put("payableBalance", snapshot.payableTotal());
+        summary.put("collectionRate", toPercent(collectionRate.divide(ONE_HUNDRED, 4, RoundingMode.HALF_UP)));
+        summary.put("deviceUtilizationRate", deviceUtilization + "%");
+        summary.put("orderProfitRate", toPercent(avgRate(profitBundle.orderMetrics())));
+
+        Map<String, Object> indicators = new LinkedHashMap<>();
+        indicators.put("浜у��", outputValue);
+        indicators.put("鍒╂鼎", profitBundle.totalProfit());
+        indicators.put("搴撳瓨璧勯噾鍗犵敤", inventoryValue);
+        indicators.put("搴旀敹浣欓", snapshot.receivableTotal());
+        indicators.put("璁惧鍒╃敤鐜�", deviceUtilization + "%");
+        indicators.put("璁㈠崟骞冲潎鍒╂鼎鐜�", toPercent(avgRate(profitBundle.orderMetrics())));
+
+        Map<String, Object> charts = new LinkedHashMap<>();
+        charts.put("kpiCardData", indicators);
+        charts.put("profitTrendOption", buildOrderProfitBar(profitBundle.orderMetrics()));
+        charts.put("receivablePayableBarOption", buildReceivablePayableBar(snapshot.receivableTotal(), snapshot.payableTotal()));
+        charts.put("inventoryProfitGaugeOption", buildInventoryProfitGauge(inventoryValue, profitBundle.totalProfit()));
+
+        return jsonResponse(true, "financial_business_cockpit", "宸茬敓鎴怉I缁忚惀椹鹃┒鑸辨暟鎹�", summary,
+                Map.of(
+                        "orderProfitTop", profitBundle.orderMetrics().stream()
+                                .sorted(Comparator.comparing(OrderProfitMetric::profit).reversed())
+                                .limit(10)
+                                .map(this::toOrderCostItem)
+                                .toList(),
+                        "indicators", indicators
+                ),
+                charts
+        );
+    }
+
+    @Tool(name = "鏃ユ姤鍛ㄦ姤鑷姩鐢熸垚", value = "鑷姩杈撳嚭缁忚惀鍒嗘瀽鏃ユ姤/鍛ㄦ姤涓庨闄╁缓璁��")
+    public String generateOperationReport(@ToolMemoryId String memoryId,
+                                          @P(value = "寮�濮嬫棩鏈� yyyy-MM-dd", required = false) String startDate,
+                                          @P(value = "缁撴潫鏃ユ湡 yyyy-MM-dd", required = false) String endDate,
+                                          @P(value = "鏃堕棿鑼冨洿鎻忚堪锛屽浠婂ぉ銆佹湰鍛�", required = false) String timeRange,
+                                          @P(value = "鎶ュ憡绫诲瀷 daily/weekly", required = false) String reportType) {
+        LoginUser loginUser = currentLoginUser(memoryId);
+        DateRange range = resolveDateRange(startDate, endDate, timeRange,
+                "weekly".equalsIgnoreCase(reportType) ? "鏈懆" : "浠婂ぉ");
+        String type = "weekly".equalsIgnoreCase(reportType) ? "weekly" : "daily";
+
+        AnalysisBundle bundle = buildOrderProfitBundle(loginUser, range, null, 30);
+        StatementSnapshot snapshot = buildStatementSnapshot(loginUser);
+        BigDecimal inventoryValue = estimateInventoryValue(loginUser, queryStockInventory(loginUser));
+        long lossCount = bundle.orderMetrics().stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count();
+
+        List<String> conclusions = new ArrayList<>();
+        conclusions.add("钀ユ敹" + bundle.totalRevenue() + "锛屽埄娑�" + bundle.totalProfit() + "锛屽埄娑︾巼" + toPercent(rate(bundle.totalProfit(), bundle.totalRevenue())) + "銆�");
+        conclusions.add("搴旀敹浣欓" + snapshot.receivableTotal() + "锛屽簲浠樹綑棰�" + snapshot.payableTotal() + "锛屽簱瀛樿祫閲戝崰鐢�" + inventoryValue + "銆�");
+        if (lossCount > 0) {
+            conclusions.add("鍙戠幇浜忔崯璁㈠崟" + lossCount + "涓紝寤鸿浼樺厛澶嶆牳鏉愭枡鎹熻�楀拰宸ュ簭浜哄伐鏁堢巼銆�");
+        } else {
+            conclusions.add("褰撳墠鏈彂鐜颁簭鎹熻鍗曪紝寤鸿鎸佺画璺熻釜浣庝簬8%鍒╂鼎鐜囪鍗曘��");
+        }
+        if (snapshot.receivableTotal().compareTo(snapshot.payableTotal()) > 0) {
+            conclusions.add("鍥炴鍘嬪姏鍋忛珮锛屽缓璁拡瀵归珮搴旀敹瀹㈡埛鎵ц鍒嗗眰鍌敹涓庤处鏈熶紭鍖栥��");
+        } else {
+            conclusions.add("璧勯噾鍘嬪姏鍙帶锛屽缓璁繚鎸佷粯娆捐鍒掍笌閲囪喘鑺傚鑱斿姩銆�");
+        }
+
+        List<Map<String, Object>> riskSuggestions = new ArrayList<>();
+        if (lossCount > 0) {
+            riskSuggestions.add(riskSuggestion("鍒╂鼎椋庨櫓", "楂�", "澶嶆牳浜忔崯璁㈠崟BOM鍜屽伐搴忓伐璧勫畾棰濓紝蹇呰鏃惰皟鏁存姤浠蜂笌浜や粯鑺傚銆�"));
+        }
+        if (snapshot.receivableTotal().compareTo(SME_RECEIVABLE_RISK_THRESHOLD) > 0) {
+            riskSuggestions.add(riskSuggestion("鍥炴椋庨櫓", "涓�", "瀵瑰簲鏀禩OP瀹㈡埛寤虹珛鍛ㄥ害鍥炴璁″垝锛屽苟璁剧疆棰勮闃堝�笺��"));
+        }
+        if (inventoryValue.compareTo(SME_INVENTORY_RISK_THRESHOLD) > 0) {
+            riskSuggestions.add(riskSuggestion("搴撳瓨椋庨櫓", "涓�", "瀵归珮閲戦鍛嗘粸搴撳瓨鎵ц闄嶄环銆佹浛浠e拰鐢熶骇娑堣�楃瓥鐣ャ��"));
+        }
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("reportType", type);
+        summary.put("timeRange", range.label());
+        summary.put("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
+        summary.put("orderCount", bundle.orderMetrics().size());
+        summary.put("lossOrderCount", lossCount);
+        summary.put("riskSuggestionCount", riskSuggestions.size());
+
+        Map<String, Object> data = new LinkedHashMap<>();
+        data.put("headline", "weekly".equals(type) ? "缁忚惀鍛ㄦ姤" : "缁忚惀鏃ユ姤");
+        data.put("conclusions", conclusions);
+        data.put("riskSuggestions", riskSuggestions);
+        data.put("orderProfitTop", bundle.orderMetrics().stream()
+                .sorted(Comparator.comparing(OrderProfitMetric::profitRate))
+                .limit(10)
+                .map(this::toRiskOrderItem)
+                .toList());
+
+        Map<String, Object> charts = new LinkedHashMap<>();
+        charts.put("reportProfitBarOption", buildOrderProfitBar(bundle.orderMetrics()));
+        charts.put("reportReceivablePayableOption", buildReceivablePayableBar(snapshot.receivableTotal(), snapshot.payableTotal()));
+        return jsonResponse(true, "financial_operation_report", "宸茶嚜鍔ㄧ敓鎴愮粡钀ュ垎鏋愭姤鍛�", summary, data, charts);
+    }
+
+    private AnalysisBundle buildOrderProfitBundle(LoginUser loginUser, DateRange range, String keyword, Integer limit) {
+        List<SalesLedger> ledgers = querySalesLedgers(loginUser, range, keyword, limit);
+        if (ledgers.isEmpty()) {
+            return AnalysisBundle.empty();
+        }
+
+        List<Long> ledgerIds = ledgers.stream().map(SalesLedger::getId).filter(Objects::nonNull).toList();
+        List<SalesLedgerProduct> ledgerProducts = queryLedgerProducts(loginUser, ledgerIds);
+        Map<Long, List<SalesLedgerProduct>> productsByLedgerId = ledgerProducts.stream()
+                .collect(Collectors.groupingBy(SalesLedgerProduct::getSalesLedgerId));
+
+        MaterialCostResult materialCostResult = calculateMaterialCost(loginUser, range, ledgerProducts);
+        ProductionCostContext productionCostContext = calculateProductionCost(loginUser, range, ledgers, ledgerProducts, materialCostResult.avgUnitCostByModelId());
+        BigDecimal totalDepreciation = calculateTotalDepreciation(loginUser);
+
+        BigDecimal totalRevenue = ledgers.stream()
+                .map(SalesLedger::getContractAmount)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        Map<Long, BigDecimal> depreciationCostByLedger = allocateDepreciation(ledgers, totalDepreciation, totalRevenue);
+
+        List<OrderProfitMetric> metrics = new ArrayList<>();
+        for (SalesLedger ledger : ledgers) {
+            BigDecimal revenue = defaultDecimal(ledger.getContractAmount());
+            BigDecimal materialCost = materialCostResult.materialCostByLedgerId().getOrDefault(ledger.getId(), fallbackMaterialCost(productsByLedgerId.get(ledger.getId()), revenue));
+            BigDecimal laborCost = productionCostContext.laborCostByLedgerId().getOrDefault(ledger.getId(), BigDecimal.ZERO);
+            BigDecimal scrapCost = productionCostContext.scrapCostByLedgerId().getOrDefault(ledger.getId(), BigDecimal.ZERO);
+            BigDecimal depreciationCost = depreciationCostByLedger.getOrDefault(ledger.getId(), BigDecimal.ZERO);
+            BigDecimal totalCost = materialCost.add(laborCost).add(scrapCost).add(depreciationCost);
+            BigDecimal profit = revenue.subtract(totalCost);
+            BigDecimal profitRate = rate(profit, revenue);
+            String riskLevel = profit.compareTo(BigDecimal.ZERO) < 0
+                    ? "high"
+                    : (profitRate.compareTo(new BigDecimal("0.08")) < 0 ? "medium" : "low");
+            List<String> reasons = buildProfitReasons(revenue, materialCost, laborCost, scrapCost, profit, profitRate);
+            String suggestion = buildProfitSuggestion(riskLevel, reasons);
+
+            metrics.add(new OrderProfitMetric(
+                    ledger.getId(),
+                    safe(ledger.getSalesContractNo()),
+                    safe(ledger.getCustomerName()),
+                    safe(ledger.getProjectName()),
+                    toLocalDate(ledger.getEntryDate()),
+                    ledger.getDeliveryDate(),
+                    revenue,
+                    materialCost,
+                    laborCost,
+                    depreciationCost,
+                    scrapCost,
+                    totalCost,
+                    profit,
+                    profitRate,
+                    riskLevel,
+                    reasons,
+                    suggestion
+            ));
+        }
+
+        metrics.sort(Comparator.comparing(OrderProfitMetric::entryDate, Comparator.nullsLast(Comparator.reverseOrder()))
+                .thenComparing(OrderProfitMetric::ledgerId, Comparator.nullsLast(Comparator.reverseOrder())));
+        BigDecimal totalMaterialCost = metrics.stream().map(OrderProfitMetric::materialCost).reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal totalLaborCost = metrics.stream().map(OrderProfitMetric::laborCost).reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal totalScrapCost = metrics.stream().map(OrderProfitMetric::scrapCost).reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal totalDepreciationCost = metrics.stream().map(OrderProfitMetric::depreciationCost).reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal totalCost = metrics.stream().map(OrderProfitMetric::totalCost).reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal totalProfit = metrics.stream().map(OrderProfitMetric::profit).reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        return new AnalysisBundle(
+                metrics,
+                productionCostContext.processCostRanking(),
+                totalRevenue,
+                totalMaterialCost,
+                totalLaborCost,
+                totalDepreciationCost,
+                totalScrapCost,
+                totalCost,
+                totalProfit
+        );
+    }
+
+    private MaterialCostResult calculateMaterialCost(LoginUser loginUser, DateRange range, List<SalesLedgerProduct> ledgerProducts) {
+        if (ledgerProducts.isEmpty()) {
+            return new MaterialCostResult(Map.of(), Map.of());
+        }
+        List<Long> ledgerProductIds = ledgerProducts.stream().map(SalesLedgerProduct::getId).filter(Objects::nonNull).toList();
+        Set<Long> productModelIds = ledgerProducts.stream().map(SalesLedgerProduct::getProductModelId).filter(Objects::nonNull).collect(Collectors.toSet());
+        Map<Long, Long> productIdToLedgerId = ledgerProducts.stream()
+                .filter(item -> item.getId() != null && item.getSalesLedgerId() != null)
+                .collect(Collectors.toMap(SalesLedgerProduct::getId, SalesLedgerProduct::getSalesLedgerId, (a, b) -> a));
+
+        Map<Long, BigDecimal> avgUnitCostByModelId = queryAverageUnitCostByModel(loginUser, productModelIds);
+        LambdaQueryWrapper<ProcurementRecordOut> outWrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(outWrapper, loginUser.getTenantId(), ProcurementRecordOut::getTenantId);
+        applyDeptFilter(outWrapper, loginUser.getCurrentDeptId(), ProcurementRecordOut::getDeptId);
+        outWrapper.eq(ProcurementRecordOut::getType, 2)
+                .in(ProcurementRecordOut::getSalesLedgerProductId, ledgerProductIds);
+        if (range.hasDateFilter()) {
+            outWrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
+                    .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        }
+        List<ProcurementRecordOut> outList = defaultList(procurementRecordOutMapper.selectList(outWrapper));
+
+        Set<Integer> storageIds = outList.stream()
+                .map(ProcurementRecordOut::getProcurementRecordStorageId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Integer, ProcurementRecordStorage> storageMap = storageIds.isEmpty()
+                ? Map.of()
+                : defaultList(procurementRecordMapper.selectBatchIds(storageIds)).stream()
+                .collect(Collectors.toMap(ProcurementRecordStorage::getId, item -> item, (a, b) -> a));
+
+        Map<Long, BigDecimal> materialCostByLedgerId = new HashMap<>();
+        for (ProcurementRecordOut out : outList) {
+            Long ledgerId = productIdToLedgerId.get(out.getSalesLedgerProductId());
+            if (ledgerId == null) {
+                continue;
+            }
+            ProcurementRecordStorage storage = storageMap.get(out.getProcurementRecordStorageId());
+            BigDecimal unitPrice = storage == null ? BigDecimal.ZERO : defaultDecimal(storage.getUnitPrice());
+            BigDecimal quantity = defaultDecimal(out.getInboundNum());
+            BigDecimal cost = quantity.multiply(unitPrice);
+            materialCostByLedgerId.merge(ledgerId, cost, BigDecimal::add);
+        }
+        return new MaterialCostResult(materialCostByLedgerId, avgUnitCostByModelId);
+    }
+
+    private ProductionCostContext calculateProductionCost(LoginUser loginUser,
+                                                          DateRange range,
+                                                          List<SalesLedger> ledgers,
+                                                          List<SalesLedgerProduct> ledgerProducts,
+                                                          Map<Long, BigDecimal> avgUnitCostByModelId) {
+        if (ledgers.isEmpty()) {
+            return ProductionCostContext.empty();
+        }
+
+        Set<Long> ledgerIds = ledgers.stream().map(SalesLedger::getId).filter(Objects::nonNull).collect(Collectors.toSet());
+        Map<Long, Set<Long>> productModelToLedgerIds = new HashMap<>();
+        for (SalesLedgerProduct product : ledgerProducts) {
+            if (product.getProductModelId() == null || product.getSalesLedgerId() == null) {
+                continue;
+            }
+            productModelToLedgerIds.computeIfAbsent(product.getProductModelId(), key -> new HashSet<>()).add(product.getSalesLedgerId());
+        }
+
+        LambdaQueryWrapper<ProductionPlan> planWrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(planWrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
+        planWrapper.in(ProductionPlan::getSalesLedgerId, ledgerIds);
+        List<ProductionPlan> plans = defaultList(productionPlanMapper.selectList(planWrapper));
+        Map<Long, Long> planIdToLedgerId = plans.stream()
+                .filter(item -> item.getId() != null && item.getSalesLedgerId() != null)
+                .collect(Collectors.toMap(ProductionPlan::getId, ProductionPlan::getSalesLedgerId, (a, b) -> a));
+
+        LambdaQueryWrapper<ProductionOrder> orderWrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(orderWrapper, loginUser.getCurrentDeptId(), ProductionOrder::getDeptId);
+        if (range.hasDateFilter()) {
+            orderWrapper.ge(ProductionOrder::getCreateTime, range.start().atStartOfDay().minusMonths(2))
+                    .lt(ProductionOrder::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
+        }
+        List<ProductionOrder> orders = defaultList(productionOrderMapper.selectList(orderWrapper));
+
+        Map<Long, Set<Long>> orderIdToLedgerIds = new HashMap<>();
+        for (ProductionOrder order : orders) {
+            Set<Long> orderLedgers = new HashSet<>();
+            for (Long planId : parseIdList(order.getProductionPlanIds())) {
+                Long ledgerId = planIdToLedgerId.get(planId);
+                if (ledgerId != null) {
+                    orderLedgers.add(ledgerId);
+                }
+            }
+            if (orderLedgers.isEmpty() && order.getProductModelId() != null) {
+                orderLedgers.addAll(productModelToLedgerIds.getOrDefault(order.getProductModelId(), Set.of()));
+            }
+            if (!orderLedgers.isEmpty()) {
+                orderIdToLedgerIds.put(order.getId(), orderLedgers);
+            }
+        }
+        if (orderIdToLedgerIds.isEmpty()) {
+            return ProductionCostContext.empty();
+        }
+
+        LambdaQueryWrapper<ProductionOperationTask> taskWrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(taskWrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
+        taskWrapper.in(ProductionOperationTask::getProductionOrderId, orderIdToLedgerIds.keySet());
+        List<ProductionOperationTask> tasks = defaultList(productionOperationTaskMapper.selectList(taskWrapper));
+        Map<Long, Long> taskIdToOrderId = tasks.stream()
+                .filter(item -> item.getId() != null && item.getProductionOrderId() != null)
+                .collect(Collectors.toMap(ProductionOperationTask::getId, ProductionOperationTask::getProductionOrderId, (a, b) -> a));
+        if (taskIdToOrderId.isEmpty()) {
+            return ProductionCostContext.empty();
+        }
+
+        LambdaQueryWrapper<ProductionProductMain> mainWrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(mainWrapper, loginUser.getCurrentDeptId(), ProductionProductMain::getDeptId);
+        mainWrapper.in(ProductionProductMain::getProductionOperationTaskId, taskIdToOrderId.keySet());
+        if (range.hasDateFilter()) {
+            mainWrapper.ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay().minusMonths(2))
+                    .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
+        }
+        List<ProductionProductMain> mainList = defaultList(productionProductMainMapper.selectList(mainWrapper));
+        Map<Long, Set<Long>> mainIdToLedgers = new HashMap<>();
+        for (ProductionProductMain main : mainList) {
+            Long orderId = taskIdToOrderId.get(main.getProductionOperationTaskId());
+            if (orderId == null) {
+                continue;
+            }
+            Set<Long> ledgerSet = orderIdToLedgerIds.get(orderId);
+            if (ledgerSet == null || ledgerSet.isEmpty()) {
+                continue;
+            }
+            mainIdToLedgers.put(main.getId(), ledgerSet);
+        }
+        if (mainIdToLedgers.isEmpty()) {
+            return ProductionCostContext.empty();
+        }
+
+        LambdaQueryWrapper<ProductionAccount> accountWrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(accountWrapper, loginUser.getCurrentDeptId(), ProductionAccount::getDeptId);
+        accountWrapper.in(ProductionAccount::getProductionProductMainId, mainIdToLedgers.keySet());
+        if (range.hasDateFilter()) {
+            accountWrapper.ge(ProductionAccount::getSchedulingDate, range.start().atStartOfDay())
+                    .lt(ProductionAccount::getSchedulingDate, range.end().plusDays(1).atStartOfDay());
+        }
+        List<ProductionAccount> accountList = defaultList(productionAccountMapper.selectList(accountWrapper));
+
+        Map<String, BigDecimal> salaryQuotaByOperation = defaultList(technologyOperationMapper.selectList(new LambdaQueryWrapper<TechnologyOperation>()
+                        .select(TechnologyOperation::getName, TechnologyOperation::getSalaryQuota)))
+                .stream()
+                .filter(item -> StringUtils.hasText(item.getName()))
+                .collect(Collectors.toMap(TechnologyOperation::getName, item -> defaultDecimal(item.getSalaryQuota()), (a, b) -> a));
+
+        Map<Long, BigDecimal> laborCostByLedger = new HashMap<>();
+        Map<String, BigDecimal> processCostMap = new HashMap<>();
+        for (ProductionAccount account : accountList) {
+            Set<Long> ledgerSet = mainIdToLedgers.get(account.getProductionProductMainId());
+            if (ledgerSet == null || ledgerSet.isEmpty()) {
+                continue;
+            }
+            BigDecimal cost = estimateLaborCost(account, salaryQuotaByOperation);
+            if (cost.compareTo(BigDecimal.ZERO) <= 0) {
+                continue;
+            }
+            BigDecimal split = cost.divide(new BigDecimal(ledgerSet.size()), 4, RoundingMode.HALF_UP);
+            for (Long ledgerId : ledgerSet) {
+                laborCostByLedger.merge(ledgerId, split, BigDecimal::add);
+            }
+            processCostMap.merge(safe(account.getTechnologyOperationName()), cost, BigDecimal::add);
+        }
+
+        LambdaQueryWrapper<ProductionProductOutput> outputWrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(outputWrapper, loginUser.getCurrentDeptId(), ProductionProductOutput::getDeptId);
+        outputWrapper.in(ProductionProductOutput::getProductionProductMainId, mainIdToLedgers.keySet());
+        if (range.hasDateFilter()) {
+            outputWrapper.ge(ProductionProductOutput::getCreateTime, range.start().atStartOfDay())
+                    .lt(ProductionProductOutput::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        }
+        List<ProductionProductOutput> outputList = defaultList(productionProductOutputMapper.selectList(outputWrapper));
+        Map<Long, BigDecimal> scrapCostByLedger = new HashMap<>();
+        for (ProductionProductOutput output : outputList) {
+            Set<Long> ledgerSet = mainIdToLedgers.get(output.getProductionProductMainId());
+            if (ledgerSet == null || ledgerSet.isEmpty()) {
+                continue;
+            }
+            BigDecimal scrapQty = defaultDecimal(output.getScrapQty());
+            if (scrapQty.compareTo(BigDecimal.ZERO) <= 0) {
+                continue;
+            }
+            BigDecimal unitCost = avgUnitCostByModelId.getOrDefault(output.getProductModelId(), BigDecimal.ZERO);
+            BigDecimal scrapCost = scrapQty.multiply(unitCost);
+            BigDecimal split = scrapCost.divide(new BigDecimal(ledgerSet.size()), 4, RoundingMode.HALF_UP);
+            for (Long ledgerId : ledgerSet) {
+                scrapCostByLedger.merge(ledgerId, split, BigDecimal::add);
+            }
+        }
+
+        Map<String, BigDecimal> processCostRanking = processCostMap.entrySet().stream()
+                .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
+                .limit(10)
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new));
+
+        return new ProductionCostContext(laborCostByLedger, scrapCostByLedger, processCostRanking);
+    }
+
+    private List<SalesLedger> querySalesLedgers(LoginUser loginUser, DateRange range, String keyword, Integer limit) {
+        LambdaQueryWrapper<SalesLedger> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), SalesLedger::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesLedger::getDeptId);
+        if (StringUtils.hasText(keyword)) {
+            wrapper.and(w -> w.like(SalesLedger::getSalesContractNo, keyword)
+                    .or().like(SalesLedger::getCustomerContractNo, keyword)
+                    .or().like(SalesLedger::getCustomerName, keyword)
+                    .or().like(SalesLedger::getProjectName, keyword)
+                    .or().like(SalesLedger::getSalesman, keyword));
+        }
+        if (range.hasDateFilter()) {
+            wrapper.ge(SalesLedger::getEntryDate, toDate(range.start()))
+                    .lt(SalesLedger::getEntryDate, toExclusiveEndDate(range.end()));
+        }
+        wrapper.orderByDesc(SalesLedger::getEntryDate, SalesLedger::getId);
+        if (limit != null && limit > 0) {
+            wrapper.last("limit " + normalizeLimit(limit));
+        }
+        return defaultList(salesLedgerMapper.selectList(wrapper));
+    }
+
+    private List<SalesLedgerProduct> queryLedgerProducts(LoginUser loginUser, List<Long> ledgerIds) {
+        if (ledgerIds == null || ledgerIds.isEmpty()) {
+            return List.of();
+        }
+        LambdaQueryWrapper<SalesLedgerProduct> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesLedgerProduct::getDeptId);
+        wrapper.in(SalesLedgerProduct::getSalesLedgerId, ledgerIds)
+                .eq(SalesLedgerProduct::getType, 1);
+        return defaultList(salesLedgerProductMapper.selectList(wrapper));
+    }
+
+    private List<StockInventory> queryStockInventory(LoginUser loginUser) {
+        LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
+        return defaultList(stockInventoryMapper.selectList(wrapper));
+    }
+
+    private Map<Long, ProductModel> queryProductModels(Set<Long> modelIds) {
+        if (modelIds == null || modelIds.isEmpty()) {
+            return defaultList(productModelMapper.selectList(null)).stream()
+                    .filter(item -> item.getId() != null)
+                    .collect(Collectors.toMap(ProductModel::getId, item -> item, (a, b) -> a));
+        }
+        LambdaQueryWrapper<ProductModel> wrapper = new LambdaQueryWrapper<>();
+        wrapper.in(ProductModel::getId, modelIds);
+        return defaultList(productModelMapper.selectList(wrapper)).stream()
+                .filter(item -> item.getId() != null)
+                .collect(Collectors.toMap(ProductModel::getId, item -> item, (a, b) -> a));
+    }
+
+    private Map<Long, Product> queryProducts(Collection<ProductModel> models) {
+        Set<Long> productIds = models == null ? Set.of() : models.stream()
+                .map(ProductModel::getProductId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        if (productIds.isEmpty()) {
+            return Map.of();
+        }
+        LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
+        wrapper.in(Product::getId, productIds);
+        return defaultList(productMapper.selectList(wrapper)).stream()
+                .filter(item -> item.getId() != null)
+                .collect(Collectors.toMap(Product::getId, item -> item, (a, b) -> a));
+    }
+
+    private Map<Long, BigDecimal> queryAverageUnitCostByModel(LoginUser loginUser, Set<Long> productModelIds) {
+        LambdaQueryWrapper<ProcurementRecordStorage> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementRecordStorage::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementRecordStorage::getDeptId);
+        wrapper.in(ProcurementRecordStorage::getType, List.of(1, 2));
+        if (productModelIds != null && !productModelIds.isEmpty()) {
+            wrapper.in(ProcurementRecordStorage::getProductModelId, productModelIds);
+        }
+        List<ProcurementRecordStorage> rows = defaultList(procurementRecordMapper.selectList(wrapper));
+        Map<Long, BigDecimal> amountByModel = new HashMap<>();
+        Map<Long, BigDecimal> qtyByModel = new HashMap<>();
+        for (ProcurementRecordStorage row : rows) {
+            if (row.getProductModelId() == null) {
+                continue;
+            }
+            BigDecimal qty = defaultDecimal(row.getInboundNum());
+            if (qty.compareTo(BigDecimal.ZERO) <= 0) {
+                continue;
+            }
+            BigDecimal amount = defaultDecimal(row.getUnitPrice()).multiply(qty);
+            amountByModel.merge(row.getProductModelId(), amount, BigDecimal::add);
+            qtyByModel.merge(row.getProductModelId(), qty, BigDecimal::add);
+        }
+        Map<Long, BigDecimal> result = new HashMap<>();
+        for (Map.Entry<Long, BigDecimal> entry : amountByModel.entrySet()) {
+            BigDecimal qty = qtyByModel.get(entry.getKey());
+            if (qty == null || qty.compareTo(BigDecimal.ZERO) <= 0) {
+                continue;
+            }
+            result.put(entry.getKey(), entry.getValue().divide(qty, 6, RoundingMode.HALF_UP));
+        }
+        return result;
+    }
+
+    private OutboundStats queryOutboundStats(LoginUser loginUser, Set<Long> productModelIds, DateRange range) {
+        LambdaQueryWrapper<ProcurementRecordOut> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementRecordOut::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementRecordOut::getDeptId);
+        if (productModelIds != null && !productModelIds.isEmpty()) {
+            wrapper.in(ProcurementRecordOut::getProductModelId, productModelIds);
+        }
+        if (range.hasDateFilter()) {
+            wrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
+                    .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
+        }
+        List<ProcurementRecordOut> outList = defaultList(procurementRecordOutMapper.selectList(wrapper));
+        if (outList.isEmpty()) {
+            return OutboundStats.empty();
+        }
+        Set<Integer> storageIds = outList.stream()
+                .map(ProcurementRecordOut::getProcurementRecordStorageId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        Map<Integer, ProcurementRecordStorage> storageMap = storageIds.isEmpty()
+                ? Map.of()
+                : defaultList(procurementRecordMapper.selectBatchIds(storageIds)).stream()
+                .collect(Collectors.toMap(ProcurementRecordStorage::getId, item -> item, (a, b) -> a));
+
+        Map<Long, BigDecimal> outboundQtyByModel = new HashMap<>();
+        Map<Long, LocalDateTime> lastOutboundTimeByModel = new HashMap<>();
+        BigDecimal totalOutboundCost = BigDecimal.ZERO;
+        for (ProcurementRecordOut out : outList) {
+            Long modelId = out.getProductModelId();
+            if (modelId == null) {
+                continue;
+            }
+            BigDecimal qty = defaultDecimal(out.getInboundNum());
+            outboundQtyByModel.merge(modelId, qty, BigDecimal::add);
+            if (out.getCreateTime() != null) {
+                LocalDateTime existing = lastOutboundTimeByModel.get(modelId);
+                if (existing == null || out.getCreateTime().isAfter(existing)) {
+                    lastOutboundTimeByModel.put(modelId, out.getCreateTime());
+                }
+            }
+            ProcurementRecordStorage storage = storageMap.get(out.getProcurementRecordStorageId());
+            BigDecimal unitPrice = storage == null ? BigDecimal.ZERO : defaultDecimal(storage.getUnitPrice());
+            totalOutboundCost = totalOutboundCost.add(unitPrice.multiply(qty));
+        }
+        return new OutboundStats(outboundQtyByModel, lastOutboundTimeByModel, totalOutboundCost);
+    }
+
+    private List<InventoryMetric> buildInventoryMetrics(List<StockInventory> inventoryRows,
+                                                        Map<Long, ProductModel> productModelMap,
+                                                        Map<Long, Product> productMap,
+                                                        Map<Long, BigDecimal> avgUnitCostByModelId,
+                                                        OutboundStats outboundStats) {
+        Map<Long, InventoryMetricBuilder> metricBuilderByModel = new HashMap<>();
+        for (StockInventory row : inventoryRows) {
+            if (row.getProductModelId() == null) {
+                continue;
+            }
+            InventoryMetricBuilder builder = metricBuilderByModel.computeIfAbsent(row.getProductModelId(), InventoryMetricBuilder::new);
+            builder.addQuantity(maxZero(defaultDecimal(row.getQualitity()).subtract(defaultDecimal(row.getLockedQuantity()))));
+            builder.addLockedQuantity(defaultDecimal(row.getLockedQuantity()));
+            builder.addWarnNum(defaultDecimal(row.getWarnNum()));
+            if (row.getCreateTime() != null) {
+                builder.updateFirstInTime(row.getCreateTime());
+            }
+        }
+
+        List<InventoryMetric> result = new ArrayList<>();
+        LocalDate today = LocalDate.now();
+        for (InventoryMetricBuilder builder : metricBuilderByModel.values()) {
+            Long modelId = builder.modelId();
+            ProductModel model = productModelMap.get(modelId);
+            Product product = model == null ? null : productMap.get(model.getProductId());
+            BigDecimal unitCost = avgUnitCostByModelId.getOrDefault(modelId, BigDecimal.ZERO);
+            BigDecimal value = builder.quantity().multiply(unitCost);
+            LocalDateTime lastOutTime = outboundStats.lastOutboundTimeByModel().get(modelId);
+            long stagnantDays;
+            if (lastOutTime != null) {
+                stagnantDays = daysBetween(lastOutTime.toLocalDate(), today);
+            } else if (builder.firstInTime() != null) {
+                stagnantDays = daysBetween(builder.firstInTime().toLocalDate(), today);
+            } else {
+                stagnantDays = 0;
+            }
+            BigDecimal outQty = outboundStats.outboundQtyByModel().getOrDefault(modelId, BigDecimal.ZERO);
+            boolean overstock = builder.warnNum().compareTo(BigDecimal.ZERO) > 0
+                    && builder.quantity().compareTo(builder.warnNum().multiply(new BigDecimal("3"))) > 0;
+            result.add(new InventoryMetric(
+                    modelId,
+                    product == null ? "鏈煡浜у搧" : safe(product.getProductName()),
+                    model == null ? "鏈煡鍨嬪彿" : safe(model.getModel()),
+                    builder.quantity(),
+                    builder.lockedQuantity(),
+                    unitCost,
+                    value,
+                    outQty,
+                    stagnantDays,
+                    overstock
+            ));
+        }
+        return result;
+    }
+
+    private List<AccountSalesCollection> queryCollections(LoginUser loginUser, DateRange range) {
+        LambdaQueryWrapper<AccountSalesCollection> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountSalesCollection::getDeptId);
+        if (range.hasDateFilter()) {
+            wrapper.ge(AccountSalesCollection::getCollectionDate, range.start())
+                    .le(AccountSalesCollection::getCollectionDate, range.end());
+        }
+        wrapper.orderByAsc(AccountSalesCollection::getCollectionDate);
+        return defaultList(accountSalesCollectionMapper.selectList(wrapper));
+    }
+
+    private List<AccountPurchasePayment> queryPayments(LoginUser loginUser, DateRange range) {
+        LambdaQueryWrapper<AccountPurchasePayment> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountPurchasePayment::getDeptId);
+        if (range.hasDateFilter()) {
+            wrapper.ge(AccountPurchasePayment::getPaymentDate, range.start())
+                    .le(AccountPurchasePayment::getPaymentDate, range.end());
+        }
+        wrapper.orderByAsc(AccountPurchasePayment::getPaymentDate);
+        return defaultList(accountPurchasePaymentMapper.selectList(wrapper));
+    }
+
+    private List<MonthlyCashFlow> buildMonthlyCashFlow(DateRange range,
+                                                       List<AccountSalesCollection> collections,
+                                                       List<AccountPurchasePayment> payments) {
+        Map<YearMonth, BigDecimal> incomeByMonth = new LinkedHashMap<>();
+        Map<YearMonth, BigDecimal> expenseByMonth = new LinkedHashMap<>();
+        DateRange monthlyRange = range.hasDateFilter() ? range : inferCashFlowRange(collections, payments);
+        if (!monthlyRange.hasDateFilter()) {
+            return List.of();
+        }
+        YearMonth startMonth = YearMonth.from(monthlyRange.start());
+        YearMonth endMonth = YearMonth.from(monthlyRange.end());
+        for (YearMonth month = startMonth; !month.isAfter(endMonth); month = month.plusMonths(1)) {
+            incomeByMonth.put(month, BigDecimal.ZERO);
+            expenseByMonth.put(month, BigDecimal.ZERO);
+        }
+
+        for (AccountSalesCollection row : collections) {
+            if (row.getCollectionDate() == null) {
+                continue;
+            }
+            YearMonth month = YearMonth.from(row.getCollectionDate());
+            if (incomeByMonth.containsKey(month)) {
+                incomeByMonth.put(month, incomeByMonth.get(month).add(defaultDecimal(row.getCollectionAmount())));
+            }
+        }
+        for (AccountPurchasePayment row : payments) {
+            if (row.getPaymentDate() == null) {
+                continue;
+            }
+            YearMonth month = YearMonth.from(row.getPaymentDate());
+            if (expenseByMonth.containsKey(month)) {
+                expenseByMonth.put(month, expenseByMonth.get(month).add(defaultDecimal(row.getPaymentAmount())));
+            }
+        }
+
+        List<MonthlyCashFlow> result = new ArrayList<>();
+        for (YearMonth month : incomeByMonth.keySet()) {
+            BigDecimal income = incomeByMonth.get(month);
+            BigDecimal expense = expenseByMonth.getOrDefault(month, BigDecimal.ZERO);
+            result.add(new MonthlyCashFlow(month.toString(), income, expense, income.subtract(expense)));
+        }
+        return result;
+    }
+
+    private DateRange inferCashFlowRange(List<AccountSalesCollection> collections,
+                                         List<AccountPurchasePayment> payments) {
+        LocalDate min = null;
+        LocalDate max = null;
+        for (AccountSalesCollection row : defaultList(collections)) {
+            if (row.getCollectionDate() == null) {
+                continue;
+            }
+            min = min == null || row.getCollectionDate().isBefore(min) ? row.getCollectionDate() : min;
+            max = max == null || row.getCollectionDate().isAfter(max) ? row.getCollectionDate() : max;
+        }
+        for (AccountPurchasePayment row : defaultList(payments)) {
+            if (row.getPaymentDate() == null) {
+                continue;
+            }
+            min = min == null || row.getPaymentDate().isBefore(min) ? row.getPaymentDate() : min;
+            max = max == null || row.getPaymentDate().isAfter(max) ? row.getPaymentDate() : max;
+        }
+        return min == null || max == null ? new DateRange(null, null, "鍏ㄩ儴") : new DateRange(min, max, "鍏ㄩ儴");
+    }
+
+    private List<MonthlyCashFlow> forecastMonthlyCashFlow(List<MonthlyCashFlow> actual, int forecastMonths) {
+        if (actual.isEmpty()) {
+            List<MonthlyCashFlow> defaults = new ArrayList<>();
+            YearMonth now = YearMonth.now();
+            for (int i = 1; i <= forecastMonths; i++) {
+                YearMonth month = now.plusMonths(i);
+                defaults.add(new MonthlyCashFlow(month.toString(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
+            }
+            return defaults;
+        }
+        List<BigDecimal> series = actual.stream().map(MonthlyCashFlow::netFlow).toList();
+        BigDecimal avg = series.stream().reduce(BigDecimal.ZERO, BigDecimal::add)
+                .divide(new BigDecimal(series.size()), 4, RoundingMode.HALF_UP);
+        BigDecimal slope = BigDecimal.ZERO;
+        if (series.size() > 1) {
+            slope = series.get(series.size() - 1).subtract(series.get(0))
+                    .divide(new BigDecimal(series.size() - 1), 4, RoundingMode.HALF_UP);
+        }
+        YearMonth lastMonth = YearMonth.parse(actual.get(actual.size() - 1).month());
+        List<MonthlyCashFlow> forecast = new ArrayList<>();
+        for (int i = 1; i <= forecastMonths; i++) {
+            YearMonth month = lastMonth.plusMonths(i);
+            BigDecimal net = avg.add(slope.multiply(new BigDecimal(i))).setScale(2, RoundingMode.HALF_UP);
+            BigDecimal income = net.compareTo(BigDecimal.ZERO) >= 0 ? net : BigDecimal.ZERO;
+            BigDecimal expense = net.compareTo(BigDecimal.ZERO) >= 0 ? BigDecimal.ZERO : net.abs();
+            forecast.add(new MonthlyCashFlow(month.toString(), income, expense, net));
+        }
+        return forecast;
+    }
+
+    private StatementSnapshot buildStatementSnapshot(LoginUser loginUser) {
+        LambdaQueryWrapper<AccountStatement> wrapper = new LambdaQueryWrapper<>();
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountStatement::getDeptId);
+        wrapper.orderByDesc(AccountStatement::getStatementMonth);
+        List<AccountStatement> rows = defaultList(accountStatementMapper.selectList(wrapper));
+        if (rows.isEmpty()) {
+            return StatementSnapshot.empty();
+        }
+
+        Map<String, AccountStatement> latestByEntity = new HashMap<>();
+        for (AccountStatement row : rows) {
+            if (row.getAccountType() == null || row.getCustomerId() == null || !StringUtils.hasText(row.getStatementMonth())) {
+                continue;
+            }
+            String key = row.getAccountType() + "::" + row.getCustomerId();
+            AccountStatement existing = latestByEntity.get(key);
+            if (existing == null || row.getStatementMonth().compareTo(existing.getStatementMonth()) > 0) {
+                latestByEntity.put(key, row);
+            }
+        }
+
+        BigDecimal receivableTotal = BigDecimal.ZERO;
+        BigDecimal payableTotal = BigDecimal.ZERO;
+        List<StatementMetric> receivableMetrics = new ArrayList<>();
+        List<StatementMetric> payableMetrics = new ArrayList<>();
+        for (AccountStatement row : latestByEntity.values()) {
+            BigDecimal closing = defaultDecimal(row.getClosingBalance());
+            if (Objects.equals(row.getAccountType(), 1)) {
+                receivableTotal = receivableTotal.add(closing);
+                receivableMetrics.add(new StatementMetric(String.valueOf(row.getCustomerId()), closing,
+                        defaultDecimal(row.getCurrentPlan()), defaultDecimal(row.getCurrentActually()), safe(row.getStatementMonth())));
+            } else if (Objects.equals(row.getAccountType(), 2)) {
+                payableTotal = payableTotal.add(closing);
+                payableMetrics.add(new StatementMetric(String.valueOf(row.getCustomerId()), closing,
+                        defaultDecimal(row.getCurrentPlan()), defaultDecimal(row.getCurrentActually()), safe(row.getStatementMonth())));
+            }
+        }
+        receivableMetrics.sort(Comparator.comparing(StatementMetric::closingBalance).reversed());
+        payableMetrics.sort(Comparator.comparing(StatementMetric::closingBalance).reversed());
+
+        return new StatementSnapshot(
+                receivableTotal,
+                payableTotal,
+                receivableMetrics.stream().limit(10).toList(),
+                payableMetrics.stream().limit(10).toList()
+        );
+    }
+
+    private BigDecimal calculateTotalDepreciation(LoginUser loginUser) {
+        LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId);
+        wrapper.eq(DeviceLedger::getIsDepr, 1);
+        List<DeviceLedger> devices = defaultList(deviceLedgerMapper.selectList(wrapper));
+        BigDecimal total = BigDecimal.ZERO;
+        for (DeviceLedger device : devices) {
+            total = total.add(defaultDecimal(AccountingServiceImpl.calculatePreciseDepreciation(device)));
+        }
+        return total;
+    }
+
+    private Map<Long, BigDecimal> allocateDepreciation(List<SalesLedger> ledgers, BigDecimal totalDepreciation, BigDecimal totalRevenue) {
+        if (ledgers.isEmpty() || totalDepreciation.compareTo(BigDecimal.ZERO) <= 0) {
+            return Map.of();
+        }
+        Map<Long, BigDecimal> result = new HashMap<>();
+        if (totalRevenue.compareTo(BigDecimal.ZERO) <= 0) {
+            BigDecimal avg = totalDepreciation.divide(new BigDecimal(ledgers.size()), 4, RoundingMode.HALF_UP);
+            for (SalesLedger ledger : ledgers) {
+                result.put(ledger.getId(), avg);
+            }
+            return result;
+        }
+        for (SalesLedger ledger : ledgers) {
+            BigDecimal revenue = defaultDecimal(ledger.getContractAmount());
+            BigDecimal ratio = revenue.divide(totalRevenue, 6, RoundingMode.HALF_UP);
+            result.put(ledger.getId(), totalDepreciation.multiply(ratio));
+        }
+        return result;
+    }
+
+    private BigDecimal fallbackMaterialCost(List<SalesLedgerProduct> products, BigDecimal revenue) {
+        if (products != null && !products.isEmpty()) {
+            BigDecimal productAmount = products.stream()
+                    .map(SalesLedgerProduct::getTaxExclusiveTotalPrice)
+                    .filter(Objects::nonNull)
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            if (productAmount.compareTo(BigDecimal.ZERO) > 0) {
+                return productAmount;
+            }
+        }
+
+        BigDecimal materialCost = revenue.multiply(DEFAULT_FALLBACK_MATERIAL_COST_RATE);
+        BigDecimal laborCost = revenue.multiply(DEFAULT_LABOR_COST_RATE);
+        BigDecimal overheadCost = revenue.multiply(DEFAULT_OVERHEAD_COST_RATE);
+
+        return materialCost.add(laborCost).add(overheadCost);
+    }
+
+    private BigDecimal estimateTotalCost(BigDecimal revenue, List<SalesLedgerProduct> products) {
+        if (revenue == null || revenue.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
+        }
+
+        BigDecimal materialCost = BigDecimal.ZERO;
+        if (products != null && !products.isEmpty()) {
+            materialCost = products.stream()
+                    .map(SalesLedgerProduct::getTaxExclusiveTotalPrice)
+                    .filter(Objects::nonNull)
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+        }
+
+        if (materialCost.compareTo(BigDecimal.ZERO) <= 0) {
+            materialCost = revenue.multiply(DEFAULT_FALLBACK_MATERIAL_COST_RATE);
+        }
+
+        BigDecimal laborCost = revenue.multiply(DEFAULT_LABOR_COST_RATE);
+        BigDecimal overheadCost = revenue.multiply(DEFAULT_OVERHEAD_COST_RATE);
+
+        return materialCost.add(laborCost).add(overheadCost);
+    }
+
+    private Map<String, String> queryCustomerNameMap(Set<String> idSet) {
+        if (idSet == null || idSet.isEmpty()) {
+            return Map.of();
+        }
+        Set<Long> ids = idSet.stream()
+                .map(this::toLongOrNull)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        if (ids.isEmpty()) {
+            return Map.of();
+        }
+        LambdaQueryWrapper<Customer> wrapper = new LambdaQueryWrapper<>();
+        wrapper.in(Customer::getId, ids);
+        return defaultList(customerMapper.selectList(wrapper)).stream()
+                .collect(Collectors.toMap(item -> String.valueOf(item.getId()), Customer::getCustomerName, (a, b) -> a));
+    }
+
+    private Map<String, String> querySupplierNameMap(Set<String> idSet) {
+        if (idSet == null || idSet.isEmpty()) {
+            return Map.of();
+        }
+        Set<Long> ids = idSet.stream()
+                .map(this::toLongOrNull)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+        if (ids.isEmpty()) {
+            return Map.of();
+        }
+        LambdaQueryWrapper<SupplierManage> wrapper = new LambdaQueryWrapper<>();
+        wrapper.in(SupplierManage::getId, ids);
+        return defaultList(supplierManageMapper.selectList(wrapper)).stream()
+                .collect(Collectors.toMap(item -> String.valueOf(item.getId()), SupplierManage::getSupplierName, (a, b) -> a));
+    }
+
+    private String riskLevelByAmount(BigDecimal amount) {
+        if (amount.compareTo(new BigDecimal("5000000")) >= 0) {
+            return "high";
+        }
+        if (amount.compareTo(new BigDecimal("1000000")) >= 0) {
+            return "medium";
+        }
+        return "low";
+    }
+
+    private long countDevices(LoginUser loginUser) {
+        LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId);
+        return deviceLedgerMapper.selectCount(wrapper);
+    }
+
+    private long countRepairingDevices(LoginUser loginUser) {
+        LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
+        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
+        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId);
+        wrapper.in(DeviceRepair::getStatus, List.of(0, 3));
+        return deviceRepairMapper.selectCount(wrapper);
+    }
+
+    private BigDecimal estimateInventoryValue(LoginUser loginUser, List<StockInventory> inventories) {
+        if (inventories == null || inventories.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+        Set<Long> modelIds = inventories.stream().map(StockInventory::getProductModelId).filter(Objects::nonNull).collect(Collectors.toSet());
+        Map<Long, BigDecimal> costMap = queryAverageUnitCostByModel(loginUser, modelIds);
+        BigDecimal total = BigDecimal.ZERO;
+        for (StockInventory inventory : inventories) {
+            BigDecimal qty = maxZero(defaultDecimal(inventory.getQualitity()).subtract(defaultDecimal(inventory.getLockedQuantity())));
+            BigDecimal unit = costMap.getOrDefault(inventory.getProductModelId(), BigDecimal.ZERO);
+            total = total.add(qty.multiply(unit));
+        }
+        return total;
+    }
+
+    private DateRange previousSameLengthRange(DateRange range) {
+        if (!range.hasDateFilter()) {
+            return new DateRange(null, null, "鍏ㄩ儴");
+        }
+        long days = daysBetween(range.start(), range.end()) + 1L;
+        LocalDate prevEnd = range.start().minusDays(1);
+        LocalDate prevStart = prevEnd.minusDays(days - 1L);
+        return new DateRange(prevStart, prevEnd, prevStart + "鑷�" + prevEnd);
+    }
+
+    private List<String> buildProfitReasons(BigDecimal revenue,
+                                            BigDecimal materialCost,
+                                            BigDecimal laborCost,
+                                            BigDecimal scrapCost,
+                                            BigDecimal profit,
+                                            BigDecimal profitRate) {
+        List<String> reasons = new ArrayList<>();
+        BigDecimal materialRate = rate(materialCost, revenue);
+        if (materialRate.compareTo(new BigDecimal("0.70")) >= 0) {
+            reasons.add("鏉愭枡鎴愭湰鍗犳瘮瓒呰繃70%");
+        } else if (materialRate.compareTo(new BigDecimal("0.55")) >= 0) {
+            reasons.add("鏉愭枡鎴愭湰鍗犳瘮鍋忛珮");
+        }
+        BigDecimal laborRate = rate(laborCost, revenue);
+        if (laborRate.compareTo(new BigDecimal("0.20")) >= 0) {
+            reasons.add("浜哄伐鎴愭湰鍗犳瘮瓒呰繃20%");
+        } else if (laborRate.compareTo(new BigDecimal("0.12")) >= 0) {
+            reasons.add("浜哄伐鎴愭湰澧為暱鍋忓揩");
+        }
+        BigDecimal scrapRate = rate(scrapCost, revenue);
+        if (scrapRate.compareTo(new BigDecimal("0.05")) >= 0) {
+            reasons.add("鎶ュ簾鎹熻�楀崰姣斿亸楂�");
+        }
+        if (profit.compareTo(BigDecimal.ZERO) < 0) {
+            reasons.add("璁㈠崟澶勪簬浜忔崯鐘舵��");
+        } else if (profitRate.compareTo(new BigDecimal("0.08")) < 0) {
+            reasons.add("鍒╂鼎鐜囦綆浜�8%");
+        }
+        if (reasons.isEmpty()) {
+            reasons.add("鎴愭湰缁撴瀯澶勪簬鍚堢悊鍖洪棿");
+        }
+        return reasons;
+    }
+
+    private String buildProfitSuggestion(String riskLevel, List<String> reasons) {
+        if ("high".equals(riskLevel)) {
+            return "浼樺厛澶嶆牳BOM鐢ㄩ噺涓庡伐搴忓畾棰濓紝蹇呰鏃惰皟鏁存姤浠峰拰浠樻鏉℃锛屽苟闄愬埗瓒呰处鏈熶氦浠樸��";
+        }
+        if ("medium".equals(riskLevel)) {
+            return "寤鸿浼樺寲閲囪喘鎵规鍜屽伐搴忔帓浜э紝鎻愬崌涓�娆″悎鏍肩巼骞跺悓姝ユ墽琛屾瘺鍒╅璀︺��";
+        }
+        if (reasons.stream().anyMatch(item -> item.contains("鏉愭枡"))) {
+            return "淇濇寔鏉愭枡閲囪喘鎴愭湰鐪嬫澘锛屾寜鍛ㄨ窡韪富瑕佹潗鏂欏崟浠锋尝鍔ㄣ��";
+        }
+        return "缁存寔褰撳墠缁忚惀鑺傚锛岀户缁窡韪鍗曞埄娑︾巼鍜屽洖娆炬晥鐜囥��";
+    }
+
+    private Map<String, Object> toOrderCostItem(OrderProfitMetric metric) {
+        Map<String, Object> item = new LinkedHashMap<>();
+        item.put("ledgerId", metric.ledgerId());
+        item.put("salesContractNo", metric.salesContractNo());
+        item.put("customerName", metric.customerName());
+        item.put("projectName", metric.projectName());
+        item.put("entryDate", formatDate(metric.entryDate()));
+        item.put("deliveryDate", formatDate(metric.deliveryDate()));
+        item.put("revenue", metric.revenue());
+        item.put("materialCost", metric.materialCost());
+        item.put("laborCost", metric.laborCost());
+        item.put("depreciationCost", metric.depreciationCost());
+        item.put("scrapCost", metric.scrapCost());
+        item.put("totalCost", metric.totalCost());
+        item.put("profit", metric.profit());
+        item.put("profitRate", toPercent(metric.profitRate()));
+        item.put("riskLevel", metric.riskLevel());
+        item.put("reasons", metric.reasons());
+        item.put("suggestion", metric.suggestion());
+        return item;
+    }
+
+    private Map<String, Object> toRiskOrderItem(OrderProfitMetric metric) {
+        Map<String, Object> map = toOrderCostItem(metric);
+        map.put("priority", "high".equals(metric.riskLevel()) ? "high" : ("medium".equals(metric.riskLevel()) ? "medium" : "low"));
+        return map;
+    }
+
+    private List<Map<String, Object>> buildCustomerProfitTop(List<OrderProfitMetric> metrics, int topN) {
+        Map<String, BigDecimal> customerProfitMap = new HashMap<>();
+        Map<String, BigDecimal> customerRevenueMap = new HashMap<>();
+        for (OrderProfitMetric metric : metrics) {
+            customerProfitMap.merge(metric.customerName(), metric.profit(), BigDecimal::add);
+            customerRevenueMap.merge(metric.customerName(), metric.revenue(), BigDecimal::add);
+        }
+        return customerProfitMap.entrySet().stream()
+                .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
+                .limit(topN)
+                .map(entry -> {
+                    Map<String, Object> map = new LinkedHashMap<>();
+                    BigDecimal revenue = customerRevenueMap.getOrDefault(entry.getKey(), BigDecimal.ZERO);
+                    map.put("customerName", entry.getKey());
+                    map.put("profit", entry.getValue());
+                    map.put("revenue", revenue);
+                    map.put("profitRate", toPercent(rate(entry.getValue(), revenue)));
+                    return map;
+                })
+                .toList();
+    }
+
+    private Map<String, Object> toInventoryItem(InventoryMetric metric) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("productModelId", metric.modelId());
+        map.put("productName", metric.productName());
+        map.put("model", metric.modelName());
+        map.put("quantity", metric.quantity());
+        map.put("lockedQuantity", metric.lockedQuantity());
+        map.put("avgUnitCost", metric.avgUnitCost());
+        map.put("inventoryValue", metric.inventoryValue());
+        map.put("outboundQuantity", metric.outboundQuantity());
+        map.put("stagnantDays", metric.stagnantDays());
+        map.put("overstock", metric.overstock());
+        map.put("riskLevel", metric.stagnantDays() >= 90 ? "high" : (metric.stagnantDays() >= 30 ? "medium" : "low"));
+        return map;
+    }
+
+    private boolean matchInventoryKeyword(InventoryMetric metric, String keyword) {
+        if (!StringUtils.hasText(keyword)) {
+            return true;
+        }
+        return metric.productName().contains(keyword.trim()) || metric.modelName().contains(keyword.trim());
+    }
+
+    private Map<String, Object> toMonthlyCashFlowItem(MonthlyCashFlow flow) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("month", flow.month());
+        map.put("income", flow.income());
+        map.put("expense", flow.expense());
+        map.put("netFlow", flow.netFlow());
+        return map;
+    }
+
+    private Map<String, Object> toStatementRiskItem(StatementMetric metric, Map<String, String> nameMap, String type) {
+        BigDecimal actualRate = rate(metric.actualAmount(), metric.planAmount());
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put(type + "Id", metric.entityId());
+        map.put(type + "Name", safe(nameMap.get(metric.entityId())));
+        map.put("statementMonth", metric.statementMonth());
+        map.put("closingBalance", metric.closingBalance());
+        map.put("planAmount", metric.planAmount());
+        map.put("actualAmount", metric.actualAmount());
+        map.put("actualRate", toPercent(actualRate));
+        map.put("riskLevel", metric.closingBalance().compareTo(new BigDecimal("1000000")) > 0 || actualRate.compareTo(new BigDecimal("0.50")) < 0 ? "high" : "medium");
+        return map;
+    }
+
+    private Map<String, Object> anomalyItem(String level, String type, String message, Map<String, Object> detail) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("riskLevel", level);
+        map.put("type", type);
+        map.put("message", message);
+        map.put("detail", detail);
+        return map;
+    }
+
+    private Map<String, Object> riskSuggestion(String type, String level, String suggestion) {
+        Map<String, Object> map = new LinkedHashMap<>();
+        map.put("type", type);
+        map.put("level", level);
+        map.put("suggestion", suggestion);
+        return map;
+    }
+
+    private Map<String, Object> buildCostCompositionPie(BigDecimal material, BigDecimal labor, BigDecimal depreciation, BigDecimal scrap) {
+        List<Map<String, Object>> data = List.of(
+                Map.of("name", "鏉愭枡鎴愭湰", "value", material),
+                Map.of("name", "浜哄伐鎴愭湰", "value", labor),
+                Map.of("name", "鎶樻棫鎴愭湰", "value", depreciation),
+                Map.of("name", "鎹熻�楁垚鏈�", "value", scrap)
+        );
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "鎴愭湰鏋勬垚", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "item"));
+        option.put("series", List.of(Map.of("name", "鎴愭湰鏋勬垚", "type", "pie", "radius", "60%", "data", data)));
+        return option;
+    }
+
+    private Map<String, Object> buildOrderProfitBar(List<OrderProfitMetric> metrics) {
+        List<OrderProfitMetric> top = metrics.stream()
+                .sorted(Comparator.comparing(OrderProfitMetric::profit))
+                .limit(10)
+                .toList();
+        List<String> xData = top.stream().map(OrderProfitMetric::salesContractNo).toList();
+        List<BigDecimal> yData = top.stream().map(OrderProfitMetric::profit).toList();
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "璁㈠崟鍒╂鼎鍒嗗竷", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", xData));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(Map.of("name", "鍒╂鼎", "type", "bar", "data", yData)));
+        return option;
+    }
+
+    private Map<String, Object> buildProcessCostBar(Map<String, BigDecimal> processCosts) {
+        List<String> xData = new ArrayList<>(processCosts.keySet());
+        List<BigDecimal> yData = new ArrayList<>(processCosts.values());
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "宸ュ簭鎴愭湰鎺掑悕", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", xData));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(Map.of("name", "鎴愭湰", "type", "bar", "data", yData)));
+        return option;
+    }
+
+    private Map<String, Object> buildProfitDistributionBar(List<OrderProfitMetric> metrics) {
+        List<OrderProfitMetric> sorted = metrics.stream()
+                .sorted(Comparator.comparing(OrderProfitMetric::profitRate))
+                .limit(15)
+                .toList();
+        List<String> xData = sorted.stream().map(OrderProfitMetric::salesContractNo).toList();
+        List<BigDecimal> yData = sorted.stream().map(metric -> metric.profitRate().multiply(ONE_HUNDRED).setScale(2, RoundingMode.HALF_UP)).toList();
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "璁㈠崟鍒╂鼎鐜囧垎甯�", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", xData));
+        option.put("yAxis", Map.of("type", "value", "name", "%"));
+        option.put("series", List.of(Map.of("name", "鍒╂鼎鐜�", "type", "bar", "data", yData)));
+        return option;
+    }
+
+    private Map<String, Object> buildLossOrderTrendLine(List<OrderProfitMetric> metrics) {
+        Map<String, Long> lossByDate = new LinkedHashMap<>();
+        List<OrderProfitMetric> sorted = metrics.stream()
+                .filter(metric -> metric.entryDate() != null)
+                .sorted(Comparator.comparing(OrderProfitMetric::entryDate))
+                .toList();
+        for (OrderProfitMetric metric : sorted) {
+            String day = formatDate(metric.entryDate());
+            long inc = metric.profit().compareTo(BigDecimal.ZERO) < 0 ? 1L : 0L;
+            lossByDate.merge(day, inc, Long::sum);
+        }
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "浜忔崯璁㈠崟瓒嬪娍", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", new ArrayList<>(lossByDate.keySet())));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(Map.of("name", "浜忔崯璁㈠崟鏁�", "type", "line", "smooth", true, "data", new ArrayList<>(lossByDate.values()))));
+        return option;
+    }
+
+    private Map<String, Object> buildCustomerProfitBar(Map<String, BigDecimal> customerProfitMap) {
+        List<Map.Entry<String, BigDecimal>> top = customerProfitMap.entrySet().stream()
+                .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
+                .limit(10)
+                .toList();
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "瀹㈡埛鍒╂鼎璐$尞TOP10", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", top.stream().map(Map.Entry::getKey).toList()));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(Map.of("name", "鍒╂鼎", "type", "bar", "data", top.stream().map(Map.Entry::getValue).toList())));
+        return option;
+    }
+
+    private Map<String, Object> buildInventoryTopBar(List<InventoryMetric> metrics) {
+        List<InventoryMetric> top = metrics.stream()
+                .sorted(Comparator.comparing(InventoryMetric::inventoryValue).reversed())
+                .limit(10)
+                .toList();
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "搴撳瓨璧勯噾鍗犵敤TOP10", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", top.stream().map(item -> item.productName() + "/" + item.modelName()).toList()));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(Map.of("name", "璧勯噾鍗犵敤", "type", "bar", "data", top.stream().map(InventoryMetric::inventoryValue).toList())));
+        return option;
+    }
+
+    private Map<String, Object> buildInventoryAgingPie(List<InventoryMetric> metrics) {
+        long normal = metrics.stream().filter(item -> item.stagnantDays() < 30).count();
+        long slow = metrics.stream().filter(item -> item.stagnantDays() >= 30 && item.stagnantDays() < 90).count();
+        long stagnant = metrics.stream().filter(item -> item.stagnantDays() >= 90).count();
+        List<Map<String, Object>> data = List.of(
+                Map.of("name", "姝e父", "value", normal),
+                Map.of("name", "缂撴參", "value", slow),
+                Map.of("name", "鍛嗘粸", "value", stagnant)
+        );
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "搴撳瓨搴撻緞鍒嗗竷", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "item"));
+        option.put("series", List.of(Map.of("type", "pie", "radius", "60%", "data", data)));
+        return option;
+    }
+
+    private Map<String, Object> buildTurnoverGauge(BigDecimal turnoverDays) {
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "搴撳瓨鍛ㄨ浆澶╂暟", "left", "center"));
+        option.put("series", List.of(Map.of(
+                "type", "gauge",
+                "min", 0,
+                "max", 180,
+                "detail", Map.of("formatter", "{value}澶�"),
+                "data", List.of(Map.of("value", turnoverDays, "name", "鍛ㄨ浆澶╂暟"))
+        )));
+        return option;
+    }
+
+    private Map<String, Object> buildCashflowTrend(List<MonthlyCashFlow> actual, List<MonthlyCashFlow> forecast) {
+        List<String> labels = new ArrayList<>();
+        List<BigDecimal> netActual = new ArrayList<>();
+        List<BigDecimal> netForecast = new ArrayList<>();
+        for (MonthlyCashFlow point : actual) {
+            labels.add(point.month());
+            netActual.add(point.netFlow());
+            netForecast.add(null);
+        }
+        for (MonthlyCashFlow point : forecast) {
+            labels.add(point.month());
+            netActual.add(null);
+            netForecast.add(point.netFlow());
+        }
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "鐜伴噾娴佽秼鍔匡紙瀹為檯+棰勬祴锛�", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", labels));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(
+                Map.of("name", "瀹為檯鍑�鐜伴噾娴�", "type", "line", "smooth", true, "data", netActual),
+                Map.of("name", "棰勬祴鍑�鐜伴噾娴�", "type", "line", "smooth", true, "data", netForecast)
+        ));
+        return option;
+    }
+
+    private Map<String, Object> buildReceivablePayableBar(BigDecimal receivable, BigDecimal payable) {
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "搴旀敹搴斾粯浣欓瀵规瘮", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", List.of("搴旀敹", "搴斾粯")));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(Map.of("name", "浣欓", "type", "bar", "data", List.of(receivable, payable))));
+        return option;
+    }
+
+    private Map<String, Object> buildFundGapGauge(BigDecimal fundGap) {
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "璧勯噾缂哄彛", "left", "center"));
+        option.put("series", List.of(Map.of(
+                "type", "gauge",
+                "min", 0,
+                "max", 10000000,
+                "detail", Map.of("formatter", "{value}"),
+                "data", List.of(Map.of("value", fundGap, "name", "璧勯噾缂哄彛"))
+        )));
+        return option;
+    }
+
+    private Map<String, Object> buildAnomalyLevelPie(List<Map<String, Object>> anomalies) {
+        long high = anomalies.stream().filter(item -> "high".equals(item.get("riskLevel"))).count();
+        long medium = anomalies.stream().filter(item -> "medium".equals(item.get("riskLevel"))).count();
+        long low = anomalies.stream().filter(item -> "low".equals(item.get("riskLevel"))).count();
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "寮傚父绛夌骇鍒嗗竷", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "item"));
+        option.put("series", List.of(Map.of("type", "pie", "radius", "60%", "data", List.of(
+                Map.of("name", "楂橀闄�", "value", high),
+                Map.of("name", "涓闄�", "value", medium),
+                Map.of("name", "浣庨闄�", "value", low)
+        ))));
+        return option;
+    }
+
+    private Map<String, Object> buildAnomalyTypeBar(List<Map<String, Object>> anomalies) {
+        Map<String, Long> countByType = new LinkedHashMap<>();
+        for (Map<String, Object> anomaly : anomalies) {
+            countByType.merge(String.valueOf(anomaly.get("type")), 1L, Long::sum);
+        }
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "寮傚父绫诲瀷鍒嗗竷", "left", "center"));
+        option.put("tooltip", Map.of("trigger", "axis"));
+        option.put("xAxis", Map.of("type", "category", "data", new ArrayList<>(countByType.keySet())));
+        option.put("yAxis", Map.of("type", "value"));
+        option.put("series", List.of(Map.of("name", "寮傚父鏁�", "type", "bar", "data", new ArrayList<>(countByType.values()))));
+        return option;
+    }
+
+    private Map<String, Object> buildInventoryProfitGauge(BigDecimal inventoryValue, BigDecimal profit) {
+        BigDecimal ratio = inventoryValue.compareTo(BigDecimal.ZERO) <= 0
+                ? BigDecimal.ZERO
+                : profit.divide(inventoryValue, 4, RoundingMode.HALF_UP).multiply(ONE_HUNDRED);
+        Map<String, Object> option = new LinkedHashMap<>();
+        option.put("title", Map.of("text", "鍒╂鼎/搴撳瓨璧勯噾姣�", "left", "center"));
+        option.put("series", List.of(Map.of(
+                "type", "gauge",
+                "min", -100,
+                "max", 100,
+                "detail", Map.of("formatter", "{value}%"),
+                "data", List.of(Map.of("value", ratio.setScale(2, RoundingMode.HALF_UP), "name", "鍒╂鼎璧勯噾姣�"))
+        )));
+        return option;
+    }
+
+    private int normalizeLimit(Integer limit) {
+        if (limit == null || limit <= 0) {
+            return DEFAULT_LIMIT;
+        }
+        return Math.min(limit, MAX_LIMIT);
+    }
+
+    private DateRange resolveDateRange(String startDate, String endDate, String timeRange, String defaultLabel) {
+        LocalDate today = LocalDate.now();
+        LocalDate explicitStart = parseLocalDate(startDate);
+        LocalDate explicitEnd = parseLocalDate(endDate);
+        if (explicitStart != null || explicitEnd != null) {
+            LocalDate start = explicitStart != null ? explicitStart : explicitEnd;
+            LocalDate end = explicitEnd != null ? explicitEnd : explicitStart;
+            if (start.isAfter(end)) {
+                LocalDate temp = start;
+                start = end;
+                end = temp;
+            }
+            return new DateRange(start, end, start + "鑷�" + end);
+        }
+
+        if (!StringUtils.hasText(timeRange)) {
+            return new DateRange(null, null, "鍏ㄩ儴");
+        }
+
+        String text = timeRange.trim();
+        if (text.contains("浠婂ぉ")) {
+            return new DateRange(today, today, "浠婂ぉ");
+        }
+        if (text.contains("鏄ㄥぉ") || text.contains("鏄ㄦ棩")) {
+            LocalDate day = today.minusDays(1);
+            return new DateRange(day, day, "鏄ㄥぉ");
+        }
+        if (text.contains("鏈懆")) {
+            LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L);
+            return new DateRange(start, today, "鏈懆");
+        }
+        if (text.contains("涓婂懆")) {
+            LocalDate thisWeekStart = today.minusDays(today.getDayOfWeek().getValue() - 1L);
+            LocalDate start = thisWeekStart.minusWeeks(1);
+            LocalDate end = thisWeekStart.minusDays(1);
+            return new DateRange(start, end, "涓婂懆");
+        }
+        if (text.contains("鏈湀")) {
+            return new DateRange(today.withDayOfMonth(1), today, "鏈湀");
+        }
+        if (text.contains("涓婃湀")) {
+            YearMonth lastMonth = YearMonth.from(today).minusMonths(1);
+            return new DateRange(lastMonth.atDay(1), lastMonth.atEndOfMonth(), "涓婃湀");
+        }
+        if (text.contains("浠婂勾") || text.contains("鏈勾")) {
+            return new DateRange(today.withDayOfYear(1), today, "浠婂勾");
+        }
+        Matcher relativeMatcher = RELATIVE_PATTERN.matcher(text);
+        if (relativeMatcher.find()) {
+            int amount = Integer.parseInt(relativeMatcher.group(2));
+            String unit = relativeMatcher.group(3);
+            LocalDate start = switch (unit) {
+                case "澶�" -> today.minusDays(Math.max(amount - 1L, 0));
+                case "鍛�" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1);
+                case "涓湀", "鏈�" -> today.minusMonths(Math.max(amount, 1)).plusDays(1);
+                case "骞�" -> today.minusYears(Math.max(amount, 1)).plusDays(1);
+                default -> today.minusDays(29);
+            };
+            return new DateRange(start, today, "杩�" + amount + unit);
+        }
+        Matcher dateMatcher = DATE_PATTERN.matcher(text);
+        if (dateMatcher.find()) {
+            LocalDate start = parseLocalDate(dateMatcher.group(1));
+            LocalDate end = dateMatcher.find() ? parseLocalDate(dateMatcher.group(1)) : start;
+            if (start != null && end != null) {
+                if (start.isAfter(end)) {
+                    LocalDate temp = start;
+                    start = end;
+                    end = temp;
+                }
+                return new DateRange(start, end, start + "鑷�" + end);
+            }
+        }
+        return new DateRange(null, null, "鍏ㄩ儴");
+    }
+
+    private LocalDate parseLocalDate(String text) {
+        if (!StringUtils.hasText(text)) {
+            return null;
+        }
+        try {
+            return LocalDate.parse(text.trim(), DATE_FMT);
+        } catch (Exception ignored) {
+            return null;
+        }
+    }
+
+    private Date toDate(LocalDate localDate) {
+        return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
+    }
+
+    private Date toExclusiveEndDate(LocalDate localDate) {
+        return Date.from(localDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
+    }
+
+    private LocalDate toLocalDate(Date date) {
+        if (date == null) {
+            return null;
+        }
+        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+    }
+
+    private String formatDate(LocalDate date) {
+        return date == null ? "" : date.format(DATE_FMT);
+    }
+
+    private String displayDate(LocalDate date) {
+        return date == null ? "" : date.format(DATE_FMT);
+    }
+
+    private long daysBetween(LocalDate start, LocalDate end) {
+        if (start == null || end == null || start.isAfter(end)) {
+            return 0;
+        }
+        return end.toEpochDay() - start.toEpochDay();
+    }
+
+    private BigDecimal defaultDecimal(BigDecimal value) {
+        return value == null ? BigDecimal.ZERO : value;
+    }
+
+    private BigDecimal maxZero(BigDecimal value) {
+        return value == null || value.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : value;
+    }
+
+    private BigDecimal rate(BigDecimal numerator, BigDecimal denominator) {
+        if (denominator == null || denominator.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
+        }
+        return defaultDecimal(numerator).divide(denominator, 6, RoundingMode.HALF_UP);
+    }
+
+    private String toPercent(BigDecimal decimal) {
+        if (decimal == null) {
+            return "0.00%";
+        }
+        BigDecimal rate = decimal.multiply(ONE_HUNDRED).setScale(2, RoundingMode.HALF_UP);
+        return rate.toPlainString() + "%";
+    }
+
+    private BigDecimal avgRate(List<OrderProfitMetric> metrics) {
+        if (metrics == null || metrics.isEmpty()) {
+            return BigDecimal.ZERO;
+        }
+        BigDecimal sum = metrics.stream().map(OrderProfitMetric::profitRate).reduce(BigDecimal.ZERO, BigDecimal::add);
+        return sum.divide(new BigDecimal(metrics.size()), 6, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal estimateLaborCost(ProductionAccount account, Map<String, BigDecimal> salaryQuotaByOperation) {
+        BigDecimal salaryQuota = salaryQuotaByOperation.getOrDefault(safe(account.getTechnologyOperationName()), BigDecimal.ZERO);
+        BigDecimal finishedNum = defaultDecimal(account.getFinishedNum());
+        BigDecimal workHours = defaultDecimal(account.getWorkHours());
+        if (salaryQuota.compareTo(BigDecimal.ZERO) > 0 && finishedNum.compareTo(BigDecimal.ZERO) > 0) {
+            return finishedNum.multiply(salaryQuota);
+        }
+        if (salaryQuota.compareTo(BigDecimal.ZERO) > 0 && workHours.compareTo(BigDecimal.ZERO) > 0) {
+            return workHours.multiply(salaryQuota);
+        }
+        if (workHours.compareTo(BigDecimal.ZERO) > 0) {
+            return workHours;
+        }
+        return finishedNum;
+    }
+
+    private List<Long> parseIdList(String raw) {
+        if (!StringUtils.hasText(raw)) {
+            return List.of();
+        }
+        String text = raw.replace("[", "").replace("]", "").replace(" ", "");
+        if (!StringUtils.hasText(text)) {
+            return List.of();
+        }
+        List<Long> result = new ArrayList<>();
+        for (String part : text.split(",")) {
+            if (!StringUtils.hasText(part)) {
+                continue;
+            }
+            try {
+                result.add(Long.parseLong(part.trim()));
+            } catch (Exception ignored) {
+            }
+        }
+        return result;
+    }
+
+    private int keywordHitCount(List<String> keywords, String question) {
+        if (!StringUtils.hasText(question) || keywords == null) {
+            return 0;
+        }
+        int count = 0;
+        for (String keyword : keywords) {
+            if (question.contains(keyword)) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    private String normalizeForMatch(String text) {
+        if (!StringUtils.hasText(text)) {
+            return "";
+        }
+        return text.replace("锛�", "")
+                .replace(",", "")
+                .replace("銆�", "")
+                .replace(".", "")
+                .replace("锛�", "")
+                .replace("!", "")
+                .replace("锛�", "")
+                .replace("?", "")
+                .replace("锛�", "")
+                .replace(":", "")
+                .replace("锛�", "")
+                .replace(";", "")
+                .replace(" ", "")
+                .trim();
+    }
+
+    private String safe(Object value) {
+        return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ').trim();
+    }
+
+    private LoginUser currentLoginUser(String memoryId) {
+        LoginUser loginUser = aiSessionUserContext.get(memoryId);
+        if (loginUser != null) {
+            return loginUser;
+        }
+        return SecurityUtils.getLoginUser();
+    }
+
+    private Map<String, Object> rangeSummary(DateRange range, int count, String keyword) {
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("timeRange", range.label());
+        summary.put("startDate", displayDate(range.start()));
+        summary.put("endDate", displayDate(range.end()));
+        summary.put("count", count);
+        summary.put("keyword", safe(keyword));
+        return summary;
+    }
+
+    private Long toLongOrNull(String value) {
+        if (!StringUtils.hasText(value)) {
+            return null;
+        }
+        try {
+            return Long.valueOf(value.trim());
+        } catch (Exception ignored) {
+            return null;
+        }
+    }
+
+    private <T> List<T> defaultList(List<T> list) {
+        return list == null ? List.of() : list;
+    }
+
+    private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, SFunction<T, Long> field) {
+        if (tenantId != null) {
+            wrapper.eq(field, tenantId);
+        }
+    }
+
+    private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, SFunction<T, Long> field) {
+        if (deptId != null) {
+            wrapper.eq(field, deptId);
+        }
+    }
+
+    private List<KnowledgeDoc> financeKnowledgeBase() {
+        return List.of(
+                new KnowledgeDoc(
+                        "鍒╂鼎涓嬮檷鍒嗘瀽妗嗘灦",
+                        List.of("鍒╂鼎涓嬮檷", "浜忔崯璁㈠崟", "姣涘埄鐜�", "鍑�鍒╃巼"),
+                        "鍏堢湅鏀跺叆绔紙璁㈠崟缁撴瀯銆佸崟浠枫�佷氦浠樺欢杩燂級锛屽啀鐪嬫垚鏈锛堟潗鏂欍�佷汉宸ャ�佹姌鏃с�佹崯鑰楋級锛屾渶鍚庣湅鐜伴噾绔紙鍥炴銆佽处鏈熴�佸潖璐﹂闄╋級銆�",
+                        List.of("sales_ledger", "sales_ledger_product", "production_account", "device_ledger", "account_statement"),
+                        List.of("涓轰粈涔堟湰鏈堝埄娑︿笅闄嶏紵", "鍝簺璁㈠崟浜忔崯鏈�涓ラ噸锛�", "鎴愭湰涓婂崌鏉ヨ嚜鍝釜宸ュ簭锛�")
+                ),
+                new KnowledgeDoc(
+                        "搴撳瓨璧勯噾鍗犵敤璇婃柇",
+                        List.of("搴撳瓨绉帇", "鍛嗘粸搴撳瓨", "鍛ㄨ浆鐜�", "璧勯噾鍗犵敤"),
+                        "搴撳瓨璧勯噾璇婃柇閲嶇偣鐪嬶細搴撳瓨浠峰�笺�佽繎30澶╁嚭搴撴垚鏈�佸憜婊炲ぉ鏁般�佽秴鍌ㄦ瘮渚嬶紝褰㈡垚鍘诲簱瀛樹笌閲囪喘鑺傚鑱斿姩绛栫暐銆�",
+                        List.of("stock_inventory", "procurement_record_storage", "procurement_record_out"),
+                        List.of("鍝簺鐗╂枡璧勯噾鍗犵敤鏈�楂橈紵", "鍝簺搴撳瓨瓒呰繃90澶╂湭鍛ㄨ浆锛�", "搴撳瓨鍛ㄨ浆澶╂暟鏄惁寮傚父锛�")
+                ),
+                new KnowledgeDoc(
+                        "鐜伴噾娴佷笌璐︽椋庨櫓",
+                        List.of("鐜伴噾娴�", "搴旀敹", "搴斾粯", "鍥炴", "璧勯噾缂哄彛"),
+                        "鐜伴噾娴佸垽鏂缁撳悎鏀舵銆佷粯娆俱�佸簲鏀跺簲浠樹綑棰濅笌棰勬祴鍑�娴侀噺锛岄噸鐐瑰叧娉ㄩ珮浣欓瀹㈡埛鍜岄珮闆嗕腑浠樻渚涘簲鍟嗐��",
+                        List.of("account_sales_collection", "account_purchase_payment", "account_statement"),
+                        List.of("鏈潵涓変釜鏈堟槸鍚︽湁璧勯噾缂哄彛锛�", "鍝釜瀹㈡埛鍥炴椋庨櫓鏈�楂橈紵", "浠樻鍘嬪姏鏈�澶х殑鏄摢浜涗緵搴斿晢锛�")
+                ),
+                new KnowledgeDoc(
+                        "涓氳储涓�浣撳寲鍙e緞",
+                        List.of("涓氳储铻嶅悎", "涓氳储鑱斿姩", "鍙e緞", "椹鹃┒鑸�"),
+                        "璁㈠崟鍒╂鼎鍙e緞=閿�鍞敹鍏�-鏉愭枡鎴愭湰-浜哄伐鎴愭湰-璁惧鎶樻棫-鎹熻�楁垚鏈紱缁忚惀椹鹃┒鑸辫仈鍔ㄨ鍗曘�佺敓浜с�佸簱瀛樸�佽澶囥�佽处娆炬暟鎹��",
+                        List.of("sales_ledger", "production_operation_task", "production_product_main", "device_ledger", "stock_inventory", "account_statement"),
+                        List.of("璁㈠崟鍒╂鼎鐜囧浣曡绠楋紵", "缁忚惀椹鹃┒鑸辨牳蹇冩寚鏍囨湁鍝簺锛�")
+                )
+        );
+    }
+
+    private String jsonResponse(boolean success,
+                                String type,
+                                String description,
+                                Map<String, Object> summary,
+                                Map<String, Object> data,
+                                Map<String, Object> charts) {
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("success", success);
+        result.put("type", type);
+        result.put("description", description);
+        result.put("summary", summary == null ? Map.of() : summary);
+        result.put("data", data == null ? Map.of() : data);
+        result.put("charts", charts == null ? Map.of() : charts);
+        return JSON.toJSONString(result);
+    }
+
+    private record DateRange(LocalDate start, LocalDate end, String label) {
+        private boolean hasDateFilter() {
+            return start != null && end != null;
+        }
+    }
+
+    private record OrderProfitMetric(Long ledgerId,
+                                     String salesContractNo,
+                                     String customerName,
+                                     String projectName,
+                                     LocalDate entryDate,
+                                     LocalDate deliveryDate,
+                                     BigDecimal revenue,
+                                     BigDecimal materialCost,
+                                     BigDecimal laborCost,
+                                     BigDecimal depreciationCost,
+                                     BigDecimal scrapCost,
+                                     BigDecimal totalCost,
+                                     BigDecimal profit,
+                                     BigDecimal profitRate,
+                                     String riskLevel,
+                                     List<String> reasons,
+                                     String suggestion) {
+    }
+
+    private record AnalysisBundle(List<OrderProfitMetric> orderMetrics,
+                                  Map<String, BigDecimal> processCostRanking,
+                                  BigDecimal totalRevenue,
+                                  BigDecimal totalMaterialCost,
+                                  BigDecimal totalLaborCost,
+                                  BigDecimal totalDepreciationCost,
+                                  BigDecimal totalScrapCost,
+                                  BigDecimal totalCost,
+                                  BigDecimal totalProfit) {
+        private static AnalysisBundle empty() {
+            return new AnalysisBundle(List.of(), Map.of(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
+                    BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);
+        }
+    }
+
+    private record MaterialCostResult(Map<Long, BigDecimal> materialCostByLedgerId,
+                                      Map<Long, BigDecimal> avgUnitCostByModelId) {
+    }
+
+    private record ProductionCostContext(Map<Long, BigDecimal> laborCostByLedgerId,
+                                         Map<Long, BigDecimal> scrapCostByLedgerId,
+                                         Map<String, BigDecimal> processCostRanking) {
+        private static ProductionCostContext empty() {
+            return new ProductionCostContext(Map.of(), Map.of(), Map.of());
+        }
+    }
+
+    private record InventoryMetric(Long modelId,
+                                   String productName,
+                                   String modelName,
+                                   BigDecimal quantity,
+                                   BigDecimal lockedQuantity,
+                                   BigDecimal avgUnitCost,
+                                   BigDecimal inventoryValue,
+                                   BigDecimal outboundQuantity,
+                                   long stagnantDays,
+                                   boolean overstock) {
+    }
+
+    private static class InventoryMetricBuilder {
+        private final Long modelId;
+        private BigDecimal quantity = BigDecimal.ZERO;
+        private BigDecimal lockedQuantity = BigDecimal.ZERO;
+        private BigDecimal warnNum = BigDecimal.ZERO;
+        private LocalDateTime firstInTime;
+
+        private InventoryMetricBuilder(Long modelId) {
+            this.modelId = modelId;
+        }
+
+        private void addQuantity(BigDecimal quantity) {
+            this.quantity = this.quantity.add(quantity);
+        }
+
+        private void addLockedQuantity(BigDecimal lockedQuantity) {
+            this.lockedQuantity = this.lockedQuantity.add(lockedQuantity);
+        }
+
+        private void addWarnNum(BigDecimal warnNum) {
+            this.warnNum = this.warnNum.max(warnNum);
+        }
+
+        private void updateFirstInTime(LocalDateTime createTime) {
+            if (this.firstInTime == null || createTime.isBefore(this.firstInTime)) {
+                this.firstInTime = createTime;
+            }
+        }
+
+        private Long modelId() {
+            return modelId;
+        }
+
+        private BigDecimal quantity() {
+            return quantity;
+        }
+
+        private BigDecimal lockedQuantity() {
+            return lockedQuantity;
+        }
+
+        private BigDecimal warnNum() {
+            return warnNum;
+        }
+
+        private LocalDateTime firstInTime() {
+            return firstInTime;
+        }
+    }
+
+    private record OutboundStats(Map<Long, BigDecimal> outboundQtyByModel,
+                                 Map<Long, LocalDateTime> lastOutboundTimeByModel,
+                                 BigDecimal totalOutboundCost) {
+        private static OutboundStats empty() {
+            return new OutboundStats(Map.of(), Map.of(), BigDecimal.ZERO);
+        }
+    }
+
+    private record MonthlyCashFlow(String month, BigDecimal income, BigDecimal expense, BigDecimal netFlow) {
+    }
+
+    private record StatementMetric(String entityId,
+                                   BigDecimal closingBalance,
+                                   BigDecimal planAmount,
+                                   BigDecimal actualAmount,
+                                   String statementMonth) {
+    }
+
+    private record StatementSnapshot(BigDecimal receivableTotal,
+                                     BigDecimal payableTotal,
+                                     List<StatementMetric> receivableTop,
+                                     List<StatementMetric> payableTop) {
+        private static StatementSnapshot empty() {
+            return new StatementSnapshot(BigDecimal.ZERO, BigDecimal.ZERO, List.of(), List.of());
+        }
+    }
+
+    private record KnowledgeDoc(String topic,
+                                List<String> keywords,
+                                String knowledge,
+                                List<String> relatedTables,
+                                List<String> suggestedQuestions) {
+    }
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/dto/ApprovalInstanceDto.java b/src/main/java/com/ruoyi/approve/bean/dto/ApprovalInstanceDto.java
new file mode 100644
index 0000000..a2df482
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/bean/dto/ApprovalInstanceDto.java
@@ -0,0 +1,21 @@
+package com.ruoyi.approve.bean.dto;
+
+import com.ruoyi.approve.pojo.ApprovalInstance;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ApprovalInstanceDto extends ApprovalInstance {
+
+    private String approveAction;
+
+    private String approveComment;
+
+    private String createTimeEnd;
+
+    private String createTimeStart;
+
+    private List<StorageBlobDTO> storageBlobDTOs;
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateDto.java b/src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateDto.java
new file mode 100644
index 0000000..35952a5
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateDto.java
@@ -0,0 +1,12 @@
+package com.ruoyi.approve.bean.dto;
+
+import com.ruoyi.approve.pojo.ApprovalTemplate;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ApprovalTemplateDto  extends ApprovalTemplate {
+
+    private List<ApprovalTemplateNodeDto> nodes;
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeApproverDto.java b/src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeApproverDto.java
new file mode 100644
index 0000000..8382619
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeApproverDto.java
@@ -0,0 +1,8 @@
+package com.ruoyi.approve.bean.dto;
+
+import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
+import lombok.Data;
+
+@Data
+public class ApprovalTemplateNodeApproverDto extends ApprovalTemplateNodeApprover {
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeDto.java b/src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeDto.java
new file mode 100644
index 0000000..e85aee9
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeDto.java
@@ -0,0 +1,12 @@
+package com.ruoyi.approve.bean.dto;
+
+import com.ruoyi.approve.pojo.ApprovalTemplateNode;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ApprovalTemplateNodeDto extends ApprovalTemplateNode {
+
+    private List<ApprovalTemplateNodeApproverDto> approvers;
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/dto/FinReimbursementDto.java b/src/main/java/com/ruoyi/approve/bean/dto/FinReimbursementDto.java
new file mode 100644
index 0000000..2f14376
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/bean/dto/FinReimbursementDto.java
@@ -0,0 +1,21 @@
+package com.ruoyi.approve.bean.dto;
+
+import com.ruoyi.approve.pojo.FinReimbursement;
+import com.ruoyi.approve.pojo.FinReimbursementDetail;
+import com.ruoyi.approve.pojo.FinReimbursementTravel;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class FinReimbursementDto extends FinReimbursement {
+
+    private String createTimeStart;
+    private String createTimeEnd;
+
+    private FinReimbursementTravel  travel;
+    private List<FinReimbursementDetail> details;
+    private List<ApprovalTemplateNodeDto> nodes;
+    private List<StorageBlobDTO> storageBlobDTOs;
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java b/src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java
new file mode 100644
index 0000000..552ed0a
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java
@@ -0,0 +1,28 @@
+package com.ruoyi.approve.bean.vo;
+
+import com.ruoyi.approve.pojo.ApprovalInstance;
+import com.ruoyi.approve.pojo.ApprovalRecord;
+import com.ruoyi.approve.pojo.ApprovalTask;
+import com.ruoyi.basic.dto.StorageBlobVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ApprovalInstanceVo extends ApprovalInstance {
+    //褰撳墠鐢ㄦ埛鏄惁鍙互瀹℃壒
+    @Schema(description = "褰撳墠鐢ㄦ埛鏄惁鍙互瀹℃壒")
+    private Boolean isApprove;
+
+    //瀹℃壒娴佺▼
+    private List<ApprovalTask> tasks;
+
+    //瀹℃壒璁板綍
+    private List<ApprovalRecord>  records;
+
+    @Schema(description = "涓氬姟鍚嶇О")
+    private String businessName;
+
+    private List<StorageBlobVO> storageBlobVOList;
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeApproverVo.java b/src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeApproverVo.java
new file mode 100644
index 0000000..4c0ce0f
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeApproverVo.java
@@ -0,0 +1,9 @@
+package com.ruoyi.approve.bean.vo;
+
+
+import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
+import lombok.Data;
+
+@Data
+public class ApprovalTemplateNodeApproverVo extends ApprovalTemplateNodeApprover {
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeVo.java b/src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeVo.java
new file mode 100644
index 0000000..9acd0af
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeVo.java
@@ -0,0 +1,13 @@
+package com.ruoyi.approve.bean.vo;
+
+
+import com.ruoyi.approve.pojo.ApprovalTemplateNode;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ApprovalTemplateNodeVo extends ApprovalTemplateNode {
+
+    private List<ApprovalTemplateNodeApproverVo> approvers;
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateVo.java b/src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateVo.java
new file mode 100644
index 0000000..2793293
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateVo.java
@@ -0,0 +1,14 @@
+package com.ruoyi.approve.bean.vo;
+
+import com.ruoyi.approve.pojo.ApprovalTemplate;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class ApprovalTemplateVo extends ApprovalTemplate {
+
+    private List<ApprovalTemplateNodeVo> nodes;
+
+    private String createdUserName;
+}
diff --git a/src/main/java/com/ruoyi/approve/bean/vo/FinReimbursementVo.java b/src/main/java/com/ruoyi/approve/bean/vo/FinReimbursementVo.java
new file mode 100644
index 0000000..5706ff3
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/bean/vo/FinReimbursementVo.java
@@ -0,0 +1,34 @@
+package com.ruoyi.approve.bean.vo;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.approve.pojo.*;
+import com.ruoyi.basic.dto.StorageBlobVO;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+public class FinReimbursementVo extends FinReimbursement {
+
+
+    private String createTimeStart;
+    private String createTimeEnd;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime startTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime endTime;
+
+    private FinReimbursementTravel travel;
+    private List<FinReimbursementDetail> details;
+    //瀹℃壒娴佺▼
+    private List<ApprovalTask> tasks;
+
+    //瀹℃壒璁板綍
+    private List<ApprovalRecord>  records;
+    private List<StorageBlobVO> storageBlobVOList;
+}
diff --git a/src/main/java/com/ruoyi/approve/controller/ApprovalInstanceController.java b/src/main/java/com/ruoyi/approve/controller/ApprovalInstanceController.java
new file mode 100644
index 0000000..36dcfc3
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/controller/ApprovalInstanceController.java
@@ -0,0 +1,67 @@
+package com.ruoyi.approve.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
+import com.ruoyi.approve.bean.vo.ApprovalInstanceVo;
+import com.ruoyi.approve.service.ApprovalInstanceService;
+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.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 瀹℃壒瀹炰緥琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:27:46
+ */
+@RestController
+@RequestMapping("/approvalInstance")
+@Tag(name = "瀹℃壒瀹炰緥琛�")
+@AllArgsConstructor
+public class ApprovalInstanceController extends BaseController {
+
+    private final ApprovalInstanceService approvalInstanceService;
+    @GetMapping("/listPage")
+    @Operation(summary = "鍒嗛〉鏌ヨ")
+    @Log(title = "瀹℃壒鍒楄〃鍒嗛〉鏌ヨ", businessType = BusinessType.OTHER)
+    public R listPage(Page<ApprovalInstanceVo> page, ApprovalInstanceDto approvalInstanceDto) {
+        return approvalInstanceService.listPage(page, approvalInstanceDto);
+    }
+
+    @PostMapping("/save")
+    @Operation(summary = "淇濆瓨")
+    @Log(title = "瀹℃壒鍒楄〃淇濆瓨", businessType = BusinessType.INSERT)
+    public R save(@RequestBody ApprovalInstanceDto approvalInstanceDto) {
+        return approvalInstanceService.add(approvalInstanceDto) ? R.ok() : R.fail();
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊")
+    @Log(title = "瀹℃壒鍒楄〃鏇存柊", businessType = BusinessType.UPDATE)
+    public R update(@RequestBody ApprovalInstanceDto approvalInstanceDto) {
+        return approvalInstanceService.update(approvalInstanceDto) ? R.ok() : R.fail();
+    }
+
+    @DeleteMapping("/delete")
+    @Log(title = "瀹℃壒鍒楄〃鍒犻櫎", businessType = BusinessType.DELETE)
+    @Operation(summary = "鍒犻櫎")
+    public R delete(@RequestBody List<Long> ids) {
+        return approvalInstanceService.delete(ids) ? R.ok() : R.fail();
+    }
+
+    @Operation(summary = "瀹℃壒")
+    @PostMapping("/approve")
+    @Log(title = "瀹℃壒鍒楄〃瀹℃壒", businessType = BusinessType.UPDATE)
+    public R approve(@RequestBody ApprovalInstanceDto approvalInstanceDto) {
+        return approvalInstanceService.approve(approvalInstanceDto);
+    }
+}
diff --git a/src/main/java/com/ruoyi/approve/controller/ApprovalInstanceNodeController.java b/src/main/java/com/ruoyi/approve/controller/ApprovalInstanceNodeController.java
new file mode 100644
index 0000000..e42af42
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/controller/ApprovalInstanceNodeController.java
@@ -0,0 +1,19 @@
+package com.ruoyi.approve.controller;
+
+import com.ruoyi.framework.web.controller.BaseController;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 瀹℃壒鑺傜偣瀹炰緥琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:27:54
+ */
+@RestController
+@RequestMapping("/approvalInstanceNode")
+public class ApprovalInstanceNodeController extends BaseController {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/controller/ApprovalRecordController.java b/src/main/java/com/ruoyi/approve/controller/ApprovalRecordController.java
new file mode 100644
index 0000000..2d6f9dd
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/controller/ApprovalRecordController.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 瀹℃壒璁板綍琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:28:21
+ */
+@RestController
+@RequestMapping("/approvalRecord")
+public class ApprovalRecordController {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/controller/ApprovalTaskController.java b/src/main/java/com/ruoyi/approve/controller/ApprovalTaskController.java
new file mode 100644
index 0000000..8682caa
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/controller/ApprovalTaskController.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 瀹℃壒浠诲姟琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:32:37
+ */
+@RestController
+@RequestMapping("/approvalTask")
+public class ApprovalTaskController {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/controller/ApprovalTemplateController.java b/src/main/java/com/ruoyi/approve/controller/ApprovalTemplateController.java
new file mode 100644
index 0000000..674d278
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/controller/ApprovalTemplateController.java
@@ -0,0 +1,74 @@
+package com.ruoyi.approve.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.approve.bean.dto.ApprovalTemplateDto;
+import com.ruoyi.approve.bean.vo.ApprovalTemplateVo;
+import com.ruoyi.approve.service.ApprovalTemplateService;
+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.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺嬮亾杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:08
+ */
+@RestController
+@RequestMapping("/approvalTemplate")
+@Tag(name = "瀹℃壒妯℃澘琛�")
+@AllArgsConstructor
+public class ApprovalTemplateController  extends BaseController {
+
+    private final ApprovalTemplateService approvalTemplateService;
+
+    @GetMapping("/listPage")
+    @Operation(summary = "鍒嗛〉鏌ヨ")
+    @Log(title = "瀹℃壒妯℃澘鍒嗛〉鏌ヨ", businessType = BusinessType.OTHER)
+    public R listPage(Page<ApprovalTemplateVo> page, ApprovalTemplateDto approvalTemplateDto) {
+        return R.ok(approvalTemplateService.listPage(page, approvalTemplateDto));
+    }
+
+    @PostMapping("/add")
+    @Operation(summary = "娣诲姞")
+    @Log(title = "娣诲姞瀹℃壒妯℃澘", businessType = BusinessType.INSERT)
+    public R add(@RequestBody ApprovalTemplateDto approvalTemplateDto) {
+        return R.ok(approvalTemplateService.saveApprovalTemplateDto(approvalTemplateDto));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "淇敼")
+    @Log(title = "淇敼瀹℃壒妯℃澘", businessType = BusinessType.UPDATE)
+    public R update(@RequestBody ApprovalTemplateDto approvalTemplateDto) {
+        return R.ok(approvalTemplateService.updateApprovalTemplateDto(approvalTemplateDto));
+    }
+
+    @PostMapping("/delete")
+    @Operation(summary = "鍒犻櫎")
+    @Log(title = "鍒犻櫎瀹℃壒妯℃澘", businessType = BusinessType.DELETE)
+    public R delete(@RequestBody List<Long> ids) {
+        return R.ok(approvalTemplateService.delete(ids));
+    }
+
+    @GetMapping("/list/{type}")
+    @Operation(summary = "鏌ヨ鎵�鏈夊鎵规ā鏉�")
+    public R list(@PathVariable("type") Integer type) {
+        return R.ok(approvalTemplateService.listApprovalTemplateVo(type));
+    }
+
+    @GetMapping("/detail/{id}")
+    @Operation(summary = "鏌ヨ瀹℃壒妯℃澘璇︽儏")
+    @Log(title = "鏌ヨ瀹℃壒妯℃澘璇︽儏", businessType = BusinessType.OTHER)
+    public R detail(@PathVariable("id") Long id) {
+        return R.ok(approvalTemplateService.getApprovalTemplateVoById(id));
+    }
+}
diff --git a/src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeApproverController.java b/src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeApproverController.java
new file mode 100644
index 0000000..b0e2bff
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeApproverController.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鑺傜偣瀹℃壒浜鸿〃 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:30
+ */
+@RestController
+@RequestMapping("/approvalTemplateNodeApprover")
+public class ApprovalTemplateNodeApproverController {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeController.java b/src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeController.java
new file mode 100644
index 0000000..5a1ed88
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeController.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鑺傜偣琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:19
+ */
+@RestController
+@RequestMapping("/approvalTemplateNode")
+public class ApprovalTemplateNodeController {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/controller/FinReimbursementController.java b/src/main/java/com/ruoyi/approve/controller/FinReimbursementController.java
new file mode 100644
index 0000000..a2eecdb
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/controller/FinReimbursementController.java
@@ -0,0 +1,60 @@
+package com.ruoyi.approve.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.approve.bean.dto.FinReimbursementDto;
+import com.ruoyi.approve.bean.vo.FinReimbursementVo;
+import com.ruoyi.approve.service.FinReimbursementService;
+import com.ruoyi.framework.web.domain.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曚富琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:15
+ */
+@RestController
+@RequestMapping("/finReimbursement")
+@Tag(name = "鎶ラ攢鍗曚富琛�", description = "鎶ラ攢鍗曚富琛�")
+@AllArgsConstructor
+public class FinReimbursementController {
+
+    private final FinReimbursementService finReimbursementService;
+
+    @GetMapping("/listPage")
+    @Operation(summary = "鍒嗛〉鏌ヨ")
+    public R listPage(Page<FinReimbursementVo> page, FinReimbursementDto finReimbursementDto) {
+        return R.ok(finReimbursementService.listPage(finReimbursementDto, page));
+    }
+
+    @PostMapping("/save")
+    @Operation(summary = "淇濆瓨")
+    public R save(@RequestBody FinReimbursementDto finReimbursementDto) {
+        return R.ok(finReimbursementService.add(finReimbursementDto));
+    }
+
+    @GetMapping("/detail")
+    @Operation(summary = "璇︽儏")
+    public R detail(Long id) {
+        return R.ok(finReimbursementService.detail(id));
+    }
+
+    @PostMapping("/update")
+    @Operation(summary = "淇敼")
+    public R update(@RequestBody FinReimbursementDto finReimbursementDto) {
+        return R.ok(finReimbursementService.update(finReimbursementDto));
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎")
+    public R delete(@RequestBody List<Long> ids) {
+        return R.ok(finReimbursementService.delete(ids));
+    }
+}
diff --git a/src/main/java/com/ruoyi/approve/controller/FinReimbursementDetailController.java b/src/main/java/com/ruoyi/approve/controller/FinReimbursementDetailController.java
new file mode 100644
index 0000000..18e4f73
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/controller/FinReimbursementDetailController.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曟槑缁嗚〃 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:38
+ */
+@RestController
+@RequestMapping("/finReimbursementDetail")
+public class FinReimbursementDetailController {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/controller/FinReimbursementTravelController.java b/src/main/java/com/ruoyi/approve/controller/FinReimbursementTravelController.java
new file mode 100644
index 0000000..0e67feb
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/controller/FinReimbursementTravelController.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 宸梾鎶ラ攢鎵╁睍琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:47
+ */
+@RestController
+@RequestMapping("/finReimbursementTravel")
+public class FinReimbursementTravelController {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceMapper.java b/src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceMapper.java
new file mode 100644
index 0000000..a541b90
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceMapper.java
@@ -0,0 +1,24 @@
+package com.ruoyi.approve.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.approve.bean.dto.ApprovalInstanceDto;
+import com.ruoyi.approve.bean.vo.ApprovalInstanceVo;
+import com.ruoyi.approve.pojo.ApprovalInstance;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * <p>
+ * 瀹℃壒瀹炰緥琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:27:46
+ */
+@Mapper
+public interface ApprovalInstanceMapper extends BaseMapper<ApprovalInstance> {
+
+    IPage<ApprovalInstanceVo> listPage(Page<ApprovalInstanceVo> page,@Param("ew") ApprovalInstanceDto approvalInstanceDto);
+}
diff --git a/src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceNodeMapper.java b/src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceNodeMapper.java
new file mode 100644
index 0000000..8787e55
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceNodeMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.approve.pojo.ApprovalInstanceNode;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 瀹℃壒鑺傜偣瀹炰緥琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:27:54
+ */
+@Mapper
+public interface ApprovalInstanceNodeMapper extends BaseMapper<ApprovalInstanceNode> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/mapper/ApprovalRecordMapper.java b/src/main/java/com/ruoyi/approve/mapper/ApprovalRecordMapper.java
new file mode 100644
index 0000000..fcf5ec6
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/ApprovalRecordMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.approve.pojo.ApprovalRecord;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 瀹℃壒璁板綍琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:28:21
+ */
+@Mapper
+public interface ApprovalRecordMapper extends BaseMapper<ApprovalRecord> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/mapper/ApprovalTaskMapper.java b/src/main/java/com/ruoyi/approve/mapper/ApprovalTaskMapper.java
new file mode 100644
index 0000000..2531d8e
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/ApprovalTaskMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.approve.pojo.ApprovalTask;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 瀹℃壒浠诲姟琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:32:37
+ */
+@Mapper
+public interface ApprovalTaskMapper extends BaseMapper<ApprovalTask> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateMapper.java b/src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateMapper.java
new file mode 100644
index 0000000..693f4b2
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateMapper.java
@@ -0,0 +1,24 @@
+package com.ruoyi.approve.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.approve.bean.dto.ApprovalTemplateDto;
+import com.ruoyi.approve.bean.vo.ApprovalTemplateVo;
+import com.ruoyi.approve.pojo.ApprovalTemplate;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:08
+ */
+@Mapper
+public interface ApprovalTemplateMapper extends BaseMapper<ApprovalTemplate> {
+
+    IPage<ApprovalTemplateVo> listPage(Page<ApprovalTemplateVo> page,@Param("ew") ApprovalTemplateDto approvalTemplateDto);
+}
diff --git a/src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeApproverMapper.java b/src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeApproverMapper.java
new file mode 100644
index 0000000..d96f1e3
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeApproverMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鑺傜偣瀹℃壒浜鸿〃 Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:30
+ */
+@Mapper
+public interface ApprovalTemplateNodeApproverMapper extends BaseMapper<ApprovalTemplateNodeApprover> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeMapper.java b/src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeMapper.java
new file mode 100644
index 0000000..dc4a67f
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.approve.pojo.ApprovalTemplateNode;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鑺傜偣琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:19
+ */
+@Mapper
+public interface ApprovalTemplateNodeMapper extends BaseMapper<ApprovalTemplateNode> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/mapper/FinReimbursementDetailMapper.java b/src/main/java/com/ruoyi/approve/mapper/FinReimbursementDetailMapper.java
new file mode 100644
index 0000000..edc039a
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/FinReimbursementDetailMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.approve.pojo.FinReimbursementDetail;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曟槑缁嗚〃 Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:38
+ */
+@Mapper
+public interface FinReimbursementDetailMapper extends BaseMapper<FinReimbursementDetail> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/mapper/FinReimbursementMapper.java b/src/main/java/com/ruoyi/approve/mapper/FinReimbursementMapper.java
new file mode 100644
index 0000000..19ac354
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/FinReimbursementMapper.java
@@ -0,0 +1,24 @@
+package com.ruoyi.approve.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.approve.bean.dto.FinReimbursementDto;
+import com.ruoyi.approve.bean.vo.FinReimbursementVo;
+import com.ruoyi.approve.pojo.FinReimbursement;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曚富琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:15
+ */
+@Mapper
+public interface FinReimbursementMapper extends BaseMapper<FinReimbursement> {
+
+    IPage<FinReimbursementVo> listPage(@Param("ew") FinReimbursementDto finReimbursementDto, Page<FinReimbursementVo> page);
+}
diff --git a/src/main/java/com/ruoyi/approve/mapper/FinReimbursementTravelMapper.java b/src/main/java/com/ruoyi/approve/mapper/FinReimbursementTravelMapper.java
new file mode 100644
index 0000000..bc5e1b8
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/mapper/FinReimbursementTravelMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.approve.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.approve.pojo.FinReimbursementTravel;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 宸梾鎶ラ攢鎵╁睍琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:47
+ */
+@Mapper
+public interface FinReimbursementTravelMapper extends BaseMapper<FinReimbursementTravel> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/pojo/ApprovalInstance.java b/src/main/java/com/ruoyi/approve/pojo/ApprovalInstance.java
new file mode 100644
index 0000000..5bac3a9
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/ApprovalInstance.java
@@ -0,0 +1,151 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 瀹℃壒瀹炰緥琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:27:46
+ */
+@Getter
+@Setter
+@ToString
+@TableName("approval_instance")
+@ApiModel(value = "ApprovalInstance瀵硅薄", description = "瀹℃壒瀹炰緥琛�")
+public class ApprovalInstance implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 瀹℃壒瀹炰緥ID
+     */
+    @Schema(description ="瀹℃壒瀹炰緥ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 瀹℃壒缂栧彿
+     */
+    @Schema(description ="瀹℃壒缂栧彿")
+    private String instanceNo;
+
+    /**
+     * 妯℃澘ID
+     */
+    @Schema(description ="妯℃澘ID")
+    private Long templateId;
+
+    /**
+     * 妯℃澘鍚嶇О
+     */
+    @Schema(description ="妯℃澘鍚嶇О")
+    private String templateName;
+
+    /**
+     * 涓氬姟ID
+     */
+    @Schema(description ="涓氬姟ID")
+    private Long businessId;
+
+    /**
+     * 涓氬姟绫诲瀷
+     */
+    @Schema(description ="涓氬姟绫诲瀷")
+    private Long businessType;
+
+    /**
+     * 瀹℃壒鏍囬
+     */
+    @Schema(description ="瀹℃壒鏍囬")
+    private String title;
+
+    /**
+     * 瀹℃壒鐘舵��
+     */
+    @Schema(description ="瀹℃壒鐘舵�� PENDING - 寰呭鎵�/杩涜涓�  APPROVED - 宸查�氳繃/宸插畬鎴�  REJECTED - 宸查┏鍥�")
+    private String status;
+
+    /**
+     * 褰撳墠瀹℃壒绾у埆
+     */
+    @Schema(description ="褰撳墠瀹℃壒绾у埆")
+    private Integer currentLevel;
+
+    /**
+     * 鐢宠浜篒D
+     */
+    @Schema(description ="鐢宠浜篒D")
+    private Long applicantId;
+
+    /**
+     * 鐢宠浜哄悕绉�
+     */
+    @Schema(description ="鐢宠浜哄悕绉�")
+    private String applicantName;
+
+    /**
+     * 鐢宠鏃堕棿
+     */
+    @Schema(description ="鐢宠鏃堕棿")
+    private LocalDateTime applyTime;
+
+    /**
+     * 瀹屾垚鏃堕棿
+     */
+    @Schema(description ="瀹屾垚鏃堕棿")
+    private LocalDateTime finishTime;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @Schema(description ="鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @Schema(description ="鍒涘缓鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    @Schema(description ="鏇存柊浜�")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Long updateUser;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @Schema(description ="鏇存柊鏃堕棿")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+    /**
+     * 閫昏緫鍒犻櫎
+     */
+    @Schema(description ="閫昏緫鍒犻櫎")
+    private Byte deleted;
+
+    @Schema(description = "琛ㄥ崟鏁版嵁")
+    private String formConfig;
+}
diff --git a/src/main/java/com/ruoyi/approve/pojo/ApprovalInstanceNode.java b/src/main/java/com/ruoyi/approve/pojo/ApprovalInstanceNode.java
new file mode 100644
index 0000000..621ce1f
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/ApprovalInstanceNode.java
@@ -0,0 +1,106 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 瀹℃壒鑺傜偣瀹炰緥琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:27:54
+ */
+@Getter
+@Setter
+@ToString
+@TableName("approval_instance_node")
+@ApiModel(value = "ApprovalInstanceNode瀵硅薄", description = "瀹℃壒鑺傜偣瀹炰緥琛�")
+public class ApprovalInstanceNode implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鑺傜偣瀹炰緥ID
+     */
+    @Schema(description ="鑺傜偣瀹炰緥ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 瀹℃壒瀹炰緥ID
+     */
+    @Schema(description ="瀹℃壒瀹炰緥ID")
+    private Long instanceId;
+
+    /**
+     * 瀹℃壒绾у埆
+     */
+    @Schema(description ="瀹℃壒绾у埆")
+    private Integer levelNo;
+
+    /**
+     * 瀹℃壒绫诲瀷
+     */
+    @Schema(description ="瀹℃壒绫诲瀷")
+    private String approveType;
+
+    /**
+     * 鑺傜偣鐘舵��
+     */
+    @Schema(description ="鑺傜偣鐘舵�� PENDING - 寰呭鐞� APPROVED - 宸查�氳繃 REJECTED - 宸查┏鍥�")
+    private String status;
+
+    /**
+     * 寮�濮嬫椂闂�
+     */
+    @Schema(description ="寮�濮嬫椂闂�")
+    private LocalDateTime startTime;
+
+    /**
+     * 瀹屾垚鏃堕棿
+     */
+    @Schema(description ="瀹屾垚鏃堕棿")
+    private LocalDateTime finishTime;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @Schema(description ="鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @Schema(description ="鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    @Schema(description ="鏇存柊浜�")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Long updateUser;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @Schema(description ="鏇存柊鏃堕棿")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    /**
+     * 閫昏緫鍒犻櫎
+     */
+    @Schema(description ="閫昏緫鍒犻櫎")
+    private Byte deleted;
+}
diff --git a/src/main/java/com/ruoyi/approve/pojo/ApprovalRecord.java b/src/main/java/com/ruoyi/approve/pojo/ApprovalRecord.java
new file mode 100644
index 0000000..21bc52b
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/ApprovalRecord.java
@@ -0,0 +1,98 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 瀹℃壒璁板綍琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:28:21
+ */
+@Getter
+@Setter
+@ToString
+@TableName("approval_record")
+@ApiModel(value = "ApprovalRecord瀵硅薄", description = "瀹℃壒璁板綍琛�")
+public class ApprovalRecord implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 瀹℃壒璁板綍ID
+     */
+    @Schema(description ="瀹℃壒璁板綍ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 瀹℃壒瀹炰緥ID
+     */
+    @Schema(description ="瀹℃壒瀹炰緥ID")
+    private Long instanceId;
+
+    /**
+     * 鑺傜偣瀹炰緥ID
+     */
+    @Schema(description ="鑺傜偣瀹炰緥ID")
+    private Long nodeId;
+
+    /**
+     * 瀹℃壒浠诲姟ID
+     */
+    @Schema(description ="瀹℃壒浠诲姟ID")
+    private Long taskId;
+
+    /**
+     * 鎿嶄綔浜篒D
+     */
+    @Schema(description ="鎿嶄綔浜篒D")
+    private Long operatorId;
+
+    /**
+     * 鎿嶄綔浜哄悕绉�
+     */
+    @Schema(description ="鎿嶄綔浜哄悕绉�")
+    private String operatorName;
+
+    /**
+     * 鎿嶄綔绫诲瀷
+     */
+    @Schema(description ="鎿嶄綔绫诲瀷")
+    private String action;
+
+    /**
+     * 瀹℃壒鎰忚
+     */
+    @Schema(description ="瀹℃壒鎰忚")
+    private String comment;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @Schema(description ="鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @Schema(description ="鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    /**
+     * 閫昏緫鍒犻櫎
+     */
+    @Schema(description ="閫昏緫鍒犻櫎")
+    private Byte deleted;
+}
diff --git a/src/main/java/com/ruoyi/approve/pojo/ApprovalTask.java b/src/main/java/com/ruoyi/approve/pojo/ApprovalTask.java
new file mode 100644
index 0000000..d78c964
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/ApprovalTask.java
@@ -0,0 +1,128 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 瀹℃壒浠诲姟琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:32:37
+ */
+@Getter
+@Setter
+@ToString
+@TableName("approval_task")
+@ApiModel(value = "ApprovalTask瀵硅薄", description = "瀹℃壒浠诲姟琛�")
+public class ApprovalTask implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 瀹℃壒浠诲姟ID
+     */
+    @Schema(description ="瀹℃壒浠诲姟ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 瀹℃壒瀹炰緥ID
+     */
+    @Schema(description ="瀹℃壒瀹炰緥ID")
+    private Long instanceId;
+
+    /**
+     * 鑺傜偣瀹炰緥ID
+     */
+    @Schema(description ="鑺傜偣瀹炰緥ID")
+    private Long nodeId;
+
+    /**
+     * 瀹℃壒绾у埆
+     */
+    @Schema(description ="瀹℃壒绾у埆")
+    private Integer levelNo;
+
+    /**
+     * 瀹℃壒浜篒D
+     */
+    @Schema(description ="瀹℃壒浜篒D")
+    private Long approverId;
+
+    /**
+     * 瀹℃壒浜哄悕绉�
+     */
+    @Schema(description ="瀹℃壒浜哄悕绉�")
+    private String approverName;
+
+    /**
+     * 浠诲姟鐘舵��
+     */
+    @Schema(description ="浠诲姟鐘舵�� PENDING - 寰呭鎵� APPROVED - 宸插悓鎰�  REJECTED - 宸叉嫆缁�")
+    private String taskStatus;
+
+    /**
+     * 瀹℃壒鏃堕棿
+     */
+    @Schema(description ="瀹℃壒鏃堕棿")
+    private LocalDateTime approveTime;
+
+    /**
+     * 瀹℃壒鎰忚
+     */
+    @Schema(description ="瀹℃壒鎰忚")
+    private String comment;
+
+    /**
+     * 鏄惁宸茶
+     */
+    @Schema(description ="鏄惁宸茶")
+    private Byte isRead;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @Schema(description ="鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @Schema(description ="鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    @Schema(description ="鏇存柊浜�")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Long updateUser;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @Schema(description ="鏇存柊鏃堕棿")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    /**
+     * 閫昏緫鍒犻櫎
+     */
+    @Schema(description ="閫昏緫鍒犻櫎")
+    private Byte deleted;
+
+    @Schema(description ="閮ㄩ棬ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
diff --git a/src/main/java/com/ruoyi/approve/pojo/ApprovalTemplate.java b/src/main/java/com/ruoyi/approve/pojo/ApprovalTemplate.java
new file mode 100644
index 0000000..272b945
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/ApprovalTemplate.java
@@ -0,0 +1,107 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:08
+ */
+@Getter
+@Setter
+@ToString
+@TableName("approval_template")
+@ApiModel(value = "ApprovalTemplate瀵硅薄", description = "瀹℃壒妯℃澘琛�")
+public class ApprovalTemplate implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 妯℃澘ID
+     */
+    @Schema(description ="妯℃澘ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 妯℃澘鍚嶇О
+     */
+    @Schema(description ="妯℃澘鍚嶇О")
+    private String templateName;
+
+    /**
+     * 鍚敤鐘舵�侊細1鍚敤锛�0鍋滅敤
+     */
+    @Schema(description ="鍚敤鐘舵�侊細1鍚敤锛�0鍋滅敤")
+    private Byte enabled;
+
+    /**
+     * 妯℃澘璇存槑
+     */
+    @Schema(description ="妯℃澘璇存槑")
+    private String description;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @Schema(description ="鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @Schema(description ="鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    @Schema(description ="鏇存柊浜�")
+    @TableField(fill = FieldFill.UPDATE)
+    private Long updateUser;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @Schema(description ="鏇存柊鏃堕棿")
+    @TableField(fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+
+    /**
+     * 閫昏緫鍒犻櫎锛�0鍚︼紝1鏄�
+     */
+    @Schema(description ="閫昏緫鍒犻櫎锛�0鍚︼紝1鏄�")
+    private Integer deleted;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    @Schema(description = "琛ㄥ崟閰嶇疆")
+    private String formConfig;
+
+    @Schema(description = "妯℃澘绫诲瀷锛�0绯荤粺鍐呯疆锛�1鑷畾涔�")
+    private Integer templateType;
+
+    @Schema(description = "涓氬姟绫诲瀷")
+    private Long businessType;
+
+}
diff --git a/src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNode.java b/src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNode.java
new file mode 100644
index 0000000..cd77198
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNode.java
@@ -0,0 +1,66 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鑺傜偣琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:19
+ */
+@Getter
+@Setter
+@ToString
+@TableName("approval_template_node")
+@ApiModel(value = "ApprovalTemplateNode瀵硅薄", description = "瀹℃壒妯℃澘鑺傜偣琛�")
+public class ApprovalTemplateNode implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鑺傜偣ID
+     */
+    @Schema(description ="鑺傜偣ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 瀹℃壒妯℃澘ID
+     */
+    @Schema(description ="瀹℃壒妯℃澘ID")
+    private Long templateId;
+
+    /**
+     * 瀹℃壒绾у埆锛屼粠1寮�濮�
+     */
+    @Schema(description ="瀹℃壒绾у埆锛屼粠1寮�濮�")
+    private Integer levelNo;
+
+    /**
+     * 瀹℃壒鏂瑰紡锛欰ND浼氱锛孫R鎴栫
+     */
+    @Schema(description ="瀹℃壒鏂瑰紡锛欰ND浼氱锛孫R鎴栫")
+    private String approveType;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.UPDATE)
+    private LocalDateTime updateTime;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
diff --git a/src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNodeApprover.java b/src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNodeApprover.java
new file mode 100644
index 0000000..4113bff
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNodeApprover.java
@@ -0,0 +1,76 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鑺傜偣瀹℃壒浜鸿〃
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:30
+ */
+@Getter
+@Setter
+@ToString
+@TableName("approval_template_node_approver")
+@ApiModel(value = "ApprovalTemplateNodeApprover瀵硅薄", description = "瀹℃壒妯℃澘鑺傜偣瀹℃壒浜鸿〃")
+public class ApprovalTemplateNodeApprover implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭ID
+     */
+    @Schema(description = "涓婚敭ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 瀹℃壒鑺傜偣ID
+     */
+    @Schema(description ="瀹℃壒鑺傜偣ID")
+    private Long nodeId;
+
+    /**
+     * 瀹℃壒妯℃澘ID
+     */
+    @Schema(description ="瀹℃壒妯℃澘ID")
+    private Long templateId;
+
+    /**
+     * 瀹℃壒浜篒D
+     */
+    @Schema(description ="瀹℃壒浜篒D")
+    private Long approverId;
+
+    /**
+     * 瀹℃壒浜哄悕绉板啑浣�
+     */
+    @Schema(description ="瀹℃壒浜哄悕绉板啑浣�")
+    private String approverName;
+
+    /**
+     * 瀹℃壒浜烘帓搴�
+     */
+    @Schema(description ="瀹℃壒浜烘帓搴�")
+    private Integer sortNo;
+
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+    @TableField(fill = FieldFill.INSERT)
+    private Long deleted ;
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+}
diff --git a/src/main/java/com/ruoyi/approve/pojo/FinReimbursement.java b/src/main/java/com/ruoyi/approve/pojo/FinReimbursement.java
new file mode 100644
index 0000000..7e5c81f
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/FinReimbursement.java
@@ -0,0 +1,209 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曚富琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:15
+ */
+@Getter
+@Setter
+@ToString
+@TableName("fin_reimbursement")
+@ApiModel(value = "FinReimbursement瀵硅薄", description = "鎶ラ攢鍗曚富琛�")
+public class FinReimbursement implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭ID
+     */
+    @Schema(description = "涓婚敭ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 鎶ラ攢鍗曞彿
+     */
+    @Schema(description = "鎶ラ攢鍗曞彿")
+    private String billNo;
+
+    /**
+     * 鎶ラ攢绫诲瀷锛�1-宸梾鎶ラ攢锛�2-璐圭敤鎶ラ攢
+     */
+    @Schema(description = "鎶ラ攢绫诲瀷锛�1-宸梾鎶ラ攢锛�2-璐圭敤鎶ラ攢")
+    private Byte reimbursementType;
+
+    /**
+     * 璐圭敤绫诲瀷锛氬樊鏃呰垂/鍔炲叕閲囪喘/涓氬姟鎷涘緟/浜ら�氳垂/閫氳璐�/鍏朵粬
+     */
+    @Schema(description = "璐圭敤绫诲瀷锛氬樊鏃呰垂/鍔炲叕閲囪喘/涓氬姟鎷涘緟/浜ら�氳垂/閫氳璐�/鍏朵粬")
+    private String expenseType;
+
+    /**
+     * 鐢宠浜篒D
+     */
+    @Schema(description = "鐢宠浜篒D")
+    private Long applicantId;
+
+    /**
+     * 鍛樺伐缂栧彿
+     */
+    @Schema(description = "鍛樺伐缂栧彿")
+    private String applicantCode;
+
+    /**
+     * 鍛樺伐濮撳悕
+     */
+    @Schema(description = "鍛樺伐濮撳悕")
+    private String applicantName;
+
+    /**
+     * 鐢宠閮ㄩ棬ID
+     */
+    @Schema(description = "鐢宠閮ㄩ棬ID")
+    private Long applicantDeptId;
+
+    /**
+     * 鐢宠閮ㄩ棬鍚嶇О
+     */
+    @Schema(description = "鐢宠閮ㄩ棬鍚嶇О")
+    private String applicantDeptName;
+
+    /**
+     * 鎶ラ攢鍘熷洜
+     */
+    @Schema(description = "鎶ラ攢鍘熷洜")
+    private String reason;
+
+    /**
+     * 鐢宠閲戦
+     */
+    @Schema(description = "鐢宠閲戦")
+    private BigDecimal applyAmount;
+
+    /**
+     * 鏄庣粏姹囨�婚噾棰�
+     */
+    @Schema(description = "鏄庣粏姹囨�婚噾棰�")
+    private BigDecimal detailTotalAmount;
+
+    /**
+     * 鏀舵浜�
+     */
+    @Schema(description = "鏀舵浜�")
+    private String payeeName;
+
+    /**
+     * 鏀舵璐﹀彿
+     */
+    @Schema(description = "鏀舵璐﹀彿")
+    private String payeeAccount;
+
+    /**
+     * 寮�鎴锋敮琛�
+     */
+    @Schema(description = "寮�鎴锋敮琛�")
+    private String payeeBank;
+
+    /**
+     * 瀹℃壒瀹炰緥ID锛屽搴� approval_instance.id
+     */
+    @Schema(description = "瀹℃壒瀹炰緥ID锛屽搴� approval_instance.id")
+    private Long approvalInstanceId;
+
+    /**
+     * 瀹℃壒娴佺▼ID锛屽搴� approve_process.id
+     */
+    @Schema(description = "瀹℃壒娴佺▼ID锛屽搴� approve_process.id")
+    private Long approveProcessId;
+
+    /**
+     * 鍗曟嵁鐘舵�侊細DRAFT-鑽夌锛孖N_APPROVAL-瀹℃壒涓紝APPROVED-瀹℃壒閫氳繃锛孯EJECTED-瀹℃壒椹冲洖锛學ITHDRAWN-宸叉挙鍥烇紝PAID-宸蹭粯娆�
+     */
+    @Schema(description = "鍗曟嵁鐘舵�侊細DRAFT-鑽夌锛孖N_APPROVAL-瀹℃壒涓紝APPROVED-瀹℃壒閫氳繃锛孯EJECTED-瀹℃壒椹冲洖锛學ITHDRAWN-宸叉挙鍥烇紝PAID-宸蹭粯娆�")
+    private String billStatus;
+
+    /**
+     * 瀹℃壒閫氳繃鏃堕棿
+     */
+    @Schema(description = "瀹℃壒閫氳繃鏃堕棿")
+    private LocalDateTime approvedTime;
+
+    /**
+     * 浠樻鏃堕棿
+     */
+    @Schema(description = "浠樻鏃堕棿")
+    private LocalDateTime paidTime;
+
+    /**
+     * 鐢熸垚鐨勮储鍔℃敮鍑鸿褰旾D锛屽搴� account_expense.id
+     */
+    @Schema(description = "鐢熸垚鐨勮储鍔℃敮鍑鸿褰旾D锛屽搴� account_expense.id")
+    private Long accountExpenseId;
+
+    /**
+     * 澶囨敞
+     */
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    /**
+     * 绉熸埛ID
+     */
+    @Schema(description = "绉熸埛ID")
+    private Long tenantId;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @Schema(description = "鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    @Schema(description = "鏇存柊浜�")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Long updateUser;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @Schema(description = "鏇存柊鏃堕棿")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    /**
+     * 褰掑睘閮ㄩ棬ID
+     */
+    @Schema(description = "褰掑睘閮ㄩ棬ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    /**
+     * 閫昏緫鍒犻櫎锛�0-鍚︼紝1-鏄�
+     */
+    @Schema(description = "閫昏緫鍒犻櫎锛�0-鍚︼紝1-鏄�")
+    private Byte deleted;
+}
diff --git a/src/main/java/com/ruoyi/approve/pojo/FinReimbursementDetail.java b/src/main/java/com/ruoyi/approve/pojo/FinReimbursementDetail.java
new file mode 100644
index 0000000..5d0220e
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/FinReimbursementDetail.java
@@ -0,0 +1,157 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曟槑缁嗚〃
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:38
+ */
+@Getter
+@Setter
+@ToString
+@TableName("fin_reimbursement_detail")
+@ApiModel(value = "FinReimbursementDetail瀵硅薄", description = "鎶ラ攢鍗曟槑缁嗚〃")
+public class FinReimbursementDetail implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭ID
+     */
+    @Schema(description = "涓婚敭ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 鎶ラ攢鍗旾D锛屽搴� fin_reimbursement.id
+     */
+    @Schema(description = "鎶ラ攢鍗旾D锛屽搴� fin_reimbursement.id")
+    private Long reimbursementId;
+
+    /**
+     * 鏄庣粏琛屽彿
+     */
+    @Schema(description = "鏄庣粏琛屽彿")
+    private Integer rowNo;
+
+    /**
+     * 鍙戠エ鏃ユ湡
+     */
+    @Schema(description = "鍙戠エ鏃ユ湡")
+    private LocalDate invoiceDate;
+
+    /**
+     * 璐圭敤绉戠洰
+     */
+    @Schema(description = "璐圭敤绉戠洰")
+    private String expenseCategory;
+
+    /**
+     * 閲戦
+     */
+    @Schema(description = "閲戦")
+    private BigDecimal amount;
+
+    /**
+     * 鎻忚堪
+     */
+    @Schema(description = "鎻忚堪")
+    private String description;
+
+    /**
+     * 鍙戠エ鍙风爜
+     */
+    @Schema(description = "鍙戠エ鍙风爜")
+    private String invoiceNo;
+
+    /**
+     * 鍙戠エ绫诲瀷
+     */
+    @Schema(description = "鍙戠エ绫诲瀷")
+    private String invoiceType;
+
+    /**
+     * 绁ㄩ潰閲戦
+     */
+    @Schema(description = "绁ㄩ潰閲戦")
+    private BigDecimal invoiceAmount;
+
+    /**
+     * 绋庣巼
+     */
+    @Schema(description = "绋庣巼")
+    private BigDecimal taxRate;
+
+    /**
+     * 绋庨
+     */
+    @Schema(description = "绋庨")
+    private BigDecimal taxAmount;
+
+    /**
+     * 澶囨敞
+     */
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    /**
+     * 绉熸埛ID
+     */
+    @Schema(description = "绉熸埛ID")
+    private Long tenantId;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @Schema(description = "鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    @Schema(description = "鏇存柊浜�")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Long updateUser;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @Schema(description = "鏇存柊鏃堕棿")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    /**
+     * 褰掑睘閮ㄩ棬ID
+     */
+    @Schema(description = "褰掑睘閮ㄩ棬ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    /**
+     * 閫昏緫鍒犻櫎锛�0-鍚︼紝1-鏄�
+     */
+    @Schema(description = "閫昏緫鍒犻櫎锛�0-鍚︼紝1-鏄�")
+    private Byte deleted;
+}
diff --git a/src/main/java/com/ruoyi/approve/pojo/FinReimbursementTravel.java b/src/main/java/com/ruoyi/approve/pojo/FinReimbursementTravel.java
new file mode 100644
index 0000000..10c607c
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/pojo/FinReimbursementTravel.java
@@ -0,0 +1,162 @@
+package com.ruoyi.approve.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 宸梾鎶ラ攢鎵╁睍琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:47
+ */
+@Getter
+@Setter
+@ToString
+@TableName("fin_reimbursement_travel")
+@ApiModel(value = "FinReimbursementTravel瀵硅薄", description = "宸梾鎶ラ攢鎵╁睍琛�")
+public class FinReimbursementTravel implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭ID
+     */
+    @Schema(description = "涓婚敭ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 鎶ラ攢鍗旾D锛屽搴� fin_reimbursement.id
+     */
+    @Schema(description = "鎶ラ攢鍗旾D锛屽搴� fin_reimbursement.id")
+    private Long reimbursementId;
+
+    /**
+     * 鍑哄樊寮�濮嬫椂闂�
+     */
+    @Schema(description = "鍑哄樊寮�濮嬫椂闂�")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime startTime;
+
+    /**
+     * 鍑哄樊缁撴潫鏃堕棿
+     */
+    @Schema(description = "鍑哄樊缁撴潫鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime endTime;
+
+    /**
+     * 鍑哄樊澶╂暟
+     */
+    @Schema(description = "鍑哄樊澶╂暟")
+    private BigDecimal travelDays;
+
+    /**
+     * 鍑哄樊鍦�/鍑哄彂鍩庡競
+     */
+    @Schema(description = "鍑哄樊鍦�/鍑哄彂鍩庡競")
+    private String departureCity;
+
+    /**
+     * 鐩殑鍦�/鐩殑鍩庡競
+     */
+    @Schema(description = "鐩殑鍦�/鐩殑鍩庡競")
+    private String destinationCity;
+
+    /**
+     * 閰掑簵鏍囧噯
+     */
+    @Schema(description = "閰掑簵鏍囧噯")
+    private BigDecimal hotelStandard;
+
+    /**
+     * 浣忓澶╂暟
+     */
+    @Schema(description = "浣忓澶╂暟")
+    private BigDecimal lodgingDays;
+
+    /**
+     * 鐢熸椿琛ヨ创
+     */
+    @Schema(description = "鐢熸椿琛ヨ创")
+    private BigDecimal mealAllowance;
+
+    /**
+     * 浜ら�氳ˉ璐�
+     */
+    @Schema(description = "浜ら�氳ˉ璐�")
+    private BigDecimal transportAllowance;
+
+    /**
+     * 浣忓闄愰
+     */
+    @Schema(description = "浣忓闄愰")
+    private BigDecimal lodgingLimit;
+
+    /**
+     * 鐗规壒鏍囪鏂囨湰锛屽鍦ㄦ爣鍑嗚寖鍥村唴/瓒呮爣鐗规壒
+     */
+    @Schema(description = "鐗规壒鏍囪鏂囨湰锛屽鍦ㄦ爣鍑嗚寖鍥村唴/瓒呮爣鐗规壒")
+    private String standardTag;
+
+    /**
+     * 鏄惁鍦ㄦ爣鍑嗗唴锛�1-鏄紝0-鍚�
+     */
+    @Schema(description = "鏄惁鍦ㄦ爣鍑嗗唴锛�1-鏄紝0-鍚�")
+    private Byte withinStandard;
+
+    /**
+     * 绉熸埛ID
+     */
+    @Schema(description = "绉熸埛ID")
+    private Long tenantId;
+
+    /**
+     * 鍒涘缓浜�
+     */
+    @Schema(description = "鍒涘缓浜�")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    /**
+     * 鏇存柊浜�
+     */
+    @Schema(description = "鏇存柊浜�")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Long updateUser;
+
+    /**
+     * 鏇存柊鏃堕棿
+     */
+    @Schema(description = "鏇存柊鏃堕棿")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    /**
+     * 褰掑睘閮ㄩ棬ID
+     */
+    @Schema(description = "褰掑睘閮ㄩ棬ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
diff --git a/src/main/java/com/ruoyi/approve/service/ApprovalInstanceNodeService.java b/src/main/java/com/ruoyi/approve/service/ApprovalInstanceNodeService.java
new file mode 100644
index 0000000..8fa7c60
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/ApprovalInstanceNodeService.java
@@ -0,0 +1,16 @@
+package com.ruoyi.approve.service;
+
+import com.ruoyi.approve.pojo.ApprovalInstanceNode;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 瀹℃壒鑺傜偣瀹炰緥琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:27:54
+ */
+public interface ApprovalInstanceNodeService extends IService<ApprovalInstanceNode> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java b/src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java
new file mode 100644
index 0000000..005795d
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java
@@ -0,0 +1,33 @@
+package com.ruoyi.approve.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
+import com.ruoyi.approve.bean.vo.ApprovalInstanceVo;
+import com.ruoyi.approve.pojo.ApprovalInstance;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.framework.web.domain.R;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 瀹℃壒瀹炰緥琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:27:46
+ */
+public interface ApprovalInstanceService extends IService<ApprovalInstance> {
+
+    R listPage(Page<ApprovalInstanceVo> page, ApprovalInstanceDto approvalInstanceDto);
+
+    Boolean add(ApprovalInstanceDto approvalInstanceDto);
+
+    Boolean update(ApprovalInstanceDto approvalInstanceDto);
+
+    Boolean delete(List<Long> ids);
+
+    R approve(ApprovalInstanceDto approvalInstanceDto);
+
+    R autoApprove(Long instanceId);
+}
diff --git a/src/main/java/com/ruoyi/approve/service/ApprovalRecordService.java b/src/main/java/com/ruoyi/approve/service/ApprovalRecordService.java
new file mode 100644
index 0000000..041571c
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/ApprovalRecordService.java
@@ -0,0 +1,16 @@
+package com.ruoyi.approve.service;
+
+import com.ruoyi.approve.pojo.ApprovalRecord;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 瀹℃壒璁板綍琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:28:21
+ */
+public interface ApprovalRecordService extends IService<ApprovalRecord> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/ApprovalTaskService.java b/src/main/java/com/ruoyi/approve/service/ApprovalTaskService.java
new file mode 100644
index 0000000..491e57d
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/ApprovalTaskService.java
@@ -0,0 +1,16 @@
+package com.ruoyi.approve.service;
+
+import com.ruoyi.approve.pojo.ApprovalTask;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 瀹℃壒浠诲姟琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:32:37
+ */
+public interface ApprovalTaskService extends IService<ApprovalTask> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeApproverService.java b/src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeApproverService.java
new file mode 100644
index 0000000..e335276
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeApproverService.java
@@ -0,0 +1,16 @@
+package com.ruoyi.approve.service;
+
+import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鑺傜偣瀹℃壒浜鸿〃 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:30
+ */
+public interface ApprovalTemplateNodeApproverService extends IService<ApprovalTemplateNodeApprover> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeService.java b/src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeService.java
new file mode 100644
index 0000000..4a8d6a8
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeService.java
@@ -0,0 +1,20 @@
+package com.ruoyi.approve.service;
+
+import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeDto;
+import com.ruoyi.approve.pojo.ApprovalTemplateNode;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鑺傜偣琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:19
+ */
+public interface ApprovalTemplateNodeService extends IService<ApprovalTemplateNode> {
+
+    Boolean saveApprovalTemplateNode(Long id, List<ApprovalTemplateNodeDto> nodes);
+}
diff --git a/src/main/java/com/ruoyi/approve/service/ApprovalTemplateService.java b/src/main/java/com/ruoyi/approve/service/ApprovalTemplateService.java
new file mode 100644
index 0000000..38bbabc
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/ApprovalTemplateService.java
@@ -0,0 +1,34 @@
+package com.ruoyi.approve.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.approve.bean.dto.ApprovalTemplateDto;
+import com.ruoyi.approve.bean.vo.ApprovalTemplateVo;
+import com.ruoyi.approve.pojo.ApprovalTemplate;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:08
+ */
+public interface ApprovalTemplateService extends IService<ApprovalTemplate> {
+
+    IPage<ApprovalTemplateVo> listPage(Page<ApprovalTemplateVo> page, ApprovalTemplateDto approvalTemplateDto);
+
+    Boolean saveApprovalTemplateDto(ApprovalTemplateDto approvalTemplateDto);
+
+    Boolean updateApprovalTemplateDto(ApprovalTemplateDto approvalTemplateDto);
+
+    Boolean delete(List<Long> ids);
+
+    List<ApprovalTemplateVo> listApprovalTemplateVo(Integer type);
+
+    ApprovalTemplateVo getApprovalTemplateVoById(Long id);
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/FinReimbursementDetailService.java b/src/main/java/com/ruoyi/approve/service/FinReimbursementDetailService.java
new file mode 100644
index 0000000..0279c25
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/FinReimbursementDetailService.java
@@ -0,0 +1,16 @@
+package com.ruoyi.approve.service;
+
+import com.ruoyi.approve.pojo.FinReimbursementDetail;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曟槑缁嗚〃 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:38
+ */
+public interface FinReimbursementDetailService extends IService<FinReimbursementDetail> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/FinReimbursementService.java b/src/main/java/com/ruoyi/approve/service/FinReimbursementService.java
new file mode 100644
index 0000000..c14b943
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/FinReimbursementService.java
@@ -0,0 +1,31 @@
+package com.ruoyi.approve.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.approve.bean.dto.FinReimbursementDto;
+import com.ruoyi.approve.bean.vo.FinReimbursementVo;
+import com.ruoyi.approve.pojo.FinReimbursement;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曚富琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:15
+ */
+public interface FinReimbursementService extends IService<FinReimbursement> {
+
+    IPage<FinReimbursementVo> listPage(FinReimbursementDto finReimbursementDto, Page<FinReimbursementVo> page);
+
+    Boolean add(FinReimbursementDto finReimbursementDto);
+
+    FinReimbursementVo detail(Long id);
+
+    Boolean update(FinReimbursementDto finReimbursementDto);
+
+    Boolean delete(List<Long> ids);
+}
diff --git a/src/main/java/com/ruoyi/approve/service/FinReimbursementTravelService.java b/src/main/java/com/ruoyi/approve/service/FinReimbursementTravelService.java
new file mode 100644
index 0000000..0733d1b
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/FinReimbursementTravelService.java
@@ -0,0 +1,16 @@
+package com.ruoyi.approve.service;
+
+import com.ruoyi.approve.pojo.FinReimbursementTravel;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 宸梾鎶ラ攢鎵╁睍琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:47
+ */
+public interface FinReimbursementTravelService extends IService<FinReimbursementTravel> {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceNodeServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceNodeServiceImpl.java
new file mode 100644
index 0000000..9ea1963
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceNodeServiceImpl.java
@@ -0,0 +1,20 @@
+package com.ruoyi.approve.service.impl;
+
+import com.ruoyi.approve.pojo.ApprovalInstanceNode;
+import com.ruoyi.approve.mapper.ApprovalInstanceNodeMapper;
+import com.ruoyi.approve.service.ApprovalInstanceNodeService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 瀹℃壒鑺傜偣瀹炰緥琛� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:27:54
+ */
+@Service
+public class ApprovalInstanceNodeServiceImpl extends ServiceImpl<ApprovalInstanceNodeMapper, ApprovalInstanceNode> implements ApprovalInstanceNodeService {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
new file mode 100644
index 0000000..5c5e540
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
@@ -0,0 +1,846 @@
+package com.ruoyi.approve.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
+import com.ruoyi.approve.bean.vo.ApprovalInstanceVo;
+import com.ruoyi.approve.mapper.ApprovalInstanceMapper;
+import com.ruoyi.approve.mapper.ApprovalTemplateNodeApproverMapper;
+import com.ruoyi.approve.mapper.FinReimbursementMapper;
+import com.ruoyi.approve.pojo.*;
+import com.ruoyi.approve.service.*;
+import com.ruoyi.approve.utils.ApproveProcessConfigNodeUtils;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.utils.FileUtil;
+import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsMapper;
+import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsScopeDeptMapper;
+import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsScopeUserMapper;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser;
+import com.ruoyi.common.enums.*;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.OrderUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.framework.security.LoginUser;
+import com.ruoyi.framework.web.domain.R;
+import com.ruoyi.procurementrecord.utils.StockUtils;
+import com.ruoyi.project.system.domain.SysDept;
+import com.ruoyi.project.system.domain.SysUser;
+import com.ruoyi.project.system.mapper.SysDeptMapper;
+import com.ruoyi.project.system.mapper.SysUserDeptMapper;
+import com.ruoyi.project.system.mapper.SysUserMapper;
+import com.ruoyi.project.system.service.ISysNoticeService;
+import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
+import com.ruoyi.purchase.pojo.PurchaseLedger;
+import com.ruoyi.quality.utils.QualityInspectHelper;
+import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
+import com.ruoyi.sales.mapper.SalesQuotationMapper;
+import com.ruoyi.sales.mapper.ShippingInfoMapper;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import com.ruoyi.sales.pojo.SalesQuotation;
+import com.ruoyi.sales.pojo.ShippingInfo;
+import com.ruoyi.staff.mapper.HolidayApplicationMapper;
+import com.ruoyi.staff.pojo.HolidayApplication;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 瀹℃壒瀹炰緥鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @since 2026-05-18 03:27:46
+ */
+@Service
+@RequiredArgsConstructor
+public class ApprovalInstanceServiceImpl extends ServiceImpl<ApprovalInstanceMapper, ApprovalInstance> implements ApprovalInstanceService {
+
+    private static final String ENTERPRISE_NEWS_STATUS_PUBLISHED = "PUBLISHED";
+    private static final String ENTERPRISE_NEWS_STATUS_REJECTED = "REJECTED";
+
+    private final ApprovalInstanceMapper approvalInstanceMapper;
+    private final ApproveProcessConfigNodeUtils approveProcessConfigNodeUtils;
+    private final ApprovalInstanceNodeService approvalInstanceNodeService;
+    private final ApprovalTaskService approvalTaskService;
+    private final ApprovalRecordService approvalRecordService;
+    private final ApprovalTemplateNodeService approvalTemplateNodeService;
+    private final FinReimbursementMapper finReimbursementMapper;
+    private final FileUtil fileUtil;
+    private final ISysNoticeService sysNoticeService;
+    private final PurchaseLedgerMapper purchaseLedgerMapper;
+    private final SalesLedgerProductMapper salesLedgerProductMapper;
+    private final StockUtils stockUtils;
+    private final SalesQuotationMapper salesQuotationMapper;
+    private final ShippingInfoMapper shippingInfoMapper;
+    private final QualityInspectHelper qualityInspectHelper;
+    private final EnterpriseNewsScopeUserMapper enterpriseNewsScopeUserMapper;
+    private final SysUserMapper sysUserMapper;
+    private final SysUserDeptMapper sysUserDeptMapper;
+    private final SysDeptMapper sysDeptMapper;
+    private final HolidayApplicationMapper holidayApplicationMapper;
+    private final EnterpriseNewsMapper enterpriseNewsMapper;
+    private final EnterpriseNewsScopeDeptMapper enterpriseNewsScopeDeptMapper;
+    private final ApprovalTemplateNodeApproverMapper approvalTemplateNodeApproverMapper;
+
+    @Override
+    public R listPage(Page<ApprovalInstanceVo> page, ApprovalInstanceDto approvalInstanceDto) {
+        IPage<ApprovalInstanceVo> approvalInstanceVoIPage = approvalInstanceMapper.listPage(page, approvalInstanceDto);
+
+        List<ApprovalInstanceVo> records = approvalInstanceVoIPage.getRecords();
+        if (records == null || records.isEmpty()) {
+            return R.ok(approvalInstanceVoIPage);
+        }
+        records.forEach(vo -> {
+            vo.setBusinessName(TypeEnums.getLabelByValue(vo.getBusinessType()));
+        });
+        Long currentUserId = SecurityUtils.getUserId();
+
+        List<Long> instanceIds = records.stream()
+                .map(ApprovalInstanceVo::getId)
+                .filter(id -> id != null)
+                .distinct()
+                .collect(Collectors.toList());
+
+        if (!instanceIds.isEmpty()) {
+            Map<Long, List<ApprovalRecord>> recordMap = approvalRecordService.list(
+                    Wrappers.<ApprovalRecord>lambdaQuery()
+                            .in(ApprovalRecord::getInstanceId, instanceIds)
+                            .eq(ApprovalRecord::getDeleted, 0)
+            ).stream().collect(Collectors.groupingBy(ApprovalRecord::getInstanceId));
+            Map<Long, List<ApprovalTask>> taskMap = approvalTaskService.list(
+                    Wrappers.<ApprovalTask>lambdaQuery()
+                            .in(ApprovalTask::getInstanceId, instanceIds)
+                            .eq(ApprovalTask::getDeleted, 0)
+            ).stream().collect(Collectors.groupingBy(ApprovalTask::getInstanceId));
+
+            for (ApprovalInstanceVo vo : records) {
+                vo.setIsApprove(approveProcessConfigNodeUtils.isCurrentApprover(vo.getId(), currentUserId));
+                vo.setRecords(recordMap.getOrDefault(vo.getId(), new ArrayList<>()));
+                vo.setTasks(taskMap.getOrDefault(vo.getId(), new ArrayList<>()));
+                vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.APPROVAL_INSTANCE, vo.getId()));
+            }
+
+        }
+        return R.ok(approvalInstanceVoIPage);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean add(ApprovalInstanceDto approvalInstanceDto) {
+        String instanceNo = OrderUtils.countTodayByCreateTime(approvalInstanceMapper, "SP", "instance_no", approvalInstanceDto.getCreateTime() != null ? approvalInstanceDto.getCreateTime() : LocalDateTime.now());
+        approvalInstanceDto.setInstanceNo(instanceNo);
+        approvalInstanceDto.setStatus("PENDING");
+        approvalInstanceDto.setCurrentLevel(1);
+        boolean saved = this.save(approvalInstanceDto);
+        if (!saved) {
+            return false;
+        }
+        approveProcessConfigNodeUtils.createCurrentNodeAndTasks(approvalInstanceDto);
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVAL_INSTANCE, approvalInstanceDto.getId(), approvalInstanceDto.getStorageBlobDTOs());
+        sendApproveNotice(approvalInstanceDto, approveProcessConfigNodeUtils.getCurrentPendingTasks(approvalInstanceDto.getId()));
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean update(ApprovalInstanceDto approvalInstanceDto) {
+        if (approvalInstanceDto == null || approvalInstanceDto.getId() == null) {
+            return false;
+        }
+        // 鍒ゆ柇鏄惁鏈夋鍦ㄨ繘琛岀殑瀹℃壒浠诲姟锛屾湁鍒欎笉鍏佽淇敼
+        long pendingTaskCount = approvalTaskService.count(
+                Wrappers.<ApprovalTask>lambdaQuery()
+                        .eq(ApprovalTask::getInstanceId, approvalInstanceDto.getId())
+                        .eq(ApprovalTask::getTaskStatus, "PENDING")
+                        .eq(ApprovalTask::getDeleted, 0)
+        );
+        if (pendingTaskCount > 0) {
+            throw new ServiceException("璇ュ鎵瑰崟鏈夋鍦ㄨ繘琛岀殑瀹℃壒浠诲姟锛屼笉鍏佽淇敼");
+        }
+        boolean updated = this.updateById(approvalInstanceDto);
+        if (!updated) {
+            return false;
+        }
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVAL_INSTANCE, approvalInstanceDto.getId(), approvalInstanceDto.getStorageBlobDTOs());
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean delete(List<Long> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return false;
+        }
+        fileUtil.deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVAL_INSTANCE, ids);
+
+        int instanceRows = approvalInstanceMapper.update(
+                null,
+                Wrappers.<ApprovalInstance>lambdaUpdate()
+                        .in(ApprovalInstance::getId, ids)
+                        .eq(ApprovalInstance::getDeleted, 0)
+                        .set(ApprovalInstance::getDeleted, (byte) 1)
+        );
+
+        LambdaUpdateWrapper<ApprovalInstanceNode> nodeUpdateWrapper = Wrappers.lambdaUpdate();
+        nodeUpdateWrapper.in(ApprovalInstanceNode::getInstanceId, ids)
+                .eq(ApprovalInstanceNode::getDeleted, 0)
+                .set(ApprovalInstanceNode::getDeleted, (byte) 1);
+        approvalInstanceNodeService.update(nodeUpdateWrapper);
+
+        LambdaUpdateWrapper<ApprovalTask> taskUpdateWrapper = Wrappers.lambdaUpdate();
+        taskUpdateWrapper.in(ApprovalTask::getInstanceId, ids)
+                .eq(ApprovalTask::getDeleted, 0)
+                .set(ApprovalTask::getDeleted, (byte) 1);
+        approvalTaskService.update(taskUpdateWrapper);
+
+        LambdaUpdateWrapper<ApprovalRecord> recordUpdateWrapper = Wrappers.lambdaUpdate();
+        recordUpdateWrapper.in(ApprovalRecord::getInstanceId, ids)
+                .eq(ApprovalRecord::getDeleted, 0)
+                .set(ApprovalRecord::getDeleted, (byte) 1);
+        approvalRecordService.update(recordUpdateWrapper);
+
+        return instanceRows > 0;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R approve(ApprovalInstanceDto approvalInstanceDto) {
+        if (approvalInstanceDto == null || approvalInstanceDto.getId() == null) {
+            return R.fail("瀹℃壒瀹炰緥 ID 涓嶈兘涓虹┖");
+        }
+        String approveAction = normalizeApproveAction(approvalInstanceDto.getApproveAction());
+        if (approveAction == null) {
+            return R.fail("瀹℃壒鍔ㄤ綔鍙敮鎸� APPROVED 鎴� REJECTED");
+        }
+
+        ApprovalInstance instance = getPendingApprovalInstance(approvalInstanceDto.getId());
+        if (instance == null) {
+            return R.fail("瀹℃壒瀹炰緥涓嶅瓨鍦�");
+        }
+
+        ApprovalInstanceNode currentNode = approveProcessConfigNodeUtils.getCurrentNode(instance.getId());
+        if (currentNode == null) {
+            return R.fail("褰撳墠娌℃湁寰呭鐞嗙殑瀹℃壒鑺傜偣");
+        }
+
+        Long currentUserId = SecurityUtils.getUserId();
+        ApprovalTask currentTask = approveProcessConfigNodeUtils.getCurrentUserTask(instance.getId(), currentUserId);
+        if (currentTask == null) {
+            return R.fail("褰撳墠鐢ㄦ埛娌℃湁鍙鎵逛换鍔�");
+        }
+
+        LoginUser loginUser = SecurityUtils.getLoginUser();
+        String operatorName = loginUser.getUser() != null ? loginUser.getUser().getNickName() : SecurityUtils.getUsername();
+        LocalDateTime now = LocalDateTime.now();
+
+        if (!updateCurrentTask(approvalInstanceDto, approveAction, currentTask, now)) {
+            return R.fail("褰撳墠浠诲姟宸茶澶勭悊锛岃鍒锋柊鍚庨噸璇�");
+        }
+
+        saveApprovalRecord(
+                instance.getId(),
+                currentNode.getId(),
+                currentTask.getId(),
+                currentUserId,
+                operatorName,
+                approveAction,
+                approvalInstanceDto.getApproveComment()
+        );
+        //瀹℃壒鎷掔粷鐨勫鐞�
+        if ("REJECTED".equals(approveAction)) {
+            return rejectCurrentNode(instance, currentNode, now);
+        }
+        if (!approveProcessConfigNodeUtils.canProceedToNextLevel(instance.getId(), currentNode.getApproveType())) {
+            return R.ok("瀹℃壒鎴愬姛锛岀瓑寰呭叾浠栧鎵逛汉澶勭悊");
+        }
+
+        return approveAndMoveNext(instance, currentNode, approvalInstanceDto, now);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R autoApprove(Long instanceId) {
+        if (instanceId == null) {
+            return R.fail("瀹℃壒瀹炰緥 ID 涓嶈兘涓虹┖");
+        }
+
+        ApprovalInstance instance = getPendingApprovalInstance(instanceId);
+        if (instance == null) {
+            return R.fail("瀹℃壒瀹炰緥涓嶅瓨鍦�");
+        }
+        if ("REJECTED".equals(instance.getStatus())) {
+            return R.fail("瀹℃壒宸查┏鍥烇紝鏃犳硶鑷姩閫氳繃");
+        }
+        if ("APPROVED".equals(instance.getStatus())) {
+            return R.ok("瀹℃壒宸插畬鎴�");
+        }
+
+        ApprovalInstanceDto autoApproveDto = new ApprovalInstanceDto();
+        autoApproveDto.setId(instanceId);
+        autoApproveDto.setApproveComment("绯荤粺鑷姩瀹℃壒");
+
+        int loopCount = 0;
+        while (loopCount++ < 20) {
+            ApprovalInstance currentInstance = getPendingApprovalInstance(instanceId);
+            if (currentInstance == null) {
+                return R.fail("瀹℃壒瀹炰緥涓嶅瓨鍦�");
+            }
+            if ("APPROVED".equals(currentInstance.getStatus())) {
+                return R.ok("瀹℃壒宸插畬鎴�");
+            }
+            if ("REJECTED".equals(currentInstance.getStatus())) {
+                return R.fail("瀹℃壒宸查┏鍥烇紝鏃犳硶鑷姩閫氳繃");
+            }
+
+            ApprovalInstanceNode currentNode = approveProcessConfigNodeUtils.getCurrentNode(currentInstance.getId());
+            if (currentNode == null) {
+                currentInstance.setStatus("APPROVED");
+                currentInstance.setFinishTime(LocalDateTime.now());
+                this.updateById(currentInstance);
+                handleBusinessAfterApprovalFinished(currentInstance);
+                return R.ok("瀹℃壒宸插畬鎴�");
+            }
+
+            List<ApprovalTask> pendingTasks = approvalTaskService.list(
+                    Wrappers.<ApprovalTask>lambdaQuery()
+                            .eq(ApprovalTask::getInstanceId, currentInstance.getId())
+                            .eq(ApprovalTask::getNodeId, currentNode.getId())
+                            .eq(ApprovalTask::getTaskStatus, "PENDING")
+                            .eq(ApprovalTask::getDeleted, 0)
+            );
+
+            LocalDateTime now = LocalDateTime.now();
+            for (ApprovalTask currentTask : pendingTasks) {
+                if (!updateCurrentTask(autoApproveDto, "APPROVED", currentTask, now)) {
+                    return R.fail("褰撳墠浠诲姟宸茶澶勭悊锛岃鍒锋柊鍚庨噸璇�");
+                }
+                saveApprovalRecord(
+                        currentInstance.getId(),
+                        currentNode.getId(),
+                        currentTask.getId(),
+                        0L,
+                        "绯荤粺鑷姩瀹℃壒",
+                        "APPROVED",
+                        autoApproveDto.getApproveComment()
+                );
+            }
+
+            if (!approveProcessConfigNodeUtils.canProceedToNextLevel(currentInstance.getId(), currentNode.getApproveType())) {
+                return R.ok("瀹℃壒鎴愬姛锛岀瓑寰呭叾浠栧鎵逛汉澶勭悊");
+            }
+
+            R moveResult = moveToNextLevel(currentInstance, currentNode, autoApproveDto, now, false);
+            if (!R.isSuccess(moveResult)) {
+                return moveResult;
+            }
+        }
+
+        return R.fail("鑷姩瀹℃壒寰幆娆℃暟瓒呴檺");
+    }
+
+    private String normalizeApproveAction(String approveAction) {
+        if (!StringUtils.hasText(approveAction)) {
+            return null;
+        }
+        String normalizedAction = approveAction.trim().toUpperCase(Locale.ROOT);
+        return "APPROVED".equals(normalizedAction) || "REJECTED".equals(normalizedAction)
+                ? normalizedAction
+                : null;
+    }
+
+    private ApprovalInstance getPendingApprovalInstance(Long instanceId) {
+        return this.getOne(
+                new LambdaQueryWrapper<ApprovalInstance>()
+                        .eq(ApprovalInstance::getId, instanceId)
+                        .eq(ApprovalInstance::getDeleted, 0)
+                        .last("LIMIT 1")
+        );
+    }
+
+    private boolean updateCurrentTask(ApprovalInstanceDto approvalInstanceDto,
+                                      String approveAction,
+                                      ApprovalTask currentTask,
+                                      LocalDateTime now) {
+        // 浠呭厑璁稿緟瀹℃壒浠诲姟琚垚鍔熷鐞嗕竴娆★紝閬垮厤骞跺彂涓嬮噸澶嶅鎵规垚鍔熴��
+        return approvalTaskService.update(
+                Wrappers.<ApprovalTask>lambdaUpdate()
+                        .eq(ApprovalTask::getId, currentTask.getId())
+                        .eq(ApprovalTask::getTaskStatus, "PENDING")
+                        .eq(ApprovalTask::getDeleted, 0)
+                        .set(ApprovalTask::getTaskStatus, approveAction)
+                        .set(ApprovalTask::getComment, approvalInstanceDto.getApproveComment())
+                        .set(ApprovalTask::getApproveTime, now)
+                        .set(ApprovalTask::getIsRead, (byte) 1)
+        );
+    }
+
+    private R rejectCurrentNode(ApprovalInstance instance, ApprovalInstanceNode currentNode, LocalDateTime now) {
+        if (!updateCurrentNodeStatus(currentNode.getId(), "REJECTED", now)) {
+            return R.ok("褰撳墠鑺傜偣宸插鐞嗗畬鎴�");
+        }
+
+        closePendingTasks(instance.getId(), currentNode.getId());
+        instance.setStatus("REJECTED");
+        instance.setFinishTime(now);
+        this.updateById(instance);
+        // 椹冲洖瀵瑰簲鐨勪紒涓氭柊闂伙紝 宸梾鎶ラ攢
+        if (instance.getBusinessType().equals(TypeEnums.ENTERPRISE_NEWS_APPROVAL.getCode())) {
+            enterpriseNewsMapper.update(
+                    new LambdaUpdateWrapper<EnterpriseNews>()
+                            .eq(EnterpriseNews::getId, instance.getBusinessId())
+                            .set(EnterpriseNews::getStatus, "REJECTED")
+            );
+        }else if (instance.getBusinessType().equals(TypeEnums.TRAVEL_REIMBURSEMENT_APPROVAL.getCode())||instance.getBusinessType().equals(TypeEnums.EXPENSE_APPROVAL.getCode())) {
+            finReimbursementMapper.update(
+                    new LambdaUpdateWrapper<FinReimbursement>()
+                            .eq(FinReimbursement::getId, instance.getBusinessId())
+                            .set(FinReimbursement::getBillStatus, "REJECTED")
+            );
+        }
+        return R.ok("瀹℃壒宸查┏鍥�");
+    }
+
+    private R approveAndMoveNext(ApprovalInstance instance,
+                                 ApprovalInstanceNode currentNode,
+                                 ApprovalInstanceDto approvalInstanceDto,
+                                 LocalDateTime now) {
+        return moveToNextLevel(instance, currentNode, approvalInstanceDto, now, true);
+    }
+
+    private R moveToNextLevel(ApprovalInstance instance,
+                              ApprovalInstanceNode currentNode,
+                              ApprovalInstanceDto approvalInstanceDto,
+                              LocalDateTime now,
+                              boolean notifyNextNode) {
+        if (!updateCurrentNodeStatus(currentNode.getId(), "APPROVED", now)) {
+            return R.ok("褰撳墠鑺傜偣宸插鐞嗗畬鎴�");
+        }
+
+        closePendingTasks(instance.getId(), currentNode.getId());
+
+        int nextLevel = currentNode.getLevelNo() + 1;
+        ApprovalInstanceNode nextInstanceNode = approvalInstanceNodeService.getOne(
+                new LambdaQueryWrapper<ApprovalInstanceNode>()
+                        .eq(ApprovalInstanceNode::getInstanceId, instance.getId())
+                        .eq(ApprovalInstanceNode::getLevelNo, nextLevel)
+                        .eq(ApprovalInstanceNode::getDeleted, 0)
+                        .orderByAsc(ApprovalInstanceNode::getId)
+                        .last("LIMIT 1")
+        );
+
+        if (nextInstanceNode != null) {
+            if (!activateNextInstanceNode(nextInstanceNode.getId(), now)) {
+                return R.ok("涓嬩竴瀹℃壒鑺傜偣宸茶婵�娲伙紝璇峰埛鏂板悗閲嶈瘯");
+            }
+            instance.setCurrentLevel(nextLevel);
+            instance.setStatus("PENDING");
+            this.updateById(instance);
+            if (notifyNextNode) {
+                List<ApprovalTask> nextTasks = approvalTaskService.list(
+                        Wrappers.<ApprovalTask>lambdaQuery()
+                                .eq(ApprovalTask::getInstanceId, instance.getId())
+                                .eq(ApprovalTask::getNodeId, nextInstanceNode.getId())
+                                .eq(ApprovalTask::getTaskStatus, "PENDING")
+                                .eq(ApprovalTask::getDeleted, 0)
+                );
+                sendApproveNotice(instance, nextTasks);
+            }
+            return R.ok("瀹℃壒鎴愬姛锛屽凡娴佽浆鍒颁笅涓�鑺傜偣");
+        }
+
+        ApprovalTemplateNode nextTemplateNode = approvalTemplateNodeService.getOne(
+                new LambdaQueryWrapper<ApprovalTemplateNode>()
+                        .eq(ApprovalTemplateNode::getTemplateId, instance.getTemplateId())
+                        .eq(ApprovalTemplateNode::getLevelNo, nextLevel)
+                        .orderByAsc(ApprovalTemplateNode::getId)
+                        .last("LIMIT 1")
+        );
+
+        if (nextTemplateNode == null) {
+            instance.setStatus("APPROVED");
+            instance.setFinishTime(now);
+            this.updateById(instance);
+            handleBusinessAfterApprovalFinished(instance);
+            return R.ok("瀹℃壒宸插畬鎴�");
+        }
+
+        instance.setCurrentLevel(nextLevel);
+        instance.setStatus("PENDING");
+        this.updateById(instance);
+        approveProcessConfigNodeUtils.createCurrentNodeAndTasks(instance, false);
+        if (notifyNextNode) {
+            sendApproveNotice(instance, approveProcessConfigNodeUtils.getCurrentPendingTasks(approvalInstanceDto.getId()));
+        }
+        return R.ok("瀹℃壒鎴愬姛锛屽凡娴佽浆鍒颁笅涓�鑺傜偣");
+    }
+
+    private boolean activateNextInstanceNode(Long nodeId, LocalDateTime now) {
+        return approvalInstanceNodeService.update(
+                Wrappers.<ApprovalInstanceNode>lambdaUpdate()
+                        .eq(ApprovalInstanceNode::getId, nodeId)
+                        .eq(ApprovalInstanceNode::getStatus, "WAITING")
+                        .eq(ApprovalInstanceNode::getDeleted, 0)
+                        .set(ApprovalInstanceNode::getStatus, "PENDING")
+                        .set(ApprovalInstanceNode::getStartTime, now)
+        );
+    }
+
+    private boolean updateCurrentNodeStatus(Long nodeId, String targetStatus, LocalDateTime now) {
+        // 浠呭厑璁镐竴涓姹傚皢褰撳墠鑺傜偣浠庡緟澶勭悊鎺ㄨ繘鍒扮洰鏍囩姸鎬侊紝閬垮厤閲嶅娴佽浆銆�
+        return approvalInstanceNodeService.update(
+                Wrappers.<ApprovalInstanceNode>lambdaUpdate()
+                        .eq(ApprovalInstanceNode::getId, nodeId)
+                        .eq(ApprovalInstanceNode::getStatus, "PENDING")
+                        .eq(ApprovalInstanceNode::getDeleted, 0)
+                        .set(ApprovalInstanceNode::getStatus, targetStatus)
+                        .set(ApprovalInstanceNode::getFinishTime, now)
+        );
+    }
+
+    private void handleBusinessAfterApprovalFinished(ApprovalInstance instance) {
+        String status = instance.getStatus();
+        Long businessType = instance.getBusinessType();
+        if (TypeEnums.PURCHASE_APPROVAL.getCode().equals(businessType)) {
+            handlePurchaseApprovalFinished(instance, status);
+            return;
+        }
+        if (TypeEnums.QUOTATION_APPROVAL.getCode().equals(businessType)) {
+            handleSalesQuotationApprovalFinished(instance, status);
+            return;
+        }
+        if (TypeEnums.SHIPPING_APPROVAL.getCode().equals(businessType)) {
+            handleShippingApprovalFinished(instance, status);
+            return;
+        }
+        if (TypeEnums.TRAVEL_REIMBURSEMENT_APPROVAL.getCode().equals(businessType)
+                || TypeEnums.EXPENSE_APPROVAL.getCode().equals(businessType)) {
+            handleReimbursementApprovalFinished(instance, status);
+            return;
+        }
+        // 椹冲洖瀵瑰簲鐨勪紒涓氭柊闂汇�佸姞鐝敵璇枫�佽鍋囩敵璇峰彲浠ラ噸鏂板啀鎻愪氦
+        if (TypeEnums.LEAVE_APPROVAL.getCode().equals(businessType)
+                || TypeEnums.OVERTIME_APPROVAL.getCode().equals(businessType)) {
+            handleHolidayApplicationApprovalFinished(instance, status);
+            return;
+        }
+        if (TypeEnums.ENTERPRISE_NEWS_APPROVAL.getCode().equals(businessType)) {
+            handleNewsApprovalFinished(instance, status);
+        }
+    }
+
+    private void handleReimbursementApprovalFinished(ApprovalInstance instance, String status) {
+        if (instance == null || instance.getBusinessId() == null) {
+            return;
+        }
+
+        FinReimbursement reimbursement = new FinReimbursement();
+        reimbursement.setId(instance.getBusinessId());
+        if ("APPROVED".equals(status)) {
+            reimbursement.setBillStatus("APPROVED");
+            reimbursement.setApprovedTime(instance.getFinishTime());
+        } else if ("REJECTED".equals(status)) {
+            reimbursement.setBillStatus("REJECTED");
+        } else if ("PENDING".equals(status)) {
+            reimbursement.setBillStatus("IN_APPROVAL");
+        } else {
+            return;
+        }
+        finReimbursementMapper.updateById(reimbursement);
+    }
+
+    private void handleNewsApprovalFinished(ApprovalInstance instance, String status) {
+        if (instance == null || instance.getBusinessId() == null) {
+            return;
+        }
+        EnterpriseNews enterpriseNews = new EnterpriseNews();
+        enterpriseNews.setId(instance.getBusinessId());
+        if ("APPROVED".equals(status)) {
+            enterpriseNews.setStatus(ENTERPRISE_NEWS_STATUS_PUBLISHED);
+            enterpriseNewsMapper.updateById(enterpriseNews);
+            sendEnterpriseNewsNotice(instance.getBusinessId());
+            return;
+        }
+        if ("REJECTED".equals(status)) {
+            enterpriseNews.setStatus(ENTERPRISE_NEWS_STATUS_REJECTED);
+            enterpriseNewsMapper.updateById(enterpriseNews);
+        }
+    }
+
+    private void handleHolidayApplicationApprovalFinished(ApprovalInstance instance, String status) {
+        if (instance == null || instance.getBusinessId() == null) {
+            return;
+        }
+        HolidayApplication holidayApplication = new HolidayApplication();
+        holidayApplication.setId(instance.getBusinessId());
+        if ("APPROVED".equals(status)) {
+            holidayApplication.setStatus("APPROVED");
+        } else if ("REJECTED".equals(status)) {
+            holidayApplication.setStatus("REJECTED");
+        } else if ("PENDING".equals(status)) {
+            holidayApplication.setStatus("PENDING");
+        } else {
+            return;
+        }
+        holidayApplicationMapper.updateById(holidayApplication);
+    }
+
+    private void handlePurchaseApprovalFinished(ApprovalInstance instance, String status) {
+        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(
+                new LambdaQueryWrapper<PurchaseLedger>()
+                        .eq(PurchaseLedger::getId, instance.getBusinessId())
+                        .last("limit 1")
+        );
+        if (purchaseLedger == null) {
+            return;
+        }
+
+        if ("APPROVED".equals(status)) {
+            purchaseLedger.setApprovalStatus(ApprovalStatusEnum.APPROVED.getCode());
+            List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(
+                    new QueryWrapper<SalesLedgerProduct>().lambda()
+                            .eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId())
+                            .eq(SalesLedgerProduct::getType, 2)
+            );
+            for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
+                if (salesLedgerProduct.getIsChecked()) {
+                    qualityInspectHelper.addQualityInspect(purchaseLedger, salesLedgerProduct);
+                } else {
+                    stockUtils.addStockWithBatchNo(
+                            salesLedgerProduct.getProductModelId(),
+                            salesLedgerProduct.getQuantity(),
+                            StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(),
+                            purchaseLedger.getId(),
+                            purchaseLedger.getPurchaseContractNumber() + "-" + salesLedgerProduct.getId()
+                    );
+                }
+            }
+        } else if ("REJECTED".equals(status)) {
+            purchaseLedger.setApprovalStatus(ApprovalStatusEnum.REJECTED.getCode());
+        } else if ("PENDING".equals(status)) {
+            purchaseLedger.setApprovalStatus(ApprovalStatusEnum.IN_PROGRESS.getCode());
+        }
+        purchaseLedgerMapper.updateById(purchaseLedger);
+    }
+
+    private void handleSalesQuotationApprovalFinished(ApprovalInstance instance, String status) {
+        SalesQuotation salesQuote = salesQuotationMapper.selectOne(
+                new LambdaQueryWrapper<SalesQuotation>()
+                        .eq(SalesQuotation::getId, instance.getBusinessId())
+                        .last("limit 1")
+        );
+        if (salesQuote == null) {
+            return;
+        }
+
+        if ("APPROVED".equals(status)) {
+            salesQuote.setStatus(SalesQuotationStatusEnum.APPROVED.getCode());
+        } else if ("REJECTED".equals(status)) {
+            salesQuote.setStatus(SalesQuotationStatusEnum.REJECTED.getCode());
+        } else if ("PENDING".equals(status)) {
+            salesQuote.setStatus(SalesQuotationStatusEnum.IN_PROGRESS.getCode());
+        }
+        salesQuotationMapper.updateById(salesQuote);
+    }
+
+    private void handleShippingApprovalFinished(ApprovalInstance instance, String status) {
+        ShippingInfo shippingInfo = shippingInfoMapper.selectOne(
+                new LambdaQueryWrapper<ShippingInfo>()
+                        .eq(ShippingInfo::getId, instance.getTitle())
+                        .orderByDesc(ShippingInfo::getCreateTime)
+                        .last("limit 1")
+        );
+        if (shippingInfo == null) {
+            return;
+        }
+
+        if ("APPROVED".equals(status)) {
+            shippingInfo.setStatus(ShippingStatusEnum.APPROVED.getCode());
+            shippingInfo.setShippingDate(new Date());
+            stockUtils.shipmentStatus(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), shippingInfo.getId());
+        } else if ("REJECTED".equals(status)) {
+            stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
+            shippingInfo.setStatus(ShippingStatusEnum.REJECTED.getCode());
+        } else if ("PENDING".equals(status)) {
+            shippingInfo.setStatus(ShippingStatusEnum.IN_PROGRESS.getCode());
+        }
+        shippingInfoMapper.updateById(shippingInfo);
+    }
+
+    private List<ApprovalTask> createNodeAndTasks(ApprovalInstance instance, ApprovalTemplateNode templateNode) {
+        List<ApprovalTemplateNodeApprover> approvers = approvalTemplateNodeApproverMapper.selectList(
+                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
+                        .eq(ApprovalTemplateNodeApprover::getTemplateId, instance.getTemplateId())
+                        .eq(ApprovalTemplateNodeApprover::getNodeId, templateNode.getId())
+                        .eq(ApprovalTemplateNodeApprover::getDeleted, 0L)
+                        .orderByAsc(ApprovalTemplateNodeApprover::getSortNo)
+        );
+        if (approvers == null || approvers.isEmpty()) {
+            throw new RuntimeException("涓嬩竴瀹℃壒鑺傜偣鏈厤缃鎵逛汉");
+        }
+
+        ApprovalInstanceNode instanceNode = new ApprovalInstanceNode();
+        instanceNode.setInstanceId(instance.getId());
+        instanceNode.setLevelNo(templateNode.getLevelNo());
+        instanceNode.setApproveType(templateNode.getApproveType());
+        instanceNode.setStatus("PENDING");
+        instanceNode.setStartTime(LocalDateTime.now());
+        instanceNode.setDeleted((byte) 0);
+        approvalInstanceNodeService.save(instanceNode);
+
+        List<ApprovalTask> taskList = new ArrayList<>(approvers.size());
+        for (ApprovalTemplateNodeApprover approver : approvers) {
+            ApprovalTask task = new ApprovalTask();
+            task.setInstanceId(instance.getId());
+            task.setNodeId(instanceNode.getId());
+            task.setLevelNo(instanceNode.getLevelNo());
+            task.setApproverId(approver.getApproverId());
+            task.setApproverName(approver.getApproverName());
+            task.setTaskStatus("PENDING");
+            task.setIsRead((byte) 0);
+            task.setDeleted((byte) 0);
+            taskList.add(task);
+        }
+        approvalTaskService.saveBatch(taskList);
+        return taskList;
+    }
+
+    private void sendApproveNotice(ApprovalInstance instance, List<ApprovalTask> tasks) {
+        if (instance == null || tasks == null || tasks.isEmpty()) {
+            return;
+        }
+
+        List<Long> approverIds = tasks.stream()
+                .map(ApprovalTask::getApproverId)
+                .filter(id -> id != null && id > 0)
+                .distinct()
+                .collect(Collectors.toList());
+        if (approverIds.isEmpty()) {
+            return;
+        }
+
+        String title = StringUtils.hasText(instance.getTemplateName()) ? instance.getTemplateName() : "瀹℃壒鎻愰啋";
+        String message = "瀹℃壒鍗曞彿 " + instance.getInstanceNo() + " 闇�瑕佹偍瀹℃壒";
+        String jumpPath = "/officeProcessAutomation/ApproveManage/approve-list?id=" + instance.getId();
+        sysNoticeService.simpleNoticeByUser(title, message, approverIds, jumpPath);
+    }
+
+    private void sendEnterpriseNewsNotice(Long newsId) {
+        EnterpriseNews enterpriseNews = enterpriseNewsMapper.selectById(newsId);
+        if (enterpriseNews == null) {
+            return;
+        }
+        List<Long> userIds = getEnterpriseNewsNoticeUserIds(enterpriseNews);
+        if (userIds == null || userIds.isEmpty()) {
+            return;
+        }
+        String title = "浼佷笟鏂伴椈";
+        String message = "鎮ㄦ湁鏂扮殑浼佷笟鏂伴椈銆�" + enterpriseNews.getTitle() + "銆嬭鍙婃椂鏌ラ槄";
+        String jumpPath = "/officeProcessAutomation/EnterpriseNews?id=" + newsId;
+        sysNoticeService.simpleNoticeByUser(title, message, userIds, jumpPath);
+    }
+
+    private List<Long> getEnterpriseNewsNoticeUserIds(EnterpriseNews enterpriseNews) {
+        if (enterpriseNews == null || !org.springframework.util.StringUtils.hasText(enterpriseNews.getReadScope())) {
+            return Collections.emptyList();
+        }
+        String readScope = enterpriseNews.getReadScope().trim();
+        if ("all".equals(readScope)) {
+            return sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>()
+                            .select(SysUser::getUserId)
+                            .eq(SysUser::getDelFlag, "0"))
+                    .stream()
+                    .map(SysUser::getUserId)
+                    .filter(id -> id != null && id > 0)
+                    .distinct()
+                    .collect(Collectors.toList());
+        }
+        if ("dept".equals(readScope)) {
+            List<Long> deptIds = enterpriseNewsScopeDeptMapper.selectList(
+                            new LambdaQueryWrapper<EnterpriseNewsScopeDept>()
+                                    .eq(EnterpriseNewsScopeDept::getNewsId, enterpriseNews.getId()))
+                    .stream()
+                    .map(EnterpriseNewsScopeDept::getDeptId)
+                    .filter(id -> id != null && id > 0)
+                    .distinct()
+                    .collect(Collectors.toList());
+            if (deptIds.isEmpty()) {
+                return Collections.emptyList();
+            }
+            return sysUserDeptMapper.selectDistinctUserIdsByDeptIds(collectDeptIdsWithChildren(deptIds));
+        }
+        if ("custom".equals(readScope)) {
+            return enterpriseNewsScopeUserMapper.selectList(
+                            new LambdaQueryWrapper<EnterpriseNewsScopeUser>()
+                                    .eq(EnterpriseNewsScopeUser::getNewsId, enterpriseNews.getId()))
+                    .stream()
+                    .map(EnterpriseNewsScopeUser::getUserId)
+                    .filter(id -> id != null && id > 0)
+                    .distinct()
+                    .collect(Collectors.toList());
+        }
+        return Collections.emptyList();
+    }
+
+    private List<Long> collectDeptIdsWithChildren(List<Long> deptIds) {
+        Set<Long> allDeptIds = new LinkedHashSet<>();
+        for (Long deptId : deptIds) {
+            if (deptId == null) {
+                continue;
+            }
+            allDeptIds.add(deptId);
+            List<SysDept> children = sysDeptMapper.selectChildrenDeptById(deptId);
+            if (children != null && !children.isEmpty()) {
+                for (SysDept child : children) {
+                    if (child != null && child.getDeptId() != null) {
+                        allDeptIds.add(child.getDeptId());
+                    }
+                }
+            }
+        }
+        return new ArrayList<>(allDeptIds);
+    }
+
+    private void closePendingTasks(Long instanceId, Long nodeId) {
+        LambdaUpdateWrapper<ApprovalTask> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.eq(ApprovalTask::getInstanceId, instanceId)
+                .eq(ApprovalTask::getNodeId, nodeId)
+                .eq(ApprovalTask::getTaskStatus, "PENDING")
+                .eq(ApprovalTask::getDeleted, 0)
+                .set(ApprovalTask::getDeleted, (byte) 1);
+        approvalTaskService.update(updateWrapper);
+    }
+
+    private void saveApprovalRecord(Long instanceId,
+                                    Long nodeId,
+                                    Long taskId,
+                                    Long operatorId,
+                                    String operatorName,
+                                    String action,
+                                    String comment) {
+        ApprovalRecord record = new ApprovalRecord();
+        record.setInstanceId(instanceId);
+        record.setNodeId(nodeId);
+        record.setTaskId(taskId);
+        record.setOperatorId(operatorId);
+        record.setOperatorName(operatorName);
+        record.setAction(action);
+        record.setComment(comment);
+        record.setDeleted((byte) 0);
+        approvalRecordService.save(record);
+    }
+
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApprovalRecordServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApprovalRecordServiceImpl.java
new file mode 100644
index 0000000..50911e4
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApprovalRecordServiceImpl.java
@@ -0,0 +1,20 @@
+package com.ruoyi.approve.service.impl;
+
+import com.ruoyi.approve.pojo.ApprovalRecord;
+import com.ruoyi.approve.mapper.ApprovalRecordMapper;
+import com.ruoyi.approve.service.ApprovalRecordService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 瀹℃壒璁板綍琛� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:28:21
+ */
+@Service
+public class ApprovalRecordServiceImpl extends ServiceImpl<ApprovalRecordMapper, ApprovalRecord> implements ApprovalRecordService {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApprovalTaskServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApprovalTaskServiceImpl.java
new file mode 100644
index 0000000..14e10ca
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApprovalTaskServiceImpl.java
@@ -0,0 +1,20 @@
+package com.ruoyi.approve.service.impl;
+
+import com.ruoyi.approve.pojo.ApprovalTask;
+import com.ruoyi.approve.mapper.ApprovalTaskMapper;
+import com.ruoyi.approve.service.ApprovalTaskService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 瀹℃壒浠诲姟琛� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 03:32:37
+ */
+@Service
+public class ApprovalTaskServiceImpl extends ServiceImpl<ApprovalTaskMapper, ApprovalTask> implements ApprovalTaskService {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeApproverServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeApproverServiceImpl.java
new file mode 100644
index 0000000..0921c35
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeApproverServiceImpl.java
@@ -0,0 +1,20 @@
+package com.ruoyi.approve.service.impl;
+
+import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
+import com.ruoyi.approve.mapper.ApprovalTemplateNodeApproverMapper;
+import com.ruoyi.approve.service.ApprovalTemplateNodeApproverService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鑺傜偣瀹℃壒浜鸿〃 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:30
+ */
+@Service
+public class ApprovalTemplateNodeApproverServiceImpl extends ServiceImpl<ApprovalTemplateNodeApproverMapper, ApprovalTemplateNodeApprover> implements ApprovalTemplateNodeApproverService {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeServiceImpl.java
new file mode 100644
index 0000000..ca65a5f
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeServiceImpl.java
@@ -0,0 +1,62 @@
+package com.ruoyi.approve.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeApproverDto;
+import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeDto;
+import com.ruoyi.approve.mapper.ApprovalTemplateNodeMapper;
+import com.ruoyi.approve.pojo.ApprovalTemplateNode;
+import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
+import com.ruoyi.approve.service.ApprovalTemplateNodeApproverService;
+import com.ruoyi.approve.service.ApprovalTemplateNodeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鑺傜偣琛� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-18 11:20:19
+ */
+@Service
+@RequiredArgsConstructor
+public class ApprovalTemplateNodeServiceImpl extends ServiceImpl<ApprovalTemplateNodeMapper, ApprovalTemplateNode> implements ApprovalTemplateNodeService {
+
+    private final ApprovalTemplateNodeMapper approvalTemplateNodeMapper;
+    private final ApprovalTemplateNodeApproverService approvalTemplateNodeApproverService;
+    @Override
+    public Boolean saveApprovalTemplateNode(Long templateId, List<ApprovalTemplateNodeDto> nodes) {
+        if (nodes == null || nodes.isEmpty()) {
+            throw new RuntimeException("鑺傜偣鍒楄〃涓嶈兘涓虹┖");
+        }
+
+        List<ApprovalTemplateNodeApprover> approverList = new ArrayList<>();
+
+        for (ApprovalTemplateNodeDto nodeDto : nodes) {
+            ApprovalTemplateNode node = new ApprovalTemplateNode();
+            BeanUtils.copyProperties(nodeDto, node);
+            node.setTemplateId(templateId);
+            approvalTemplateNodeMapper.insert(node);
+
+            List<ApprovalTemplateNodeApproverDto> approvers = nodeDto.getApprovers();
+            if (approvers == null || approvers.isEmpty()) {
+                throw new RuntimeException("鑺傜偣瀹℃壒浜轰笉鑳戒负绌�");
+            }
+            for (ApprovalTemplateNodeApproverDto approverDto : approvers) {
+                ApprovalTemplateNodeApprover approver = new ApprovalTemplateNodeApprover();
+                BeanUtils.copyProperties(approverDto, approver);
+                approver.setNodeId(node.getId());
+                approver.setTemplateId(templateId);
+                approver.setDeleted(0L);
+                approverList.add(approver);
+            }
+        }
+        approvalTemplateNodeApproverService.saveBatch(approverList);
+        return true;
+    }
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java
new file mode 100644
index 0000000..af5a774
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java
@@ -0,0 +1,250 @@
+package com.ruoyi.approve.service.impl;
+
+import cn.hutool.core.collection.CollUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.approve.bean.dto.ApprovalTemplateDto;
+import com.ruoyi.approve.bean.vo.ApprovalTemplateNodeApproverVo;
+import com.ruoyi.approve.bean.vo.ApprovalTemplateNodeVo;
+import com.ruoyi.approve.bean.vo.ApprovalTemplateVo;
+import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
+import com.ruoyi.approve.mapper.ApprovalTemplateNodeApproverMapper;
+import com.ruoyi.approve.pojo.ApprovalTemplate;
+import com.ruoyi.approve.pojo.ApprovalTemplateNode;
+import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
+import com.ruoyi.approve.service.ApprovalTemplateNodeService;
+import com.ruoyi.approve.service.ApprovalTemplateService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 瀹℃壒妯℃澘鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @since 2026-05-18 11:20:08
+ */
+@Service
+@RequiredArgsConstructor
+public class ApprovalTemplateServiceImpl extends ServiceImpl<ApprovalTemplateMapper, ApprovalTemplate> implements ApprovalTemplateService {
+
+    private final ApprovalTemplateMapper approvalTemplateMapper;
+    private final ApprovalTemplateNodeService approvalTemplateNodeService;
+    private final ApprovalTemplateNodeApproverMapper approvalTemplateNodeApproverMapper;
+
+    @Override
+    public IPage<ApprovalTemplateVo> listPage(Page<ApprovalTemplateVo> page, ApprovalTemplateDto approvalTemplateDto) {
+        IPage<ApprovalTemplateVo> approvalTemplateVoIPage = approvalTemplateMapper.listPage(page, approvalTemplateDto);
+        fillTemplateVoNodes(approvalTemplateVoIPage.getRecords());
+        return approvalTemplateVoIPage;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean saveApprovalTemplateDto(ApprovalTemplateDto approvalTemplateDto) {
+        approvalTemplateMapper.insert(approvalTemplateDto);
+        approvalTemplateNodeService.remove(
+                new LambdaQueryWrapper<ApprovalTemplateNode>()
+                        .eq(ApprovalTemplateNode::getTemplateId, approvalTemplateDto.getId())
+        );
+        approvalTemplateNodeApproverMapper.delete(
+                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
+                        .eq(ApprovalTemplateNodeApprover::getTemplateId, approvalTemplateDto.getId())
+        );
+        return approvalTemplateNodeService.saveApprovalTemplateNode(
+                approvalTemplateDto.getId(),
+                approvalTemplateDto.getNodes()
+        );
+    }
+
+    @Override
+    public Boolean updateApprovalTemplateDto(ApprovalTemplateDto approvalTemplateDto) {
+        approvalTemplateMapper.updateById(approvalTemplateDto);
+        approvalTemplateNodeService.remove(
+                new LambdaQueryWrapper<ApprovalTemplateNode>()
+                        .eq(ApprovalTemplateNode::getTemplateId, approvalTemplateDto.getId())
+        );
+        approvalTemplateNodeApproverMapper.delete(
+                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
+                        .eq(ApprovalTemplateNodeApprover::getTemplateId, approvalTemplateDto.getId())
+        );
+        return approvalTemplateNodeService.saveApprovalTemplateNode(
+                approvalTemplateDto.getId(),
+                approvalTemplateDto.getNodes()
+        );
+    }
+
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean delete(List<Long> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return false;
+        }
+        ApprovalTemplate updateEntity = new ApprovalTemplate();
+        updateEntity.setDeleted(1);
+        LambdaUpdateWrapper<ApprovalTemplate> updateWrapper = Wrappers.lambdaUpdate();
+        updateWrapper.in(ApprovalTemplate::getId, ids)
+                .eq(ApprovalTemplate::getDeleted, 0);
+
+        int rows = approvalTemplateMapper.update(updateEntity, updateWrapper);
+        return rows == ids.size();
+    }
+
+    @Override
+    public List<ApprovalTemplateVo> listApprovalTemplateVo(Integer type) {
+        List<ApprovalTemplate> templateList = this.list(
+                new LambdaQueryWrapper<ApprovalTemplate>()
+                        .eq(ApprovalTemplate::getDeleted, 0)
+                        .eq(ApprovalTemplate::getEnabled, 1)
+                        .orderByDesc(ApprovalTemplate::getTemplateType)
+                        .orderByDesc(ApprovalTemplate::getId)
+        );
+        if (CollUtil.isEmpty(templateList)) {
+            return Collections.emptyList();
+        }
+
+        List<ApprovalTemplateVo> templateVos = templateList.stream()
+                .map(template -> {
+                    ApprovalTemplateVo templateVo = new ApprovalTemplateVo();
+                    BeanUtils.copyProperties(template, templateVo);
+                    return templateVo;
+                })
+                .collect(Collectors.toList());
+        fillTemplateVoNodes(templateVos);
+        return templateVos;
+    }
+
+    @Override
+    public ApprovalTemplateVo getApprovalTemplateVoById(Long id) {
+        if (id == null) {
+            throw new IllegalArgumentException("鍙傛暟 id 涓嶈兘涓虹┖");
+        }
+
+        ApprovalTemplate template = this.getOne(
+                new LambdaQueryWrapper<ApprovalTemplate>()
+                        .eq(ApprovalTemplate::getId, id)
+                        .eq(ApprovalTemplate::getDeleted, 0)
+        );
+        if (template == null) {
+            throw new IllegalArgumentException("妯℃澘涓嶅瓨鍦�");
+        }
+
+        List<ApprovalTemplateNode> nodeList = approvalTemplateNodeService.list(
+                new LambdaQueryWrapper<ApprovalTemplateNode>()
+                        .eq(ApprovalTemplateNode::getTemplateId, id)
+                        .orderByAsc(ApprovalTemplateNode::getLevelNo)
+        );
+
+        List<ApprovalTemplateNodeApprover> approverList = approvalTemplateNodeApproverMapper.selectList(
+                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
+                        .eq(ApprovalTemplateNodeApprover::getTemplateId, id)
+                        .eq(ApprovalTemplateNodeApprover::getDeleted, 0L)
+        );
+
+        Map<Long, List<ApprovalTemplateNode>> nodeMap = nodeList.stream()
+                .collect(Collectors.groupingBy(ApprovalTemplateNode::getTemplateId));
+
+        Map<Long, List<ApprovalTemplateNodeApprover>> approverMap = approverList.stream()
+                .collect(Collectors.groupingBy(ApprovalTemplateNodeApprover::getNodeId));
+
+        return buildTemplateVo(template, nodeMap, approverMap);
+    }
+
+    /**
+     * 鎵归噺濉厖妯℃澘鑺傜偣鍙婅妭鐐瑰鎵逛汉锛岄伩鍏嶅惊鐜煡搴撱��
+     */
+    private void fillTemplateVoNodes(List<ApprovalTemplateVo> templateVos) {
+        if (CollUtil.isEmpty(templateVos)) {
+            return;
+        }
+
+        List<Long> templateIds = templateVos.stream()
+                .map(ApprovalTemplateVo::getId)
+                .collect(Collectors.toList());
+
+        List<ApprovalTemplateNode> nodeList = approvalTemplateNodeService.list(
+                new LambdaQueryWrapper<ApprovalTemplateNode>()
+                        .in(ApprovalTemplateNode::getTemplateId, templateIds)
+                        .orderByAsc(ApprovalTemplateNode::getLevelNo)
+        );
+
+        List<ApprovalTemplateNodeApprover> approverList = approvalTemplateNodeApproverMapper.selectList(
+                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
+                        .in(ApprovalTemplateNodeApprover::getTemplateId, templateIds)
+                        .eq(ApprovalTemplateNodeApprover::getDeleted, 0L)
+        );
+
+        Map<Long, List<ApprovalTemplateNode>> nodeMap = nodeList.stream()
+                .collect(Collectors.groupingBy(ApprovalTemplateNode::getTemplateId));
+
+        Map<Long, List<ApprovalTemplateNodeApprover>> approverMap = approverList.stream()
+                .collect(Collectors.groupingBy(ApprovalTemplateNodeApprover::getNodeId));
+
+        templateVos.forEach(templateVo -> templateVo.setNodes(
+                nodeMap.getOrDefault(templateVo.getId(), Collections.emptyList())
+                        .stream()
+                        .sorted(Comparator.comparing(
+                                ApprovalTemplateNode::getLevelNo,
+                                Comparator.nullsLast(Integer::compareTo)
+                        ))
+                        .map(node ->  buildNodeVo(node, approverMap))
+                        .collect(Collectors.toList())
+        ));
+    }
+
+    private ApprovalTemplateVo buildTemplateVo(ApprovalTemplate template,
+                                               Map<Long, List<ApprovalTemplateNode>> nodeMap,
+                                               Map<Long, List<ApprovalTemplateNodeApprover>> approverMap) {
+        ApprovalTemplateVo templateVo = new ApprovalTemplateVo();
+        BeanUtils.copyProperties(template, templateVo);
+
+        List<ApprovalTemplateNodeVo> nodeVos = nodeMap
+                .getOrDefault(template.getId(), Collections.emptyList())
+                .stream()
+                .sorted(Comparator.comparing(
+                        ApprovalTemplateNode::getLevelNo,
+                        Comparator.nullsLast(Integer::compareTo)
+                ))
+                .map(node -> buildNodeVo(node, approverMap))
+                .collect(Collectors.toList());
+
+        templateVo.setNodes(nodeVos);
+        return templateVo;
+    }
+
+    private ApprovalTemplateNodeVo buildNodeVo(ApprovalTemplateNode node,
+                                               Map<Long, List<ApprovalTemplateNodeApprover>> approverMap) {
+        ApprovalTemplateNodeVo nodeVo = new ApprovalTemplateNodeVo();
+        BeanUtils.copyProperties(node, nodeVo);
+
+        List<ApprovalTemplateNodeApproverVo> approverVos = approverMap
+                .getOrDefault(node.getId(), Collections.emptyList())
+                .stream()
+                .sorted(Comparator.comparing(
+                        ApprovalTemplateNodeApprover::getSortNo,
+                        Comparator.nullsLast(Integer::compareTo)
+                ))
+                .map(this::buildApproverVo)
+                .collect(Collectors.toList());
+
+        nodeVo.setApprovers(approverVos);
+        return nodeVo;
+    }
+
+    private ApprovalTemplateNodeApproverVo buildApproverVo(ApprovalTemplateNodeApprover approver) {
+        ApprovalTemplateNodeApproverVo approverVo = new ApprovalTemplateNodeApproverVo();
+        BeanUtils.copyProperties(approver, approverVo);
+        return approverVo;
+    }
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApproveBusinessStatusService.java b/src/main/java/com/ruoyi/approve/service/impl/ApproveBusinessStatusService.java
new file mode 100644
index 0000000..8135c3a
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApproveBusinessStatusService.java
@@ -0,0 +1,175 @@
+package com.ruoyi.approve.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
+import com.ruoyi.procurementrecord.utils.StockUtils;
+import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
+import com.ruoyi.purchase.pojo.PurchaseLedger;
+import com.ruoyi.quality.mapper.QualityInspectMapper;
+import com.ruoyi.quality.mapper.QualityInspectParamMapper;
+import com.ruoyi.quality.mapper.QualityTestStandardMapper;
+import com.ruoyi.quality.mapper.QualityTestStandardParamMapper;
+import com.ruoyi.quality.pojo.QualityInspect;
+import com.ruoyi.quality.pojo.QualityInspectParam;
+import com.ruoyi.quality.pojo.QualityTestStandard;
+import com.ruoyi.quality.pojo.QualityTestStandardParam;
+import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
+import com.ruoyi.sales.mapper.SalesQuotationMapper;
+import com.ruoyi.sales.mapper.ShippingInfoMapper;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import com.ruoyi.sales.pojo.SalesQuotation;
+import com.ruoyi.sales.pojo.ShippingInfo;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.Date;
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class ApproveBusinessStatusService {
+
+    private final PurchaseLedgerMapper purchaseLedgerMapper;
+    private final SalesQuotationMapper salesQuotationMapper;
+    private final ShippingInfoMapper shippingInfoMapper;
+    private final SalesLedgerProductMapper salesLedgerProductMapper;
+    private final StockUtils stockUtils;
+    private final QualityInspectMapper qualityInspectMapper;
+    private final QualityTestStandardMapper qualityTestStandardMapper;
+    private final QualityTestStandardParamMapper qualityTestStandardParamMapper;
+    private final QualityInspectParamMapper qualityInspectParamMapper;
+
+    /**
+     * 缁熶竴鍚屾瀹℃壒缁撴灉瀵瑰簲鐨勪笟鍔″崟鎹姸鎬併��
+     * status锛�1-瀹℃牳涓紝2-瀹℃牳瀹屾垚锛�3-瀹℃牳鏈�氳繃銆�
+     */
+    public void syncBusinessStatus(Integer approveType, String approveReason, Integer status) {
+        if (approveType == null || status == null || !StringUtils.hasText(approveReason)) {
+            return;
+        }
+        switch (approveType) {
+            case 5:
+                syncPurchaseStatus(approveReason, status);
+                break;
+            case 6:
+                syncSalesQuotationStatus(approveReason, status);
+                break;
+            case 7:
+                syncShippingStatus(approveReason, status);
+                break;
+            default:
+                break;
+        }
+    }
+
+    // 閲囪喘瀹℃壒閫氳繃鏃讹紝鎸変骇鍝佽川妫�閰嶇疆鍐冲畾鐢熸垚璐ㄦ鍗曟垨鐩存帴鍏ュ簱銆�
+    private void syncPurchaseStatus(String approveReason, Integer status) {
+        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
+                .eq(PurchaseLedger::getPurchaseContractNumber, approveReason)
+                .last("limit 1"));
+        if (purchaseLedger == null) {
+            return;
+        }
+        if (status.equals(2)) {
+            purchaseLedger.setApprovalStatus(3);
+            List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
+                    .lambda().eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId()).eq(SalesLedgerProduct::getType, 2));
+            for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
+                if (Boolean.TRUE.equals(salesLedgerProduct.getIsChecked())) {
+                    addQualityInspect(purchaseLedger, salesLedgerProduct);
+                } else {
+                    stockUtils.addStockWithBatchNo(
+                            salesLedgerProduct.getProductModelId(),
+                            salesLedgerProduct.getQuantity(),
+                            StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(),
+                            purchaseLedger.getId(),
+                            purchaseLedger.getPurchaseContractNumber() + "-" + salesLedgerProduct.getId());
+                }
+            }
+        } else if (status.equals(3)) {
+            purchaseLedger.setApprovalStatus(4);
+        } else if (status.equals(1)) {
+            purchaseLedger.setApprovalStatus(2);
+        } else {
+            return;
+        }
+        purchaseLedgerMapper.updateById(purchaseLedger);
+    }
+
+    // 鎶ヤ环瀹℃壒鐘舵�佸洖鍐欏埌閿�鍞姤浠峰崟鐘舵�併��
+    private void syncSalesQuotationStatus(String approveReason, Integer status) {
+        SalesQuotation salesQuote = salesQuotationMapper.selectOne(new LambdaQueryWrapper<SalesQuotation>()
+                .eq(SalesQuotation::getQuotationNo, approveReason)
+                .last("limit 1"));
+        if (salesQuote == null) {
+            return;
+        }
+        if (status.equals(2)) {
+            salesQuote.setStatus("閫氳繃");
+        } else if (status.equals(3)) {
+            salesQuote.setStatus("鎷掔粷");
+        } else if (status.equals(1)) {
+            salesQuote.setStatus("瀹℃牳涓�");
+        } else {
+            return;
+        }
+        salesQuotationMapper.updateById(salesQuote);
+    }
+
+    // 鍙戣揣瀹℃壒閫氳繃鏃跺悓姝ュ彂璐х姸鎬佸拰鍑哄簱瀹℃壒鐘舵�侊紱鎷掔粷鏃跺垹闄ゅ緟纭鍑哄簱璁板綍銆�
+    private void syncShippingStatus(String approveReason, Integer status) {
+        ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
+                .eq(ShippingInfo::getShippingNo, approveReason)
+                .orderByDesc(ShippingInfo::getCreateTime)
+                .last("limit 1"));
+        if (shippingInfo == null) {
+            return;
+        }
+        if (status.equals(2)) {
+            shippingInfo.setStatus("瀹℃牳閫氳繃");
+            shippingInfo.setShippingDate(new Date());
+            stockUtils.shipmentStatus(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), shippingInfo.getId());
+        } else if (status.equals(3)) {
+            stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
+            shippingInfo.setStatus("瀹℃牳鎷掔粷");
+        } else if (status.equals(1)) {
+            shippingInfo.setStatus("瀹℃牳涓�");
+        } else {
+            return;
+        }
+        shippingInfoMapper.updateById(shippingInfo);
+    }
+
+    // 鐢熸垚閲囪喘璐ㄦ鍗曪紝骞舵寜浜у搧璐ㄦ鏍囧噯鍒濆鍖栬川妫�鍙傛暟銆�
+    private void addQualityInspect(PurchaseLedger purchaseLedger, SalesLedgerProduct saleProduct) {
+        QualityInspect qualityInspect = new QualityInspect();
+        qualityInspect.setInspectType(0);
+        qualityInspect.setSupplier(purchaseLedger.getSupplierName());
+        qualityInspect.setPurchaseLedgerId(purchaseLedger.getId());
+        qualityInspect.setProductId(saleProduct.getProductId());
+        qualityInspect.setProductName(saleProduct.getProductCategory());
+        qualityInspect.setModel(saleProduct.getSpecificationModel());
+        qualityInspect.setProductModelId(saleProduct.getProductModelId());
+        qualityInspect.setUnit(saleProduct.getUnit());
+        qualityInspect.setQuantity(saleProduct.getQuantity());
+        qualityInspectMapper.insert(qualityInspect);
+        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(saleProduct.getProductId(), 0, null);
+        if (qualityTestStandard.size() > 0) {
+            qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
+            qualityInspectMapper.updateById(qualityInspect);
+            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
+                            .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))
+                    .forEach(qualityTestStandardParam -> {
+                        QualityInspectParam param = new QualityInspectParam();
+                        com.ruoyi.common.utils.bean.BeanUtils.copyProperties(qualityTestStandardParam, param);
+                        param.setId(null);
+                        param.setInspectId(qualityInspect.getId());
+                        qualityInspectParamMapper.insert(param);
+                    });
+        }
+    }
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
index 0b1a854..964be23 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -1,7 +1,6 @@
 package com.ruoyi.approve.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -14,26 +13,12 @@
 import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.common.enums.FileNameType;
-import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
-import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
 import com.ruoyi.common.utils.SecurityUtils;
-import com.ruoyi.device.mapper.DeviceRepairMapper;
-import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.project.system.domain.SysUser;
 import com.ruoyi.project.system.mapper.SysUserMapper;
 import com.ruoyi.project.system.service.ISysNoticeService;
-import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
-import com.ruoyi.purchase.pojo.PurchaseLedger;
-import com.ruoyi.quality.mapper.QualityInspectMapper;
-import com.ruoyi.quality.mapper.QualityInspectParamMapper;
-import com.ruoyi.quality.mapper.QualityTestStandardMapper;
-import com.ruoyi.quality.mapper.QualityTestStandardParamMapper;
-import com.ruoyi.quality.pojo.QualityInspect;
-import com.ruoyi.quality.pojo.QualityInspectParam;
-import com.ruoyi.quality.pojo.QualityTestStandard;
-import com.ruoyi.quality.pojo.QualityTestStandardParam;
-import com.ruoyi.sales.mapper.*;
-import com.ruoyi.sales.pojo.*;
+import com.ruoyi.sales.mapper.CommonFileMapper;
+import com.ruoyi.sales.pojo.CommonFile;
 import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
@@ -55,18 +40,8 @@
     private final SysUserMapper sysUserMapper;
     private final ISysNoticeService sysNoticeService;
     private final CommonFileMapper fileMapper;
-    private final DeviceRepairMapper deviceRepairMapper;
-    private final PurchaseLedgerMapper purchaseLedgerMapper;
-    private final SalesQuotationMapper salesQuotationMapper;
-    private final ShippingInfoMapper shippingInfoMapper;
-    private final ShippingProductDetailMapper shippingProductDetailMapper;
     private final CommonFileServiceImpl commonFileService;
-    private final StockUtils stockUtils;
-    private final SalesLedgerProductMapper salesLedgerProductMapper;
-    private final QualityInspectMapper qualityInspectMapper;
-    private final QualityTestStandardMapper qualityTestStandardMapper;
-    private final QualityTestStandardParamMapper qualityTestStandardParamMapper;
-    private final QualityInspectParamMapper qualityInspectParamMapper;
+    private final ApproveBusinessStatusService approveBusinessStatusService;
     private final FileUtil fileUtil;
 
 
@@ -162,74 +137,7 @@
         }
         approveProcessMapper.updateById(approveProcess);
 
-        //閲囪喘瀹℃牳
-        if (approveProcess.getApproveType().equals(5)) {
-            PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
-                    .eq(PurchaseLedger::getPurchaseContractNumber, approveProcess.getApproveReason())
-                    .last("limit 1"));
-            if (purchaseLedger != null) {
-                if (status.equals(2)) {
-                    // 鍚屾剰
-                    purchaseLedger.setApprovalStatus(3);
-                    List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
-                            .lambda().eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId()).eq(SalesLedgerProduct::getType, 2));
-                    for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
-                        // 璐ㄦ
-                        if (salesLedgerProduct.getIsChecked()) {
-                            addQualityInspect(purchaseLedger, salesLedgerProduct);
-                        } else {
-                            //鐩存帴鍏ュ簱
-                            stockUtils.addStockWithBatchNo(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(), purchaseLedger.getPurchaseContractNumber() + "-" + salesLedgerProduct.getId());
-                        }
-                    }
-                } else if (status.equals(3)) {
-                    // 鎷掔粷
-                    purchaseLedger.setApprovalStatus(4);
-                } else if (status.equals(1)) {
-                    // 瀹℃牳涓�
-                    purchaseLedger.setApprovalStatus(2);
-                }
-                purchaseLedgerMapper.updateById(purchaseLedger);
-            }
-        }
-        // 閿�鍞姤浠风姸鎬佷慨鏀�
-        if (approveProcess.getApproveType().equals(6)) {
-            SalesQuotation salesQuote = salesQuotationMapper.selectOne(new LambdaQueryWrapper<SalesQuotation>()
-                    .eq(SalesQuotation::getQuotationNo, approveProcess.getApproveReason())
-                    .last("limit 1"));
-            // 鍚屾剰
-            if (status.equals(2) && salesQuote != null) {
-                salesQuote.setStatus("閫氳繃");
-            } else if (status.equals(3) && salesQuote != null) {
-                salesQuote.setStatus("鎷掔粷");
-            } else if (status.equals(1) && salesQuote != null) {
-                salesQuote.setStatus("瀹℃牳涓�");
-            }
-            salesQuotationMapper.updateById(salesQuote);
-        }
-        // 鍑哄簱瀹℃壒淇敼=鍙戣揣瀹℃壒
-        if (approveProcess.getApproveType().equals(7)) {
-            ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
-                    .eq(ShippingInfo::getShippingNo, approveProcess.getApproveReason())
-                    .orderByDesc(ShippingInfo::getCreateTime)
-                    .last("limit 1"));
-            if (shippingInfo != null) {
-                if (status.equals(2)) {
-                    shippingInfo.setStatus("瀹℃牳閫氳繃");
-                    shippingInfo.setShippingDate(new Date());
-                    //鏇存敼鍑哄簱瀹℃牳鐘舵�侊紙寰呯‘璁ゆ敼鎴愬緟瀹℃牳锛�
-                    stockUtils.shipmentStatus(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), shippingInfo.getId());
-                } else if (status.equals(3)) {
-                    //鍒犻櫎鍘熸湰锛堝緟纭锛夌殑鍑哄簱瀹℃牳鐘舵��
-                    stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
-                    shippingInfo.setStatus("瀹℃牳鎷掔粷");
-                } else if (status.equals(1)) {
-                    shippingInfo.setStatus("瀹℃牳涓�");
-                }
-                shippingInfoMapper.updateById(shippingInfo);
-            }
-
-        }
+        approveBusinessStatusService.syncBusinessStatus(approveProcess.getApproveType(), approveProcess.getApproveReason(), status);
         fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_NODE, approveNode.getId(), approveNode.getStorageBlobDTOS());
     }
 
@@ -305,34 +213,6 @@
                 return "鍔炲叕鐢ㄥ搧瀹℃壒";
         }
         return null;
-    }
-
-    private void addQualityInspect(PurchaseLedger purchaseLedger, SalesLedgerProduct saleProduct) {
-        QualityInspect qualityInspect = new QualityInspect();
-        qualityInspect.setInspectType(0);
-        qualityInspect.setSupplier(purchaseLedger.getSupplierName());
-        qualityInspect.setPurchaseLedgerId(purchaseLedger.getId());
-        qualityInspect.setProductId(saleProduct.getProductId());
-        qualityInspect.setProductName(saleProduct.getProductCategory());
-        qualityInspect.setModel(saleProduct.getSpecificationModel());
-        qualityInspect.setProductModelId(saleProduct.getProductModelId());
-        qualityInspect.setUnit(saleProduct.getUnit());
-        qualityInspect.setQuantity(saleProduct.getQuantity());
-        qualityInspectMapper.insert(qualityInspect);
-        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(saleProduct.getProductId(), 0, null);
-        if (qualityTestStandard.size() > 0) {
-            qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
-            qualityInspectMapper.updateById(qualityInspect);
-            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
-                            .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))
-                    .forEach(qualityTestStandardParam -> {
-                        QualityInspectParam param = new QualityInspectParam();
-                        com.ruoyi.common.utils.bean.BeanUtils.copyProperties(qualityTestStandardParam, param);
-                        param.setId(null);
-                        param.setInspectId(qualityInspect.getId());
-                        qualityInspectParamMapper.insert(param);
-                    });
-        }
     }
 
 }
diff --git a/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
index 185fa68..41f71e8 100644
--- a/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
+++ b/src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -1,8 +1,6 @@
 package com.ruoyi.approve.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -25,10 +23,8 @@
 import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.common.enums.FileNameType;
-import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
 import com.ruoyi.common.utils.OrderUtils;
 import com.ruoyi.common.utils.SecurityUtils;
-import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.project.system.domain.SysDept;
 import com.ruoyi.project.system.domain.SysNotice;
 import com.ruoyi.project.system.domain.SysUser;
@@ -38,10 +34,8 @@
 import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
 import com.ruoyi.purchase.pojo.PurchaseLedger;
 import com.ruoyi.sales.mapper.CommonFileMapper;
-import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
 import com.ruoyi.sales.mapper.ShippingInfoMapper;
 import com.ruoyi.sales.pojo.CommonFile;
-import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import com.ruoyi.sales.pojo.ShippingInfo;
 import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
 import lombok.RequiredArgsConstructor;
@@ -53,15 +47,12 @@
 import java.io.IOException;
 import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.stream.Collectors;
 
 @Service
 @RequiredArgsConstructor
 public class ApproveProcessServiceImpl extends ServiceImpl<ApproveProcessMapper, ApproveProcess> implements IApproveProcessService {
-    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
-
     private final SysDeptMapper sysDeptMapper;
     private final IApproveNodeService approveNodeService;
     private final SysUserMapper sysUserMapper;
@@ -70,11 +61,10 @@
     private final CommonFileServiceImpl commonFileService;
     private final ISysNoticeService sysNoticeService;
     private final PurchaseLedgerMapper purchaseLedgerMapper;
-    private final SalesLedgerProductMapper salesLedgerProductMapper;
-    private final StockUtils stockUtils;
     private final ShippingInfoMapper shippingInfoMapper;
     private final ApproveNodeMapper approveNodeMapper;
     private final ApproveProcessConfigNodeService approveProcessConfigNodeService;
+    private final ApproveBusinessStatusService approveBusinessStatusService;
     private final FileUtil fileUtil;
     private final ApproveProcessConfigNodeMapper approveProcessConfigNodeMapper;
 
@@ -82,63 +72,34 @@
     public void addApprove(ApproveProcessVO approveProcessVO) throws Exception {
         SysUser sysUser = SecurityUtils.getLoginUser().getUser();
         SysDept sysDept = sysDeptMapper.selectDeptById(SecurityUtils.getLoginUser().getCurrentDeptId());
-        List<ApproveProcessConfigNodeVo> list = approveProcessConfigNodeService.listNode(approveProcessVO.getApproveType());
+        if (sysDept == null) throw new RuntimeException("閮ㄩ棬涓嶅瓨鍦�");
+        if (sysUser == null) throw new RuntimeException("鐢宠浜轰笉瀛樺湪");
+
+        List<ApproveProcessConfigNodeVo> list = Optional.ofNullable(approveProcessConfigNodeService.listNode(approveProcessVO.getApproveType()))
+                .orElse(Collections.emptyList());
         List<Long> nodeIds = list.stream()
                 .map(ApproveProcessConfigNodeVo::getApproverId)
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
-        // 鏃犲鏍镐汉閫昏緫娣诲姞
+
+        // 瀹℃壒閰嶇疆娌℃湁鏈夋晥瀹℃牳浜烘椂锛屼笉鏂板鍗忓悓瀹℃壒娴佺▼锛岀洿鎺ユ墽琛屼笟鍔″鏍搁�氳繃閫昏緫銆�
         if (CollectionUtils.isEmpty(nodeIds)) {
-            autoPassPurchaseApproveIfNoApprover(approveProcessVO); // 閲囪喘鍗曟棤瀹℃牳浜洪�昏緫
+            approveBusinessStatusService.syncBusinessStatus(approveProcessVO.getApproveType(), approveProcessVO.getApproveReason(), 2);
             return;
         }
+
         List<SysUser> sysUsers = sysUserMapper.selectUserByIds(nodeIds);
         if (CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("瀹℃牳鐢ㄦ埛涓嶅瓨鍦�");
-        if (sysDept == null) throw new RuntimeException("閮ㄩ棬涓嶅瓨鍦�");
-        if (sysUser == null) throw new RuntimeException("鐢宠浜轰笉瀛樺湪");
-        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
-        ApproveProcess approveProcess = new ApproveProcess();
-        String no = OrderUtils.countTodayByCreateTime(approveProcessMapper, "", "approve_id");
-        approveProcess.setApproveId(no);
-        approveProcess.setApproveUser(sysUser.getUserId());
-        approveProcess.setApproveUserName(sysUser.getNickName());
-        approveProcess.setApproveDeptId(sysDept.getDeptId());
-        approveProcess.setApproveUserIds(nodeIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
-        approveProcess.setApproveDeptName(sysDept.getDeptName());
-        approveProcess.setApproveUserNames(sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
-        approveProcess.setApproveTime(StringUtils.isEmpty(approveProcessVO.getApproveTime()) ? new Date() : dateFormat.parse(approveProcessVO.getApproveTime()));
-        approveProcess.setApproveReason(approveProcessVO.getApproveReason());
-        approveProcess.setDeviceRepairId(approveProcessVO.getDeviceRepairId());
-        approveProcess.setMaintenancePrice(approveProcessVO.getMaintenancePrice());
-        approveProcess.setPrice(approveProcessVO.getPrice());
-        approveProcess.setStartDate(approveProcessVO.getStartDate());
-        approveProcess.setEndDate(approveProcessVO.getEndDate());
-        approveProcess.setStartDateTime(approveProcessVO.getStartDateTime());
-        approveProcess.setEndDateTime(approveProcessVO.getEndDateTime());
-        approveProcess.setApproveStatus(0);
-        approveProcess.setApproveDelete(0);
-        approveProcess.setApproveType(approveProcessVO.getApproveType());
-        approveProcess.setCreateTime(LocalDateTime.now());
-        approveProcess.setTenantId(approveProcessVO.getApproveDeptId());
-        approveProcess.setApproveUserCurrentId(nodeIds.get(0));
-        approveProcess.setApproveUserCurrentName(sysUsers
-                .stream()
-                .filter(SysUser -> SysUser.getUserId().equals(nodeIds.get(0)))
-                .collect(Collectors.toList())
-                .get(0)
-                .getNickName());
-        // 璁剧疆鐘舵�佷负閲嶆柊鎻愪氦
-        if (approveProcessVO.getId() != null) {
-            ApproveProcess approveProcess1 = approveProcessMapper.selectById(approveProcessVO.getId());
-            approveProcess1.setApproveStatus(4);
-            approveProcessMapper.updateById(approveProcess1);
-        }
+
+        // 鏈夊鏍镐汉鏃讹紝鎸夋甯稿崗鍚屽鎵规祦绋嬪垱寤哄鎵逛富琛ㄣ�佸鎵硅妭鐐瑰苟閫氱煡棣栦釜瀹℃牳浜恒��
+        ApproveProcess approveProcess = buildApproveProcess(approveProcessVO, sysUser, sysDept, nodeIds, sysUsers, 0);
+        markResubmitted(approveProcessVO);
         save(approveProcess);
         //鍒濆鍖栧鎵硅妭鐐�
         String nodeIdStr = nodeIds.stream()
                 .map(String::valueOf)
                 .collect(Collectors.joining(","));
-        approveNodeService.initApproveNodes(nodeIdStr, no, approveProcessVO.getApproveDeptId());
+        approveNodeService.initApproveNodes(nodeIdStr, approveProcess.getApproveId(), approveProcessVO.getApproveDeptId());
         // 闄勪欢缁戝畾
         fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_PROCESS, approveProcess.getId(), approveProcessVO.getStorageBlobDTOS());
         /*娑堟伅閫氱煡*/
@@ -156,24 +117,56 @@
         }
     }
 
-    private void autoPassPurchaseApproveIfNoApprover(ApproveProcessVO approveProcessVO) {
-        if (!Objects.equals(approveProcessVO.getApproveType(), 5)
-                || !StringUtils.hasText(approveProcessVO.getApproveReason())) {
-            throw new RuntimeException("瀹℃牳鐢ㄦ埛涓嶅瓨鍦�");
+    private ApproveProcess buildApproveProcess(ApproveProcessVO approveProcessVO, SysUser sysUser, SysDept sysDept,
+                                               List<Long> nodeIds, List<SysUser> sysUsers, Integer approveStatus) throws Exception {
+        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+        ApproveProcess approveProcess = new ApproveProcess();
+        String no = OrderUtils.countTodayByCreateTime(approveProcessMapper, "", "approve_id", approveProcess.getCreateTime() != null ? approveProcess.getCreateTime() : LocalDateTime.now());
+        approveProcess.setApproveId(no);
+        approveProcess.setApproveUser(sysUser.getUserId());
+        approveProcess.setApproveUserName(sysUser.getNickName());
+        approveProcess.setApproveDeptId(sysDept.getDeptId());
+        approveProcess.setApproveDeptName(sysDept.getDeptName());
+        approveProcess.setApproveUserIds(nodeIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
+        approveProcess.setApproveUserNames(sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
+        approveProcess.setApproveTime(StringUtils.isEmpty(approveProcessVO.getApproveTime()) ? new Date() : dateFormat.parse(approveProcessVO.getApproveTime()));
+        approveProcess.setApproveReason(approveProcessVO.getApproveReason());
+        approveProcess.setDeviceRepairId(approveProcessVO.getDeviceRepairId());
+        approveProcess.setMaintenancePrice(approveProcessVO.getMaintenancePrice());
+        approveProcess.setPrice(approveProcessVO.getPrice());
+        approveProcess.setStartDate(approveProcessVO.getStartDate());
+        approveProcess.setEndDate(approveProcessVO.getEndDate());
+        approveProcess.setStartDateTime(approveProcessVO.getStartDateTime());
+        approveProcess.setEndDateTime(approveProcessVO.getEndDateTime());
+        approveProcess.setApproveStatus(approveStatus);
+        approveProcess.setApproveDelete(0);
+        approveProcess.setApproveType(approveProcessVO.getApproveType());
+        approveProcess.setCreateTime(LocalDateTime.now());
+        approveProcess.setTenantId(approveProcessVO.getApproveDeptId());
+        if (!CollectionUtils.isEmpty(nodeIds)) {
+            SysUser currentUser = sysUsers.stream()
+                    .filter(user -> user.getUserId().equals(nodeIds.get(0)))
+                    .findFirst()
+                    .orElseThrow(() -> new RuntimeException("瀹℃牳鐢ㄦ埛涓嶅瓨鍦�"));
+            approveProcess.setApproveUserCurrentId(currentUser.getUserId());
+            approveProcess.setApproveUserCurrentName(currentUser.getNickName());
         }
+        if (approveStatus.equals(2) || approveStatus.equals(3) || approveStatus.equals(4)) {
+            approveProcess.setApproveOverTime(new Date());
+        }
+        return approveProcess;
+    }
 
-        purchaseLedgerMapper.update(null, new LambdaUpdateWrapper<PurchaseLedger>()
-                .eq(PurchaseLedger::getPurchaseContractNumber, approveProcessVO.getApproveReason())
-                .set(PurchaseLedger::getApprovalStatus, 3));
-        //閲囪喘鍏ュ簱
-        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
-                .eq(PurchaseLedger::getPurchaseContractNumber, approveProcessVO.getApproveReason())
-                .last("limit 1"));
-        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
-                .lambda().eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId()).eq(SalesLedgerProduct::getType, 2));
-        for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
-            stockUtils.addStockWithBatchNo(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(),purchaseLedger.getPurchaseContractNumber()+"-"+salesLedgerProduct.getId());
+    private void markResubmitted(ApproveProcessVO approveProcessVO) {
+        if (approveProcessVO.getId() == null) {
+            return;
         }
+        ApproveProcess approveProcess = approveProcessMapper.selectById(approveProcessVO.getId());
+        if (approveProcess == null) {
+            return;
+        }
+        approveProcess.setApproveStatus(4);
+        approveProcessMapper.updateById(approveProcess);
     }
 
     @Override
diff --git a/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementDetailServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementDetailServiceImpl.java
new file mode 100644
index 0000000..1897440
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementDetailServiceImpl.java
@@ -0,0 +1,20 @@
+package com.ruoyi.approve.service.impl;
+
+import com.ruoyi.approve.pojo.FinReimbursementDetail;
+import com.ruoyi.approve.mapper.FinReimbursementDetailMapper;
+import com.ruoyi.approve.service.FinReimbursementDetailService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曟槑缁嗚〃 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:38
+ */
+@Service
+public class FinReimbursementDetailServiceImpl extends ServiceImpl<FinReimbursementDetailMapper, FinReimbursementDetail> implements FinReimbursementDetailService {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java
new file mode 100644
index 0000000..305ea9d
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java
@@ -0,0 +1,544 @@
+package com.ruoyi.approve.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
+import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeApproverDto;
+import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeDto;
+import com.ruoyi.approve.bean.dto.FinReimbursementDto;
+import com.ruoyi.approve.bean.vo.FinReimbursementVo;
+import com.ruoyi.approve.mapper.ApprovalInstanceMapper;
+import com.ruoyi.approve.mapper.FinReimbursementDetailMapper;
+import com.ruoyi.approve.mapper.FinReimbursementMapper;
+import com.ruoyi.approve.mapper.FinReimbursementTravelMapper;
+import com.ruoyi.approve.pojo.*;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.utils.FileUtil;
+import com.ruoyi.approve.service.*;
+import com.ruoyi.common.enums.TypeEnums;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.OrderUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.project.system.service.ISysNoticeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 鎶ラ攢鍗曚富琛� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:15
+ */
+@Service
+@RequiredArgsConstructor
+public class FinReimbursementServiceImpl extends ServiceImpl<FinReimbursementMapper, FinReimbursement> implements FinReimbursementService {
+
+    private static final String BILL_STATUS_DRAFT = "DRAFT";
+    private static final String BILL_STATUS_IN_APPROVAL = "IN_APPROVAL";
+    private static final String NODE_STATUS_WAITING = "WAITING";
+
+    private final ApprovalInstanceMapper approvalInstanceMapper;
+    private final ApprovalInstanceService approvalInstanceService;
+    private final ApprovalInstanceNodeService approvalInstanceNodeService;
+    private final ApprovalTaskService approvalTaskService;
+    private final ApprovalRecordService approvalRecordService;
+    private final FinReimbursementMapper finReimbursementMapper;
+    private final FinReimbursementTravelMapper finReimbursementTravelMapper;
+    private final FinReimbursementDetailMapper finReimbursementDetailMapper;
+    private final FileUtil fileUtil;
+    private final ISysNoticeService sysNoticeService;
+    @Override
+    public IPage<FinReimbursementVo> listPage(FinReimbursementDto finReimbursementDto, Page<FinReimbursementVo> page) {
+        IPage<FinReimbursementVo> finReimbursementVoIPage = finReimbursementMapper.listPage(finReimbursementDto, page);
+        finReimbursementVoIPage.getRecords().forEach(vo -> {
+            vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.FIN_REIMBURSEMENT,  vo.getId()));
+        });
+
+        return finReimbursementVoIPage;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean add(FinReimbursementDto finReimbursementDto) {
+        String billStatus = validateAddParam(finReimbursementDto);
+
+        // 鐢熸垚鎶ラ攢鍗曞彿
+        String billNo = OrderUtils.countTodayByCreateTime(finReimbursementMapper, "BXD", "bill_no", finReimbursementDto.getCreateTime() != null ? finReimbursementDto.getCreateTime() : LocalDateTime.now());
+        List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
+        BigDecimal totalAmount = details.stream()
+                .map(FinReimbursementDetail::getAmount)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        FinReimbursement reimbursement = buildReimbursement(finReimbursementDto, billNo, totalAmount, billStatus);
+        // 淇濆瓨鎶ラ攢鍗曚富琛�
+        boolean saved = this.save(reimbursement);
+        if (!saved || reimbursement.getId() == null) {
+            throw new ServiceException("鏂板鎶ラ攢鍗曞け璐�");
+        }
+        Long reimbursementId = reimbursement.getId();
+
+        // 淇濆瓨宸梾鎶ラ攢鎵╁睍淇℃伅锛堟姤閿�绫诲瀷涓哄樊鏃呮姤閿�鏃讹級
+        FinReimbursementTravel travel = finReimbursementDto.getTravel();
+        if (isTravelReimbursement(finReimbursementDto.getReimbursementType())) {
+            travel.setReimbursementId(reimbursementId);
+            int travelRows = finReimbursementTravelMapper.insert(travel);
+            if (travelRows != 1) {
+                throw new ServiceException("鏂板宸梾鎶ラ攢鎵╁睍淇℃伅澶辫触");
+            }
+        }
+
+        // 淇濆瓨鎶ラ攢鍗曟槑缁�
+        for (int i = 0; i < details.size(); i++) {
+            FinReimbursementDetail detail = details.get(i);
+            detail.setId(null);
+            detail.setReimbursementId(reimbursementId);
+            detail.setRowNo(i + 1);
+            int detailRows = finReimbursementDetailMapper.insert(detail);
+            if (detailRows != 1) {
+                throw new ServiceException("鏂板鎶ラ攢鍗曟槑缁嗗け璐�");
+            }
+        }
+
+        if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
+            startApproval(reimbursement, finReimbursementDto);
+        }
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_REIMBURSEMENT, reimbursementId, finReimbursementDto.getStorageBlobDTOs());
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean update(FinReimbursementDto finReimbursementDto) {
+        String billStatus = validateUpdateParam(finReimbursementDto);
+
+        Long reimbursementId = finReimbursementDto.getId();
+        FinReimbursement existing = finReimbursementMapper.selectById(reimbursementId);
+        if (existing == null) {
+            throw new ServiceException("鎶ラ攢鍗曚笉瀛樺湪");
+        }
+
+        // 璁$畻鏄庣粏姹囨�婚噾棰�
+        List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
+        BigDecimal totalAmount = details.stream()
+                .map(FinReimbursementDetail::getAmount)
+                .filter(Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        // 鏇存柊涓昏〃
+        FinReimbursement reimbursement = buildReimbursement(
+                finReimbursementDto,
+                existing.getBillNo(),
+                totalAmount,
+                billStatus
+        );
+        reimbursement.setId(reimbursementId);
+        int mainRows = finReimbursementMapper.updateById(reimbursement);
+        if (mainRows != 1) {
+            throw new ServiceException("鏇存柊鎶ラ攢鍗曚富琛ㄥけ璐�");
+        }
+
+        // 鏌ヨ鏁版嵁搴撲腑宸叉湁鐨勬槑缁�
+        List<FinReimbursementDetail> existingDetails = finReimbursementDetailMapper.selectList(
+                new LambdaQueryWrapper<FinReimbursementDetail>()
+                        .eq(FinReimbursementDetail::getReimbursementId, reimbursementId));
+        Set<Long> existingDetailIds = existingDetails.stream()
+                .map(FinReimbursementDetail::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        // 鏂版槑缁嗕腑鏈塈D鐨� 鈫� 鏇存柊锛涙棤ID鐨� 鈫� 鏂板
+        Set<Long> submittedDetailIds = new HashSet<>();
+        for (int i = 0; i < details.size(); i++) {
+            FinReimbursementDetail detail = details.get(i);
+            detail.setReimbursementId(reimbursementId);
+            detail.setRowNo(i + 1);
+            if (detail.getId() != null && existingDetailIds.contains(detail.getId())) {
+                finReimbursementDetailMapper.updateById(detail);
+                submittedDetailIds.add(detail.getId());
+            } else {
+                detail.setId(null);
+                finReimbursementDetailMapper.insert(detail);
+            }
+        }
+
+        // 鏁版嵁搴撲腑宸叉湁浣嗘柊鏄庣粏涓病鏈夌殑 鈫� 鍒犻櫎
+        for (Long existingId : existingDetailIds) {
+            if (!submittedDetailIds.contains(existingId)) {
+                finReimbursementDetailMapper.deleteById(existingId);
+            }
+        }
+
+        // 宸梾鎵╁睍锛氭湁鍒欐洿鏂帮紝鏃犲垯鏂板
+        FinReimbursementTravel existingTravel = finReimbursementTravelMapper.selectOne(
+                new LambdaQueryWrapper<FinReimbursementTravel>()
+                        .eq(FinReimbursementTravel::getReimbursementId, reimbursementId)
+                        .last("LIMIT 1"));
+        FinReimbursementTravel travel = finReimbursementDto.getTravel();
+        if (isTravelReimbursement(finReimbursementDto.getReimbursementType()) && travel != null) {
+            travel.setReimbursementId(reimbursementId);
+            if (existingTravel != null) {
+                travel.setId(existingTravel.getId());
+                finReimbursementTravelMapper.updateById(travel);
+            } else {
+                travel.setId(null);
+                finReimbursementTravelMapper.insert(travel);
+            }
+        }
+
+        resetApprovalFlow(existing, reimbursementId);
+        if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
+            reimbursement.setApprovalInstanceId(null);
+            startApproval(reimbursement, finReimbursementDto);
+        }
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_REIMBURSEMENT, reimbursementId, finReimbursementDto.getStorageBlobDTOs());
+
+        return true;
+    }
+
+    @Override
+    public FinReimbursementVo detail(Long id) {
+        if (id == null ) {
+            throw new ServiceException("鎶ラ攢鍗旾D涓嶈兘涓虹┖");
+        }
+
+        FinReimbursement reimbursement = finReimbursementMapper.selectById(id);
+        if (reimbursement == null) {
+            throw new ServiceException("鎶ラ攢鍗曚笉瀛樺湪");
+        }
+
+        FinReimbursementVo vo = new FinReimbursementVo();
+        vo.setId(reimbursement.getId());
+        vo.setBillNo(reimbursement.getBillNo());
+        vo.setReimbursementType(reimbursement.getReimbursementType());
+        vo.setExpenseType(reimbursement.getExpenseType());
+        vo.setApplicantId(reimbursement.getApplicantId());
+        vo.setApplicantCode(reimbursement.getApplicantCode());
+        vo.setApplicantName(reimbursement.getApplicantName());
+        vo.setApplicantDeptId(reimbursement.getApplicantDeptId());
+        vo.setApplicantDeptName(reimbursement.getApplicantDeptName());
+        vo.setReason(reimbursement.getReason());
+        vo.setApplyAmount(reimbursement.getApplyAmount());
+        vo.setDetailTotalAmount(reimbursement.getDetailTotalAmount());
+        vo.setPayeeName(reimbursement.getPayeeName());
+        vo.setPayeeAccount(reimbursement.getPayeeAccount());
+        vo.setPayeeBank(reimbursement.getPayeeBank());
+        vo.setApprovalInstanceId(reimbursement.getApprovalInstanceId());
+        vo.setApproveProcessId(reimbursement.getApproveProcessId());
+        vo.setBillStatus(reimbursement.getBillStatus());
+        vo.setApprovedTime(reimbursement.getApprovedTime());
+        vo.setPaidTime(reimbursement.getPaidTime());
+        vo.setAccountExpenseId(reimbursement.getAccountExpenseId());
+        vo.setRemark(reimbursement.getRemark());
+        vo.setTenantId(reimbursement.getTenantId());
+        vo.setCreateUser(reimbursement.getCreateUser());
+        vo.setCreateTime(reimbursement.getCreateTime());
+        vo.setUpdateUser(reimbursement.getUpdateUser());
+        vo.setUpdateTime(reimbursement.getUpdateTime());
+        vo.setDeptId(reimbursement.getDeptId());
+        vo.setDeleted(reimbursement.getDeleted());
+
+        vo.setDetails(finReimbursementDetailMapper.selectList(
+                new LambdaQueryWrapper<FinReimbursementDetail>()
+                        .eq(FinReimbursementDetail::getReimbursementId, reimbursement.getId())
+                        .orderByAsc(FinReimbursementDetail::getRowNo)
+        ));
+
+        if (isTravelReimbursement(reimbursement.getReimbursementType())) {
+            vo.setTravel(finReimbursementTravelMapper.selectOne(
+                    new LambdaQueryWrapper<FinReimbursementTravel>()
+                            .eq(FinReimbursementTravel::getReimbursementId, reimbursement.getId())
+                            .last("LIMIT 1")
+            ));
+        }
+        vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.FIN_REIMBURSEMENT, reimbursement.getId()));
+        //瀹℃壒璁板綍杩斿洖
+        vo.setTasks(approvalTaskService.list(new LambdaQueryWrapper<ApprovalTask>().eq(ApprovalTask::getInstanceId, reimbursement.getApprovalInstanceId())));
+        vo.setRecords(approvalRecordService.list(new LambdaQueryWrapper<ApprovalRecord>().eq(ApprovalRecord::getInstanceId, reimbursement.getApprovalInstanceId())));
+        return vo;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean delete(List<Long> ids) {
+        fileUtil.deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_REIMBURSEMENT, ids);
+        //鍏堝垹闄ゆ槑缁�
+        finReimbursementDetailMapper.delete(new LambdaQueryWrapper<FinReimbursementDetail>().in(FinReimbursementDetail::getReimbursementId, ids));
+        //鍒犻櫎宸梾
+        finReimbursementTravelMapper.delete(new LambdaQueryWrapper<FinReimbursementTravel>().in(FinReimbursementTravel::getReimbursementId, ids));
+        //鍒犻櫎涓昏〃
+        int rows = finReimbursementMapper.delete(new LambdaQueryWrapper<FinReimbursement>().in(FinReimbursement::getId, ids));
+        return rows == ids.size();
+    }
+
+    private String validateUpdateParam(FinReimbursementDto finReimbursementDto) {
+        if (finReimbursementDto == null || finReimbursementDto.getId() == null) {
+            throw new ServiceException("鎶ラ攢鍗旾D涓嶈兘涓虹┖");
+        }
+        if (finReimbursementDto.getReimbursementType() == null) {
+            throw new ServiceException("鎶ラ攢绫诲瀷涓嶈兘涓虹┖");
+        }
+        String billStatus = normalizeBillStatus(finReimbursementDto.getBillStatus());
+        if (billStatus == null) {
+            throw new ServiceException("鍗曟嵁鐘舵�佸彧鏀寔 DRAFT 鎴� IN_APPROVAL");
+        }
+        if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
+            validateApprovalNodes(finReimbursementDto.getNodes());
+        }
+        List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
+        if (details == null || details.isEmpty()) {
+            throw new ServiceException("鎶ラ攢鍗曟槑缁嗕笉鑳戒负绌�");
+        }
+        for (FinReimbursementDetail detail : details) {
+            if (detail == null) {
+                throw new ServiceException("鎶ラ攢鍗曟槑缁嗕笉鑳戒负绌�");
+            }
+            if (detail.getAmount() == null || detail.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
+                throw new ServiceException("鎶ラ攢鍗曟槑缁嗛噾棰濆繀椤诲ぇ浜�0");
+            }
+        }
+        if (isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() == null) {
+            throw new ServiceException("宸梾鎶ラ攢蹇呴』濉啓宸梾鎵╁睍淇℃伅");
+        }
+        if (!isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() != null) {
+            throw new ServiceException("闈炲樊鏃呮姤閿�涓嶅厑璁稿~鍐欏樊鏃呮墿灞曚俊鎭�");
+        }
+        return billStatus;
+    }
+
+    private String validateAddParam(FinReimbursementDto finReimbursementDto) {
+        if (finReimbursementDto == null) {
+            throw new ServiceException("鎶ラ攢鍗曟暟鎹笉鑳戒负绌�");
+        }
+        if (finReimbursementDto.getReimbursementType() == null) {
+            throw new ServiceException("鎶ラ攢绫诲瀷涓嶈兘涓虹┖");
+        }
+        String billStatus = normalizeBillStatus(finReimbursementDto.getBillStatus());
+        if (billStatus == null) {
+            throw new ServiceException("鍗曟嵁鐘舵�佸彧鏀寔 DRAFT 鎴� IN_APPROVAL");
+        }
+        if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
+            validateApprovalNodes(finReimbursementDto.getNodes());
+        }
+        List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
+        if (details == null || details.isEmpty()) {
+            throw new ServiceException("鎶ラ攢鍗曟槑缁嗕笉鑳戒负绌�");
+        }
+        for (FinReimbursementDetail detail : details) {
+            if (detail == null) {
+                throw new ServiceException("鎶ラ攢鍗曟槑缁嗕笉鑳戒负绌�");
+            }
+            if (detail.getAmount() == null || detail.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
+                throw new ServiceException("鎶ラ攢鍗曟槑缁嗛噾棰濆繀椤诲ぇ浜�0");
+            }
+        }
+        if (isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() == null) {
+            throw new ServiceException("宸梾鎶ラ攢蹇呴』濉啓宸梾鎵╁睍淇℃伅");
+        }
+        if (!isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() != null) {
+            throw new ServiceException("闈炲樊鏃呮姤閿�涓嶅厑璁稿~鍐欏樊鏃呮墿灞曚俊鎭�");
+        }
+        return billStatus;
+    }
+
+    private FinReimbursement buildReimbursement(FinReimbursementDto finReimbursementDto, String billNo, BigDecimal totalAmount, String billStatus) {
+        FinReimbursement reimbursement = new FinReimbursement();
+        reimbursement.setId(null);
+        reimbursement.setBillNo(billNo);
+        reimbursement.setReimbursementType(finReimbursementDto.getReimbursementType());
+        reimbursement.setExpenseType(finReimbursementDto.getExpenseType());
+        reimbursement.setApplicantId(finReimbursementDto.getApplicantId());
+        reimbursement.setApplicantCode(finReimbursementDto.getApplicantCode());
+        reimbursement.setApplicantName(finReimbursementDto.getApplicantName());
+        reimbursement.setApplicantDeptId(finReimbursementDto.getApplicantDeptId());
+        reimbursement.setApplicantDeptName(finReimbursementDto.getApplicantDeptName());
+        reimbursement.setReason(finReimbursementDto.getReason());
+        reimbursement.setApplyAmount(finReimbursementDto.getApplyAmount());
+        reimbursement.setDetailTotalAmount(totalAmount);
+        reimbursement.setPayeeName(finReimbursementDto.getPayeeName());
+        reimbursement.setPayeeAccount(finReimbursementDto.getPayeeAccount());
+        reimbursement.setPayeeBank(finReimbursementDto.getPayeeBank());
+        reimbursement.setRemark(finReimbursementDto.getRemark());
+        reimbursement.setTenantId(finReimbursementDto.getTenantId());
+        reimbursement.setApproveProcessId(null);
+        reimbursement.setBillStatus(billStatus);
+        return reimbursement;
+    }
+
+    private void startApproval(FinReimbursement reimbursement, FinReimbursementDto finReimbursementDto) {
+        Long businessType = resolveBusinessType(finReimbursementDto.getReimbursementType());
+        ApprovalInstanceDto approvalInstanceDto = new ApprovalInstanceDto();
+        approvalInstanceDto.setInstanceNo(OrderUtils.countTodayByCreateTime(approvalInstanceMapper, "SP", "instance_no", approvalInstanceDto.getCreateTime() != null ? approvalInstanceDto.getCreateTime() : LocalDateTime.now()));
+        approvalInstanceDto.setBusinessId(reimbursement.getId());
+        approvalInstanceDto.setTemplateId(null);
+        approvalInstanceDto.setTemplateName(TypeEnums.getLabelByValue(businessType) + "瀹℃壒");
+        approvalInstanceDto.setBusinessType(businessType);
+        approvalInstanceDto.setTitle("鎶ラ攢鍗曞彿锛�" + reimbursement.getBillNo());
+        approvalInstanceDto.setApplicantId(reimbursement.getApplicantId() != null ? reimbursement.getApplicantId() : SecurityUtils.getUserId());
+        approvalInstanceDto.setApplicantName(reimbursement.getApplicantName() != null ? reimbursement.getApplicantName() : SecurityUtils.getLoginUser().getNickName());
+        approvalInstanceDto.setApplyTime(LocalDateTime.now());
+        approvalInstanceDto.setStatus("PENDING");
+        approvalInstanceDto.setCurrentLevel(1);
+
+        boolean approvalSaved = approvalInstanceService.save(approvalInstanceDto);
+        if (!approvalSaved || approvalInstanceDto.getId() == null) {
+            throw new ServiceException("鍙戣捣瀹℃壒澶辫触");
+        }
+        List<ApprovalTask> firstTasks = createApprovalNodes(approvalInstanceDto, finReimbursementDto.getNodes());
+        sendApproveNotice(approvalInstanceDto, firstTasks);
+
+        FinReimbursement update = new FinReimbursement();
+        update.setId(reimbursement.getId());
+        update.setApprovalInstanceId(approvalInstanceDto.getId());
+        update.setBillStatus(BILL_STATUS_IN_APPROVAL);
+        int rows = finReimbursementMapper.updateById(update);
+        if (rows != 1) {
+            throw new ServiceException("鍥炲~瀹℃壒瀹炰緥澶辫触");
+        }
+    }
+
+    private List<ApprovalTask> createApprovalNodes(ApprovalInstanceDto approvalInstanceDto, List<ApprovalTemplateNodeDto> nodes) {
+        List<ApprovalTask> firstTasks = Collections.emptyList();
+        for (int i = 0; i < nodes.size(); i++) {
+            ApprovalTemplateNodeDto nodeDto = nodes.get(i);
+            ApprovalInstanceNode instanceNode = new ApprovalInstanceNode();
+            instanceNode.setInstanceId(approvalInstanceDto.getId());
+            instanceNode.setLevelNo(nodeDto.getLevelNo());
+            instanceNode.setApproveType(nodeDto.getApproveType());
+            instanceNode.setStatus(i == 0 ? "PENDING" : NODE_STATUS_WAITING);
+            instanceNode.setStartTime(i == 0 ? LocalDateTime.now() : null);
+            instanceNode.setDeleted((byte) 0);
+            approvalInstanceNodeService.save(instanceNode);
+
+            List<ApprovalTask> tasks = nodeDto.getApprovers().stream().map(approver -> {
+                ApprovalTask task = new ApprovalTask();
+                task.setInstanceId(approvalInstanceDto.getId());
+                task.setNodeId(instanceNode.getId());
+                task.setLevelNo(instanceNode.getLevelNo());
+                task.setApproverId(approver.getApproverId());
+                task.setApproverName(approver.getApproverName());
+                task.setTaskStatus("PENDING");
+                task.setIsRead((byte) 0);
+                task.setDeleted((byte) 0);
+                return task;
+            }).collect(Collectors.toList());
+            approvalTaskService.saveBatch(tasks);
+
+            if (i == 0) {
+                firstTasks = tasks;
+                ApprovalRecord record = new ApprovalRecord();
+                record.setInstanceId(approvalInstanceDto.getId());
+                record.setNodeId(instanceNode.getId());
+                record.setOperatorId(approvalInstanceDto.getApplicantId());
+                record.setOperatorName(approvalInstanceDto.getApplicantName());
+                record.setAction("SUBMIT");
+                record.setComment("鍙戣捣瀹℃壒");
+                record.setDeleted((byte) 0);
+                approvalRecordService.save(record);
+            }
+        }
+        return firstTasks;
+    }
+
+    private void validateApprovalNodes(List<ApprovalTemplateNodeDto> nodes) {
+        if (nodes == null || nodes.isEmpty()) {
+            throw new ServiceException("鎻愪氦瀹℃壒鏃跺鎵硅妭鐐逛笉鑳戒负绌�");
+        }
+        for (int i = 0; i < nodes.size(); i++) {
+            ApprovalTemplateNodeDto node = nodes.get(i);
+            if (node == null) {
+                throw new ServiceException("瀹℃壒鑺傜偣涓嶈兘涓虹┖");
+            }
+            if (node.getLevelNo() == null) {
+                node.setLevelNo(i + 1);
+            }
+            if (!StringUtils.hasText(node.getApproveType())) {
+                throw new ServiceException("瀹℃壒鑺傜偣瀹℃壒鏂瑰紡涓嶈兘涓虹┖");
+            }
+            List<ApprovalTemplateNodeApproverDto> approvers = node.getApprovers();
+            if (approvers == null || approvers.isEmpty()) {
+                throw new ServiceException("瀹℃壒鑺傜偣瀹℃壒浜轰笉鑳戒负绌�");
+            }
+            for (ApprovalTemplateNodeApproverDto approver : approvers) {
+                if (approver == null || approver.getApproverId() == null) {
+                    throw new ServiceException("瀹℃壒浜轰笉鑳戒负绌�");
+                }
+            }
+        }
+    }
+
+    private void sendApproveNotice(ApprovalInstanceDto instance, List<ApprovalTask> tasks) {
+        if (instance == null || tasks == null || tasks.isEmpty()) {
+            return;
+        }
+        List<Long> approverIds = tasks.stream()
+                .map(ApprovalTask::getApproverId)
+                .filter(id -> id != null && id > 0)
+                .distinct()
+                .collect(Collectors.toList());
+        if (approverIds.isEmpty()) {
+            return;
+        }
+        String title = "鎶ラ攢瀹℃壒";
+        String message = "瀹℃壒鍗曞彿 " + instance.getInstanceNo() + " 闇�瑕佹偍瀹℃壒";
+        String jumpPath = "/approvalInstance?id=" + instance.getId();
+        sysNoticeService.simpleNoticeByUser(title, message, approverIds, jumpPath);
+    }
+
+    private void resetApprovalFlow(FinReimbursement existing, Long reimbursementId) {
+        if (existing == null || existing.getApprovalInstanceId() == null) {
+            return;
+        }
+        Long approvalInstanceId = existing.getApprovalInstanceId();
+        if (!"REJECTED".equals(existing.getBillStatus())) {
+            approvalInstanceService.delete(Collections.singletonList(approvalInstanceId));
+        }
+        clearApprovalBinding(reimbursementId);
+    }
+
+    private void clearApprovalBinding(Long reimbursementId) {
+        int rows = finReimbursementMapper.update(
+                null,
+                Wrappers.<FinReimbursement>lambdaUpdate()
+                        .eq(FinReimbursement::getId, reimbursementId)
+                        .set(FinReimbursement::getApprovalInstanceId, null)
+        );
+        if (rows != 1) {
+            throw new ServiceException("閲嶇疆瀹℃壒娴佺▼澶辫触");
+        }
+    }
+
+    private Long resolveBusinessType(Byte reimbursementType) {
+        return isTravelReimbursement(reimbursementType)
+                ? TypeEnums.TRAVEL_REIMBURSEMENT_APPROVAL.getCode()
+                : TypeEnums.EXPENSE_APPROVAL.getCode();
+    }
+
+    private String normalizeBillStatus(String billStatus) {
+        if (billStatus == null) {
+            return BILL_STATUS_DRAFT;
+        }
+        String normalized = billStatus.trim().toUpperCase();
+        if (BILL_STATUS_DRAFT.equals(normalized) || BILL_STATUS_IN_APPROVAL.equals(normalized)) {
+            return normalized;
+        }
+        return null;
+    }
+
+    private boolean isTravelReimbursement(Byte reimbursementType) {
+        return Byte.valueOf((byte) 1).equals(reimbursementType);
+    }
+}
diff --git a/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementTravelServiceImpl.java b/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementTravelServiceImpl.java
new file mode 100644
index 0000000..3329f9a
--- /dev/null
+++ b/src/main/java/com/ruoyi/approve/service/impl/FinReimbursementTravelServiceImpl.java
@@ -0,0 +1,20 @@
+package com.ruoyi.approve.service.impl;
+
+import com.ruoyi.approve.pojo.FinReimbursementTravel;
+import com.ruoyi.approve.mapper.FinReimbursementTravelMapper;
+import com.ruoyi.approve.service.FinReimbursementTravelService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 宸梾鎶ラ攢鎵╁睍琛� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-21 09:56:47
+ */
+@Service
+public class FinReimbursementTravelServiceImpl extends ServiceImpl<FinReimbursementTravelMapper, FinReimbursementTravel> implements FinReimbursementTravelService {
+
+}
diff --git a/src/main/java/com/ruoyi/approve/utils/ApproveProcessConfigNodeUtils.java b/src/main/java/com/ruoyi/approve/utils/ApproveProcessConfigNodeUtils.java
index 4ae03e6..239726a 100644
--- a/src/main/java/com/ruoyi/approve/utils/ApproveProcessConfigNodeUtils.java
+++ b/src/main/java/com/ruoyi/approve/utils/ApproveProcessConfigNodeUtils.java
@@ -1,9 +1,370 @@
 package com.ruoyi.approve.utils;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ruoyi.approve.pojo.ApprovalInstance;
+import com.ruoyi.approve.pojo.ApprovalInstanceNode;
+import com.ruoyi.approve.pojo.ApprovalRecord;
+import com.ruoyi.approve.pojo.ApprovalTask;
+import com.ruoyi.approve.pojo.ApprovalTemplateNode;
+import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
+import com.ruoyi.approve.service.ApprovalInstanceNodeService;
+import com.ruoyi.approve.service.ApprovalRecordService;
+import com.ruoyi.approve.service.ApprovalTaskService;
+import com.ruoyi.approve.service.ApprovalTemplateNodeApproverService;
+import com.ruoyi.approve.service.ApprovalTemplateNodeService;
 import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 瀹℃壒娴佺▼鑺傜偣宸ュ叿绫�
+ */
+@Component
 @RequiredArgsConstructor
 public class ApproveProcessConfigNodeUtils {
 
+    private final ApprovalInstanceNodeService instanceNodeService;
+    private final ApprovalTaskService approvalTaskService;
+    private final ApprovalRecordService approvalRecordService;
+    private final ApprovalTemplateNodeService approvalTemplateNodeService;
+    private final ApprovalTemplateNodeApproverService approvalTemplateNodeApproverService;
 
-}
+    /**
+     * 鎸夊綋鍓嶅眰绾у垱寤哄鎵硅妭鐐瑰拰瀹℃壒浠诲姟銆�
+     * 璇ラ噸杞戒細鍚屾椂鍐欏叆涓�鏉″彂璧峰鎵硅褰曘��
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public ApprovalInstanceNode createCurrentNodeAndTasks(ApprovalInstance instance) {
+        return createCurrentNodeAndTasks(instance, true);
+    }
+
+    /**
+     * 鎸夊綋鍓嶅眰绾у垱寤哄鎵硅妭鐐瑰拰瀹℃壒浠诲姟銆�
+     *
+     * @param instance 瀹℃壒瀹炰緥
+     * @param createSubmitRecord 鏄惁鍒涘缓鍙戣捣瀹℃壒璁板綍
+     * @return 鍒涘缓鍑虹殑褰撳墠鑺傜偣瀹炰緥
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public ApprovalInstanceNode createCurrentNodeAndTasks(ApprovalInstance instance, boolean createSubmitRecord) {
+        if (instance == null || instance.getId() == null) {
+            throw new RuntimeException("瀹℃壒瀹炰緥涓嶈兘涓虹┖");
+        }
+        if (instance.getTemplateId() == null) {
+            throw new RuntimeException("瀹℃壒妯℃澘涓嶈兘涓虹┖");
+        }
+
+        Integer currentLevel = instance.getCurrentLevel() == null ? 1 : instance.getCurrentLevel();
+
+        ApprovalInstanceNode existsNode = instanceNodeService.getOne(
+                new LambdaQueryWrapper<ApprovalInstanceNode>()
+                        .eq(ApprovalInstanceNode::getInstanceId, instance.getId())
+                        .eq(ApprovalInstanceNode::getLevelNo, currentLevel)
+                        .eq(ApprovalInstanceNode::getDeleted, 0)
+                        .last("LIMIT 1")
+        );
+        if (existsNode != null) {
+            return existsNode;
+        }
+
+        ApprovalTemplateNode templateNode = approvalTemplateNodeService.getOne(
+                new LambdaQueryWrapper<ApprovalTemplateNode>()
+                        .eq(ApprovalTemplateNode::getTemplateId, instance.getTemplateId())
+                        .eq(ApprovalTemplateNode::getLevelNo, currentLevel)
+                        .orderByAsc(ApprovalTemplateNode::getId)
+                        .last("LIMIT 1")
+        );
+        if (templateNode == null) {
+            throw new RuntimeException("鏈壘鍒板綋鍓嶅眰绾у搴旂殑瀹℃壒妯℃澘鑺傜偣");
+        }
+
+        List<ApprovalTemplateNodeApprover> approvers = approvalTemplateNodeApproverService.list(
+                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
+                        .eq(ApprovalTemplateNodeApprover::getTemplateId, instance.getTemplateId())
+                        .eq(ApprovalTemplateNodeApprover::getNodeId, templateNode.getId())
+                        .eq(ApprovalTemplateNodeApprover::getDeleted, 0L)
+                        .orderByAsc(ApprovalTemplateNodeApprover::getSortNo)
+        );
+        if (approvers == null || approvers.isEmpty()) {
+            throw new RuntimeException("褰撳墠瀹℃壒鑺傜偣鏈厤缃鎵逛汉");
+        }
+
+        ApprovalInstanceNode instanceNode = new ApprovalInstanceNode();
+        instanceNode.setInstanceId(instance.getId());
+        instanceNode.setLevelNo(templateNode.getLevelNo());
+        instanceNode.setApproveType(templateNode.getApproveType());
+        instanceNode.setStatus("PENDING");
+        instanceNode.setStartTime(LocalDateTime.now());
+        instanceNode.setDeleted((byte) 0);
+        instanceNodeService.save(instanceNode);
+
+        List<ApprovalTask> taskList = new ArrayList<>(approvers.size());
+        for (ApprovalTemplateNodeApprover approver : approvers) {
+            ApprovalTask task = new ApprovalTask();
+            task.setInstanceId(instance.getId());
+            task.setNodeId(instanceNode.getId());
+            task.setLevelNo(instanceNode.getLevelNo());
+            task.setApproverId(approver.getApproverId());
+            task.setApproverName(approver.getApproverName());
+            task.setTaskStatus("PENDING");
+            task.setIsRead((byte) 0);
+            task.setDeleted((byte) 0);
+            taskList.add(task);
+        }
+        approvalTaskService.saveBatch(taskList);
+
+        if (createSubmitRecord) {
+            ApprovalRecord record = new ApprovalRecord();
+            record.setInstanceId(instance.getId());
+            record.setNodeId(instanceNode.getId());
+            record.setOperatorId(instance.getApplicantId());
+            record.setOperatorName(instance.getApplicantName());
+            record.setAction("SUBMIT");
+            record.setComment("鍙戣捣瀹℃壒");
+            record.setDeleted((byte) 0);
+            approvalRecordService.save(record);
+        }
+
+        return instanceNode;
+    }
+
+    /**
+     * 鏌ヨ褰撳墠寰呭鐞嗚妭鐐广��
+     */
+    public ApprovalInstanceNode getCurrentNode(Long instanceId) {
+        if (instanceId == null) {
+            return null;
+        }
+
+        return instanceNodeService.getOne(
+                new LambdaQueryWrapper<ApprovalInstanceNode>()
+                        .eq(ApprovalInstanceNode::getInstanceId, instanceId)
+                        .eq(ApprovalInstanceNode::getStatus, "PENDING")
+                        .eq(ApprovalInstanceNode::getDeleted, 0)
+                        .orderByAsc(ApprovalInstanceNode::getLevelNo)
+                        .last("LIMIT 1")
+        );
+    }
+
+    /**
+     * 鏌ヨ褰撳墠瀹℃壒灞傜骇銆�
+     */
+    public Integer getCurrentLevel(Long instanceId) {
+        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
+        return currentNode != null ? currentNode.getLevelNo() : null;
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鑺傜偣涓嬬殑寰呭鎵逛换鍔°��
+     */
+    public List<ApprovalTask> getCurrentPendingTasks(Long instanceId) {
+        if (instanceId == null) {
+            return List.of();
+        }
+
+        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
+        if (currentNode == null) {
+            return List.of();
+        }
+
+        return approvalTaskService.list(
+                new LambdaQueryWrapper<ApprovalTask>()
+                        .eq(ApprovalTask::getInstanceId, instanceId)
+                        .eq(ApprovalTask::getNodeId, currentNode.getId())
+                        .eq(ApprovalTask::getTaskStatus, "PENDING")
+                        .eq(ApprovalTask::getDeleted, 0)
+                        .orderByAsc(ApprovalTask::getLevelNo)
+        );
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鍦ㄥ綋鍓嶈妭鐐逛笂鐨勫緟瀹℃壒浠诲姟銆�
+     */
+    public ApprovalTask getCurrentUserTask(Long instanceId, Long userId) {
+        if (instanceId == null || userId == null) {
+            return null;
+        }
+
+        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
+        if (currentNode == null) {
+            return null;
+        }
+
+        return approvalTaskService.getOne(
+                new LambdaQueryWrapper<ApprovalTask>()
+                        .eq(ApprovalTask::getInstanceId, instanceId)
+                        .eq(ApprovalTask::getNodeId, currentNode.getId())
+                        .eq(ApprovalTask::getApproverId, userId)
+                        .eq(ApprovalTask::getTaskStatus, "PENDING")
+                        .eq(ApprovalTask::getDeleted, 0)
+                        .last("LIMIT 1")
+        );
+    }
+
+    /**
+     * 鍒ゆ柇褰撳墠鐢ㄦ埛鏄惁鏄綋鍓嶅鎵逛汉銆�
+     */
+    public boolean isCurrentApprover(Long instanceId, Long userId) {
+        return getCurrentUserTask(instanceId, userId) != null;
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鑺傜偣鐨勫鎵逛汉 ID 鍒楄〃銆�
+     */
+    public List<Long> getCurrentNodeApproverIds(Long instanceId) {
+        return getCurrentPendingTasks(instanceId).stream()
+                .map(ApprovalTask::getApproverId)
+                .distinct()
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鑺傜偣鍓╀綑寰呭鎵逛汉鏁般��
+     */
+    public int getRemainingApproverCount(Long instanceId) {
+        return getCurrentPendingTasks(instanceId).size();
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鑺傜偣宸插悓鎰忎汉鏁般��
+     */
+    public int getApprovedCount(Long instanceId) {
+        if (instanceId == null) {
+            return 0;
+        }
+
+        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
+        if (currentNode == null) {
+            return 0;
+        }
+
+        return Math.toIntExact(approvalTaskService.count(
+                new LambdaQueryWrapper<ApprovalTask>()
+                        .eq(ApprovalTask::getInstanceId, instanceId)
+                        .eq(ApprovalTask::getNodeId, currentNode.getId())
+                        .eq(ApprovalTask::getTaskStatus, "APPROVED")
+                        .eq(ApprovalTask::getDeleted, 0)
+        ));
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鑺傜偣宸叉嫆缁濅汉鏁般��
+     */
+    public int getRejectedCount(Long instanceId) {
+        if (instanceId == null) {
+            return 0;
+        }
+
+        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
+        if (currentNode == null) {
+            return 0;
+        }
+
+        return Math.toIntExact(approvalTaskService.count(
+                new LambdaQueryWrapper<ApprovalTask>()
+                        .eq(ApprovalTask::getInstanceId, instanceId)
+                        .eq(ApprovalTask::getNodeId, currentNode.getId())
+                        .eq(ApprovalTask::getTaskStatus, "REJECTED")
+                        .eq(ApprovalTask::getDeleted, 0)
+        ));
+    }
+
+    /**
+     * 鍒ゆ柇褰撳墠鑺傜偣鏄惁鍙互娴佽浆鍒颁笅涓�灞傘��
+     */
+    public boolean canProceedToNextLevel(Long instanceId, String approveType) {
+        if (instanceId == null || approveType == null) {
+            return false;
+        }
+
+        if (getRejectedCount(instanceId) > 0) {
+            return false;
+        }
+
+        int totalApproverCount = getCurrentPendingTasks(instanceId).size() + getApprovedCount(instanceId);
+        int approvedCount = getApprovedCount(instanceId);
+
+        if ("AND".equalsIgnoreCase(approveType)) {
+            return approvedCount > 0 && approvedCount == totalApproverCount;
+        }
+        if ("OR".equalsIgnoreCase(approveType)) {
+            return approvedCount > 0;
+        }
+        return false;
+    }
+
+    /**
+     * 鏌ヨ褰撳墠鐢ㄦ埛鍦ㄥ綋鍓嶈妭鐐逛笂鐨勪换鍔$姸鎬併��
+     */
+    public String getUserTaskStatus(Long instanceId, Long userId) {
+        if (instanceId == null || userId == null) {
+            return null;
+        }
+
+        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
+        if (currentNode == null) {
+            return null;
+        }
+
+        ApprovalTask task = approvalTaskService.getOne(
+                new LambdaQueryWrapper<ApprovalTask>()
+                        .eq(ApprovalTask::getInstanceId, instanceId)
+                        .eq(ApprovalTask::getNodeId, currentNode.getId())
+                        .eq(ApprovalTask::getApproverId, userId)
+                        .eq(ApprovalTask::getDeleted, 0)
+                        .last("LIMIT 1")
+        );
+
+        return task != null ? task.getTaskStatus() : null;
+    }
+
+    /**
+     * 鏌ヨ鎸囧畾鐢ㄦ埛鐨勫叏閮ㄥ緟瀹℃壒浠诲姟銆�
+     */
+    public List<ApprovalTask> getUserAllPendingTasks(Long userId) {
+        if (userId == null) {
+            return List.of();
+        }
+
+        return approvalTaskService.list(
+                new LambdaQueryWrapper<ApprovalTask>()
+                        .eq(ApprovalTask::getApproverId, userId)
+                        .eq(ApprovalTask::getTaskStatus, "PENDING")
+                        .eq(ApprovalTask::getDeleted, 0)
+                        .orderByDesc(ApprovalTask::getCreateTime)
+        );
+    }
+
+    /**
+     * 鏌ヨ瀹℃壒瀹炰緥鐨勮繘搴︽憳瑕併��
+     */
+    public String getApprovalProgress(Long instanceId) {
+        if (instanceId == null) {
+            return "鏃犳晥鐨勫鎵瑰疄渚�";
+        }
+
+        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
+        if (currentNode == null) {
+            return "瀹℃壒宸插畬鎴愭垨灏氭湭寮�濮�";
+        }
+
+        int approvedCount = getApprovedCount(instanceId);
+        int rejectedCount = getRejectedCount(instanceId);
+        int pendingCount = getRemainingApproverCount(instanceId);
+        int totalCount = approvedCount + rejectedCount + pendingCount;
+
+        return String.format(
+                "绗�%d绾у鎵癸細鎬讳汉鏁�=%d锛屽凡鍚屾剰=%d锛屽凡鎷掔粷=%d锛屽緟瀹℃壒=%d",
+                currentNode.getLevelNo(),
+                totalCount,
+                approvedCount,
+                rejectedCount,
+                pendingCount
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java b/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
index 6fa2822..506a9d8 100644
--- a/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
+++ b/src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -194,7 +194,12 @@
     // Account
     SALES_REFUND_AMOUNT_ORDER("sales_refund_amount_order"),
     SALES_RECEIPT_RETURN("sales_receipt_return"),
+    ACCOUNT_EXPENSE("account_expense"),
+    FIN_REIMBURSEMENT("fin_reimbursement"),
     FIN_VOUCHER("fin_voucher"),
+    ACCOUNT_FILE("account_file"),
+    ENTERPRISE_NEWS("enterprise_news"),
+    APPROVAL_INSTANCE("approval_instance"),
     ACCOUNT_INVOICE_APPLICATION("account_invoice_application"),
     ACCOUNT_PURCHASE_INVOICE("account_purchase_invoice");
 
diff --git a/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
index eef85aa..92a632b 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -21,8 +21,12 @@
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.framework.security.LoginUser;
 import com.ruoyi.framework.web.domain.R;
+import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
+import com.ruoyi.procurementrecord.pojo.ReturnManagement;
 import com.ruoyi.sales.mapper.SalesLedgerMapper;
+import com.ruoyi.sales.mapper.SalesQuotationMapper;
 import com.ruoyi.sales.pojo.SalesLedger;
+import com.ruoyi.sales.pojo.SalesQuotation;
 import com.ruoyi.sales.vo.CustomerTransactionsDetailsVo;
 import com.ruoyi.sales.vo.CustomerTransactionsVo;
 import lombok.AllArgsConstructor;
@@ -50,7 +54,11 @@
 @Slf4j
 public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements ICustomerService {
     @Autowired
-    private  SalesLedgerMapper salesLedgerMapper;
+    private SalesLedgerMapper salesLedgerMapper;
+    @Autowired
+    private SalesQuotationMapper salesQuotationMapper;
+    @Autowired
+    private ReturnManagementMapper returnManagementMapper;
     @Autowired
     private CustomerMapper customerMapper;
 
@@ -218,10 +226,24 @@
     @Transactional(rollbackFor = Exception.class)
     public int deleteCustomerByIds(Long[] ids) {
         List<Long> idList = Arrays.asList(ids);
+        // 妫�鏌ユ槸鍚︽湁閿�鍞彴璐﹀叧鑱�
         List<SalesLedger> salesLedgers = salesLedgerMapper.selectList(new QueryWrapper<SalesLedger>().lambda().in(SalesLedger::getCustomerId, idList));
         if (!salesLedgers.isEmpty()) {
-            throw new RuntimeException("瀹㈡埛妗f涓嬫湁閿�鍞悎鍚岋紝璇峰厛鍒犻櫎閿�鍞悎鍚�");
+            throw new RuntimeException("瀹㈡埛妗f涓嬫湁閿�鍞彴璐︼紝璇峰厛鍒犻櫎閿�鍞彴璐�");
         }
+
+        // 妫�鏌ユ槸鍚︽湁閿�鍞��璐у叧鑱�
+        List<ReturnManagement> returnManagements = returnManagementMapper.selectList(new QueryWrapper<ReturnManagement>().lambda().in(ReturnManagement::getCustomerId, idList));
+        if (!returnManagements.isEmpty()) {
+            throw new RuntimeException("瀹㈡埛妗f涓嬫湁閿�鍞��璐э紝璇峰厛鍒犻櫎閿�鍞��璐�");
+        }
+
+        // 妫�鏌ユ槸鍚︽湁閿�鍞姤浠峰叧鑱�
+        List<SalesQuotation> salesQuotations = salesQuotationMapper.selectList(new QueryWrapper<SalesQuotation>().lambda().in(SalesQuotation::getCustomerId, idList));
+        if (!salesQuotations.isEmpty()) {
+            throw new RuntimeException("瀹㈡埛妗f涓嬫湁閿�鍞姤浠凤紝璇峰厛鍒犻櫎閿�鍞姤浠�");
+        }
+
         // 鏌ヨ鏄惁鏈夊凡鍒嗛厤鐨勫叕娴峰鎴�
         List<Customer> assignedPools = customerMapper.selectList(
                 new QueryWrapper<Customer>().lambda()
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsController.java b/src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsController.java
new file mode 100644
index 0000000..36f1e21
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsController.java
@@ -0,0 +1,61 @@
+package com.ruoyi.collaborativeApproval.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.collaborativeApproval.dto.EnterpriseNewsDto;
+import com.ruoyi.collaborativeApproval.service.EnterpriseNewsService;
+import com.ruoyi.collaborativeApproval.vo.EnterpriseNewsVo;
+import com.ruoyi.framework.aspectj.lang.annotation.Log;
+import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
+import com.ruoyi.framework.web.domain.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:50:59
+ */
+@RestController
+@RequestMapping("/enterpriseNews")
+@Tag(name = "浼佷笟鏂伴椈琛�")
+@AllArgsConstructor
+public class EnterpriseNewsController {
+
+    private final EnterpriseNewsService enterpriseNewsService;
+
+    @Operation(summary = "鍒嗛〉鏌ヨ")
+    @GetMapping("/listPage")
+    @Log(title = "浼佷笟鏂伴椈鍒嗛〉鏌ヨ", businessType = BusinessType.OTHER)
+    public R listPage(Page<EnterpriseNewsVo>  page , EnterpriseNewsDto enterpriseNewsDto) {
+        return R.ok(enterpriseNewsService.listPage(page, enterpriseNewsDto));
+    }
+
+    @PostMapping("/save")
+    @Operation(summary = "淇濆瓨")
+    @Log(title = "淇濆瓨浼佷笟鏂伴椈", businessType = BusinessType.INSERT)
+    public R save(@RequestBody EnterpriseNewsDto enterpriseNewsDto) {
+        return R.ok(enterpriseNewsService.add(enterpriseNewsDto));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊")
+    @Log(title = "鏇存柊浼佷笟鏂伴椈", businessType = BusinessType.UPDATE)
+    public R update(@RequestBody EnterpriseNewsDto enterpriseNewsDto) {
+        return R.ok(enterpriseNewsService.updateEnterpriseNewsDto(enterpriseNewsDto));
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎")
+    @Log(title = "鍒犻櫎浼佷笟鏂伴椈", businessType = BusinessType.DELETE)
+    public R delete(@RequestBody List<Long> ids) {
+        return R.ok(enterpriseNewsService.delete(ids));
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeDeptController.java b/src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeDeptController.java
new file mode 100644
index 0000000..8c68d1a
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeDeptController.java
@@ -0,0 +1,18 @@
+package com.ruoyi.collaborativeApproval.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈闃呰鑼冨洿閮ㄩ棬琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:51:12
+ */
+@RestController
+@RequestMapping("/enterpriseNewsScopeDept")
+public class EnterpriseNewsScopeDeptController {
+
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeUserController.java b/src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeUserController.java
new file mode 100644
index 0000000..7ceba13
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeUserController.java
@@ -0,0 +1,18 @@
+package com.ruoyi.collaborativeApproval.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈闃呰鑼冨洿鐢ㄦ埛琛� 鍓嶇鎺у埗鍣�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:51:23
+ */
+@RestController
+@RequestMapping("/enterpriseNewsScopeUser")
+public class EnterpriseNewsScopeUserController {
+
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/dto/EnterpriseNewsDto.java b/src/main/java/com/ruoyi/collaborativeApproval/dto/EnterpriseNewsDto.java
new file mode 100644
index 0000000..108cf66
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/dto/EnterpriseNewsDto.java
@@ -0,0 +1,26 @@
+package com.ruoyi.collaborativeApproval.dto;
+
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class EnterpriseNewsDto extends EnterpriseNews {
+
+    private String createUserName;
+
+    private List<Long> deptIds;
+
+    private List<Long> userIds;
+
+    private Long templateId;
+    private String templateName;
+
+    private String createTimeStart;
+    private String createTimeEnd;
+
+    private List<StorageBlobDTO> storageBlobDTOs;
+
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsMapper.java b/src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsMapper.java
new file mode 100644
index 0000000..799ae82
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsMapper.java
@@ -0,0 +1,24 @@
+package com.ruoyi.collaborativeApproval.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.collaborativeApproval.dto.EnterpriseNewsDto;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
+import com.ruoyi.collaborativeApproval.vo.EnterpriseNewsVo;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:50:59
+ */
+@Mapper
+public interface EnterpriseNewsMapper extends BaseMapper<EnterpriseNews> {
+
+    IPage<EnterpriseNewsVo> listPage(Page<EnterpriseNewsVo> page,@Param("enterpriseNewsDto") EnterpriseNewsDto enterpriseNewsDto);
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeDeptMapper.java b/src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeDeptMapper.java
new file mode 100644
index 0000000..5b4639f
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeDeptMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.collaborativeApproval.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈闃呰鑼冨洿閮ㄩ棬琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:51:12
+ */
+@Mapper
+public interface EnterpriseNewsScopeDeptMapper extends BaseMapper<EnterpriseNewsScopeDept> {
+
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeUserMapper.java b/src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeUserMapper.java
new file mode 100644
index 0000000..ab9a64f
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeUserMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.collaborativeApproval.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈闃呰鑼冨洿鐢ㄦ埛琛� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:51:23
+ */
+@Mapper
+public interface EnterpriseNewsScopeUserMapper extends BaseMapper<EnterpriseNewsScopeUser> {
+
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNews.java b/src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNews.java
new file mode 100644
index 0000000..f5e001b
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNews.java
@@ -0,0 +1,88 @@
+package com.ruoyi.collaborativeApproval.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+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 io.swagger.annotations.ApiModel;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 浼佷笟鏂伴椈琛�
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:50:59
+ */
+@Getter
+@Setter
+@ToString
+@TableName("enterprise_news")
+@ApiModel(value = "EnterpriseNews瀵硅薄", description = "浼佷笟鏂伴椈琛�")
+public class EnterpriseNews implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "缂栧彿 ID")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @Schema(description = "鏍囬 Title")
+    private String title;
+
+    @Schema(description = "鎽樿 Summary")
+    private String summary;
+
+    @Schema(description = "姝f枃 Content")
+    private String content;
+
+    @Schema(description = "鍒嗙被 Category")
+    private String category;
+
+    @Schema(description = "闃呰鑼冨洿 Read scope: all 鍏ㄥ憳, dept 閮ㄩ棬, custom 鑷畾涔�")
+    private String readScope;
+
+    @Schema(description = "鏄惁蹇呰 Required flag: 0 鍚�, 1 鏄�")
+    private Byte isRequired;
+
+    @Schema(description = "鐘舵�� Status: DRAFT 鑽夌, PENDING 寰呭鎵�, PUBLISHED 宸插彂甯�, REJECTED 椹冲洖, OFFLINE 宸蹭笅绾�")
+    private String status;
+
+    @Schema(description = "搴旇浜烘暟 Required read count")
+    private Integer requiredReadCount;
+
+    @Schema(description = "宸茶浜烘暟 Read count")
+    private Integer readCount;
+
+    @Schema(description = "鍒涘缓浜� Create user")
+    @TableField(fill = FieldFill.INSERT)
+    private Long createUser;
+
+    @Schema(description = "鍒涘缓鏃堕棿 Create time")
+    @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss", iso = DateTimeFormat.ISO.DATE_TIME)
+    private LocalDateTime createTime;
+
+    @Schema(description = "鏇存柊浜� Update user")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Long updateUser;
+
+    @Schema(description = "鏇存柊鏃堕棿 Update time")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss", iso = DateTimeFormat.ISO.DATE_TIME)
+    private LocalDateTime updateTime;
+
+    @Schema(description = "閮ㄩ棬ID Dept ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeDept.java b/src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeDept.java
new file mode 100644
index 0000000..97ff9de
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeDept.java
@@ -0,0 +1,59 @@
+package com.ruoyi.collaborativeApproval.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+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 io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈闃呰鑼冨洿閮ㄩ棬琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:51:12
+ */
+@Getter
+@Setter
+@ToString
+@TableName("enterprise_news_scope_dept")
+@ApiModel(value = "EnterpriseNewsScopeDept瀵硅薄", description = "浼佷笟鏂伴椈闃呰鑼冨洿閮ㄩ棬琛�")
+public class EnterpriseNewsScopeDept implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 缂栧彿
+     */
+    @ApiModelProperty("缂栧彿")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 浼佷笟鏂伴椈ID
+     */
+    @ApiModelProperty("浼佷笟鏂伴椈ID")
+    private Long newsId;
+
+    /**
+     * 閮ㄩ棬ID
+     */
+    @ApiModelProperty("閮ㄩ棬ID")
+    private Long deptId;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @ApiModelProperty("鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeUser.java b/src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeUser.java
new file mode 100644
index 0000000..9d43df2
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeUser.java
@@ -0,0 +1,59 @@
+package com.ruoyi.collaborativeApproval.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+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 io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈闃呰鑼冨洿鐢ㄦ埛琛�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:51:23
+ */
+@Getter
+@Setter
+@ToString
+@TableName("enterprise_news_scope_user")
+@ApiModel(value = "EnterpriseNewsScopeUser瀵硅薄", description = "浼佷笟鏂伴椈闃呰鑼冨洿鐢ㄦ埛琛�")
+public class EnterpriseNewsScopeUser implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 缂栧彿
+     */
+    @ApiModelProperty("缂栧彿")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 浼佷笟鏂伴椈ID
+     */
+    @ApiModelProperty("浼佷笟鏂伴椈ID")
+    private Long newsId;
+
+    /**
+     * 鐢ㄦ埛ID
+     */
+    @ApiModelProperty("鐢ㄦ埛ID")
+    private Long userId;
+
+    /**
+     * 鍒涘缓鏃堕棿
+     */
+    @ApiModelProperty("鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeDeptService.java b/src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeDeptService.java
new file mode 100644
index 0000000..b41a7d8
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeDeptService.java
@@ -0,0 +1,16 @@
+package com.ruoyi.collaborativeApproval.service;
+
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈闃呰鑼冨洿閮ㄩ棬琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:51:12
+ */
+public interface EnterpriseNewsScopeDeptService extends IService<EnterpriseNewsScopeDept> {
+
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeUserService.java b/src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeUserService.java
new file mode 100644
index 0000000..28ac6c7
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeUserService.java
@@ -0,0 +1,16 @@
+package com.ruoyi.collaborativeApproval.service;
+
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈闃呰鑼冨洿鐢ㄦ埛琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:51:23
+ */
+public interface EnterpriseNewsScopeUserService extends IService<EnterpriseNewsScopeUser> {
+
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsService.java b/src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsService.java
new file mode 100644
index 0000000..21e7e6c
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsService.java
@@ -0,0 +1,29 @@
+package com.ruoyi.collaborativeApproval.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.collaborativeApproval.dto.EnterpriseNewsDto;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.collaborativeApproval.vo.EnterpriseNewsVo;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈琛� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:50:59
+ */
+public interface EnterpriseNewsService extends IService<EnterpriseNews> {
+
+    IPage<EnterpriseNewsVo> listPage(Page<EnterpriseNewsVo> page, EnterpriseNewsDto enterpriseNewsDto);
+
+    Boolean add(EnterpriseNewsDto enterpriseNewsDto);
+
+    Boolean updateEnterpriseNewsDto(EnterpriseNewsDto enterpriseNewsDto);
+
+    Boolean delete(List<Long> ids);
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeDeptServiceImpl.java b/src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeDeptServiceImpl.java
new file mode 100644
index 0000000..22283c7
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeDeptServiceImpl.java
@@ -0,0 +1,20 @@
+package com.ruoyi.collaborativeApproval.service.impl;
+
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept;
+import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsScopeDeptMapper;
+import com.ruoyi.collaborativeApproval.service.EnterpriseNewsScopeDeptService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈闃呰鑼冨洿閮ㄩ棬琛� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:51:12
+ */
+@Service
+public class EnterpriseNewsScopeDeptServiceImpl extends ServiceImpl<EnterpriseNewsScopeDeptMapper, EnterpriseNewsScopeDept> implements EnterpriseNewsScopeDeptService {
+
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeUserServiceImpl.java b/src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeUserServiceImpl.java
new file mode 100644
index 0000000..db1521e
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeUserServiceImpl.java
@@ -0,0 +1,20 @@
+package com.ruoyi.collaborativeApproval.service.impl;
+
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser;
+import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsScopeUserMapper;
+import com.ruoyi.collaborativeApproval.service.EnterpriseNewsScopeUserService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 浼佷笟鏂伴椈闃呰鑼冨洿鐢ㄦ埛琛� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:51:23
+ */
+@Service
+public class EnterpriseNewsScopeUserServiceImpl extends ServiceImpl<EnterpriseNewsScopeUserMapper, EnterpriseNewsScopeUser> implements EnterpriseNewsScopeUserService {
+
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsServiceImpl.java b/src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsServiceImpl.java
new file mode 100644
index 0000000..4a4d9c1
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsServiceImpl.java
@@ -0,0 +1,409 @@
+package com.ruoyi.collaborativeApproval.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.approve.mapper.ApprovalInstanceMapper;
+import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
+import com.ruoyi.approve.pojo.ApprovalInstance;
+import com.ruoyi.approve.pojo.ApprovalTask;
+import com.ruoyi.approve.pojo.ApprovalTemplate;
+import com.ruoyi.approve.service.ApprovalInstanceService;
+import com.ruoyi.approve.utils.ApproveProcessConfigNodeUtils;
+import com.ruoyi.basic.enums.ApplicationTypeEnum;
+import com.ruoyi.basic.enums.RecordTypeEnum;
+import com.ruoyi.basic.utils.FileUtil;
+import com.ruoyi.collaborativeApproval.dto.EnterpriseNewsDto;
+import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsMapper;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser;
+import com.ruoyi.collaborativeApproval.service.EnterpriseNewsScopeDeptService;
+import com.ruoyi.collaborativeApproval.service.EnterpriseNewsScopeUserService;
+import com.ruoyi.collaborativeApproval.service.EnterpriseNewsService;
+import com.ruoyi.collaborativeApproval.vo.EnterpriseNewsVo;
+import com.ruoyi.common.enums.TypeEnums;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.OrderUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.project.system.domain.SysDept;
+import com.ruoyi.project.system.domain.SysUser;
+import com.ruoyi.project.system.mapper.SysDeptMapper;
+import com.ruoyi.project.system.mapper.SysUserDeptMapper;
+import com.ruoyi.project.system.mapper.SysUserMapper;
+import com.ruoyi.project.system.service.ISysNoticeService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 浼佷笟鏂伴椈琛ㄦ湇鍔″疄鐜扮被
+ *
+ * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃
+ * @since 2026-05-20 11:50:59
+ */
+@Service
+@RequiredArgsConstructor
+public class EnterpriseNewsServiceImpl extends ServiceImpl<EnterpriseNewsMapper, EnterpriseNews> implements EnterpriseNewsService {
+
+    private static final String READ_SCOPE_ALL = "all";
+    private static final String READ_SCOPE_DEPT = "dept";
+    private static final String READ_SCOPE_CUSTOM = "custom";
+
+    private static final String STATUS_DRAFT = "DRAFT";
+    private static final String STATUS_PENDING = "PENDING";
+    private static final String STATUS_PUBLISHED = "PUBLISHED";
+    private static final String STATUS_REJECTED = "REJECTED";
+    private static final String STATUS_OFFLINE = "OFFLINE";
+
+    private final EnterpriseNewsMapper enterpriseNewsMapper;
+    private final EnterpriseNewsScopeDeptService enterpriseNewsScopeDeptService;
+    private final EnterpriseNewsScopeUserService enterpriseNewsScopeUserService;
+    private final SysUserMapper sysUserMapper;
+    private final SysDeptMapper sysDeptMapper;
+    private final SysUserDeptMapper sysUserDeptMapper;
+    private final ApprovalInstanceMapper approvalInstanceMapper;
+    private final ApprovalInstanceService approvalInstanceService;
+    private final ApprovalTemplateMapper approvalTemplateMapper;
+    private final ApproveProcessConfigNodeUtils approveProcessConfigNodeUtils;
+    private final ISysNoticeService sysNoticeService;
+    private final FileUtil fileUtil;
+
+    @Override
+    public IPage<EnterpriseNewsVo> listPage(Page<EnterpriseNewsVo> page, EnterpriseNewsDto enterpriseNewsDto) {
+        IPage<EnterpriseNewsVo> enterpriseNewsVoIPage = enterpriseNewsMapper.listPage(page, enterpriseNewsDto);
+        enterpriseNewsVoIPage.getRecords().forEach(enterpriseNewsVo -> {
+            enterpriseNewsVo.setStorageBlobDTOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.ENTERPRISE_NEWS, enterpriseNewsVo.getId()));
+        });
+        return enterpriseNewsVoIPage;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean add(EnterpriseNewsDto enterpriseNewsDto) {
+        validateForSave(enterpriseNewsDto);
+        String readScope = normalizeReadScope(enterpriseNewsDto.getReadScope());
+        List<Long> deptIds = distinctIds(enterpriseNewsDto.getDeptIds());
+        List<Long> userIds = distinctIds(enterpriseNewsDto.getUserIds());
+
+        EnterpriseNews enterpriseNews = new EnterpriseNews();
+        BeanUtils.copyProperties(enterpriseNewsDto, enterpriseNews);
+        enterpriseNews.setReadScope(readScope);
+        enterpriseNews.setIsRequired(enterpriseNewsDto.getIsRequired() == null ? (byte) 0 : enterpriseNewsDto.getIsRequired());
+        enterpriseNews.setStatus(normalizeStatus(enterpriseNewsDto.getStatus(), STATUS_DRAFT));
+        enterpriseNews.setReadCount(0);
+        enterpriseNews.setRequiredReadCount(calculateRequiredReadCount(readScope, deptIds, userIds));
+
+        Long[] loginDeptIds = SecurityUtils.getDeptId();
+        if (StringUtils.isNotEmpty(loginDeptIds)) {
+            enterpriseNews.setDeptId(loginDeptIds[0]);
+        }
+
+        if (!save(enterpriseNews) || enterpriseNews.getId() == null) {
+            throw new ServiceException("鏂板浼佷笟鏂伴椈澶辫触");
+        }
+
+        saveReadScopeRelations(enterpriseNews.getId(), readScope, deptIds, userIds);
+        if (STATUS_PENDING.equals(enterpriseNews.getStatus())) {
+            startEnterpriseNewsApproval(enterpriseNews, enterpriseNewsDto);
+        }
+        //娣诲姞闄勪欢
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.ENTERPRISE_NEWS, enterpriseNews.getId(), enterpriseNewsDto.getStorageBlobDTOs());
+        return true;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateEnterpriseNewsDto(EnterpriseNewsDto enterpriseNewsDto) {
+        if (enterpriseNewsDto == null || enterpriseNewsDto.getId() == null) {
+            throw new ServiceException("浼佷笟鏂伴椈ID涓嶈兘涓虹┖");
+        }
+
+        EnterpriseNews oldEnterpriseNews = getById(enterpriseNewsDto.getId());
+        if (oldEnterpriseNews == null) {
+            throw new ServiceException("浼佷笟鏂伴椈涓嶅瓨鍦�");
+        }
+        if (!STATUS_DRAFT.equals(oldEnterpriseNews.getStatus())
+                && !STATUS_REJECTED.equals(oldEnterpriseNews.getStatus())) {
+            throw new ServiceException("寰呭鎵规垨宸插彂甯冪殑浼佷笟鏂伴椈涓嶅厑璁镐慨鏀�");
+        }
+
+        validateForSave(enterpriseNewsDto);
+        String readScope = normalizeReadScope(enterpriseNewsDto.getReadScope());
+        List<Long> deptIds = distinctIds(enterpriseNewsDto.getDeptIds());
+        List<Long> userIds = distinctIds(enterpriseNewsDto.getUserIds());
+
+        EnterpriseNews enterpriseNews = new EnterpriseNews();
+        BeanUtils.copyProperties(enterpriseNewsDto, enterpriseNews);
+        enterpriseNews.setReadScope(readScope);
+        enterpriseNews.setIsRequired(enterpriseNewsDto.getIsRequired() == null ? oldEnterpriseNews.getIsRequired() : enterpriseNewsDto.getIsRequired());
+        enterpriseNews.setStatus(normalizeStatus(enterpriseNewsDto.getStatus(), oldEnterpriseNews.getStatus()));
+        enterpriseNews.setReadCount(oldEnterpriseNews.getReadCount());
+        enterpriseNews.setRequiredReadCount(calculateRequiredReadCount(readScope, deptIds, userIds));
+        enterpriseNews.setCreateUser(oldEnterpriseNews.getCreateUser());
+        enterpriseNews.setCreateTime(oldEnterpriseNews.getCreateTime());
+        enterpriseNews.setDeptId(oldEnterpriseNews.getDeptId());
+
+        if (!updateById(enterpriseNews)) {
+            throw new ServiceException("淇敼浼佷笟鏂伴椈澶辫触");
+        }
+
+        clearReadScopeRelations(enterpriseNews.getId());
+        saveReadScopeRelations(enterpriseNews.getId(), readScope, deptIds, userIds);
+        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.ENTERPRISE_NEWS, enterpriseNews.getId(), enterpriseNewsDto.getStorageBlobDTOs());
+        if (STATUS_PENDING.equals(enterpriseNews.getStatus())) {
+            resetEnterpriseNewsApprovalFlow(oldEnterpriseNews);
+            startEnterpriseNewsApproval(enterpriseNews, enterpriseNewsDto);
+        }
+        return true;
+    }
+
+    @Override
+    public Boolean delete(List<Long> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return false;
+        }
+        if (!removeByIds(ids)) {
+            throw new ServiceException("鍒犻櫎浼佷笟鏂伴椈澶辫触");
+        }
+        ids.forEach(this::clearReadScopeRelations);
+        return true;
+    }
+
+    private void validateForSave(EnterpriseNewsDto enterpriseNewsDto) {
+        if (enterpriseNewsDto == null) {
+            throw new ServiceException("浼佷笟鏂伴椈鏁版嵁涓嶈兘涓虹┖");
+        }
+        if (StringUtils.isEmpty(enterpriseNewsDto.getTitle())) {
+            throw new ServiceException("鏍囬涓嶈兘涓虹┖");
+        }
+        if (StringUtils.isEmpty(enterpriseNewsDto.getContent())) {
+            throw new ServiceException("姝f枃涓嶈兘涓虹┖");
+        }
+
+        normalizeStatus(enterpriseNewsDto.getStatus(), STATUS_DRAFT);
+        String readScope = normalizeReadScope(enterpriseNewsDto.getReadScope());
+        List<Long> deptIds = distinctIds(enterpriseNewsDto.getDeptIds());
+        List<Long> userIds = distinctIds(enterpriseNewsDto.getUserIds());
+
+        if (READ_SCOPE_DEPT.equals(readScope) && StringUtils.isEmpty(deptIds)) {
+            throw new ServiceException("璇烽�夋嫨闃呰鑼冨洿閮ㄩ棬");
+        }
+        if (READ_SCOPE_CUSTOM.equals(readScope) && StringUtils.isEmpty(userIds)) {
+            throw new ServiceException("璇烽�夋嫨鑷畾涔夐槄璇讳汉鍛�");
+        }
+
+        validateDeptIds(deptIds);
+        validateUserIds(userIds);
+    }
+
+    private String normalizeReadScope(String readScope) {
+        String normalized = StringUtils.isEmpty(readScope) ? READ_SCOPE_ALL : readScope.trim();
+        if (!READ_SCOPE_ALL.equals(normalized)
+                && !READ_SCOPE_DEPT.equals(normalized)
+                && !READ_SCOPE_CUSTOM.equals(normalized)) {
+            throw new ServiceException("闃呰鑼冨洿涓嶅悎娉�");
+        }
+        return normalized;
+    }
+
+    private String normalizeStatus(String status, String defaultStatus) {
+        String normalized = StringUtils.isEmpty(status) ? defaultStatus : status.trim().toUpperCase();
+        if (!STATUS_DRAFT.equals(normalized)
+                && !STATUS_PENDING.equals(normalized)
+                && !STATUS_PUBLISHED.equals(normalized)
+                && !STATUS_REJECTED.equals(normalized)
+                && !STATUS_OFFLINE.equals(normalized)) {
+            throw new ServiceException("浼佷笟鏂伴椈鐘舵�佷笉鍚堟硶");
+        }
+        return normalized;
+    }
+
+    private void validateDeptIds(List<Long> deptIds) {
+        if (StringUtils.isEmpty(deptIds)) {
+            return;
+        }
+        for (Long deptId : deptIds) {
+            SysDept sysDept = sysDeptMapper.selectDeptById(deptId);
+            if (deptId == null || sysDept == null) {
+                throw new ServiceException("闃呰鑼冨洿閮ㄩ棬涓嶅瓨鍦�");
+            }
+        }
+    }
+
+    private void validateUserIds(List<Long> userIds) {
+        if (StringUtils.isEmpty(userIds)) {
+            return;
+        }
+        List<SysUser> users = sysUserMapper.selectUsersByIds(userIds);
+        if (users.size() != userIds.size()) {
+            throw new ServiceException("鑷畾涔夐槄璇讳汉鍛樺寘鍚棤鏁堢敤鎴�");
+        }
+    }
+
+    private Integer calculateRequiredReadCount(String readScope, List<Long> deptIds, List<Long> userIds) {
+        if (READ_SCOPE_ALL.equals(readScope)) {
+            Long count = sysUserMapper.selectCount(new LambdaQueryWrapper<SysUser>()
+                    .eq(SysUser::getDelFlag, "0"));
+            return count == null ? 0 : count.intValue();
+        }
+        if (READ_SCOPE_DEPT.equals(readScope)) {
+            List<Long> allDeptIds = collectDeptIdsWithChildren(deptIds);
+            if (StringUtils.isEmpty(allDeptIds)) {
+                return 0;
+            }
+            Long count = sysUserDeptMapper.countDistinctUserIdsByDeptIds(allDeptIds);
+            return count == null ? 0 : count.intValue();
+        }
+        return userIds.size();
+    }
+
+    private List<Long> collectDeptIdsWithChildren(List<Long> deptIds) {
+        Set<Long> allDeptIds = new LinkedHashSet<>();
+        for (Long deptId : deptIds) {
+            if (deptId == null) {
+                continue;
+            }
+            allDeptIds.add(deptId);
+            List<SysDept> children = sysDeptMapper.selectChildrenDeptById(deptId);
+            if (StringUtils.isNotEmpty(children)) {
+                for (SysDept child : children) {
+                    allDeptIds.add(child.getDeptId());
+                }
+            }
+        }
+        return new ArrayList<>(allDeptIds);
+    }
+
+    private void saveReadScopeRelations(Long newsId, String readScope, List<Long> deptIds, List<Long> userIds) {
+        if (READ_SCOPE_DEPT.equals(readScope)) {
+            List<EnterpriseNewsScopeDept> scopeDeptList = new ArrayList<>();
+            for (Long deptId : deptIds) {
+                EnterpriseNewsScopeDept scopeDept = new EnterpriseNewsScopeDept();
+                scopeDept.setNewsId(newsId);
+                scopeDept.setDeptId(deptId);
+                scopeDeptList.add(scopeDept);
+            }
+            if (StringUtils.isNotEmpty(scopeDeptList)) {
+                enterpriseNewsScopeDeptService.saveBatch(scopeDeptList);
+            }
+            return;
+        }
+
+        if (READ_SCOPE_CUSTOM.equals(readScope)) {
+            List<EnterpriseNewsScopeUser> scopeUserList = new ArrayList<>();
+            for (Long userId : userIds) {
+                EnterpriseNewsScopeUser scopeUser = new EnterpriseNewsScopeUser();
+                scopeUser.setNewsId(newsId);
+                scopeUser.setUserId(userId);
+                scopeUserList.add(scopeUser);
+            }
+            if (StringUtils.isNotEmpty(scopeUserList)) {
+                enterpriseNewsScopeUserService.saveBatch(scopeUserList);
+            }
+        }
+    }
+
+    private void clearReadScopeRelations(Long newsId) {
+        enterpriseNewsScopeDeptService.remove(new LambdaQueryWrapper<EnterpriseNewsScopeDept>()
+                .eq(EnterpriseNewsScopeDept::getNewsId, newsId));
+        enterpriseNewsScopeUserService.remove(new LambdaQueryWrapper<EnterpriseNewsScopeUser>()
+                .eq(EnterpriseNewsScopeUser::getNewsId, newsId));
+    }
+
+    private void resetEnterpriseNewsApprovalFlow(EnterpriseNews oldEnterpriseNews) {
+        if (oldEnterpriseNews == null || !STATUS_DRAFT.equals(oldEnterpriseNews.getStatus())) {
+            return;
+        }
+        List<Long> approvalInstanceIds = approvalInstanceMapper.selectList(new LambdaQueryWrapper<ApprovalInstance>()
+                        .eq(ApprovalInstance::getBusinessId, oldEnterpriseNews.getId())
+                        .eq(ApprovalInstance::getBusinessType, TypeEnums.ENTERPRISE_NEWS_APPROVAL.getCode())
+                        .eq(ApprovalInstance::getDeleted, (byte) 0))
+                .stream()
+                .map(ApprovalInstance::getId)
+                .filter(id -> id != null && id > 0)
+                .collect(Collectors.toList());
+        if (StringUtils.isEmpty(approvalInstanceIds)) {
+            return;
+        }
+        approvalInstanceService.delete(approvalInstanceIds);
+    }
+
+    private List<Long> distinctIds(List<Long> ids) {
+        if (StringUtils.isEmpty(ids)) {
+            return new ArrayList<>();
+        }
+        Set<Long> distinctSet = new LinkedHashSet<>();
+        for (Long id : ids) {
+            if (id != null) {
+                distinctSet.add(id);
+            }
+        }
+        return new ArrayList<>(distinctSet);
+    }
+
+    private void startEnterpriseNewsApproval(EnterpriseNews enterpriseNews, EnterpriseNewsDto enterpriseNewsDto) {
+        if (enterpriseNewsDto.getTemplateId() == null) {
+            throw new ServiceException("瀹℃壒妯℃澘涓嶈兘涓虹┖");
+        }
+
+        String templateName = enterpriseNewsDto.getTemplateName();
+        if (StringUtils.isEmpty(templateName)) {
+            ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectById(enterpriseNewsDto.getTemplateId());
+            if (approvalTemplate == null) {
+                throw new ServiceException("瀹℃壒妯℃澘涓嶅瓨鍦�");
+            }
+            templateName = approvalTemplate.getTemplateName();
+        }
+
+        ApprovalInstance approvalInstance = new ApprovalInstance();
+        approvalInstance.setInstanceNo(OrderUtils.countTodayByCreateTime(approvalInstanceMapper, "SP", "instance_no", enterpriseNews.getCreateTime() != null ? enterpriseNews.getCreateTime() : LocalDateTime.now()));
+        approvalInstance.setTemplateId(enterpriseNewsDto.getTemplateId());
+        approvalInstance.setTemplateName(templateName);
+        approvalInstance.setBusinessId(enterpriseNews.getId());
+        approvalInstance.setBusinessType(TypeEnums.ENTERPRISE_NEWS_APPROVAL.getCode());
+        approvalInstance.setTitle(enterpriseNews.getTitle());
+        approvalInstance.setStatus("PENDING");
+        approvalInstance.setCurrentLevel(1);
+        approvalInstance.setApplicantId(SecurityUtils.getUserId());
+        approvalInstance.setApplicantName(SecurityUtils.getLoginUser().getNickName());
+        approvalInstance.setApplyTime(LocalDateTime.now());
+        approvalInstance.setDeleted((byte) 0);
+        approvalInstance.setCreateUser(SecurityUtils.getUserId());
+        approvalInstance.setUpdateUser(SecurityUtils.getUserId());
+        approvalInstance.setDeptId(enterpriseNews.getDeptId());
+        approvalInstanceMapper.insert(approvalInstance);
+
+        approveProcessConfigNodeUtils.createCurrentNodeAndTasks(approvalInstance);
+        sendApproveNotice(approvalInstance, approveProcessConfigNodeUtils.getCurrentPendingTasks(approvalInstance.getId()));
+    }
+
+    private void sendApproveNotice(ApprovalInstance instance, List<ApprovalTask> tasks) {
+        if (instance == null || tasks == null || tasks.isEmpty()) {
+            return;
+        }
+        List<Long> approverIds = tasks.stream()
+                .map(ApprovalTask::getApproverId)
+                .filter(id -> id != null && id > 0)
+                .distinct()
+                .collect(Collectors.toList());
+        if (approverIds.isEmpty()) {
+            return;
+        }
+
+        String title = StringUtils.isNotEmpty(instance.getTemplateName()) ? instance.getTemplateName() : "瀹℃壒鎻愰啋";
+        String message = "瀹℃壒鍗曞彿 " + instance.getInstanceNo() + " 闇�瑕佹偍瀹℃壒";
+        String jumpPath = "/officeProcessAutomation/ApproveManage/approve-list/?id=" + instance.getId();
+        sysNoticeService.simpleNoticeByUser(title, message, approverIds, jumpPath);
+    }
+}
diff --git a/src/main/java/com/ruoyi/collaborativeApproval/vo/EnterpriseNewsVo.java b/src/main/java/com/ruoyi/collaborativeApproval/vo/EnterpriseNewsVo.java
new file mode 100644
index 0000000..66123b9
--- /dev/null
+++ b/src/main/java/com/ruoyi/collaborativeApproval/vo/EnterpriseNewsVo.java
@@ -0,0 +1,15 @@
+package com.ruoyi.collaborativeApproval.vo;
+
+import com.ruoyi.basic.dto.StorageBlobVO;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class EnterpriseNewsVo extends EnterpriseNews {
+
+    private String createUserName;
+
+    private List<StorageBlobVO> storageBlobDTOs;
+}
diff --git a/src/main/java/com/ruoyi/common/enums/ApprovalStatusEnum.java b/src/main/java/com/ruoyi/common/enums/ApprovalStatusEnum.java
new file mode 100644
index 0000000..8913ebd
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/enums/ApprovalStatusEnum.java
@@ -0,0 +1,49 @@
+package com.ruoyi.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 瀹℃壒鐘舵�佹灇涓�
+ */
+@Getter
+@AllArgsConstructor
+public enum ApprovalStatusEnum implements BaseEnum<Integer> {
+
+    DRAFT(0, "鑽夌"),
+    PENDING(1, "寰呭鎵�"),
+    IN_PROGRESS(2, "瀹℃壒涓�"),
+    APPROVED(3, "宸查�氳繃"),
+    REJECTED(4, "宸查┏鍥�");
+
+
+    private final Integer value;
+    private final String label;
+
+    @Override
+    public Integer getCode() {
+        return value;
+    }
+
+    @Override
+    public String getValue() {
+        return label;
+    }
+
+    public static ApprovalStatusEnum fromValue(Integer value) {
+        if (value == null) {
+            return null;
+        }
+        for (ApprovalStatusEnum status : values()) {
+            if (status.getCode().equals(value)) {
+                return status;
+            }
+        }
+        return null;
+    }
+
+    public static String getLabelByValue(Integer value) {
+        ApprovalStatusEnum statusEnum = fromValue(value);
+        return statusEnum != null ? statusEnum.getValue() : "鏈煡鐘舵��";
+    }
+}
diff --git a/src/main/java/com/ruoyi/common/enums/EnterpriseNewsStatusEnum.java b/src/main/java/com/ruoyi/common/enums/EnterpriseNewsStatusEnum.java
new file mode 100644
index 0000000..8e6e480
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/enums/EnterpriseNewsStatusEnum.java
@@ -0,0 +1,143 @@
+package com.ruoyi.common.enums;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+
+/**
+ * 浼佷笟鏂伴椈鐘舵�佹灇涓剧被
+ *
+ * @author ruoyi
+ * @date 2026-05-20
+ */
+@Schema(description = "浼佷笟鏂伴椈鐘舵�佹灇涓�")
+public enum EnterpriseNewsStatusEnum implements BaseEnum<String> {
+
+    /**
+     * 鑽夌
+     */
+    @Schema(description = "鑽夌")
+    DRAFT("DRAFT", "鑽夌"),
+
+    /**
+     * 寰呭鎵�
+     */
+    @Schema(description = "寰呭鎵�")
+    PENDING("PENDING", "寰呭鎵�"),
+
+    /**
+     * 宸插彂甯�
+     */
+    @Schema(description = "宸插彂甯�")
+    PUBLISHED("PUBLISHED", "宸插彂甯�"),
+
+    /**
+     * 椹冲洖
+     */
+    @Schema(description = "椹冲洖")
+    REJECTED("REJECTED", "椹冲洖"),
+
+    /**
+     * 宸蹭笅绾�
+     */
+    @Schema(description = "宸蹭笅绾�")
+    OFFLINE("OFFLINE", "宸蹭笅绾�");
+
+    /**
+     * 鐘舵�佺爜
+     */
+    private final String code;
+
+    /**
+     * 鐘舵�佹弿杩�
+     * -- GETTER --
+     *  鑾峰彇鐘舵�佹弿杩�
+     *
+     * @return 鐘舵�佹弿杩�
+
+     */
+    @Getter
+    private final String description;
+
+    EnterpriseNewsStatusEnum(String code, String description) {
+        this.code = code;
+        this.description = description;
+    }
+
+    /**
+     * 鑾峰彇鐘舵�佺爜
+     *
+     * @return 鐘舵�佺爜
+     */
+    @JsonValue
+    public String getCode() {
+        return code;
+    }
+
+    @Override
+    public String getValue() {
+        return "";
+    }
+
+    /**
+     * 鏍规嵁鐘舵�佺爜鑾峰彇鏋氫妇
+     *
+     * @param code 鐘舵�佺爜
+     * @return 鏋氫妇鍊�
+     */
+    @JsonCreator
+    public static EnterpriseNewsStatusEnum getByCode(String code) {
+        for (EnterpriseNewsStatusEnum status : values()) {
+            if (status.code.equals(code)) {
+                return status;
+            }
+        }
+        throw new IllegalArgumentException("Invalid enterprise news status code: " + code);
+    }
+
+    /**
+     * 鍒ゆ柇鏄惁涓鸿崏绋跨姸鎬�
+     *
+     * @return 鏄惁涓鸿崏绋跨姸鎬�
+     */
+    public boolean isDraft() {
+        return DRAFT.equals(this);
+    }
+
+    /**
+     * 鍒ゆ柇鏄惁涓哄緟瀹℃壒鐘舵��
+     *
+     * @return 鏄惁涓哄緟瀹℃壒鐘舵��
+     */
+    public boolean isPending() {
+        return PENDING.equals(this);
+    }
+
+    /**
+     * 鍒ゆ柇鏄惁涓哄凡鍙戝竷鐘舵��
+     *
+     * @return 鏄惁涓哄凡鍙戝竷鐘舵��
+     */
+    public boolean isPublished() {
+        return PUBLISHED.equals(this);
+    }
+
+    /**
+     * 鍒ゆ柇鏄惁涓洪┏鍥炵姸鎬�
+     *
+     * @return 鏄惁涓洪┏鍥炵姸鎬�
+     */
+    public boolean isRejected() {
+        return REJECTED.equals(this);
+    }
+
+    /**
+     * 鍒ゆ柇鏄惁涓哄凡涓嬬嚎鐘舵��
+     *
+     * @return 鏄惁涓哄凡涓嬬嚎鐘舵��
+     */
+    public boolean isOffline() {
+        return OFFLINE.equals(this);
+    }
+}
diff --git a/src/main/java/com/ruoyi/common/enums/SalesQuotationStatusEnum.java b/src/main/java/com/ruoyi/common/enums/SalesQuotationStatusEnum.java
new file mode 100644
index 0000000..0789c26
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/enums/SalesQuotationStatusEnum.java
@@ -0,0 +1,43 @@
+package com.ruoyi.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 閿�鍞姤浠风姸鎬佹灇涓�
+ */
+@Getter
+@AllArgsConstructor
+public enum SalesQuotationStatusEnum implements BaseEnum<String> {
+
+    DRAFT("鑽夌", "鑽夌"),
+    PENDING("寰呭鎵�", "寰呭鎵�"),
+    IN_PROGRESS("瀹℃牳涓�", "瀹℃牳涓�"),
+    APPROVED("閫氳繃", "閫氳繃"),
+    REJECTED("鎷掔粷", "鎷掔粷");
+
+    private final String value;
+    private final String label;
+
+    @Override
+    public String getCode() {
+        return value;
+    }
+
+    @Override
+    public String getValue() {
+        return label;
+    }
+
+    public static SalesQuotationStatusEnum fromValue(String value) {
+        if (value == null) {
+            return null;
+        }
+        for (SalesQuotationStatusEnum status : values()) {
+            if (status.getCode().equals(value)) {
+                return status;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/com/ruoyi/common/enums/ShippingStatusEnum.java b/src/main/java/com/ruoyi/common/enums/ShippingStatusEnum.java
new file mode 100644
index 0000000..e8155f2
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/enums/ShippingStatusEnum.java
@@ -0,0 +1,42 @@
+package com.ruoyi.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 鍙戣揣瀹℃壒鐘舵�佹灇涓�
+ */
+@Getter
+@AllArgsConstructor
+public enum ShippingStatusEnum implements BaseEnum<String> {
+
+    PENDING("寰呯‘璁�", "寰呯‘璁�"),
+    IN_PROGRESS("瀹℃牳涓�", "瀹℃牳涓�"),
+    APPROVED("瀹℃牳閫氳繃", "瀹℃牳閫氳繃"),
+    REJECTED("瀹℃牳鎷掔粷", "瀹℃牳鎷掔粷");
+
+    private final String value;
+    private final String label;
+
+    @Override
+    public String getCode() {
+        return value;
+    }
+
+    @Override
+    public String getValue() {
+        return label;
+    }
+
+    public static ShippingStatusEnum fromValue(String value) {
+        if (value == null) {
+            return null;
+        }
+        for (ShippingStatusEnum status : values()) {
+            if (status.getCode().equals(value)) {
+                return status;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/com/ruoyi/common/enums/TypeEnums.java b/src/main/java/com/ruoyi/common/enums/TypeEnums.java
new file mode 100644
index 0000000..ba1c1f3
--- /dev/null
+++ b/src/main/java/com/ruoyi/common/enums/TypeEnums.java
@@ -0,0 +1,70 @@
+package com.ruoyi.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum TypeEnums implements BaseEnum<Long> {
+
+    PUBLIC_OUT(1L, "鍏嚭绠$悊"),
+    LEAVE(2L, "璇峰亣绠$悊"),
+    BUSINESS_TRIP(3L, "鍑哄樊绠$悊"),
+    REIMBURSEMENT(4L, "鎶ラ攢绠$悊"),
+    PURCHASE_APPROVAL(5L, "閲囪喘瀹℃壒"),
+    QUOTATION_APPROVAL(6L, "鎶ヤ环瀹℃壒"),
+    SHIPPING_APPROVAL(7L, "鍙戣揣瀹℃壒"),
+    DANGEROUS_OPERATION(8L, "鍗遍櫓浣滀笟瀹℃壒"),
+    OFFICE_SUPPLIES(9L, "鍔炲叕鐢ㄥ搧瀹℃壒"),
+    REGULARIZATION_APPROVAL(10L, "杞瀹℃壒"),
+    TRANSFER_APPROVAL(11L, "璋冨姩瀹℃壒"),
+    RESIGNATION_APPROVAL(12L, "绂昏亴瀹℃壒"),
+    WORK_HANDOVER_APPROVAL(13L, "宸ヤ綔浜ゆ帴瀹℃壒"),
+    LEAVE_APPROVAL(14L, "璇峰亣瀹℃壒"),
+    OVERTIME_APPROVAL(15L, "鍔犵彮瀹℃壒"),
+    TRAVEL_REIMBURSEMENT_APPROVAL(16L, "鍑哄樊鎶ラ攢瀹℃壒"),
+    EXPENSE_APPROVAL(17L, "璐圭敤瀹℃壒"),
+    ENTERPRISE_NEWS_APPROVAL(18L, "浼佷笟鏂伴椈瀹℃壒");
+
+
+
+    private final Long value;
+    private final String label;
+
+    @Override
+    public Long getCode() {
+        return value;
+    }
+
+    @Override
+    public String getValue() {
+        return label;
+    }
+
+    /**
+     * 鏍规嵁鍊艰幏鍙栧搴旂殑鏋氫妇
+     * @param value 涓氬姟绫诲瀷鍊�
+     * @return 瀵瑰簲鐨勬灇涓撅紝鏈尮閰嶈繑鍥瀗ull
+     */
+    public static TypeEnums fromValue(Long value) {
+        if (value == null) {
+            return null;
+        }
+        for (TypeEnums type : values()) {
+            if (type.getCode().equals(value)) {
+                return type;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 鏍规嵁鍊艰幏鍙栨弿杩�
+     * @param value 涓氬姟绫诲瀷鍊�
+     * @return 涓氬姟绫诲瀷鎻忚堪锛屾湭鍖归厤杩斿洖"鑷畾涔夊鎵�"
+     */
+    public static String getLabelByValue(Long value) {
+        TypeEnums typeEnum = fromValue(value);
+        return typeEnum != null ? typeEnum.getValue() : "鑷畾涔夊鎵�";
+    }
+}
diff --git a/src/main/java/com/ruoyi/common/utils/OrderUtils.java b/src/main/java/com/ruoyi/common/utils/OrderUtils.java
index a110ec9..6f47375 100644
--- a/src/main/java/com/ruoyi/common/utils/OrderUtils.java
+++ b/src/main/java/com/ruoyi/common/utils/OrderUtils.java
@@ -61,8 +61,11 @@
      * @param <T> 瀹炰綋绫诲瀷
      * @return 璁㈠崟缂栧彿
      */
-    public static <T> String countTodayByCreateTime(BaseMapper<T> mapper,String preFix,String code) {
-        LocalDate today = LocalDate.now();
+    public static <T> String countTodayByCreateTime(BaseMapper<T> mapper,String preFix,String code, LocalDateTime createTime) {
+        if (createTime == null) {
+            createTime = LocalDateTime.now();
+        }
+        LocalDate today = createTime.toLocalDate();
         LocalDateTime todayStart = today.atStartOfDay();
         LocalDateTime tomorrowStart = today.plusDays(1).atStartOfDay();
         String dateStr = today.format(DateTimeFormatter.BASIC_ISO_DATE);
@@ -109,13 +112,17 @@
      * @param <T> 瀹炰綋绫绘硾鍨�
      * @return 褰撳ぉ璁板綍鏁伴噺
      */
-    public static <T> String countAfterServiceTodayByCreateTime(BaseMapper<T> mapper,String preFix) {
+    public static <T> String countAfterServiceTodayByCreateTime(BaseMapper<T> mapper,String preFix, LocalDateTime createTime) {
+        if (createTime == null) {
+            createTime = LocalDateTime.now();
+        }
+        LocalDate localDate = createTime.toLocalDate();
         LocalDateTime todayStart = LocalDateTime.of(
-                LocalDateTime.now().toLocalDate(),
+                localDate,
                 LocalTime.MIN
         );
         LocalDateTime todayEnd = LocalDateTime.of(
-                LocalDateTime.now().toLocalDate(),
+                localDate,
                 LocalTime.MAX
         );
 
@@ -127,6 +134,6 @@
                 .lt("create_time", endDate);
 
         Long aLong = mapper.selectCount(queryWrapper);
-        return preFix + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE).replaceAll("-", "") + String.format("%03d", (aLong + 1));
+        return preFix + localDate.format(DateTimeFormatter.ISO_LOCAL_DATE).replaceAll("-", "") + String.format("%03d", (aLong + 1));
     }
 }
diff --git a/src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java b/src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
index 2e0a0b0..55c00b9 100644
--- a/src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
+++ b/src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
@@ -37,7 +37,7 @@
                 wrapper.like(CustomerVisits::getVisitingPeople, customerVisits.getVisitingPeople());
             }
         }
-
+        wrapper.orderByDesc(CustomerVisits::getId);
         return customerVisitsMapper.selectPage(page, wrapper);
     }
 
diff --git a/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java b/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
index c666f3f..a0e8308 100644
--- a/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
@@ -1,5 +1,6 @@
 package com.ruoyi.device.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.utils.bean.BeanUtils;
@@ -34,7 +35,7 @@
 
     @Override
     public AjaxResult listPage(Page page, MaintenanceTask maintenanceTask) {
-        Page<MaintenanceTask> taskPage = maintenanceTaskMapper.selectPage(page, null);
+        Page<MaintenanceTask> taskPage = maintenanceTaskMapper.selectPage(page, new QueryWrapper<MaintenanceTask>().orderByDesc("create_time"));
         // 2. 濡傛灉娌℃湁鏁版嵁锛岀洿鎺ヨ繑鍥炵┖鍒嗛〉
         if (taskPage.getRecords().isEmpty()) {
             return AjaxResult.success(taskPage);
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
index 5826fa7..9729b40 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
@@ -67,6 +67,7 @@
         if (StringUtils.isNotBlank(inspectionTaskDto.getInspectionProject())) {
             queryWrapper.like(InspectionTask::getInspectionProject, inspectionTaskDto.getInspectionProject());
         }
+        queryWrapper.orderByDesc(InspectionTask::getCreateTime);
         IPage<InspectionTask> entityPage = inspectionTaskMapper.selectPage(page, queryWrapper);
 
         //  鏃犳暟鎹彁鍓嶈繑鍥�
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
index 779dc5f..ebe615d 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -53,6 +53,7 @@
         if (timingTask.getIsEnabled() != null) {
             queryWrapper.eq(TimingTask::getIsEnabled, timingTask.getIsEnabled());
         }
+        queryWrapper.orderByDesc(TimingTask::getCreateTime);
         IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, queryWrapper);
 
         // 2. 濡傛灉娌℃湁鏁版嵁锛岀洿鎺ヨ繑鍥炵┖鍒嗛〉
diff --git a/src/main/java/com/ruoyi/procurementrecord/pojo/CustomStorage.java b/src/main/java/com/ruoyi/procurementrecord/pojo/CustomStorage.java
index f4e6709..934552e 100644
--- a/src/main/java/com/ruoyi/procurementrecord/pojo/CustomStorage.java
+++ b/src/main/java/com/ruoyi/procurementrecord/pojo/CustomStorage.java
@@ -97,7 +97,6 @@
     /**
      * 鍏ュ簱鏃堕棿
      */
-    @TableField(fill = FieldFill.INSERT)
     @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private LocalDateTime createTime;
diff --git a/src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java b/src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java
index 4e8e3fb..046fb5c 100644
--- a/src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java
+++ b/src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java
@@ -63,7 +63,6 @@
     @Schema(description = "鍒涘缓鏃堕棿")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
-    @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createTime;
     @Schema(description = "鏇存柊鏃堕棿")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
diff --git a/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java b/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java
index 1d99dec..eeeda2f 100644
--- a/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java
@@ -535,7 +535,7 @@
             Long aLong = customStorageMapper.selectCount(null);
             item.setInboundBatches(aLong.equals(0L) ? "绗�1鎵规(鑷畾涔夊叆搴�)" : "绗�"+ (aLong + 1) + "鎵规(鑷畾涔夊叆搴�)" );
             item.setCreateBy(loginUser.getNickName());
-            item.setCode(OrderUtils.countTodayByCreateTime(customStorageMapper, "", "code"));
+            item.setCode(OrderUtils.countTodayByCreateTime(customStorageMapper, "", "code", item.getCreateTime() != null ? item.getCreateTime() : LocalDateTime.now()));
             customStorageMapper.insert(item);
         });
         return AjaxResult.success("鑷畾涔夊叆搴撴垚鍔�");
diff --git a/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java b/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
index 5834e65..28bbb43 100644
--- a/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
+++ b/src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java
@@ -8,6 +8,8 @@
 import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
 import com.ruoyi.common.utils.OrderUtils;
 import com.ruoyi.common.utils.SecurityUtils;
+
+import java.time.LocalDateTime;
 import com.ruoyi.procurementrecord.bean.dto.ReturnManagementDto;
 import com.ruoyi.procurementrecord.bean.dto.ReturnSaleProductDto;
 import com.ruoyi.procurementrecord.bean.vo.ShippingInfoVo;
@@ -56,7 +58,7 @@
     @Override
     public boolean addReturnManagementDto(ReturnManagementDto returnManagementDto) {
         if (ObjectUtils.isEmpty(returnManagementDto.getReturnNo())){
-            String rt = OrderUtils.countTodayByCreateTime(returnManagementMapper, "RT","return_no");
+            String rt = OrderUtils.countTodayByCreateTime(returnManagementMapper, "RT","return_no", returnManagementDto.getCreateTime() != null ? returnManagementDto.getCreateTime() : LocalDateTime.now());
             returnManagementDto.setReturnNo(rt);
         }
         save(returnManagementDto);
diff --git a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
index 0beecd2..9b8735f 100644
--- a/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
+++ b/src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java
@@ -18,6 +18,7 @@
 import org.springframework.stereotype.Component;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.Collections;
 
 @Component
@@ -88,11 +89,21 @@
      * @param recordId
      */
     public void addStock(Long productModelId, BigDecimal quantity, String recordType, Long recordId) {
+        addStock(productModelId, quantity, recordType, recordId, null);
+    }
+
+    /**
+     * 鍚堟牸鍏ュ簱
+     * @param recordType
+     * @param recordId
+     */
+    public void addStock(Long productModelId, BigDecimal quantity, String recordType, Long recordId, LocalDateTime createTime) {
         StockInventoryDto stockInventoryDto = new StockInventoryDto();
         stockInventoryDto.setRecordId(recordId);
         stockInventoryDto.setRecordType(String.valueOf(recordType));
         stockInventoryDto.setQualitity(quantity);
         stockInventoryDto.setProductModelId(productModelId);
+        stockInventoryDto.setCreateTime(createTime);
         stockInventoryService.addStockInRecordOnly(stockInventoryDto);
     }
 
@@ -104,12 +115,22 @@
      * @param recordId
      */
     public void addStockWithBatchNo(Long productModelId, BigDecimal quantity, String recordType, Long recordId, String batchNo) {
+        addStockWithBatchNo(productModelId, quantity, recordType, recordId, batchNo, null);
+    }
+
+    /**
+     * 鍚堟牸鍏ュ簱甯︽壒娆″彿
+     * @param recordType
+     * @param recordId
+     */
+    public void addStockWithBatchNo(Long productModelId, BigDecimal quantity, String recordType, Long recordId, String batchNo, LocalDateTime createTime) {
         StockInventoryDto stockInventoryDto = new StockInventoryDto();
         stockInventoryDto.setRecordId(recordId);
         stockInventoryDto.setRecordType(String.valueOf(recordType));
         stockInventoryDto.setQualitity(quantity);
         stockInventoryDto.setProductModelId(productModelId);
         stockInventoryDto.setBatchNo(batchNo);
+        stockInventoryDto.setCreateTime(createTime);
         stockInventoryService.addStockInRecordOnly(stockInventoryDto);
     }
 
diff --git a/src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingController.java b/src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingController.java
index 277f949..a295185 100644
--- a/src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingController.java
+++ b/src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingController.java
@@ -43,7 +43,7 @@
     @PostMapping("/updateRouteItem")
     @Operation(summary = "淇敼鐢熶骇璁㈠崟鐨勫伐鑹鸿矾绾胯鎯�")
     public R updateRouteItem(@RequestBody ProductionOrderRoutingOperation productionOrderRoutingOperation) {
-        return R.ok(productionOrderRoutingOperationService.updateRouteItem(productionOrderRoutingOperation));
+        return productionOrderRoutingOperationService.updateRouteItem(productionOrderRoutingOperation);
     }
 
     @Log(title = "鐢熶骇宸ュ簭璺敱", businessType = BusinessType.DELETE)
diff --git a/src/main/java/com/ruoyi/production/pojo/ProductionOrder.java b/src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
index e52ee9d..6208146 100644
--- a/src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
+++ b/src/main/java/com/ruoyi/production/pojo/ProductionOrder.java
@@ -41,7 +41,6 @@
     private String npsNo;
 
     @Schema(description = "褰曞叆鏃堕棿")
-    @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createTime;
 
     @Schema(description = "鏇存柊鏃堕棿")
diff --git a/src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationService.java b/src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationService.java
index 8878006..e14b36f 100644
--- a/src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationService.java
+++ b/src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationService.java
@@ -13,4 +13,5 @@
     R deleteRouteItem(Long id);
 
     int sortRouteItem(ProductionOrderRoutingOperation productionOrderRoutingOperation);
+
 }
diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
index 8562b01..9543cad 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java
@@ -44,6 +44,7 @@
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -115,7 +116,7 @@
         // 涓嬪崟鍏ュ彛缁熶竴琛ラ綈鏉ユ簮鍗曟嵁銆佽鍒掑拰宸ヨ壓淇℃伅锛岄伩鍏嶅墠绔垎鍒紶澶氬瀛楁銆�
         validateAndFillOrder(productionOrder, oldOrder);
         if (productionOrder.getNpsNo() == null || productionOrder.getNpsNo().trim().isEmpty()) {
-            productionOrder.setNpsNo(generateNextOrderNo());
+            productionOrder.setNpsNo(generateNextOrderNo(productionOrder.getCreateTime() != null ? productionOrder.getCreateTime() : LocalDateTime.now()));
         }
         if (productionOrder.getCompleteQuantity() == null) {
             productionOrder.setCompleteQuantity(BigDecimal.ZERO);
@@ -508,9 +509,10 @@
                 .orderByDesc(ProductionOrder::getId);
     }
 
-    private String generateNextOrderNo() {
+    private String generateNextOrderNo(LocalDateTime createTime) {
         // 鐢熸垚涓嬩竴涓敓浜ц鍗曞彿
-        String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        LocalDate localDate = createTime.toLocalDate();
+        String datePrefix = localDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
         String prefix = "SC" + datePrefix;
         ProductionOrder latestOrder = this.getOne(Wrappers.<ProductionOrder>lambdaQuery()
                 .likeRight(ProductionOrder::getNpsNo, prefix)
diff --git a/src/main/java/com/ruoyi/project/system/mapper/SysUserDeptMapper.java b/src/main/java/com/ruoyi/project/system/mapper/SysUserDeptMapper.java
index 8c0ca53..f5baaec 100644
--- a/src/main/java/com/ruoyi/project/system/mapper/SysUserDeptMapper.java
+++ b/src/main/java/com/ruoyi/project/system/mapper/SysUserDeptMapper.java
@@ -3,6 +3,7 @@
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.ruoyi.project.system.domain.SysUserDept;
 import com.ruoyi.project.system.domain.vo.SysUserDeptVo;
+import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
@@ -15,4 +16,28 @@
     List<SysUserDeptVo> userLoginFacotryList(@Param("userDeptVo") SysUserDeptVo userDeptVo);
 
     List<Map<String, Object>> setSchemeApplicableStaffUserInfo(@Param("ids") List<Long> ids);
+
+    @Select("<script>" +
+            "select count(distinct sud.user_id) " +
+            "from sys_user_dept sud " +
+            "inner join sys_user su on su.user_id = sud.user_id " +
+            "where su.del_flag = '0' " +
+            "and sud.dept_id in " +
+            "<foreach collection='deptIds' item='deptId' open='(' separator=',' close=')'>" +
+            "#{deptId}" +
+            "</foreach>" +
+            "</script>")
+    Long countDistinctUserIdsByDeptIds(@Param("deptIds") List<Long> deptIds);
+
+    @Select("<script>" +
+            "select distinct sud.user_id " +
+            "from sys_user_dept sud " +
+            "inner join sys_user su on su.user_id = sud.user_id " +
+            "where su.del_flag = '0' " +
+            "and sud.dept_id in " +
+            "<foreach collection='deptIds' item='deptId' open='(' separator=',' close=')'>" +
+            "#{deptId}" +
+            "</foreach>" +
+            "</script>")
+    List<Long> selectDistinctUserIdsByDeptIds(@Param("deptIds") List<Long> deptIds);
 }
diff --git a/src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java b/src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java
index a1b97d8..3fc9475 100644
--- a/src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java
+++ b/src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java
@@ -4,6 +4,8 @@
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsMapper;
+import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.project.system.domain.SysDept;
 import com.ruoyi.project.system.domain.SysNotice;
@@ -19,8 +21,12 @@
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * 鍏憡 鏈嶅姟灞傚疄鐜�
@@ -32,10 +38,14 @@
 @RequiredArgsConstructor
 public class SysNoticeServiceImpl  extends ServiceImpl<SysNoticeMapper, SysNotice> implements ISysNoticeService {
 
+    private static final Pattern ENTERPRISE_NEWS_ID_PATTERN = Pattern.compile("[?&]id=(\\d+)");
+
     private final SysNoticeMapper noticeMapper;
     private final SysUserMapper userMapper;
     private final SysDeptMapper deptMapper;
     private final SysUserDeptMapper userDeptMapper;
+    private final UnipushService unipushService;
+    private final EnterpriseNewsMapper enterpriseNewsMapper;
 
     /**
      * 鏌ヨ鍏憡淇℃伅
@@ -79,9 +89,24 @@
      * @return 缁撴灉
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public int updateNotice(SysNotice notice)
     {
-        return noticeMapper.updateNotice(notice);
+        if (notice == null || notice.getNoticeId() == null) {
+            return 0;
+        }
+        SysNotice dbNotice = noticeMapper.selectNoticeById(notice.getNoticeId());
+        if (dbNotice == null) {
+            return 0;
+        }
+        boolean needSyncNewsReadCount = isEnterpriseNewsNotice(dbNotice)
+                && notice.getStatus() != null
+                && !notice.getStatus().equals(dbNotice.getStatus());
+        int rows = noticeMapper.updateNotice(notice);
+        if (rows > 0 && needSyncNewsReadCount) {
+            syncEnterpriseNewsReadCount(dbNotice.getJumpPath());
+        }
+        return rows;
     }
 
     /**
@@ -118,10 +143,17 @@
     @Override
     public int readAll() {
         Long userId = SecurityUtils.getUserId();
-        return noticeMapper.update(null, Wrappers.<SysNotice>lambdaUpdate()
+        List<SysNotice> unreadNotices = noticeMapper.selectList(Wrappers.<SysNotice>lambdaQuery()
+                .eq(SysNotice::getConsigneeId, userId)
+                .eq(SysNotice::getStatus, "0"));
+        int rows = noticeMapper.update(null, Wrappers.<SysNotice>lambdaUpdate()
                 .eq(SysNotice::getConsigneeId, userId)
                 .eq(SysNotice::getStatus, "0")
                 .set(SysNotice::getStatus, "1"));
+        if (rows > 0) {
+            syncEnterpriseNewsReadCount(unreadNotices);
+        }
+        return rows;
     }
 
     @Override
@@ -213,9 +245,63 @@
             return false;
         }
         sysNotice.setStatus("1");
-        return noticeMapper.update(null, Wrappers.<SysNotice>lambdaUpdate()
+        boolean updated = noticeMapper.update(null, Wrappers.<SysNotice>lambdaUpdate()
                 .eq(SysNotice::getNoticeId, noticeId)
                 .eq(SysNotice::getStatus, "0")
                 .set(SysNotice::getStatus, "1")) > 0;
+        if (updated) {
+            syncEnterpriseNewsReadCount(sysNotice.getJumpPath());
+        }
+        return updated;
+    }
+
+    private boolean isEnterpriseNewsNotice(SysNotice sysNotice) {
+        return sysNotice != null
+                && sysNotice.getJumpPath() != null
+                && sysNotice.getJumpPath().contains("/enterpriseNews?id=");
+    }
+
+    private void syncEnterpriseNewsReadCount(List<SysNotice> notices) {
+        if (notices == null || notices.isEmpty()) {
+            return;
+        }
+        Set<String> jumpPaths = new HashSet<>();
+        for (SysNotice notice : notices) {
+            if (isEnterpriseNewsNotice(notice)) {
+                jumpPaths.add(notice.getJumpPath());
+            }
+        }
+        for (String jumpPath : jumpPaths) {
+            syncEnterpriseNewsReadCount(jumpPath);
+        }
+    }
+
+    private void syncEnterpriseNewsReadCount(String jumpPath) {
+        Long newsId = parseEnterpriseNewsId(jumpPath);
+        if (newsId == null) {
+            return;
+        }
+        long readCount = noticeMapper.selectCount(Wrappers.<SysNotice>lambdaQuery()
+                .eq(SysNotice::getStatus, "1")
+                .eq(SysNotice::getJumpPath, jumpPath));
+        EnterpriseNews enterpriseNews = new EnterpriseNews();
+        enterpriseNews.setId(newsId);
+        enterpriseNews.setReadCount((int) readCount);
+        enterpriseNewsMapper.updateById(enterpriseNews);
+    }
+
+    private Long parseEnterpriseNewsId(String jumpPath) {
+        if (jumpPath == null || !jumpPath.startsWith("/enterpriseNews")) {
+            return null;
+        }
+        Matcher matcher = ENTERPRISE_NEWS_ID_PATTERN.matcher(jumpPath);
+        if (!matcher.find()) {
+            return null;
+        }
+        try {
+            return Long.parseLong(matcher.group(1));
+        } catch (NumberFormatException e) {
+            return null;
+        }
     }
 }
diff --git a/src/main/java/com/ruoyi/projectManagement/controller/RolesController.java b/src/main/java/com/ruoyi/projectManagement/controller/RolesController.java
index 02217e5..b294c03 100644
--- a/src/main/java/com/ruoyi/projectManagement/controller/RolesController.java
+++ b/src/main/java/com/ruoyi/projectManagement/controller/RolesController.java
@@ -7,6 +7,8 @@
 import com.ruoyi.framework.aspectj.lang.annotation.Log;
 import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
 import com.ruoyi.framework.web.domain.AjaxResult;
+
+import java.time.LocalDateTime;
 import com.ruoyi.projectManagement.dto.RoleDto;
 import com.ruoyi.projectManagement.mapper.RolesMapper;
 import com.ruoyi.projectManagement.pojo.Roles;
@@ -38,7 +40,7 @@
     @Log(title = "椤圭洰瑙掕壊-鏂板", businessType = BusinessType.INSERT)
     public AjaxResult add(@RequestBody RoleDto roleDto) {
         if (roleDto.getIsDefaultNo()) {
-            roleDto.setNo(OrderUtils.countTodayByCreateTime(rolesMapper, "XMJS","no"));
+            roleDto.setNo(OrderUtils.countTodayByCreateTime(rolesMapper, "XMJS","no", roleDto.getCreateTime() != null ? roleDto.getCreateTime() : LocalDateTime.now()));
         }
         return AjaxResult.success(rolesservice.save(roleDto));
     }
diff --git a/src/main/java/com/ruoyi/projectManagement/pojo/Roles.java b/src/main/java/com/ruoyi/projectManagement/pojo/Roles.java
index 1b65ea4..ed111b8 100644
--- a/src/main/java/com/ruoyi/projectManagement/pojo/Roles.java
+++ b/src/main/java/com/ruoyi/projectManagement/pojo/Roles.java
@@ -38,7 +38,6 @@
     private Integer status;
 
     @Schema(description = "鍒涘缓鏃堕棿")
-    @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createTime;
 
     @Schema(description = "鍒涘缓鐢ㄦ埛")
diff --git a/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java b/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
index 816ce27..630a2d5 100644
--- a/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
+++ b/src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java
@@ -7,6 +7,7 @@
 import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
 import com.ruoyi.framework.web.controller.BaseController;
 import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.framework.web.page.TableDataInfo;
 import com.ruoyi.purchase.dto.PurchaseLedgerDto;
 import com.ruoyi.purchase.mapper.PurchaseLedgerTemplateMapper;
@@ -31,6 +32,7 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URLEncoder;
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -134,6 +136,13 @@
     @PostMapping("/addOrEditPurchase")
     public AjaxResult addOrEditPurchase(@RequestBody PurchaseLedgerDto purchaseLedgerDto) throws Exception {
         return toAjax(purchaseLedgerService.addOrEditPurchase(purchaseLedgerDto));
+    }
+
+    @Operation(summary = "鎵归噺鎺ㄨ繘閲囪喘鍙拌处鍒板叆搴�")
+    @Log(title = "鎵归噺鎺ㄨ繘閲囪喘鍙拌处鍒板叆搴�", businessType = BusinessType.OTHER)
+    @PostMapping("/batchInsertPurchaseSteps")
+    public R batchInsertPurchaseSteps(@RequestBody PurchaseLedgerDto purchaseLedgerDto) {
+        return purchaseLedgerService.batchInsertPurchaseSteps(purchaseLedgerDto == null ? null : purchaseLedgerDto.getIds());
     }
 
     /**
@@ -247,7 +256,7 @@
     @Operation(summary = "鐢熸垚閲囪喘搴忓垪鍙�")
     @GetMapping("/createPurchaseNo")
     @Log(title = "鐢熸垚閲囪喘搴忓垪鍙�", businessType = BusinessType.OTHER)
-    public AjaxResult createPurchaseNo() {
-        return AjaxResult.success("鐢熸垚鎴愬姛",purchaseLedgerService.getPurchaseNo());
+    public AjaxResult createPurchaseNo(@RequestParam Date entryDate) {
+        return AjaxResult.success("鐢熸垚鎴愬姛",purchaseLedgerService.getPurchaseNo(entryDate));
     }
 }
diff --git a/src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java b/src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java
index b362909..7bd0416 100644
--- a/src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java
+++ b/src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java
@@ -13,6 +13,8 @@
 import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
 import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
 import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
+
+import java.time.LocalDateTime;
 import com.ruoyi.purchase.service.PurchaseReturnOrdersService;
 import com.ruoyi.purchase.vo.PurchaseStockInProductVo;
 import io.swagger.v3.oas.annotations.Operation;
@@ -50,7 +52,7 @@
     @PostMapping("/add")
     public AjaxResult add(@RequestBody PurchaseReturnOrderDto purchaseReturnOrderDto) throws Exception {
         if (purchaseReturnOrderDto.getIsDefaultNo()) {
-            purchaseReturnOrderDto.setNo(OrderUtils.countTodayByCreateTime(purchaseReturnOrdersMapper, "CGTL", "no"));
+            purchaseReturnOrderDto.setNo(OrderUtils.countTodayByCreateTime(purchaseReturnOrdersMapper, "CGTL", "no", purchaseReturnOrderDto.getCreateTime() != null ? purchaseReturnOrderDto.getCreateTime() : LocalDateTime.now()));
         }
         return AjaxResult.success(purchaseReturnOrdersService.add(purchaseReturnOrderDto));
     }
diff --git a/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java b/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
index 22faea3..cbca6f7 100644
--- a/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
+++ b/src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java
@@ -133,6 +133,9 @@
 
     private List<SalesLedgerProduct> productData;
 
+    @Schema(description = "鎵归噺澶勭悊閲囪喘鍙拌处ID鍒楄〃")
+    private List<Long> ids;
+
     private List<String> tempFileIds;
 
     private List<CommonFile> SalesLedgerFiles;
diff --git a/src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java b/src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java
index b55242c..e7ec1eb 100644
--- a/src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java
+++ b/src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java
@@ -157,5 +157,4 @@
 
     @TableField(fill = FieldFill.INSERT)
     private Long deptId;
-
 }
diff --git a/src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java b/src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java
index a89317b..936b5b5 100644
--- a/src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java
+++ b/src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java
@@ -82,7 +82,6 @@
     private BigDecimal totalAmount;
 
     @Schema(description = "褰曞叆鏃堕棿")
-    @TableField(fill = FieldFill.INSERT)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
diff --git a/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java b/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
index a0378af..4b91b89 100644
--- a/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
+++ b/src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java
@@ -4,11 +4,13 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.purchase.dto.PurchaseLedgerDto;
 import com.ruoyi.purchase.pojo.PurchaseLedger;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.util.Date;
 import java.util.List;
 
 /**
@@ -22,6 +24,8 @@
     List<PurchaseLedger> selectPurchaseLedgerList(PurchaseLedger purchaseLedger);
 
     int addOrEditPurchase(PurchaseLedgerDto purchaseLedgerDto) throws Exception;
+
+    R batchInsertPurchaseSteps(List<Long> ids);
 
     void addQualityInspect(PurchaseLedger purchaseLedger, SalesLedgerProduct saleProduct);
 
@@ -39,7 +43,7 @@
 
     IPage<PurchaseLedgerDto> selectPurchaseLedgerListPage(IPage ipage, PurchaseLedgerDto purchaseLedger);
 
-    String getPurchaseNo();
+    String getPurchaseNo(Date entryDate);
 
     AjaxResult importData(MultipartFile file);
 
diff --git a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
index 09d5d20..7a70435 100644
--- a/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -6,8 +6,12 @@
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.ruoyi.approve.bean.vo.ApproveProcessVO;
+import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
+import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
+import com.ruoyi.approve.pojo.ApprovalInstance;
+import com.ruoyi.approve.pojo.ApprovalTemplate;
 import com.ruoyi.approve.pojo.ApproveProcess;
+import com.ruoyi.approve.service.ApprovalInstanceService;
 import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
 import com.ruoyi.basic.enums.ApplicationTypeEnum;
 import com.ruoyi.basic.enums.RecordTypeEnum;
@@ -18,16 +22,21 @@
 import com.ruoyi.basic.pojo.ProductModel;
 import com.ruoyi.basic.pojo.SupplierManage;
 import com.ruoyi.basic.utils.FileUtil;
-import com.ruoyi.common.enums.FileNameType;
 import com.ruoyi.common.exception.base.BaseException;
+import com.ruoyi.common.enums.ApprovalStatusEnum;
+import com.ruoyi.common.enums.ReviewStatusEnum;
+import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
+import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.framework.security.LoginUser;
 import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.other.mapper.TempFileMapper;
 import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
 import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
+import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.project.system.domain.SysUser;
 import com.ruoyi.project.system.mapper.SysUserMapper;
 import com.ruoyi.purchase.dto.PurchaseLedgerDto;
@@ -44,13 +53,15 @@
 import com.ruoyi.quality.pojo.QualityInspectParam;
 import com.ruoyi.quality.pojo.QualityTestStandard;
 import com.ruoyi.quality.pojo.QualityTestStandardParam;
+import com.ruoyi.quality.service.IQualityInspectService;
 import com.ruoyi.sales.mapper.CommonFileMapper;
 import com.ruoyi.sales.mapper.SalesLedgerMapper;
 import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
-import com.ruoyi.sales.pojo.CommonFile;
 import com.ruoyi.sales.pojo.SalesLedger;
 import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
+import com.ruoyi.stock.pojo.StockInRecord;
+import com.ruoyi.stock.service.StockInRecordService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
@@ -100,6 +111,11 @@
     private final ApproveProcessServiceImpl approveProcessService;
     private final ProcurementRecordMapper procurementRecordStorageMapper;
     private final FileUtil fileUtil;
+    private final ApprovalInstanceService approvalInstanceService;
+    private final IQualityInspectService qualityInspectService;
+    private final StockInRecordService stockInRecordService;
+    private final StockUtils stockUtils;
+    private final ApprovalTemplateMapper approvalTemplateMapper;
 
     @Override
     public List<PurchaseLedger> selectPurchaseLedgerList(PurchaseLedger purchaseLedger) {
@@ -174,6 +190,188 @@
         return 1;
     }
 
+    @Override
+    public R batchInsertPurchaseSteps(List<Long> ids) {
+        if (CollectionUtils.isEmpty(ids)) {
+            return R.fail("璇烽�夋嫨閲囪喘鍙拌处");
+        }
+
+        List<Long> distinctIds = ids.stream()
+                .filter(Objects::nonNull)
+                .distinct()
+                .collect(Collectors.toList());
+        if (CollectionUtils.isEmpty(distinctIds)) {
+            return R.fail("璇烽�夋嫨閲囪喘鍙拌处");
+        }
+
+        PurchaseLedgerDto queryDto = new PurchaseLedgerDto();
+        queryDto.setIds(distinctIds);
+        IPage<PurchaseLedgerDto> pageResult = this.selectPurchaseLedgerListPage(
+                new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(1, distinctIds.size()),
+                queryDto
+        );
+        List<PurchaseLedgerDto> ledgerDtos = pageResult == null || pageResult.getRecords() == null
+                ? Collections.emptyList()
+                : pageResult.getRecords();
+
+        Map<Long, PurchaseLedgerDto> ledgerDtoMap = ledgerDtos.stream()
+                .filter(item -> item.getId() != null)
+                .collect(Collectors.toMap(PurchaseLedgerDto::getId, item -> item, (left, right) -> left, LinkedHashMap::new));
+
+        List<Map<String, Object>> details = new ArrayList<>();
+        int successCount = 0;
+        int skipCount = 0;
+        int failCount = 0;
+        int autoApprovedCount = 0;
+
+        for (Long id : distinctIds) {
+            Map<String, Object> detail = new LinkedHashMap<>();
+            detail.put("purchaseLedgerId", id);
+            PurchaseLedgerDto purchaseLedgerDto = ledgerDtoMap.get(id);
+            if (purchaseLedgerDto == null) {
+                failCount++;
+                detail.put("status", "FAIL");
+                detail.put("message", "閲囪喘鍙拌处涓嶅瓨鍦ㄦ垨鏈煡璇㈠埌");
+                details.add(detail);
+                continue;
+            }
+
+            detail.put("purchaseContractNumber", purchaseLedgerDto.getPurchaseContractNumber());
+            detail.put("approvalStatus", purchaseLedgerDto.getApprovalStatus());
+            detail.put("stockInStatus", purchaseLedgerDto.getStockInStatus());
+
+            if (ApprovalStatusEnum.REJECTED.getCode().equals(purchaseLedgerDto.getApprovalStatus())) {
+                skipCount++;
+                detail.put("status", "SKIP");
+                detail.put("message", "閲囪喘鍗曞凡椹冲洖");
+                details.add(detail);
+                continue;
+            }
+
+            if ("瀹屽叏鍏ュ簱".equals(purchaseLedgerDto.getStockInStatus())) {
+                skipCount++;
+                detail.put("status", "SKIP");
+                detail.put("message", "閲囪喘鍗曞凡瀹屽叏鍏ュ簱");
+                details.add(detail);
+                continue;
+            }
+
+            try {
+                PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(id);
+                if (purchaseLedger == null) {
+                    failCount++;
+                    detail.put("status", "FAIL");
+                    detail.put("message", "閲囪喘鍙拌处涓嶅瓨鍦�");
+                    details.add(detail);
+                    continue;
+                }
+
+                if (!ApprovalStatusEnum.APPROVED.getCode().equals(purchaseLedger.getApprovalStatus())) {
+                    ApprovalInstance approvalInstance = approvalInstanceService.getOne(
+                            Wrappers.<ApprovalInstance>lambdaQuery()
+                                    .eq(ApprovalInstance::getBusinessId, purchaseLedger.getId())
+                                    .eq(ApprovalInstance::getBusinessType, 5L)
+                                    .eq(ApprovalInstance::getDeleted, 0)
+                                    .orderByDesc(ApprovalInstance::getId)
+                                    .last("limit 1")
+                    );
+                    if (approvalInstance == null) {
+                        failCount++;
+                        detail.put("status", "FAIL");
+                        detail.put("message", "鏈壘鍒板搴旂殑閲囪喘瀹℃壒瀹炰緥");
+                        details.add(detail);
+                        continue;
+                    }
+
+                    if ("APPROVED".equals(approvalInstance.getStatus())
+                            && !ApprovalStatusEnum.APPROVED.getCode().equals(purchaseLedger.getApprovalStatus())) {
+                        purchaseLedger.setApprovalStatus(ApprovalStatusEnum.APPROVED.getCode());
+                        purchaseLedgerMapper.updateById(purchaseLedger);
+                    } else if (!"APPROVED".equals(approvalInstance.getStatus())) {
+                        R autoApproveResult = approvalInstanceService.autoApprove(approvalInstance.getId());
+                        if (autoApproveResult == null || !R.isSuccess(autoApproveResult)) {
+                            failCount++;
+                            detail.put("status", "FAIL");
+                            detail.put("message", autoApproveResult == null ? "閲囪喘瀹℃壒鑷姩閫氳繃澶辫触" : autoApproveResult.getMsg());
+                            details.add(detail);
+                            continue;
+                        }
+                        autoApprovedCount++;
+                    }
+
+                    purchaseLedger = purchaseLedgerMapper.selectById(id);
+                    if (purchaseLedger == null || !ApprovalStatusEnum.APPROVED.getCode().equals(purchaseLedger.getApprovalStatus())) {
+                        failCount++;
+                        detail.put("status", "FAIL");
+                        detail.put("message", "閲囪喘鍗曞鎵圭姸鎬佹湭鏇存柊涓哄凡閫氳繃");
+                        details.add(detail);
+                        continue;
+                    }
+                }
+
+                List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(
+                        Wrappers.<SalesLedgerProduct>lambdaQuery()
+                                .eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId())
+                                .eq(SalesLedgerProduct::getType, 2)
+                                .orderByAsc(SalesLedgerProduct::getId)
+                );
+                if (CollectionUtils.isEmpty(products)) {
+                    skipCount++;
+                    detail.put("status", "SKIP");
+                    detail.put("message", "閲囪喘鍗曟病鏈変骇鍝佹槑缁�");
+                    details.add(detail);
+                    continue;
+                }
+
+                int processedProductCount = 0;
+                int skippedProductCount = 0;
+                int failedProductCount = 0;
+                for (SalesLedgerProduct product : products) {
+                    try {
+                        boolean processed;
+                        if (Boolean.TRUE.equals(product.getIsChecked())) {
+                            processed = processPurchaseQualityProduct(purchaseLedger, product);
+                        } else {
+                            processed = processPurchaseDirectProduct(purchaseLedger, product);
+                        }
+                        if (processed) {
+                            processedProductCount++;
+                        } else {
+                            skippedProductCount++;
+                        }
+                    } catch (Exception ex) {
+                        failedProductCount++;
+                        log.error("鎵归噺鎺ㄨ繘閲囪喘鍙拌处澶辫触, purchaseLedgerId={}, productId={}, productModelId={}",
+                                purchaseLedger.getId(), product.getId(), product.getProductModelId(), ex);
+                    }
+                }
+
+                successCount++;
+                detail.put("status", failedProductCount > 0 ? "PARTIAL" : "SUCCESS");
+                detail.put("processedProductCount", processedProductCount);
+                detail.put("skippedProductCount", skippedProductCount);
+                detail.put("failedProductCount", failedProductCount);
+                detail.put("message", failedProductCount > 0 ? "閮ㄥ垎浜у搧澶勭悊澶辫触" : "澶勭悊瀹屾垚");
+                details.add(detail);
+            } catch (Exception ex) {
+                failCount++;
+                detail.put("status", "FAIL");
+                detail.put("message", ex.getMessage());
+                details.add(detail);
+                log.error("鎵归噺鎺ㄨ繘閲囪喘鍙拌处澶辫触, purchaseLedgerId={}", id, ex);
+            }
+        }
+
+        Map<String, Object> summary = new LinkedHashMap<>();
+        summary.put("totalCount", distinctIds.size());
+        summary.put("successCount", successCount);
+        summary.put("skipCount", skipCount);
+        summary.put("failCount", failCount);
+        summary.put("autoApprovedCount", autoApprovedCount);
+        summary.put("details", details);
+        return R.ok(summary, "鎵归噺鎺ㄨ繘閲囪喘姝ラ瀹屾垚");
+    }
+
 
     public void addQualityInspect(PurchaseLedger purchaseLedger, SalesLedgerProduct saleProduct) {
         QualityInspect qualityInspect = new QualityInspect();
@@ -199,7 +397,219 @@
                         param.setId(null);
                         param.setInspectId(qualityInspect.getId());
                         qualityInspectParamMapper.insert(param);
-                    });
+            });
+        }
+    }
+
+    private boolean processPurchaseQualityProduct(PurchaseLedger purchaseLedger, SalesLedgerProduct product) {
+        if (purchaseLedger == null || product == null || product.getProductModelId() == null) {
+            return false;
+        }
+
+        QualityInspect qualityInspect = findLatestPurchaseQualityInspect(purchaseLedger.getId(), product.getProductModelId());
+        if (qualityInspect == null) {
+            addQualityInspect(purchaseLedger, product);
+            qualityInspect = findLatestPurchaseQualityInspect(purchaseLedger.getId(), product.getProductModelId());
+        }
+        if (qualityInspect == null) {
+            return false;
+        }
+
+        LocalDateTime purchaseInspectTime = toStartOfDayPlusDays(purchaseLedger.getEntryDate(), 1);
+        if (purchaseInspectTime != null && (qualityInspect.getCheckTime() == null
+                || !DateUtils.toLocalDate(qualityInspect.getCheckTime()).equals(purchaseInspectTime.toLocalDate()))) {
+            qualityInspect.setCheckTime(DateUtils.toDate(purchaseInspectTime.toLocalDate()));
+            qualityInspectMapper.updateById(qualityInspect);
+        }
+
+        List<StockInRecord> stockRecords = findQualityStockRecords(qualityInspect.getId(), product.getProductModelId());
+        if (hasApprovedStockRecord(stockRecords)) {
+            return true;
+        }
+
+        if (!Integer.valueOf(1).equals(qualityInspect.getInspectState())) {
+            R autoSubmitResult = qualityInspectService.autoSubmit(qualityInspect.getId());
+            if (autoSubmitResult == null || !R.isSuccess(autoSubmitResult)) {
+                return false;
+            }
+            qualityInspect = qualityInspectMapper.selectById(qualityInspect.getId());
+        }
+
+        stockRecords = findQualityStockRecords(qualityInspect.getId(), product.getProductModelId());
+        if (CollectionUtils.isEmpty(stockRecords)
+                && qualityInspect.getQualifiedQuantity() != null
+                && qualityInspect.getQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
+            stockUtils.addStockWithBatchNo(
+                    product.getProductModelId(),
+                    qualityInspect.getQualifiedQuantity(),
+                    StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode(),
+                    qualityInspect.getId(),
+                    null,
+                    purchaseInspectTime == null ? null : purchaseInspectTime.plusDays(1)
+            );
+            stockRecords = findQualityStockRecords(qualityInspect.getId(), product.getProductModelId());
+        }
+
+        StockInRecord targetStockRecord = findLatestUnapprovedStockRecord(stockRecords);
+        if (targetStockRecord == null) {
+            return false;
+        }
+
+        LocalDateTime qualityStockCreateTime = resolveQualityStockCreateTime(qualityInspect);
+        if (qualityStockCreateTime != null && (targetStockRecord.getCreateTime() == null
+                || !qualityStockCreateTime.equals(targetStockRecord.getCreateTime()))) {
+            targetStockRecord.setCreateTime(qualityStockCreateTime);
+            stockInRecordService.updateById(targetStockRecord);
+        }
+
+        approveStockRecords(Collections.singletonList(targetStockRecord));
+        stockRecords = findQualityStockRecords(qualityInspect.getId(), product.getProductModelId());
+        return hasApprovedStockRecord(stockRecords);
+    }
+
+    private boolean processPurchaseDirectProduct(PurchaseLedger purchaseLedger, SalesLedgerProduct product) {
+        if (purchaseLedger == null || product == null || product.getProductModelId() == null) {
+            return false;
+        }
+        if (product.getQuantity() == null) {
+            return false;
+        }
+        if (!StringUtils.hasText(purchaseLedger.getPurchaseContractNumber())) {
+            return false;
+        }
+
+        LocalDateTime stockCreateTime = toStartOfDayPlusDays(purchaseLedger.getEntryDate(), 1);
+
+        List<StockInRecord> stockRecords = findDirectStockRecords(purchaseLedger.getId(), purchaseLedger.getPurchaseContractNumber(), product.getProductModelId(), product.getId());
+        if (hasApprovedStockRecord(stockRecords)) {
+            return true;
+        }
+
+        if (CollectionUtils.isEmpty(stockRecords)) {
+            stockUtils.addStockWithBatchNo(
+                    product.getProductModelId(),
+                    product.getQuantity(),
+                    StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(),
+                    purchaseLedger.getId(),
+                    purchaseLedger.getPurchaseContractNumber() + "-" + product.getId(),
+                    stockCreateTime
+            );
+            stockRecords = findDirectStockRecords(purchaseLedger.getId(), purchaseLedger.getPurchaseContractNumber(), product.getProductModelId(), product.getId());
+        }
+
+        if (CollectionUtils.isEmpty(stockRecords)) {
+            return false;
+        }
+
+        StockInRecord targetStockRecord = findLatestUnapprovedStockRecord(stockRecords);
+        if (targetStockRecord == null) {
+            return false;
+        }
+
+        if (stockCreateTime != null && (targetStockRecord.getCreateTime() == null
+                || !stockCreateTime.equals(targetStockRecord.getCreateTime()))) {
+            targetStockRecord.setCreateTime(stockCreateTime);
+            stockInRecordService.updateById(targetStockRecord);
+        }
+
+        approveStockRecords(Collections.singletonList(targetStockRecord));
+        stockRecords = findDirectStockRecords(purchaseLedger.getId(), purchaseLedger.getPurchaseContractNumber(), product.getProductModelId(), product.getId());
+        return hasApprovedStockRecord(stockRecords);
+    }
+
+    private LocalDateTime toStartOfDayPlusDays(Date date, int days) {
+        if (date == null) {
+            return null;
+        }
+        return DateUtils.toLocalDate(date).plusDays(days).atStartOfDay();
+    }
+
+    private LocalDateTime resolveQualityStockCreateTime(QualityInspect qualityInspect) {
+        if (qualityInspect == null || qualityInspect.getCheckTime() == null) {
+            return null;
+        }
+        return DateUtils.toLocalDate(qualityInspect.getCheckTime()).plusDays(1).atStartOfDay();
+    }
+
+    private QualityInspect findLatestPurchaseQualityInspect(Long purchaseLedgerId, Long productModelId) {
+        if (purchaseLedgerId == null || productModelId == null) {
+            return null;
+        }
+        return qualityInspectMapper.selectOne(
+                Wrappers.<QualityInspect>lambdaQuery()
+                        .eq(QualityInspect::getInspectType, 0)
+                        .eq(QualityInspect::getPurchaseLedgerId, purchaseLedgerId)
+                        .eq(QualityInspect::getProductModelId, productModelId)
+                        .orderByDesc(QualityInspect::getId)
+                        .last("limit 1")
+        );
+    }
+
+    private List<StockInRecord> findQualityStockRecords(Long qualityInspectId, Long productModelId) {
+        if (qualityInspectId == null || productModelId == null) {
+            return Collections.emptyList();
+        }
+        return stockInRecordService.list(
+                Wrappers.<StockInRecord>lambdaQuery()
+                        .eq(StockInRecord::getRecordType, StockInQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_OUT.getCode())
+                        .eq(StockInRecord::getRecordId, qualityInspectId)
+                        .eq(StockInRecord::getProductModelId, productModelId)
+                        .orderByDesc(StockInRecord::getId)
+        );
+    }
+
+    private List<StockInRecord> findDirectStockRecords(Long purchaseLedgerId, String purchaseContractNumber, Long productModelId, Long purchaseProductId) {
+        if (purchaseLedgerId == null || productModelId == null || purchaseProductId == null || !StringUtils.hasText(purchaseContractNumber)) {
+            return Collections.emptyList();
+        }
+        String batchNo = purchaseContractNumber + "-" + purchaseProductId;
+        return stockInRecordService.list(
+                Wrappers.<StockInRecord>lambdaQuery()
+                        .eq(StockInRecord::getRecordType, StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode())
+                        .eq(StockInRecord::getRecordId, purchaseLedgerId)
+                        .eq(StockInRecord::getProductModelId, productModelId)
+                        .eq(StockInRecord::getBatchNo, batchNo)
+                        .orderByDesc(StockInRecord::getId)
+        );
+    }
+
+    private boolean hasApprovedStockRecord(List<StockInRecord> stockRecords) {
+        return stockRecords != null && stockRecords.stream()
+                .anyMatch(item -> ReviewStatusEnum.APPROVED.getCode().equals(item.getApprovalStatus()));
+    }
+
+    private StockInRecord findLatestUnapprovedStockRecord(List<StockInRecord> stockRecords) {
+        if (CollectionUtils.isEmpty(stockRecords)) {
+            return null;
+        }
+        return stockRecords.stream()
+                .filter(item -> !ReviewStatusEnum.APPROVED.getCode().equals(item.getApprovalStatus()))
+                .findFirst()
+                .orElse(null);
+    }
+
+    private void approveStockRecords(List<StockInRecord> stockRecords) {
+        if (CollectionUtils.isEmpty(stockRecords)) {
+            return;
+        }
+        List<Long> rejectedIds = stockRecords.stream()
+                .filter(item -> ReviewStatusEnum.REJECTED.getCode().equals(item.getApprovalStatus()))
+                .map(StockInRecord::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        if (!rejectedIds.isEmpty()) {
+            stockInRecordService.batchReAudit(rejectedIds);
+        }
+
+        List<Long> pendingIds = stockRecords.stream()
+                .filter(item -> item.getApprovalStatus() == null
+                        || ReviewStatusEnum.PENDING_REVIEW.getCode().equals(item.getApprovalStatus())
+                        || ReviewStatusEnum.REJECTED.getCode().equals(item.getApprovalStatus()))
+                .map(StockInRecord::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+        if (!pendingIds.isEmpty()) {
+            stockInRecordService.batchApprove(pendingIds, ReviewStatusEnum.APPROVED.getCode());
         }
     }
 
@@ -455,12 +865,13 @@
     }
 
     @Override
-    public String getPurchaseNo() {
+    public String getPurchaseNo(Date entryDate) {
+        LocalDate localDate = entryDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
         // 鐢熸垚鏃ユ湡鍓嶇紑锛堜緥濡傦細CG20250405锛�
-        String purchaseNo = "CG" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        String purchaseNo = "CG" + localDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
 
         // 鏋勫缓 Redis Key锛堟寜澶╁垎闅旓級
-        String redisKey = "purchase_no:" + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE);
+        String redisKey = "purchase_no:" + localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
 
         // 鑾峰彇褰撳墠搴忓彿骞堕�掑锛堝師瀛愭搷浣滐級
         Long sequence = redisTemplate.opsForValue().increment(redisKey);
@@ -616,14 +1027,28 @@
         if (loginUser == null) {
             return;
         }
-        ApproveProcessVO approveProcessVO = new ApproveProcessVO();
-        approveProcessVO.setApproveType(5);
-        approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
-        approveProcessVO.setApproveReason(purchaseLedger.getPurchaseContractNumber());
-        approveProcessVO.setApproveUserIds(purchaseLedger.getApproveUserIds());
-        approveProcessVO.setApproveUser(loginUser.getUserId());
-        approveProcessVO.setApproveTime(LocalDate.now().toString());
-        approveProcessService.addApprove(approveProcessVO);
+        ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectOne(
+                new LambdaQueryWrapper<ApprovalTemplate>()
+                        .eq(ApprovalTemplate::getBusinessType, 5L)
+                        .orderByDesc(ApprovalTemplate::getId)
+                        .last("LIMIT 1")
+        );
+        if (approvalTemplate == null) {
+            throw new BaseException("璇峰厛閰嶇疆閲囪喘瀹℃壒妯℃澘");
+        }
+
+        ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto();
+        approvalInstance.setTemplateId(approvalTemplate.getId());
+        approvalInstance.setTemplateName(approvalTemplate.getTemplateName());
+        approvalInstance.setBusinessId(purchaseLedger.getId());
+        approvalInstance.setBusinessType(5L);
+        approvalInstance.setCurrentLevel(1);
+        approvalInstance.setApplicantId(loginUser.getUserId());
+        approvalInstance.setTitle(purchaseLedger.getPurchaseContractNumber()+"瀹℃壒");
+        approvalInstance.setApplicantName(loginUser.getNickName());
+        approvalInstance.setApplyTime(LocalDateTime.now());
+        approvalInstanceService.add(approvalInstance);
+
     }
 
     /**
diff --git a/src/main/java/com/ruoyi/quality/controller/QualityTestStandardParamController.java b/src/main/java/com/ruoyi/quality/controller/QualityTestStandardParamController.java
index 16788c9..cc46446 100644
--- a/src/main/java/com/ruoyi/quality/controller/QualityTestStandardParamController.java
+++ b/src/main/java/com/ruoyi/quality/controller/QualityTestStandardParamController.java
@@ -80,7 +80,9 @@
     @Operation(summary = "妫�娴嬫寚鏍囩淮鎶ゆ煡璇�")
     @Log(title = "妫�娴嬫寚鏍囩淮鎶ゆ煡璇�", businessType = BusinessType.OTHER)
     public R<?> list(Long testStandardId) {
-        return R.ok(qualityTestStandardParamService.list(Wrappers.<QualityTestStandardParam>lambdaQuery().eq(QualityTestStandardParam::getTestStandardId,testStandardId)));
+        return R.ok(qualityTestStandardParamService.list(Wrappers.<QualityTestStandardParam>lambdaQuery()
+                .eq(QualityTestStandardParam::getTestStandardId, testStandardId)
+                .orderByDesc(QualityTestStandardParam::getCreateTime)));
     }
 
 }
diff --git a/src/main/java/com/ruoyi/quality/service/IQualityInspectService.java b/src/main/java/com/ruoyi/quality/service/IQualityInspectService.java
index d78a5ca..8b42207 100644
--- a/src/main/java/com/ruoyi/quality/service/IQualityInspectService.java
+++ b/src/main/java/com/ruoyi/quality/service/IQualityInspectService.java
@@ -3,6 +3,7 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.quality.dto.QualityInspectDto;
 import com.ruoyi.quality.pojo.QualityInspect;
 
@@ -23,5 +24,7 @@
 
     int submit(QualityInspect qualityInspect);
 
+    R autoSubmit(Long id);
+
     void down(HttpServletResponse response, QualityInspect qualityInspect);
 }
diff --git a/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java b/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
index 43d8d32..2b28f79 100644
--- a/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
+++ b/src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java
@@ -11,8 +11,10 @@
 import com.deepoove.poi.config.Configure;
 import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
 import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.HackLoopTableRenderPolicy;
 import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.framework.web.domain.R;
 import com.ruoyi.procurementrecord.service.ProcurementRecordService;
 import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.quality.dto.QualityInspectDto;
@@ -39,6 +41,8 @@
 import java.io.OutputStream;
 import java.math.BigDecimal;
 import java.net.URLEncoder;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
@@ -123,6 +127,10 @@
             stockInventoryDto.setRecordId(qualityInspect.getId());
             stockInventoryDto.setProductModelId(qualityInspect.getProductModelId());
             stockInventoryDto.setQualitity(qualityInspect.getQualifiedQuantity());
+            if (qualityInspect.getCheckTime() != null) {
+                LocalDate stockCreateDate = DateUtils.toLocalDate(qualityInspect.getCheckTime()).plusDays(1);
+                stockInventoryDto.setCreateTime(LocalDateTime.of(stockCreateDate, java.time.LocalTime.MIDNIGHT));
+            }
             stockInventoryDto.setBatchNo(resolveProductionBatchNo(
                     qualityInspect.getProductMainId(),
                     qualityInspect.getId(),
@@ -147,6 +155,33 @@
         return qualityInspectMapper.updateById(qualityInspect);
     }
 
+    @Override
+    public R autoSubmit(Long id) {
+        if (id == null) {
+            return R.fail("妫�楠屽崟ID涓嶈兘涓虹┖");
+        }
+        QualityInspect qualityInspect = qualityInspectMapper.selectById(id);
+        if (qualityInspect == null) {
+            return R.fail("妫�楠屽崟涓嶅瓨鍦�");
+        }
+        if (Integer.valueOf(1).equals(qualityInspect.getInspectState())) {
+            return R.ok("妫�楠屽崟宸叉彁浜�");
+        }
+
+        if (ObjectUtils.isNull(qualityInspect.getCheckResult())) {
+            qualityInspect.setCheckResult("鍚堟牸");
+        }
+        if (ObjectUtils.isNull(qualityInspect.getQualifiedQuantity())) {
+            qualityInspect.setQualifiedQuantity(qualityInspect.getQuantity() == null ? BigDecimal.ZERO : qualityInspect.getQuantity());
+        }
+        if (ObjectUtils.isNull(qualityInspect.getUnqualifiedQuantity())) {
+            qualityInspect.setUnqualifiedQuantity(BigDecimal.ZERO);
+        }
+        qualityInspectMapper.updateById(qualityInspect);
+        int rows = submit(qualityInspect);
+        return rows > 0 ? R.ok("妫�楠屽崟鎻愪氦鎴愬姛") : R.fail("妫�楠屽崟鎻愪氦澶辫触");
+    }
+
     private String resolveProductionBatchNo(Long productionProductMainId,
                                             Long qualityInspectId,
                                             Long productModelId) {
diff --git a/src/main/java/com/ruoyi/quality/utils/QualityInspectHelper.java b/src/main/java/com/ruoyi/quality/utils/QualityInspectHelper.java
new file mode 100644
index 0000000..766515a
--- /dev/null
+++ b/src/main/java/com/ruoyi/quality/utils/QualityInspectHelper.java
@@ -0,0 +1,73 @@
+package com.ruoyi.quality.utils;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.purchase.pojo.PurchaseLedger;
+import com.ruoyi.quality.mapper.QualityInspectMapper;
+import com.ruoyi.quality.mapper.QualityInspectParamMapper;
+import com.ruoyi.quality.mapper.QualityTestStandardMapper;
+import com.ruoyi.quality.mapper.QualityTestStandardParamMapper;
+import com.ruoyi.quality.pojo.QualityInspect;
+import com.ruoyi.quality.pojo.QualityInspectParam;
+import com.ruoyi.quality.pojo.QualityTestStandard;
+import com.ruoyi.quality.pojo.QualityTestStandardParam;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 璐ㄦ鍗曞垱寤哄伐鍏风被
+ */
+@Component
+@RequiredArgsConstructor
+public class QualityInspectHelper {
+
+    private final QualityInspectMapper qualityInspectMapper;
+    private final QualityTestStandardMapper qualityTestStandardMapper;
+    private final QualityTestStandardParamMapper qualityTestStandardParamMapper;
+    private final QualityInspectParamMapper qualityInspectParamMapper;
+
+    /**
+     * 鍒涘缓璐ㄦ鍗�
+     * @param purchaseLedger 閲囪喘鍙拌处
+     * @param saleProduct 閲囪喘浜у搧
+     */
+    public void addQualityInspect(PurchaseLedger purchaseLedger, SalesLedgerProduct saleProduct) {
+        QualityInspect qualityInspect = new QualityInspect();
+        qualityInspect.setInspectType(0);
+        qualityInspect.setSupplier(purchaseLedger.getSupplierName());
+        qualityInspect.setPurchaseLedgerId(purchaseLedger.getId());
+        qualityInspect.setProductId(saleProduct.getProductId());
+        qualityInspect.setProductName(saleProduct.getProductCategory());
+        qualityInspect.setModel(saleProduct.getSpecificationModel());
+        qualityInspect.setProductModelId(saleProduct.getProductModelId());
+        qualityInspect.setUnit(saleProduct.getUnit());
+        qualityInspect.setQuantity(saleProduct.getQuantity());
+        qualityInspectMapper.insert(qualityInspect);
+
+        List<QualityTestStandard> qualityTestStandardList = qualityTestStandardMapper
+                .getQualityTestStandardByProductId(saleProduct.getProductId(), 0, null);
+
+        if (qualityTestStandardList.isEmpty()) {
+            return;
+        }
+
+        QualityTestStandard firstStandard = qualityTestStandardList.get(0);
+        qualityInspect.setTestStandardId(firstStandard.getId());
+        qualityInspectMapper.updateById(qualityInspect);
+
+        List<QualityTestStandardParam> standardParams = qualityTestStandardParamMapper.selectList(
+                Wrappers.<QualityTestStandardParam>lambdaQuery()
+                        .eq(QualityTestStandardParam::getTestStandardId, firstStandard.getId()));
+
+        for (QualityTestStandardParam standardParam : standardParams) {
+            QualityInspectParam param = new QualityInspectParam();
+            BeanUtils.copyProperties(standardParam, param);
+            param.setId(null);
+            param.setInspectId(qualityInspect.getId());
+            qualityInspectParamMapper.insert(param);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/sales/controller/PaymentShippingController.java b/src/main/java/com/ruoyi/sales/controller/PaymentShippingController.java
index 75e27f3..54c7f04 100644
--- a/src/main/java/com/ruoyi/sales/controller/PaymentShippingController.java
+++ b/src/main/java/com/ruoyi/sales/controller/PaymentShippingController.java
@@ -17,6 +17,7 @@
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 /**
@@ -44,7 +45,7 @@
     @Operation(summary = "娣诲姞鏀粯涓庡彂璐т俊鎭�")
     @Transactional(rollbackFor = Exception.class)
     public AjaxResult add(@RequestBody PaymentShipping paymentShipping) {
-        String ord = OrderUtils.countTodayByCreateTime(paymentShippingMapper, "ORD","order_no");
+        String ord = OrderUtils.countTodayByCreateTime(paymentShippingMapper, "ORD","order_no", paymentShipping.getCreateTime() != null ? paymentShipping.getCreateTime() : LocalDateTime.now());
         paymentShipping.setOrderNo(ord);
         boolean save = paymentShippingService.save(paymentShipping);
         return save ? success() : error();
diff --git a/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java b/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
index 44b8563..ea18246 100644
--- a/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
+++ b/src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java
@@ -262,7 +262,7 @@
 
             //  濡傛灉宸茬粡鏈夎繃寮�绁ㄦ垨鍥炴鎿嶄綔,鍒欎笉鍏佽缂栬緫
             boolean hasReceiptOperation = receiptPaymentAmountTotal.compareTo(BigDecimal.ZERO) > 0;
-            salesLedgerVo.setIsEdit(hasReceiptOperation);
+            salesLedgerVo.setIsEdit(!hasReceiptOperation);
 
             salesLedgerVo.setStorageBlobVOs(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.FILE, RecordTypeEnum.SALES_LEDGER, ledgerId));
         }
diff --git a/src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java b/src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java
index 5f02d86..0a64f9a 100644
--- a/src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java
+++ b/src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java
@@ -2,14 +2,10 @@
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.ruoyi.approve.bean.vo.ApproveProcessVO;
 import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
-import com.ruoyi.common.utils.OrderUtils;
-import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.framework.aspectj.lang.annotation.Log;
 import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
-import com.ruoyi.framework.security.LoginUser;
 import com.ruoyi.framework.web.controller.BaseController;
 import com.ruoyi.framework.web.domain.AjaxResult;
 import com.ruoyi.framework.web.domain.R;
@@ -25,7 +21,6 @@
 import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
-import java.time.LocalDate;
 import java.util.List;
 
 /**
@@ -55,22 +50,7 @@
     @Transactional(rollbackFor = Exception.class)
     @Log(title = "鍙戣揣淇℃伅绠$悊", businessType = BusinessType.INSERT)
     public AjaxResult add(@RequestBody ShippingInfoDto req) throws Exception {
-        LoginUser loginUser = SecurityUtils.getLoginUser();
-        String sh = OrderUtils.countTodayByCreateTime(shippingInfoMapper, "SH","shipping_no");
-        // 鍙戣揣瀹℃壒
-        ApproveProcessVO approveProcessVO = new ApproveProcessVO();
-        approveProcessVO.setApproveType(7);
-        approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
-        approveProcessVO.setApproveReason(sh);//鍙戣揣缂栧彿
-        approveProcessVO.setApproveUserIds(req.getApproveUserIds());
-        approveProcessVO.setApproveUser(loginUser.getUserId());
-        approveProcessVO.setApproveTime(LocalDate.now().toString());
-        approveProcessService.addApprove(approveProcessVO);
-        // 娣诲姞鍙戣揣娑堟伅
-        req.setShippingNo(sh);
-        req.setStatus("寰呭鏍�");
-        boolean save = shippingInfoService.add(req);
-        return save ? AjaxResult.success() : AjaxResult.error();
+        return AjaxResult.success(shippingInfoService.addReq(req) ? "娣诲姞鎴愬姛" : "娣诲姞澶辫触");
     }
 
     @Operation(summary = "鍙戣揣鎵e簱瀛�")
diff --git a/src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java b/src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java
index 4841ffd..4b20ff8 100644
--- a/src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java
+++ b/src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java
@@ -17,4 +17,6 @@
      */
     // 瀹℃壒浜�
     private String approveUserIds;
+
+    private Long templateId;
 }
diff --git a/src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java b/src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java
index 43409bc..724473a 100644
--- a/src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java
+++ b/src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java
@@ -47,5 +47,9 @@
     //鍙戣揣鏁伴噺
     private BigDecimal totalQuantity;
 
+    private Long templateId;
+
+    private String templateName;
+
 
 }
diff --git a/src/main/java/com/ruoyi/sales/pojo/PaymentShipping.java b/src/main/java/com/ruoyi/sales/pojo/PaymentShipping.java
index 51589cd..8c37b72 100644
--- a/src/main/java/com/ruoyi/sales/pojo/PaymentShipping.java
+++ b/src/main/java/com/ruoyi/sales/pojo/PaymentShipping.java
@@ -60,7 +60,6 @@
     private String remark;
 
     @Schema(description = "鍒涘缓鏃堕棿")
-    @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createTime;
 
     @Schema(description = "鍒涘缓鐢ㄦ埛")
diff --git a/src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java b/src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java
index b72b3af..d8a97a6 100644
--- a/src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java
+++ b/src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java
@@ -52,7 +52,6 @@
     @ApiModelProperty(value = "澶囨敞")
     private String remark;
     @ApiModelProperty(value = "鍒涘缓鏃堕棿")
-    @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createTime;
 
     @ApiModelProperty(value = "淇敼鏃堕棿")
diff --git a/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java b/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java
index ed9a268..f244453 100644
--- a/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java
+++ b/src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java
@@ -68,7 +68,6 @@
     private String shippingCarNumber;
 
     @Schema(description = "鍒涘缓鏃堕棿")
-    @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createTime;
 
     @Schema(description = "淇敼鏃堕棿")
diff --git a/src/main/java/com/ruoyi/sales/service/ShippingInfoService.java b/src/main/java/com/ruoyi/sales/service/ShippingInfoService.java
index aa7bad5..12ed935 100644
--- a/src/main/java/com/ruoyi/sales/service/ShippingInfoService.java
+++ b/src/main/java/com/ruoyi/sales/service/ShippingInfoService.java
@@ -31,4 +31,6 @@
     List<ShippingProductDetailDto> getDetail(Long id);
 
     ShippingApproveDto getDateilByShippingNo(String shippingNo);
+
+    boolean addReq(ShippingInfoDto req);
 }
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
index 921a844..309a328 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java
@@ -35,6 +35,7 @@
 import java.math.RoundingMode;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.Arrays;
 import java.util.List;
@@ -229,6 +230,20 @@
             productionPlan.setPromisedDeliveryDate(salesLedger.getDeliveryDate());//鎵胯鏃ユ湡=浜よ揣鏃ユ湡
             productionPlanMapper.insert(productionPlan);
         }
+        SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerProduct.getSalesLedgerId());
+        ProductionPlan productionPlan = new ProductionPlan();
+        productionPlan.setSalesLedgerId(salesLedgerProduct.getSalesLedgerId());
+        productionPlan.setSalesLedgerProductId(salesLedgerProduct.getId());
+        productionPlan.setMpsNo(generateNextPlanNo(salesLedger.getEntryDate().toInstant()
+                .atZone(ZoneId.systemDefault())
+                .toLocalDate().format(DateTimeFormatter.ofPattern("yyyyMMdd"))));
+        productionPlan.setProductModelId(salesLedgerProduct.getProductModelId());
+        productionPlan.setQtyRequired(salesLedgerProduct.getQuantity());
+        productionPlan.setSource("閿�鍞�");
+        productionPlan.setStatus(0);
+        productionPlan.setRequiredDate(salesLedger.getDeliveryDate());//闇�姹傛棩鏈�=浜よ揣鏃ユ湡
+        productionPlan.setPromisedDeliveryDate(salesLedger.getDeliveryDate());//鎵胯鏃ユ湡=浜よ揣鏃ユ湡
+        productionPlanMapper.insert(productionPlan);
 
     }
 
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
index 6303b04..19cd5a0 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -63,6 +63,7 @@
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 
 import java.io.InputStream;
 import java.lang.reflect.Field;
@@ -334,9 +335,17 @@
             if (CollectionUtils.isEmpty(salesLedgerImportDtoList)) return AjaxResult.error("閿�鍞彴璐︽暟鎹负绌猴紒");
             List<SalesLedgerImportDto> salesLedgerProductImportDtoList = stringListMap.get("閿�鍞骇鍝佹暟鎹�");
             if (CollectionUtils.isEmpty(salesLedgerProductImportDtoList)) return AjaxResult.error("閿�鍞骇鍝佹暟鎹负绌猴紒");
-            // 瀹㈡埛鏁版嵁
-            List<Customer> customers = customerMapper.selectList(new LambdaQueryWrapper<Customer>().in(Customer::getCustomerName,
-                    salesLedgerImportDtoList.stream().map(SalesLedgerImportDto::getCustomerName).collect(Collectors.toList())));
+            // 瀹㈡埛鏁版嵁 - 鍙傝�� listPage 鏌ヨ绉佹捣瀹㈡埛锛坱ype = 0锛�
+            // type = 0锛堢娴峰鎴凤級鎴栬�� type = 1锛堝叕娴峰鎴凤級涓斿凡琚垎閰嶏紝骞朵笖鏄嚜宸遍鐢ㄣ�佽嚜宸卞垱寤烘垨鑰呭叡浜粰鑷繁鐨勫鎴�
+            Long loginUserId = loginUser.getUser().getUserId();
+            List<Customer> customers = customerMapper.selectList(new QueryWrapper<Customer>()
+                    .in("customer_name", salesLedgerImportDtoList.stream()
+                            .map(SalesLedgerImportDto::getCustomerName).collect(Collectors.toList()))
+                    .and(wrapper -> wrapper.eq("type", 0)
+                            .or(wrapper2 -> wrapper2.eq("type", 1).eq("is_assigned", 1)))
+                    .and(wrapper -> wrapper.eq("usage_user", loginUserId)
+                            .or(wrapper2 -> wrapper2.eq("create_user", loginUserId)
+                                    .or(wrapper3 -> wrapper3.exists("select 1 from customer_user cu where cu.customer_id = customer.id and cu.user_id = " + loginUserId)))));
 //            // 瑙勬牸鍨嬪彿鏁版嵁
 //            List<ProductModel> productModels = productModelMapper.selectList(new LambdaQueryWrapper<ProductModel>().in(ProductModel::getModel,
 //                    salesLedgerProductImportDtoList.stream().map(SalesLedgerImportDto::getSpecificationModel).collect(Collectors.toList())));
@@ -354,21 +363,26 @@
                 if (salesLedger1 != null) {
                     continue;
                 }
+
+                // 鍒ゆ柇涓氬姟鍛樻槸鍚﹀瓨鍦�
+                SysUser salesman = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>()
+                        .eq(SysUser::getNickName, salesLedgerImportDto.getSalesman()));
+                if (salesman == null) {
+                    throw new RuntimeException("涓氬姟鍛�:" + salesLedgerImportDto.getSalesman() + "涓嶅瓨鍦紒");
+                }
                 SalesLedger salesLedger = new SalesLedger();
                 BeanUtils.copyProperties(salesLedgerImportDto, salesLedger);
                 salesLedger.setExecutionDate(DateUtils.toLocalDate(salesLedgerImportDto.getExecutionDate()));
                 salesLedger.setDeliveryDate(DateUtils.toLocalDate(salesLedgerImportDto.getDeliveryDate()));
                 // 閫氳繃瀹㈡埛鍚嶇О鏌ヨ瀹㈡埛ID锛屽鎴峰悎鍚屽彿
-                salesLedger.setCustomerId(customers.stream()
+                Optional<Customer> customerOptional = customers.stream()
                         .filter(customer -> customer.getCustomerName().equals(salesLedger.getCustomerName()))
-                        .findFirst()
-                        .map(Customer::getId)
-                        .orElse(null));
-                salesLedger.setCustomerContractNo(customers.stream()
-                        .filter(customer -> customer.getCustomerName().equals(salesLedger.getCustomerName()))
-                        .findFirst()
-                        .map(Customer::getTaxpayerIdentificationNumber)
-                        .orElse(null));
+                        .findFirst();
+                if (customerOptional.isEmpty()) {
+                    throw new RuntimeException("瀹㈡埛:" + salesLedger.getCustomerName() + "涓嶅瓨鍦紒鎴栬�呴潪绉佹捣鐢ㄦ埛");
+                }
+                salesLedger.setCustomerId(customerOptional.get().getId());
+                salesLedger.setCustomerContractNo(customerOptional.get().getTaxpayerIdentificationNumber());
                 Long aLong = sysUsers.stream()
                         .filter(sysUser -> sysUser.getNickName().equals(salesLedger.getEntryPerson()))
                         .findFirst()
@@ -396,13 +410,16 @@
                     salesLedgerProduct.setType(1);
                     // 璁$畻涓嶅惈绋庢�讳环
                     salesLedgerProduct.setTaxExclusiveTotalPrice(salesLedgerProduct.getTaxInclusiveTotalPrice().divide(new BigDecimal(1).add(salesLedgerProduct.getTaxRate().divide(new BigDecimal(100))), 2, RoundingMode.HALF_UP));
-                    list.stream()
+                    // 鏍¢獙浜у搧瑙勬牸鏄惁瀛樺湪
+                    Optional<Map<String, Object>> productModelOptional = list.stream()
                             .filter(map -> Objects.equals(map.get("productName"), salesLedgerProduct.getProductCategory()) && Objects.equals(map.get("model"), salesLedgerProduct.getSpecificationModel()))
-                            .findFirst()
-                            .ifPresent(map -> {
-                                salesLedgerProduct.setProductModelId(Long.parseLong(map.get("modelId").toString()));
-                                salesLedgerProduct.setProductId(Long.parseLong(map.get("id").toString()));
-                            });
+                            .findFirst();
+                    if (productModelOptional.isEmpty()) {
+                        throw new RuntimeException("浜у搧澶х被:" + salesLedgerProduct.getProductCategory() + ",瑙勬牸鍨嬪彿:" + salesLedgerProduct.getSpecificationModel() + "涓嶅瓨鍦紒");
+                    }
+                    Map<String, Object> productModelMap = productModelOptional.get();
+                    salesLedgerProduct.setProductModelId(Long.parseLong(productModelMap.get("modelId").toString()));
+                    salesLedgerProduct.setProductId(Long.parseLong(productModelMap.get("id").toString()));
 //                    salesLedgerProduct.setProductId(productList.stream()
 //                            .filter(product -> product.getProductName().equals(salesLedgerProduct.getProductCategory()))
 //                            .findFirst()
@@ -564,7 +581,7 @@
         if (salesLedger.getId() == null) {
             String contractNo = salesLedger.getSalesContractNo();
             if (StringUtils.isEmpty(contractNo)) {
-                contractNo = generateSalesContractNo();
+                contractNo = generateSalesContractNo(salesLedgerDto.getEntryDate());
             }
             salesLedger.setSalesContractNo(contractNo);
             salesLedgerMapper.insert(salesLedger);
@@ -627,8 +644,8 @@
     }
 
     @Transactional(readOnly = true)
-    public String generateSalesContractNo() {
-        LocalDate currentDate = LocalDate.now();
+    public String generateSalesContractNo(Date entryDate) {
+        LocalDate currentDate = entryDate != null ? DateUtils.toLocalDate(entryDate) : LocalDate.now();
         String datePart = currentDate.format(DateTimeFormatter.BASIC_ISO_DATE);
         String lockKey = LOCK_PREFIX + datePart;
         String lockValue = Thread.currentThread().getId() + "-" + System.nanoTime(); // 鍞竴鏍囪瘑閿佹寔鏈夎��
diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
index de86ad0..5e31cfa 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java
@@ -7,10 +7,14 @@
 import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.ruoyi.approve.pojo.ApproveProcess;
-import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
+import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
 import com.ruoyi.approve.bean.vo.ApproveGetAndUpdateVo;
-import com.ruoyi.approve.bean.vo.ApproveProcessVO;
+import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
+import com.ruoyi.approve.pojo.ApprovalInstance;
+import com.ruoyi.approve.pojo.ApprovalTemplate;
+import com.ruoyi.approve.pojo.ApproveProcess;
+import com.ruoyi.approve.service.ApprovalInstanceService;
+import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
 import com.ruoyi.basic.mapper.CustomerMapper;
 import com.ruoyi.basic.pojo.Customer;
 import com.ruoyi.common.enums.IsDeleteEnum;
@@ -29,9 +33,8 @@
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.time.LocalDate;
-import java.util.Collections;
-import java.util.List;
+import java.time.LocalDateTime;
+import java.util.*;
 import java.util.stream.Collectors;
 
 @Service
@@ -44,6 +47,8 @@
 
     private final ApproveProcessServiceImpl approveProcessService;
     private final CustomerMapper customerMapper;
+    private final ApprovalTemplateMapper approvalTemplateMapper;
+    private final ApprovalInstanceService approvalInstanceService;
 
     @Override
     public IPage<SalesQuotationDto> listPage(Page page, SalesQuotationDto salesQuotationDto) {
@@ -51,10 +56,26 @@
         if(CollectionUtils.isEmpty(salesQuotationDtoIPage.getRecords())){
             return salesQuotationDtoIPage;
         }
-        salesQuotationDtoIPage.getRecords().forEach(record -> {
-            List<SalesQuotationProduct> products = salesQuotationProductMapper.selectBySalesQuotationId(record.getId());
-            record.setProducts(products);
-        });
+
+        // 鎵归噺鏌ヨ浜у搧锛岄伩鍏� N+1 闂
+        List<Long> quotationIds = salesQuotationDtoIPage.getRecords().stream()
+                .map(SalesQuotationDto::getId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+
+        if (!quotationIds.isEmpty()) {
+            List<SalesQuotationProduct> allProducts = salesQuotationProductMapper.selectList(
+                    new LambdaQueryWrapper<SalesQuotationProduct>()
+                            .in(SalesQuotationProduct::getSalesQuotationId, quotationIds)
+            );
+
+            Map<Long, List<SalesQuotationProduct>> productMap = allProducts.stream()
+                    .collect(Collectors.groupingBy(SalesQuotationProduct::getSalesQuotationId));
+
+            salesQuotationDtoIPage.getRecords().forEach(record ->
+                    record.setProducts(productMap.getOrDefault(record.getId(), new ArrayList<>()))
+            );
+        }
         return salesQuotationDtoIPage;
     }
 
@@ -68,7 +89,7 @@
         if (ObjectUtils.isNotEmpty(customer))  {
             salesQuotation.setCustomer(customer.getCustomerName());
         }
-        String quotationNo = OrderUtils.countTodayByCreateTime(salesQuotationMapper, "QT","quotation_no");
+        String quotationNo = OrderUtils.countTodayByCreateTime(salesQuotationMapper, "QT","quotation_no", salesQuotationDto.getCreateTime() != null ? salesQuotationDto.getCreateTime() : LocalDateTime.now());
         salesQuotation.setQuotationNo(quotationNo);
         salesQuotation.setStatus("寰呭鎵�");
         salesQuotationMapper.insert(salesQuotation);
@@ -83,19 +104,21 @@
         }).collect(Collectors.toList());
         salesQuotationProductService.saveBatch(products);
         // 鎶ヤ环瀹℃壒
-        ApproveProcessVO approveProcessVO = new ApproveProcessVO();
-        approveProcessVO.setApproveType(6);
-        approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
-        approveProcessVO.setApproveReason(quotationNo);
-        approveProcessVO.setApproveUserIds(salesQuotationDto.getApproveUserIds());
-        approveProcessVO.setApproveUser(loginUser.getUserId());
-        approveProcessVO.setApproveTime(LocalDate.now().toString());
-        approveProcessVO.setPrice(salesQuotationDto.getTotalAmount());
+        ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto();
+        approvalInstance.setTemplateId(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,6L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getId());
+        approvalInstance.setTemplateName(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,6L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getTemplateName());
+        approvalInstance.setBusinessId(salesQuotation.getId());
+        approvalInstance.setBusinessType(6L);
+        approvalInstance.setCurrentLevel(1);
+        approvalInstance.setTitle(quotationNo+"瀹℃壒");
+        approvalInstance.setApplicantId(loginUser.getUserId());
+        approvalInstance.setApplicantName(loginUser.getNickName());
+        approvalInstance.setApplyTime(LocalDateTime.now());
         try {
-            approveProcessService.addApprove(approveProcessVO);
-        }catch (Exception e){
-            log.error("SalesQuotationServiceImpl error:{}", e);
-            throw                                new RuntimeException("瀹℃壒澶辫触");
+            approvalInstanceService.add(approvalInstance);
+        } catch (Exception e) {
+            log.error("SalesQuotationServiceImpl approve error for quotationNo: {}", e);
+            throw new RuntimeException("瀹℃壒澶辫触: " + e.getMessage(), e);
         }
         return true;
     }
@@ -124,10 +147,25 @@
 
         salesQuotationProductService.saveBatch(products);
         // 淇敼鎶ヤ环瀹℃壒
-        vo.setApproveUserIds(salesQuotationDto.getApproveUserIds());
-        vo.setApproveType(6);
-        vo.setApproveReason(salesQuotationDto.getQuotationNo());
-        approveProcessService.updateApproveUser(vo);
+        // 鍏堢粨鏉熶箣鍓嶆湭缁撴潫鐨勬姤浠峰鎵�
+        approvalInstanceService.lambdaUpdate().set(ApprovalInstance::getStatus,"REJECTED").eq(ApprovalInstance::getBusinessId,salesQuotation.getId()).eq(ApprovalInstance::getBusinessType,6L).update();
+
+        ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto();
+        approvalInstance.setTemplateId(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,6L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getId());
+        approvalInstance.setTemplateName(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,6L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getTemplateName());
+        approvalInstance.setBusinessId(salesQuotation.getId());
+        approvalInstance.setBusinessType(6L);
+        approvalInstance.setCurrentLevel(1);
+        approvalInstance.setTitle(salesQuotation.getQuotationNo()+"瀹℃壒");
+        approvalInstance.setApplicantId(SecurityUtils.getUserId());
+        approvalInstance.setApplicantName(SecurityUtils.getLoginUser().getNickName());
+        approvalInstance.setApplyTime(LocalDateTime.now());
+        try {
+            approvalInstanceService.add(approvalInstance);
+        } catch (Exception e) {
+            log.error("SalesQuotationServiceImpl approve error for quotationNo: {}", e);
+            throw new RuntimeException("瀹℃壒澶辫触: " + e.getMessage(), e);
+        }
         return true;
     }
     @Override
diff --git a/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
index 0a84bf9..15b56ea 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -4,13 +4,20 @@
 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.approve.bean.dto.ApprovalInstanceDto;
+import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
+import com.ruoyi.approve.pojo.ApprovalTemplate;
 import com.ruoyi.approve.pojo.ApproveProcess;
+import com.ruoyi.approve.service.ApprovalInstanceService;
 import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
 import com.ruoyi.basic.enums.ApplicationTypeEnum;
 import com.ruoyi.basic.enums.RecordTypeEnum;
 import com.ruoyi.basic.utils.FileUtil;
 import com.ruoyi.common.enums.FileNameType;
 import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
+import com.ruoyi.common.utils.OrderUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.framework.security.LoginUser;
 import com.ruoyi.procurementrecord.bean.vo.ShippingProductVo;
 import com.ruoyi.procurementrecord.utils.StockUtils;
 import com.ruoyi.sales.dto.ShippingApproveDto;
@@ -28,6 +35,7 @@
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 /**
@@ -52,6 +60,8 @@
     private final ApproveProcessServiceImpl approveProcessService;
     private final FileUtil fileUtil;
     private final ShippingProductDetailMapper shippingProductDetailMapper;
+    private final ApprovalTemplateMapper approvalTemplateMapper;
+    private final ApprovalInstanceService approvalInstanceService;
 
     @Override
     public IPage<ShippingInfoDto> listPage(Page page, ShippingInfo req) {
@@ -158,4 +168,28 @@
         shippingApproveDto.setShippingProductDetailDtoList(dateilByShippingNo);
         return shippingApproveDto;
     }
+
+    @Override
+    public boolean addReq(ShippingInfoDto req) {
+
+                LoginUser loginUser = SecurityUtils.getLoginUser();
+        String sh = OrderUtils.countTodayByCreateTime(shippingInfoMapper, "SH","shipping_no",req.getCreateTime());
+        // 鍏堜繚瀛樺彂璐у崟锛屽啀鍙戣捣瀹℃壒锛涙棤瀹℃牳浜鸿嚜鍔ㄩ�氳繃鏃堕渶瑕佹寜鍙戣揣缂栧彿鍥炲啓鍙戣揣鐘舵�併��
+        req.setShippingNo(sh);
+        req.setStatus("寰呭鏍�");
+        boolean save = this.add(req);
+        // 鍙戣揣瀹℃壒
+        ApprovalInstanceDto approvalInstance = new ApprovalInstanceDto();
+        approvalInstance.setTemplateId(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,6L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getId());
+        approvalInstance.setTemplateName(approvalTemplateMapper.selectOne(new LambdaQueryWrapper<ApprovalTemplate>().eq(ApprovalTemplate::getBusinessType,6L).orderByDesc(ApprovalTemplate::getId).last("LIMIT 1")).getTemplateName());
+        approvalInstance.setBusinessId(req.getId());
+        approvalInstance.setBusinessType(7L);
+        approvalInstance.setCurrentLevel(1);
+        approvalInstance.setTitle(sh+"瀹℃壒");
+        approvalInstance.setApplicantId(loginUser.getUserId());
+        approvalInstance.setApplicantName(loginUser.getNickName());
+        approvalInstance.setApplyTime(LocalDateTime.now());
+        approvalInstanceService.add(approvalInstance);
+        return true;
+    }
 }
diff --git a/src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java b/src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java
index be2c171..f257d3a 100644
--- a/src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java
+++ b/src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java
@@ -1,5 +1,6 @@
 package com.ruoyi.staff.controller;
 
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.ruoyi.framework.aspectj.lang.annotation.Log;
 import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
@@ -45,6 +46,7 @@
     @Operation(summary = "鍒嗛〉鏌ヨ浜哄憳鎵撳崱瑙勫垯閰嶇疆")
     @GetMapping("/listPage")
     public R listPage(Page page){
+        page.addOrder(OrderItem.desc("id"));
         return R.ok(personalAttendanceLocationConfigService.page(page));
     }
 
diff --git a/src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java b/src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java
index 9dfa6b6..0abbdff 100644
--- a/src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java
+++ b/src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java
@@ -39,7 +39,7 @@
      * @return
      */
     @GetMapping("/listPage")
-    public AjaxResult staffOnJobListPage(Page page, StaffOnJob staffOnJob) {
+    public AjaxResult staffOnJobListPage(Page page, StaffOnJobDto staffOnJob) {
         return AjaxResult.success(staffOnJobService.staffOnJobListPage(page, staffOnJob));
     }
 
diff --git a/src/main/java/com/ruoyi/staff/dto/StaffOnJobDto.java b/src/main/java/com/ruoyi/staff/dto/StaffOnJobDto.java
index 0a2c0ae..69fdbe8 100644
--- a/src/main/java/com/ruoyi/staff/dto/StaffOnJobDto.java
+++ b/src/main/java/com/ruoyi/staff/dto/StaffOnJobDto.java
@@ -25,4 +25,6 @@
     @JsonFormat(pattern = "yyyy-MM-dd")
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private Date contractEndTime;
+
+    private Long sysDeptId;
 }
diff --git a/src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java b/src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java
index de8a3c0..7cad87f 100644
--- a/src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java
+++ b/src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java
@@ -14,7 +14,7 @@
 @Mapper
 public interface StaffOnJobMapper extends BaseMapper<StaffOnJob> {
 
-    IPage<StaffOnJobDto> staffOnJobListPage(Page page, @Param("staffOnJob") StaffOnJob staffOnJob);
+    IPage<StaffOnJobDto> staffOnJobListPage(Page page, @Param("staffOnJob") StaffOnJobDto staffOnJob);
 
     List<StaffOnJobDto> staffOnJobList(@Param("staffOnJob") StaffOnJob staffOnJob);
 
@@ -42,4 +42,4 @@
      * @return 鍛樺伐鏁版嵁
      */
     StaffOnJob selectStaffByNickName(String staffName);
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/ruoyi/staff/service/IStaffOnJobService.java b/src/main/java/com/ruoyi/staff/service/IStaffOnJobService.java
index a859d14..46588d1 100644
--- a/src/main/java/com/ruoyi/staff/service/IStaffOnJobService.java
+++ b/src/main/java/com/ruoyi/staff/service/IStaffOnJobService.java
@@ -14,7 +14,7 @@
 public interface IStaffOnJobService extends IService<StaffOnJob> {
 
 
-    IPage<StaffOnJobDto> staffOnJobListPage(Page page, StaffOnJob staffOnJob);
+    IPage<StaffOnJobDto> staffOnJobListPage(Page page, StaffOnJobDto staffOnJob);
 
      StaffOnJobDto staffOnJobDetail(Long id);
 
diff --git a/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java b/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
index bd61ca3..793f94f 100644
--- a/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
+++ b/src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java
@@ -59,6 +59,7 @@
                 schemeApplicableStaffLambdaQueryWrapper.like(SchemeApplicableStaff::getTitle, schemeApplicableStaff.getTitle());
             }
         }
+        schemeApplicableStaffLambdaQueryWrapper.orderByDesc(SchemeApplicableStaff::getId);
         Page<SchemeApplicableStaff> page1 = schemeApplicableStaffMapper.selectPage(page, schemeApplicableStaffLambdaQueryWrapper);
         List<Long> collect = page1.getRecords().stream().map(SchemeApplicableStaff::getId).collect(Collectors.toList());
         if(CollectionUtils.isEmpty(collect)){
diff --git a/src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java b/src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java
index 548323d..4978d0f 100644
--- a/src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java
+++ b/src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java
@@ -59,6 +59,7 @@
         StaffLeave staffLeave = new StaffLeave();
         staffLeave.setStaffOnJobId(staffLeaveDto.getStaffOnJobId());
         staffLeave.setReason(staffLeaveDto.getReason());
+        staffLeave.setLeaveDate(staffLeaveDto.getLeaveDate());
         String reason = staffLeaveDto.getReason();
         if (StaffLeaveReasonOther.getCode().equals(reason)){
             staffLeave.setRemark(staffLeaveDto.getRemark());
diff --git a/src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java b/src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java
index 7c11041..1f20da2 100644
--- a/src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java
+++ b/src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java
@@ -7,6 +7,7 @@
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.exception.base.BaseException;
 import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
 import com.ruoyi.dto.WordDateDto;
 import com.ruoyi.project.system.domain.SysDept;
@@ -67,8 +68,9 @@
 
     //鍦ㄨ亴鍛樺伐鍙拌处鍒嗛〉鏌ヨ
     @Override
-    public IPage<StaffOnJobDto> staffOnJobListPage(Page page, StaffOnJob staffOnJob) {
-        return staffOnJobMapper.staffOnJobListPage(page,staffOnJob);
+    public IPage<StaffOnJobDto> staffOnJobListPage(Page page, StaffOnJobDto staffOnJob) {
+        IPage<StaffOnJobDto> staffOnJobDtoIPage = staffOnJobMapper.staffOnJobListPage(page, staffOnJob);
+        return staffOnJobDtoIPage;
     }
 
     //鏂板鍏ヨ亴
@@ -83,6 +85,7 @@
         }
 
         // 鍒涘缓鍏ヨ亴鏁版嵁
+        syncStudyInfoFromEducation(staffOnJobPrams);
         staffOnJobPrams.setContractExpireTime(staffOnJobPrams.getContractEndTime());
         staffOnJobPrams.setStaffState(1);
         staffOnJobMapper.insert(staffOnJobPrams);
@@ -141,6 +144,7 @@
         // 缁戝畾瀛愯〃鏁版嵁
         bingingStaffOnJobExtra(id,staffOnJobParams);
         // 鏇存柊鍛樺伐鏁版嵁
+        syncStudyInfoFromEducation(staffOnJobParams);
         staffOnJobParams.setContractExpireTime(staffOnJobParams.getContractEndTime());
         return staffOnJobMapper.updateById(staffOnJobParams);
     }
@@ -158,6 +162,7 @@
                     .forEach(staff -> staff.setStaffOnJobId(id)); // 璧嬪��
             staffEducationService.saveBatch(staffOnJobPrams.getStaffEducationList());
         }
+
         // 鏂板宸ヤ綔缁忓巻
         if(CollectionUtils.isNotEmpty(staffOnJobPrams.getStaffWorkExperienceList())){
             staffOnJobPrams.getStaffWorkExperienceList().stream()
@@ -174,6 +179,28 @@
         }
     }
 
+    private void syncStudyInfoFromEducation(StaffOnJob staffOnJobPrams) {
+        if (staffOnJobPrams == null || CollectionUtils.isEmpty(staffOnJobPrams.getStaffEducationList())) {
+            if (staffOnJobPrams != null) {
+                staffOnJobPrams.setFirstStudy("/");
+                staffOnJobPrams.setProfession("/");
+            }
+            return;
+        }
+        Optional<StaffEducation> matchedEducation = staffOnJobPrams.getStaffEducationList().stream()
+                .filter(Objects::nonNull)
+                .filter(education -> StringUtils.isNotEmpty(education.getMajor()))
+                .findFirst();
+        if (matchedEducation.isPresent()) {
+            StaffEducation education = matchedEducation.get();
+            staffOnJobPrams.setFirstStudy(education.getEducation());
+            staffOnJobPrams.setProfession(education.getMajor());
+            return;
+        }
+        staffOnJobPrams.setFirstStudy("/");
+        staffOnJobPrams.setProfession("/");
+    }
+
 
     /**
      * 閫氳繃鍛樺伐id鍒犻櫎鏁欒偛缁忓巻锛屽伐浣滅粡鍘嗭紝绱ф�ヨ仈绯讳汉
diff --git a/src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java b/src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java
index aad785a..dcbd9c7 100644
--- a/src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java
+++ b/src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java
@@ -56,6 +56,7 @@
                 staffSalaryMainLambdaQueryWrapper.eq(StaffSalaryMain::getStatus, staffSalaryMain.getStatus());
             }
         }
+        staffSalaryMainLambdaQueryWrapper.orderByDesc(StaffSalaryMain::getId);
         Page<StaffSalaryMain> page1 = staffSalaryMainMapper.selectPage(page, staffSalaryMainLambdaQueryWrapper);
         page1.getRecords().forEach(main -> {
             List<StaffSalaryDetail> staffSalaryDetailList = staffSalaryDetailMapper.selectList(new LambdaQueryWrapper<StaffSalaryDetail>().eq(StaffSalaryDetail::getMainId, main.getId()));
diff --git a/src/main/java/com/ruoyi/stock/pojo/StockInRecord.java b/src/main/java/com/ruoyi/stock/pojo/StockInRecord.java
index f9525ee..df39946 100644
--- a/src/main/java/com/ruoyi/stock/pojo/StockInRecord.java
+++ b/src/main/java/com/ruoyi/stock/pojo/StockInRecord.java
@@ -47,14 +47,12 @@
     private String remark;
 
     @Schema(description = "棰勮鏁伴噺")
-    @TableField(exist = false)
     private BigDecimal warnNum;
 
     @Schema(description = "绫诲瀷  0鍚堟牸鍏ュ簱 1涓嶅悎鏍煎叆搴�")
     private String type;
 
     @Schema(description = "鍒涘缓鏃堕棿")
-    @TableField(fill = FieldFill.INSERT)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
diff --git a/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java b/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
index 1fd2893..5a35020 100644
--- a/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
+++ b/src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java
@@ -53,7 +53,6 @@
     private String remark;
 
     @Schema(description = "鍒涘缓鏃堕棿")
-    @TableField(fill = FieldFill.INSERT)
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
index 5207cc9..2dfbbe7 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java
@@ -13,6 +13,8 @@
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
+
+import java.time.LocalDateTime;
 import com.ruoyi.stock.dto.StockInRecordDto;
 import com.ruoyi.stock.dto.StockInventoryDto;
 import com.ruoyi.stock.dto.StockUninventoryDto;
@@ -53,7 +55,7 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public int add(StockInRecordDto stockInRecordDto) {
-        String no = OrderUtils.countTodayByCreateTime(stockInRecordMapper, "RK","inbound_batches");
+        String no = OrderUtils.countTodayByCreateTime(stockInRecordMapper, "RK","inbound_batches", stockInRecordDto.getCreateTime() != null ? stockInRecordDto.getCreateTime() : LocalDateTime.now());
         stockInRecordDto.setInboundBatches(no);
         StockInRecord stockInRecord = new StockInRecord();
         BeanUtils.copyProperties(stockInRecordDto, stockInRecord);
@@ -293,6 +295,7 @@
                             setWarnNum(stockInRecord.getWarnNum());
                             setBatchNo(stockInRecord.getBatchNo());
                             setRemark(stockInRecord.getRemark());
+                            setWarnNum(stockInRecord.getWarnNum());
                             setVersion(1);
                         }});
                     } else {
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
index ba74248..28adead 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -169,6 +169,7 @@
         stockInRecordDto.setWarnNum(stockInventoryDto.getWarnNum());
         stockInRecordDto.setType("0");
         stockInRecordDto.setRemark(stockInventoryDto.getRemark());
+        stockInRecordDto.setWarnNum(stockInventoryDto.getWarnNum());
         stockInRecordService.add(stockInRecordDto);
         return true;
     }
diff --git a/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java b/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
index 851a0e2..26f0c2d 100644
--- a/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
+++ b/src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java
@@ -15,6 +15,8 @@
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.common.utils.poi.ExcelUtil;
+
+import java.time.LocalDateTime;
 import com.ruoyi.stock.dto.StockInventoryDto;
 import com.ruoyi.stock.dto.StockOutRecordDto;
 import com.ruoyi.stock.dto.StockUninventoryDto;
@@ -58,7 +60,7 @@
 
     @Override
     public int add(StockOutRecordDto stockOutRecordDto) {
-        String no = OrderUtils.countTodayByCreateTime(stockOutRecordMapper, "CK","outbound_batches");
+        String no = OrderUtils.countTodayByCreateTime(stockOutRecordMapper, "CK","outbound_batches", stockOutRecordDto.getCreateTime() != null ? stockOutRecordDto.getCreateTime() : LocalDateTime.now());
         stockOutRecordDto.setOutboundBatches(no);
         if (StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode().equals(stockOutRecordDto.getRecordType())){
             stockOutRecordDto.setApprovalStatus(3);
diff --git a/src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java b/src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java
index c24c93f..ecf4f38 100644
--- a/src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java
+++ b/src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java
@@ -24,28 +24,28 @@
     private final TechnologyOperationService technologyOperationService;
 
     @GetMapping("/listPage")
-    @Log(title = "Technology operation page", businessType = BusinessType.OTHER)
+    @Log(title = "宸ュ簭鍒嗛〉鏌ヨ", businessType = BusinessType.OTHER)
     @Operation(summary = "宸ュ簭鍒嗛〉鏌ヨ")
     public R<IPage<TechnologyOperationVo>> listPage(Page<TechnologyOperationDto> page, TechnologyOperationDto technologyOperationDto) {
         return R.ok(technologyOperationService.listPage(page, technologyOperationDto));
     }
 
     @PostMapping("/add")
-    @Log(title = "Add technology operation", businessType = BusinessType.INSERT)
+    @Log(title = "宸ュ簭鏂板", businessType = BusinessType.INSERT)
     @Operation(summary = "鏂板宸ュ簭")
     public R add(@RequestBody TechnologyOperationDto technologyOperationDto) {
         return technologyOperationService.add(technologyOperationDto);
     }
 
     @PutMapping("/update")
-    @Log(title = "Update technology operation", businessType = BusinessType.UPDATE)
+    @Log(title = "宸ュ簭鏇存柊", businessType = BusinessType.UPDATE)
     @Operation(summary = "淇敼宸ュ簭")
     public R update(@RequestBody com.ruoyi.technology.pojo.TechnologyOperation technologyOperation) {
         return R.ok(technologyOperationService.updateById(technologyOperation));
     }
 
     @DeleteMapping("/batchDelete")
-    @Log(title = "Delete technology operation", businessType = BusinessType.DELETE)
+    @Log(title = "鍒犻櫎宸ュ簭", businessType = BusinessType.DELETE)
     @Operation(summary = "鎵归噺鍒犻櫎宸ュ簭")
     public R batchDelete(@RequestBody List<Long> ids) {
         return R.ok(technologyOperationService.batchDelete(ids));
diff --git a/src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java b/src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java
index 061a50c..92bb773 100644
--- a/src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java
+++ b/src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java
@@ -36,7 +36,7 @@
     }
 
     @DeleteMapping("/batchDelete/{id}")
-    @Log(title = "Delete technology operation param", businessType = BusinessType.DELETE)
+    @Log(title = "鍒犻櫎宸ュ簭鍙傛暟", businessType = BusinessType.DELETE)
     @Operation(summary = "鍒犻櫎宸ュ簭鍙傛暟")
     public AjaxResult batchDelete(@PathVariable("id") Long id) {
         return AjaxResult.success(technologyOperationParamService.batchDelete(id));
diff --git a/src/main/java/com/ruoyi/technology/pojo/TechnologyParam.java b/src/main/java/com/ruoyi/technology/pojo/TechnologyParam.java
index 75d56cf..8f2672d 100644
--- a/src/main/java/com/ruoyi/technology/pojo/TechnologyParam.java
+++ b/src/main/java/com/ruoyi/technology/pojo/TechnologyParam.java
@@ -1,8 +1,10 @@
 package com.ruoyi.technology.pojo;
 
 import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
 
 import java.io.Serializable;
 import java.time.LocalDateTime;
@@ -53,6 +55,8 @@
 
     @Schema(description = "鍒涘缓鏃堕棿")
     @TableField(fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime createTime;
 
     @Schema(description = "淇敼浜�")
@@ -61,6 +65,8 @@
 
     @Schema(description = "淇敼鏃堕棿")
     @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime updateTime;
 
     @Schema(description = "閮ㄩ棬ID")
diff --git a/src/main/java/com/ruoyi/technology/pojo/TechnologyRouting.java b/src/main/java/com/ruoyi/technology/pojo/TechnologyRouting.java
index 1427de6..880662b 100644
--- a/src/main/java/com/ruoyi/technology/pojo/TechnologyRouting.java
+++ b/src/main/java/com/ruoyi/technology/pojo/TechnologyRouting.java
@@ -32,7 +32,6 @@
     private String description;
 
     @Schema(description = "褰曞叆鏃堕棿")
-    @TableField(fill = FieldFill.INSERT)
     private LocalDateTime createTime;
 
     @Schema(description = "鏇存柊鏃堕棿")
diff --git a/src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java b/src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java
index 6750f03..f41f659 100644
--- a/src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java
+++ b/src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java
@@ -7,6 +7,8 @@
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.OrderUtils;
+
+import java.time.LocalDateTime;
 import com.ruoyi.technology.bean.dto.TechnologyRoutingDto;
 import com.ruoyi.technology.bean.vo.TechnologyRoutingVo;
 import com.ruoyi.production.mapper.ProductionOrderRoutingMapper;
@@ -60,7 +62,7 @@
 
     @Override
     public Long saveTechnologyRouting(TechnologyRouting technologyRouting) {
-        String code = OrderUtils.countTodayByCreateTime(technologyRoutingMapper, "GYLX", "process_route_code");
+        String code = OrderUtils.countTodayByCreateTime(technologyRoutingMapper, "GYLX", "process_route_code", technologyRouting.getCreateTime() != null ? technologyRouting.getCreateTime() : LocalDateTime.now());
         technologyRouting.setProcessRouteCode(code);
         technologyRoutingMapper.insert(technologyRouting);
         // 甯﹀叆bom浜у搧缁撴瀯
diff --git a/src/main/resources/application-ckgm.yml b/src/main/resources/application-ckgm.yml
new file mode 100644
index 0000000..c775901
--- /dev/null
+++ b/src/main/resources/application-ckgm.yml
@@ -0,0 +1,268 @@
+# 椤圭洰鐩稿叧閰嶇疆
+ruoyi:
+  # 鍚嶇О
+  name: RuoYi
+  # 鐗堟湰
+  version: 3.8.9
+  # 鐗堟潈骞翠唤
+  copyrightYear: 2025
+  # 鏂囦欢璺緞 绀轰緥锛� Windows閰嶇疆D:/ruoyi/uploadPath锛孡inux閰嶇疆 /home/ruoyi/uploadPath锛�
+  profile: /javaWork/product-inventory-management/file
+
+  # 鑾峰彇ip鍦板潃寮�鍏�
+  addressEnabled: false
+  # 楠岃瘉鐮佺被鍨� math 鏁板瓧璁$畻 char 瀛楃楠岃瘉
+  captchaType: math
+  # 鍗忓悓瀹℃壒缂栧彿鍓嶇紑(閰嶇疆鏂囦欢鍚庣紑鍛藉悕)
+  approvalNumberPrefix: NEW
+
+  # 涓帹 Unipush 閰嶇疆
+  getui:
+    appId: PfjyAAE0FK64FaO1w2CMb1
+    appKey: zTMb831OEL6J4GK1uE3Ob4
+    masterSecret: K1GFtsv42v61tXGnF7SGE5
+    domain: https://restapi.getui.cn/v2/
+    # 绂荤嚎鎺ㄩ�佷娇鐢ㄧ殑鍖呭悕/缁勪欢鍚�
+    intentComponent: uni.app.UNI099A590/io.dcloud.PandoraEntry
+
+# 寮�鍙戠幆澧冮厤缃�
+server:
+  # 鏈嶅姟鍣ㄧ殑HTTP绔彛锛岄粯璁や负8080
+  port: 9003
+  servlet:
+    # 搴旂敤鐨勮闂矾寰�
+    context-path: /
+  tomcat:
+    # tomcat鐨刄RI缂栫爜
+    uri-encoding: UTF-8
+    # 杩炴帴鏁版弧鍚庣殑鎺掗槦鏁帮紝榛樿涓�100
+    accept-count: 1000
+    threads:
+      # tomcat鏈�澶х嚎绋嬫暟锛岄粯璁や负200
+      max: 800
+      # Tomcat鍚姩鍒濆鍖栫殑绾跨▼鏁帮紝榛樿鍊�10
+      min-spare: 100
+
+# 鏃ュ織閰嶇疆
+logging:
+  level:
+    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: jxc
+# 鐢ㄦ埛閰嶇疆
+user:
+  password:
+    # 瀵嗙爜鏈�澶ч敊璇鏁�
+    maxRetryCount: 5
+    # 瀵嗙爜閿佸畾鏃堕棿锛堥粯璁�10鍒嗛挓锛�
+    lockTime: 10
+
+# Spring閰嶇疆
+spring:
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    druid:
+      # 涓诲簱鏁版嵁婧�
+      master:
+        url: jdbc:mysql://172.17.0.1:3306/product-inventory-management-ckgm?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+        username: root
+        password: xd@123456..
+      # 浠庡簱鏁版嵁婧�
+      slave:
+        # 浠庢暟鎹簮寮�鍏�/榛樿鍏抽棴
+        enabled: false
+        url:
+        username:
+        password:
+      # 鍒濆杩炴帴鏁�
+      initialSize: 5
+      # 鏈�灏忚繛鎺ユ睜鏁伴噺
+      minIdle: 10
+      # 鏈�澶ц繛鎺ユ睜鏁伴噺
+      maxActive: 20
+      # 閰嶇疆鑾峰彇杩炴帴绛夊緟瓒呮椂鐨勬椂闂�
+      maxWait: 60000
+      # 閰嶇疆杩炴帴瓒呮椂鏃堕棿
+      connectTimeout: 30000
+      # 閰嶇疆缃戠粶瓒呮椂鏃堕棿
+      socketTimeout: 60000
+      # 閰嶇疆闂撮殧澶氫箙鎵嶈繘琛屼竴娆℃娴嬶紝妫�娴嬮渶瑕佸叧闂殑绌洪棽杩炴帴锛屽崟浣嶆槸姣
+      timeBetweenEvictionRunsMillis: 60000
+      # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�灏忕敓瀛樼殑鏃堕棿锛屽崟浣嶆槸姣
+      minEvictableIdleTimeMillis: 300000
+      # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�澶х敓瀛樼殑鏃堕棿锛屽崟浣嶆槸姣
+      maxEvictableIdleTimeMillis: 900000
+      # 閰嶇疆妫�娴嬭繛鎺ユ槸鍚︽湁鏁�
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      webStatFilter:
+        enabled: true
+      statViewServlet:
+        enabled: true
+        # 璁剧疆鐧藉悕鍗曪紝涓嶅~鍒欏厑璁告墍鏈夎闂�
+        allow:
+        url-pattern: /druid/*
+        # 鎺у埗鍙扮鐞嗙敤鎴峰悕鍜屽瘑鐮�
+        login-username: ruoyi
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 鎱QL璁板綍
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+  # 璧勬簮淇℃伅
+  messages:
+    # 鍥介檯鍖栬祫婧愭枃浠惰矾寰�
+    basename: i18n/messages
+  # 鏂囦欢涓婁紶
+  servlet:
+    multipart:
+      # 鍗曚釜鏂囦欢澶у皬
+      max-file-size: 1GB
+      # 璁剧疆鎬讳笂浼犵殑鏂囦欢澶у皬
+      max-request-size: 2GB
+  # 鏈嶅姟妯″潡
+  devtools:
+    restart:
+      # 鐑儴缃插紑鍏�
+      enabled: false
+  # redis 閰嶇疆
+  data:
+    mongodb:
+      uri: mongodb://114.132.189.42:9028/chat_memory_db_ckgm
+    # redis 閰嶇疆
+    redis:
+      # 鍦板潃
+#      host: 127.0.0.1
+      host: 172.17.0.1
+      # 绔彛锛岄粯璁や负6379
+      port: 6379
+      # 鏁版嵁搴撶储寮�
+      database: 0
+      # 瀵嗙爜
+      #    password: root2022!
+      password:
+
+      # 杩炴帴瓒呮椂鏃堕棿
+      timeout: 10s
+      lettuce:
+        pool:
+          # 杩炴帴姹犱腑鐨勬渶灏忕┖闂茶繛鎺�
+          min-idle: 0
+          # 杩炴帴姹犱腑鐨勬渶澶х┖闂茶繛鎺�
+          max-idle: 8
+          # 杩炴帴姹犵殑鏈�澶ф暟鎹簱杩炴帴鏁�
+          max-active: 8
+          # #杩炴帴姹犳渶澶ч樆濉炵瓑寰呮椂闂达紙浣跨敤璐熷�艰〃绀烘病鏈夐檺鍒讹級
+          max-wait: -1ms
+
+  # Quartz瀹氭椂浠诲姟閰嶇疆锛堟柊澧為儴鍒嗭級
+  quartz:
+    job-store-type: jdbc  # 浣跨敤鏁版嵁搴撳瓨鍌�
+    jdbc:
+      initialize-schema: never  # 棣栨杩愯鏃惰嚜鍔ㄥ垱寤鸿〃缁撴瀯锛屾垚鍔熷悗鏀逛负never
+      schema: classpath:org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql  # MySQL琛ㄧ粨鏋勮剼鏈�
+    properties:
+      org:
+        quartz:
+          scheduler:
+            instanceName: RuoYiScheduler
+            instanceId: AUTO
+          jobStore:
+            class: org.quartz.impl.jdbcjobstore.JobStoreTX
+            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate  # MySQL閫傞厤
+            tablePrefix: qrtz_  # 琛ㄥ悕鍓嶇紑锛屼笌鑴氭湰涓�鑷�
+            isClustered: false  # 鍗曡妭鐐规ā寮忥紙闆嗙兢闇�鏀逛负true锛�
+            clusterCheckinInterval: 10000
+            txIsolationLevelSerializable: true
+          threadPool:
+            class: org.quartz.simpl.SimpleThreadPool
+            threadCount: 10  # 绾跨▼姹犲ぇ灏�
+            threadPriority: 5
+            makeThreadsDaemons: true
+          updateCheck: false  # 鍏抽棴鐗堟湰妫�鏌�
+# token閰嶇疆
+token:
+  # 浠ょ墝鑷畾涔夋爣璇�
+  header: Authorization
+  # 浠ょ墝瀵嗛挜
+  secret: xpAVjhCjQDaDB7mjPAzMDSbQWXNu2zYkTdDNUsPMS5Xx8QMmQVYN7n74eZrYJxDJ
+  # 浠ょ墝鏈夋晥鏈燂紙榛樿30鍒嗛挓锛�
+  expireTime: 450
+
+# MyBatis Plus閰嶇疆
+mybatis-plus:
+  # 鎼滅储鎸囧畾鍖呭埆鍚�   鏍规嵁鑷繁鐨勯」鐩潵
+  typeAliasesPackage: com.ruoyi.**.pojo
+  # 閰嶇疆mapper鐨勬壂鎻忥紝鎵惧埌鎵�鏈夌殑mapper.xml鏄犲皠鏂囦欢
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 鍔犺浇鍏ㄥ眬鐨勯厤缃枃浠�
+  configLocation: classpath:mybatis/mybatis-config.xml
+  global-config:
+    enable-sql-runner: true
+    db-config:
+      id-type: auto
+
+# PageHelper鍒嗛〉鎻掍欢
+pagehelper:
+  helperDialect: mysql
+  supportMethodsArguments: true
+  params: count=countSql
+
+# Swagger閰嶇疆
+swagger:
+  # 鏄惁寮�鍚痵wagger
+  enabled: true
+  # 璇锋眰鍓嶇紑
+  pathMapping: /dev-api
+
+# 闃叉XSS鏀诲嚮
+xss:
+  # 杩囨护寮�鍏�
+  enabled: true
+  # 鎺掗櫎閾炬帴锛堝涓敤閫楀彿鍒嗛殧锛�
+  excludes: /system/notice
+  # 鍖归厤閾炬帴
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+# 浠g爜鐢熸垚
+gen:
+  # 浣滆��
+  author: ruoyi
+  # 榛樿鐢熸垚鍖呰矾寰� system 闇�鏀规垚鑷繁鐨勬ā鍧楀悕绉� 濡� system monitor tool
+  packageName: com.ruoyi.project.system
+  # 鑷姩鍘婚櫎琛ㄥ墠缂�锛岄粯璁ゆ槸true
+  autoRemovePre: false
+  # 琛ㄥ墠缂�锛堢敓鎴愮被鍚嶄笉浼氬寘鍚〃鍓嶇紑锛屽涓敤閫楀彿鍒嗛殧锛�
+  tablePrefix: sys_
+  # 鏄惁鍏佽鐢熸垚鏂囦欢瑕嗙洊鍒版湰鍦帮紙鑷畾涔夎矾寰勶級锛岄粯璁や笉鍏佽
+  allowOverwrite: false
+
+# 鏂囦欢涓婁紶閰嶇疆
+file:
+  temp-dir: /javaWork/product-inventory-management/file/temp/uploads   # 涓存椂鐩綍
+  upload-dir: /javaWork/product-inventory-management/file/prod/uploads # 姝e紡鐩綍
+  path: /javaWork/product-inventory-management/file # 涓婁紶鐩綍
+  urlPrefix: /prod-api/common # 閾炬帴鍓嶇紑
+  domain: http://1.15.17.182:9071 # 鍩熷悕鍓嶇紑
+  expired: 120 # 杩囨湡鏃堕棿(鍗曚綅:鍒嗛挓)
+  useLimit: 10 # 浣跨敤娆℃暟
+  compress: true # 鏄惁鍘嬬缉
+  needCompressSize: 10MB # 鍘嬬缉闃堝��
+  compressQuality: 0.5 # 鍘嬬缉璐ㄩ噺(0.0-1.0)
diff --git a/src/main/resources/application-hqjc.yml b/src/main/resources/application-hqjc.yml
new file mode 100644
index 0000000..6157353
--- /dev/null
+++ b/src/main/resources/application-hqjc.yml
@@ -0,0 +1,268 @@
+# 椤圭洰鐩稿叧閰嶇疆
+ruoyi:
+  # 鍚嶇О
+  name: RuoYi
+  # 鐗堟湰
+  version: 3.8.9
+  # 鐗堟潈骞翠唤
+  copyrightYear: 2025
+  # 鏂囦欢璺緞 绀轰緥锛� Windows閰嶇疆D:/ruoyi/uploadPath锛孡inux閰嶇疆 /home/ruoyi/uploadPath锛�
+  profile: /javaWork/product-inventory-management/file
+
+  # 鑾峰彇ip鍦板潃寮�鍏�
+  addressEnabled: false
+  # 楠岃瘉鐮佺被鍨� math 鏁板瓧璁$畻 char 瀛楃楠岃瘉
+  captchaType: math
+  # 鍗忓悓瀹℃壒缂栧彿鍓嶇紑(閰嶇疆鏂囦欢鍚庣紑鍛藉悕)
+  approvalNumberPrefix: NEW
+
+  # 涓帹 Unipush 閰嶇疆
+  getui:
+    appId: PfjyAAE0FK64FaO1w2CMb1
+    appKey: zTMb831OEL6J4GK1uE3Ob4
+    masterSecret: K1GFtsv42v61tXGnF7SGE5
+    domain: https://restapi.getui.cn/v2/
+    # 绂荤嚎鎺ㄩ�佷娇鐢ㄧ殑鍖呭悕/缁勪欢鍚�
+    intentComponent: uni.app.UNI099A590/io.dcloud.PandoraEntry
+
+# 寮�鍙戠幆澧冮厤缃�
+server:
+  # 鏈嶅姟鍣ㄧ殑HTTP绔彛锛岄粯璁や负8080
+  port: 9003
+  servlet:
+    # 搴旂敤鐨勮闂矾寰�
+    context-path: /
+  tomcat:
+    # tomcat鐨刄RI缂栫爜
+    uri-encoding: UTF-8
+    # 杩炴帴鏁版弧鍚庣殑鎺掗槦鏁帮紝榛樿涓�100
+    accept-count: 1000
+    threads:
+      # tomcat鏈�澶х嚎绋嬫暟锛岄粯璁や负200
+      max: 800
+      # Tomcat鍚姩鍒濆鍖栫殑绾跨▼鏁帮紝榛樿鍊�10
+      min-spare: 100
+
+# 鏃ュ織閰嶇疆
+logging:
+  level:
+    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: jxc
+# 鐢ㄦ埛閰嶇疆
+user:
+  password:
+    # 瀵嗙爜鏈�澶ч敊璇鏁�
+    maxRetryCount: 5
+    # 瀵嗙爜閿佸畾鏃堕棿锛堥粯璁�10鍒嗛挓锛�
+    lockTime: 10
+
+# Spring閰嶇疆
+spring:
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.cj.jdbc.Driver
+    druid:
+      # 涓诲簱鏁版嵁婧�
+      master:
+        url: jdbc:mysql://172.17.0.1:9002/product-inventory-management-hqjc?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
+        username: root
+        password: hqjc@123456..
+      # 浠庡簱鏁版嵁婧�
+      slave:
+        # 浠庢暟鎹簮寮�鍏�/榛樿鍏抽棴
+        enabled: false
+        url:
+        username:
+        password:
+      # 鍒濆杩炴帴鏁�
+      initialSize: 5
+      # 鏈�灏忚繛鎺ユ睜鏁伴噺
+      minIdle: 10
+      # 鏈�澶ц繛鎺ユ睜鏁伴噺
+      maxActive: 20
+      # 閰嶇疆鑾峰彇杩炴帴绛夊緟瓒呮椂鐨勬椂闂�
+      maxWait: 60000
+      # 閰嶇疆杩炴帴瓒呮椂鏃堕棿
+      connectTimeout: 30000
+      # 閰嶇疆缃戠粶瓒呮椂鏃堕棿
+      socketTimeout: 60000
+      # 閰嶇疆闂撮殧澶氫箙鎵嶈繘琛屼竴娆℃娴嬶紝妫�娴嬮渶瑕佸叧闂殑绌洪棽杩炴帴锛屽崟浣嶆槸姣
+      timeBetweenEvictionRunsMillis: 60000
+      # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�灏忕敓瀛樼殑鏃堕棿锛屽崟浣嶆槸姣
+      minEvictableIdleTimeMillis: 300000
+      # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�澶х敓瀛樼殑鏃堕棿锛屽崟浣嶆槸姣
+      maxEvictableIdleTimeMillis: 900000
+      # 閰嶇疆妫�娴嬭繛鎺ユ槸鍚︽湁鏁�
+      validationQuery: SELECT 1 FROM DUAL
+      testWhileIdle: true
+      testOnBorrow: false
+      testOnReturn: false
+      webStatFilter:
+        enabled: true
+      statViewServlet:
+        enabled: true
+        # 璁剧疆鐧藉悕鍗曪紝涓嶅~鍒欏厑璁告墍鏈夎闂�
+        allow:
+        url-pattern: /druid/*
+        # 鎺у埗鍙扮鐞嗙敤鎴峰悕鍜屽瘑鐮�
+        login-username: ruoyi
+        login-password: 123456
+      filter:
+        stat:
+          enabled: true
+          # 鎱QL璁板綍
+          log-slow-sql: true
+          slow-sql-millis: 1000
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+  # 璧勬簮淇℃伅
+  messages:
+    # 鍥介檯鍖栬祫婧愭枃浠惰矾寰�
+    basename: i18n/messages
+  # 鏂囦欢涓婁紶
+  servlet:
+    multipart:
+      # 鍗曚釜鏂囦欢澶у皬
+      max-file-size: 1GB
+      # 璁剧疆鎬讳笂浼犵殑鏂囦欢澶у皬
+      max-request-size: 2GB
+  # 鏈嶅姟妯″潡
+  devtools:
+    restart:
+      # 鐑儴缃插紑鍏�
+      enabled: false
+  # redis 閰嶇疆
+  data:
+    mongodb:
+      uri: mongodb://114.132.189.42:9028/chat_memory_db_hqjc
+    # redis 閰嶇疆
+    redis:
+      # 鍦板潃
+#      host: 127.0.0.1
+      host: 172.17.0.1
+      # 绔彛锛岄粯璁や负6379
+      port: 6379
+      # 鏁版嵁搴撶储寮�
+      database: 0
+      # 瀵嗙爜
+      #    password: root2022!
+      password:
+
+      # 杩炴帴瓒呮椂鏃堕棿
+      timeout: 10s
+      lettuce:
+        pool:
+          # 杩炴帴姹犱腑鐨勬渶灏忕┖闂茶繛鎺�
+          min-idle: 0
+          # 杩炴帴姹犱腑鐨勬渶澶х┖闂茶繛鎺�
+          max-idle: 8
+          # 杩炴帴姹犵殑鏈�澶ф暟鎹簱杩炴帴鏁�
+          max-active: 8
+          # #杩炴帴姹犳渶澶ч樆濉炵瓑寰呮椂闂达紙浣跨敤璐熷�艰〃绀烘病鏈夐檺鍒讹級
+          max-wait: -1ms
+
+  # Quartz瀹氭椂浠诲姟閰嶇疆锛堟柊澧為儴鍒嗭級
+  quartz:
+    job-store-type: jdbc  # 浣跨敤鏁版嵁搴撳瓨鍌�
+    jdbc:
+      initialize-schema: never  # 棣栨杩愯鏃惰嚜鍔ㄥ垱寤鸿〃缁撴瀯锛屾垚鍔熷悗鏀逛负never
+      schema: classpath:org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql  # MySQL琛ㄧ粨鏋勮剼鏈�
+    properties:
+      org:
+        quartz:
+          scheduler:
+            instanceName: RuoYiScheduler
+            instanceId: AUTO
+          jobStore:
+            class: org.quartz.impl.jdbcjobstore.JobStoreTX
+            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate  # MySQL閫傞厤
+            tablePrefix: qrtz_  # 琛ㄥ悕鍓嶇紑锛屼笌鑴氭湰涓�鑷�
+            isClustered: false  # 鍗曡妭鐐规ā寮忥紙闆嗙兢闇�鏀逛负true锛�
+            clusterCheckinInterval: 10000
+            txIsolationLevelSerializable: true
+          threadPool:
+            class: org.quartz.simpl.SimpleThreadPool
+            threadCount: 10  # 绾跨▼姹犲ぇ灏�
+            threadPriority: 5
+            makeThreadsDaemons: true
+          updateCheck: false  # 鍏抽棴鐗堟湰妫�鏌�
+# token閰嶇疆
+token:
+  # 浠ょ墝鑷畾涔夋爣璇�
+  header: Authorization
+  # 浠ょ墝瀵嗛挜
+  secret: xpAVjhCjQDaDB7mjPAzMDSbQWXNu2zYkTdDNUsPMS5Xx8QMmQVYN7n74eZrYJxDJ
+  # 浠ょ墝鏈夋晥鏈燂紙榛樿30鍒嗛挓锛�
+  expireTime: 450
+
+# MyBatis Plus閰嶇疆
+mybatis-plus:
+  # 鎼滅储鎸囧畾鍖呭埆鍚�   鏍规嵁鑷繁鐨勯」鐩潵
+  typeAliasesPackage: com.ruoyi.**.pojo
+  # 閰嶇疆mapper鐨勬壂鎻忥紝鎵惧埌鎵�鏈夌殑mapper.xml鏄犲皠鏂囦欢
+  mapperLocations: classpath*:mapper/**/*Mapper.xml
+  # 鍔犺浇鍏ㄥ眬鐨勯厤缃枃浠�
+  configLocation: classpath:mybatis/mybatis-config.xml
+  global-config:
+    enable-sql-runner: true
+    db-config:
+      id-type: auto
+
+# PageHelper鍒嗛〉鎻掍欢
+pagehelper:
+  helperDialect: mysql
+  supportMethodsArguments: true
+  params: count=countSql
+
+# Swagger閰嶇疆
+swagger:
+  # 鏄惁寮�鍚痵wagger
+  enabled: true
+  # 璇锋眰鍓嶇紑
+  pathMapping: /dev-api
+
+# 闃叉XSS鏀诲嚮
+xss:
+  # 杩囨护寮�鍏�
+  enabled: true
+  # 鎺掗櫎閾炬帴锛堝涓敤閫楀彿鍒嗛殧锛�
+  excludes: /system/notice
+  # 鍖归厤閾炬帴
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+# 浠g爜鐢熸垚
+gen:
+  # 浣滆��
+  author: ruoyi
+  # 榛樿鐢熸垚鍖呰矾寰� system 闇�鏀规垚鑷繁鐨勬ā鍧楀悕绉� 濡� system monitor tool
+  packageName: com.ruoyi.project.system
+  # 鑷姩鍘婚櫎琛ㄥ墠缂�锛岄粯璁ゆ槸true
+  autoRemovePre: false
+  # 琛ㄥ墠缂�锛堢敓鎴愮被鍚嶄笉浼氬寘鍚〃鍓嶇紑锛屽涓敤閫楀彿鍒嗛殧锛�
+  tablePrefix: sys_
+  # 鏄惁鍏佽鐢熸垚鏂囦欢瑕嗙洊鍒版湰鍦帮紙鑷畾涔夎矾寰勶級锛岄粯璁や笉鍏佽
+  allowOverwrite: false
+
+# 鏂囦欢涓婁紶閰嶇疆
+file:
+  temp-dir: /javaWork/product-inventory-management/file/temp/uploads   # 涓存椂鐩綍
+  upload-dir: /javaWork/product-inventory-management/file/prod/uploads # 姝e紡鐩綍
+  path: /javaWork/product-inventory-management/file # 涓婁紶鐩綍
+  urlPrefix: /prod-api/common # 閾炬帴鍓嶇紑
+  domain: http://36.134.77.64:9001 # 鍩熷悕鍓嶇紑
+  expired: 120 # 杩囨湡鏃堕棿(鍗曚綅:鍒嗛挓)
+  useLimit: 10 # 浣跨敤娆℃暟
+  compress: true # 鏄惁鍘嬬缉
+  needCompressSize: 10MB # 鍘嬬缉闃堝��
+  compressQuality: 0.5 # 鍘嬬缉璐ㄩ噺(0.0-1.0)
diff --git a/src/main/resources/financial-agent-prompt.txt b/src/main/resources/financial-agent-prompt.txt
index c7a4a3f..5b5fb02 100644
--- a/src/main/resources/financial-agent-prompt.txt
+++ b/src/main/resources/financial-agent-prompt.txt
@@ -2,10 +2,10 @@
 褰撳墠鏃ユ湡锛歿{currentDate}}锛堜腑鍥芥椂鍖猴級銆�
 
 宸ヤ綔瑙勫垯锛�
-1. 鐢ㄦ埛鎻愬嚭鈥滄煡銆侀棶銆佺粺璁°�佸垎鏋愩�侀璀︺�佸缓璁�佹姤鍛娾�濋渶姹傛椂锛屼紭鍏堣皟鐢ㄥ伐鍏疯繑鍥炵粨鏋勫寲 JSON锛屼笉缂栭�犱笟鍔℃暟鎹��
-2. 鍛戒腑鎴愭湰銆佸埄娑︺�佸簱瀛樿祫閲戙�佺幇閲戞祦銆侀璀︺�侀┚椹惰埍銆佹棩鎶ュ懆鎶ュ満鏅椂锛屼紭鍏堣皟鐢ㄥ搴斿伐鍏枫��
+1. 鐢ㄦ埛鎻愬嚭鈥滄煡銆侀棶銆佺粺璁°�佸垎鏋愩�侀璀︺�佸缓璁�佹姤鍛娾�濈被闇�姹傛椂锛屼紭鍏堣皟鐢ㄥ伐鍏疯繑鍥炵粨鏋勫寲 JSON锛屼笉缂栭�犱笟鍔℃暟鎹��
+2. 鍛戒腑鎴愭湰銆佸埄娑︺�佸簱瀛樿祫閲戙�佺幇閲戞祦銆侀璀︺�侀┚椹惰埍銆佹棩鎶ャ�佸懆鎶ョ瓑鍦烘櫙鏃讹紝浼樺厛璋冪敤瀵瑰簲宸ュ叿銆�
 3. 宸ュ叿杩斿洖 JSON 鏃讹紝鐩存帴杈撳嚭鍘熷 JSON 瀛楃涓诧紝涓嶈棰濆鍖呰9 Markdown锛屼篃涓嶈鍦ㄥ墠鍚庤拷鍔犺В閲婃枃鏈��
-4. 褰撶敤鎴烽棶棰樼己灏戞椂闂磋寖鍥存椂锛岄粯璁や娇鐢ㄥ伐鍏峰唴缃彛寰勶紙濡傝繎30澶┿�佹湰鏈堛�佽繎90澶╋級锛屽苟鍦ㄥ悗缁彲鎻愰啋鐢ㄦ埛琛ュ厖鑼冨洿銆�
-5. 鐢ㄦ埛闂�滀负浠�涔堝埄娑︿笅闄嶁�濃�滃摢涓鍗曚簭鎹熲�濃�滃摢涓鎴锋渶璧氶挶鈥濃�滃摢涓鎴峰埄娑﹁础鐚渶楂樷�濃�滃摢涓溅闂�/宸ュ簭鎴愭湰鏈�楂樷�濈瓑闂鏃讹紝浼樺厛鍩轰簬璁㈠崟鍒╂鼎涓庡伐搴忔垚鏈垎鏋愬伐鍏蜂綔绛斻��
-6. 鍥炵瓟蹇呴』浣跨敤涓枃锛涜嫢鏁版嵁涓嶈冻浠ュ緱鍑虹粨璁猴紝鏄庣‘鎸囧嚭缂哄皯鍝簺鍏抽敭瀛楁鎴栫瓫閫夋潯浠躲��
-7. 鐢ㄦ埛鎻愬埌鈥滀粖骞�/鏈湀/浠婂ぉ/鏈�杩�/涓婃湀/鍘诲勾鈥濈瓑鐩稿鏃堕棿鏃讹紝蹇呴』涓ユ牸鍩轰簬鈥滃綋鍓嶆棩鏈熲�濇崲绠楋紝绂佹鑷鍋囪骞翠唤銆�
+4. 鐢ㄦ埛娌℃湁鏄庣‘缁欏嚭鏃堕棿銆佸鎴枫�佷緵搴斿晢銆佷骇鍝併�佽鍗曘�佹暟閲忕瓑绛涢�夋潯浠舵椂锛屼笉瑕佽嚜琛岃ˉ鍏呮潯浠讹紱宸ュ叿鍙傛暟淇濇寔涓虹┖锛岀敱宸ュ叿鎸夊綋鍓嶄笟鍔″彛寰勬煡璇€��
+5. 鐢ㄦ埛鎻愬埌鈥滄湰鍛ㄣ�佹湰鏈堛�佷粖骞淬�佷粖澶┿�佹渶杩戙�佽繎30澶┿�佷笂鏈堛�佸幓骞粹�濈瓑鐩稿鏃堕棿鏃讹紝蹇呴』涓ユ牸鍩轰簬鈥滃綋鍓嶆棩鏈熲�濇崲绠楋紝绂佹鑷鍋囪骞翠唤銆�
+6. 鐢ㄦ埛闂�滀负浠�涔堝埄娑︿笅闄嶁�濃�滃摢涓鍗曚簭鎹熲�濃�滃摢涓鎴锋渶璧氶挶鈥濃�滃摢涓鎴峰埄娑﹁础鐚渶楂樷�濃�滃摢涓伐搴忔垚鏈渶楂樷�濈瓑闂鏃讹紝浼樺厛鍩轰簬璁㈠崟鍒╂鼎涓庡伐搴忔垚鏈垎鏋愬伐鍏蜂綔绛斻��
+7. 鍥炵瓟蹇呴』浣跨敤涓枃锛涜嫢鏁版嵁涓嶈冻浠ュ緱鍑虹粨璁猴紝鏄庣‘鎸囧嚭缂哄皯鍝簺鍏抽敭瀛楁鎴栫瓫閫夋潯浠躲��
diff --git a/src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml b/src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml
index 8b23627..267de0b 100644
--- a/src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml
+++ b/src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml
@@ -44,6 +44,7 @@
                 AND A.apply_date BETWEEN #{req.startDate} AND #{req.endDate}
             </if>
         </where>
+        order by A.id desc
     </select>
 
     <select id="getInboundBatchesBySupplier"
diff --git a/src/main/resources/mapper/account/purchase/AccountPurchaseInvoiceMapper.xml b/src/main/resources/mapper/account/purchase/AccountPurchaseInvoiceMapper.xml
index 041f2f7..a164b5c 100644
--- a/src/main/resources/mapper/account/purchase/AccountPurchaseInvoiceMapper.xml
+++ b/src/main/resources/mapper/account/purchase/AccountPurchaseInvoiceMapper.xml
@@ -42,6 +42,7 @@
                 AND api.issue_date BETWEEN #{req.startDate} AND #{req.endDate}
             </if>
         </where>
+        order by api.id desc
     </select>
     <select id="getInboundBatchesBySupplier"
             resultType="com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo">
diff --git a/src/main/resources/mapper/account/purchase/AccountPurchasePaymentMapper.xml b/src/main/resources/mapper/account/purchase/AccountPurchasePaymentMapper.xml
index 89a3681..ed247e5 100644
--- a/src/main/resources/mapper/account/purchase/AccountPurchasePaymentMapper.xml
+++ b/src/main/resources/mapper/account/purchase/AccountPurchasePaymentMapper.xml
@@ -44,6 +44,7 @@
                 AND app.payment_date BETWEEN #{req.startDate} AND #{req.endDate}
             </if>
         </where>
+        order by app.id desc
     </select>
     <select id="selectPayment" resultType="com.ruoyi.home.dto.IncomeExpenseAnalysisDto">
         SELECT DATE_FORMAT(payment_date, #{dateFormat}) AS dateStr,
diff --git a/src/main/resources/mapper/account/sales/AccountInvoiceApplicationMapper.xml b/src/main/resources/mapper/account/sales/AccountInvoiceApplicationMapper.xml
index 842e94c..7884e75 100644
--- a/src/main/resources/mapper/account/sales/AccountInvoiceApplicationMapper.xml
+++ b/src/main/resources/mapper/account/sales/AccountInvoiceApplicationMapper.xml
@@ -25,6 +25,7 @@
                 AND A.apply_date BETWEEN #{req.startDate} AND #{req.endDate}
             </if>
         </where>
+        order by A.id desc
     </select>
     <select id="getOutboundBatchesByCustomer"
             resultType="com.ruoyi.account.bean.vo.sales.SalesOutboundVo">
diff --git a/src/main/resources/mapper/account/sales/AccountSalesCollectionMapper.xml b/src/main/resources/mapper/account/sales/AccountSalesCollectionMapper.xml
index 4ad55d4..4898072 100644
--- a/src/main/resources/mapper/account/sales/AccountSalesCollectionMapper.xml
+++ b/src/main/resources/mapper/account/sales/AccountSalesCollectionMapper.xml
@@ -45,7 +45,7 @@
                 AND A.collection_date BETWEEN #{req.startDate} AND #{req.endDate}
             </if>
         </where>
-
+        order by A.id desc
     </select>
     <select id="existsByStockOutRecordId" resultType="java.lang.Boolean">
         SELECT COUNT(*) > 0
diff --git a/src/main/resources/mapper/account/sales/AccountSalesInvoiceMapper.xml b/src/main/resources/mapper/account/sales/AccountSalesInvoiceMapper.xml
index b289b8f..e69d79d 100644
--- a/src/main/resources/mapper/account/sales/AccountSalesInvoiceMapper.xml
+++ b/src/main/resources/mapper/account/sales/AccountSalesInvoiceMapper.xml
@@ -42,6 +42,7 @@
                 AND asi.issue_date BETWEEN #{req.startDate} AND #{req.endDate}
             </if>
         </where>
+        order by asi.id desc
     </select>
 
 </mapper>
diff --git a/src/main/resources/mapper/aftersalesservice/AfterSalesNearExpiryMapper.xml b/src/main/resources/mapper/aftersalesservice/AfterSalesNearExpiryMapper.xml
index e4b3c77..d1cde99 100644
--- a/src/main/resources/mapper/aftersalesservice/AfterSalesNearExpiryMapper.xml
+++ b/src/main/resources/mapper/aftersalesservice/AfterSalesNearExpiryMapper.xml
@@ -25,17 +25,18 @@
 
     <select id="listPage" resultType="com.ruoyi.aftersalesservice.pojo.AfterSalesNearExpiry">
         select * from after_sales_near_expiry
-        where 1 = 1
-        <if test="req.expireDate != null">
-            AND expire_date = #{req.expireDate}
-        </if>
-        <if test="req.disDate != null">
-            AND dis_date = #{req.disDate}
-        </if>
-        <if test="req.status != null">
-            AND status = #{req.status}
-        </if>
+        <where>
+            <if test="req.expireDate != null">
+                AND expire_date = #{req.expireDate}
+            </if>
+            <if test="req.disDate != null">
+                AND dis_date = #{req.disDate}
+            </if>
+            <if test="req.status != null">
+                AND status = #{req.status}
+            </if>
+        </where>
         order by create_time desc
     </select>
 
-</mapper>
\ No newline at end of file
+</mapper>
diff --git a/src/main/resources/mapper/aftersalesservice/AfterSalesServiceMapper.xml b/src/main/resources/mapper/aftersalesservice/AfterSalesServiceMapper.xml
index 2130c09..1d36599 100644
--- a/src/main/resources/mapper/aftersalesservice/AfterSalesServiceMapper.xml
+++ b/src/main/resources/mapper/aftersalesservice/AfterSalesServiceMapper.xml
@@ -32,7 +32,7 @@
         <if test="req.serviceType != null">
             and service_type = #{req.serviceType}
         </if>
-        order by update_time desc
+        order by create_time desc
     </select>
     <select id="countAfterSalesService" resultType="com.ruoyi.aftersalesservice.dto.CountDto">
         select
@@ -42,4 +42,4 @@
         group by status
     </select>
 
-</mapper>
\ No newline at end of file
+</mapper>
diff --git a/src/main/resources/mapper/approve/ApprovalInstanceMapper.xml b/src/main/resources/mapper/approve/ApprovalInstanceMapper.xml
new file mode 100644
index 0000000..f632e74
--- /dev/null
+++ b/src/main/resources/mapper/approve/ApprovalInstanceMapper.xml
@@ -0,0 +1,58 @@
+<?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.approve.mapper.ApprovalInstanceMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.ApprovalInstance">
+        <id column="id" property="id" />
+        <result column="instance_no" property="instanceNo" />
+        <result column="template_id" property="templateId" />
+        <result column="template_name" property="templateName" />
+        <result column="business_id" property="businessId" />
+        <result column="business_type" property="businessType" />
+        <result column="title" property="title" />
+        <result column="status" property="status" />
+        <result column="current_level" property="currentLevel" />
+        <result column="applicant_id" property="applicantId" />
+        <result column="applicant_name" property="applicantName" />
+        <result column="apply_time" property="applyTime" />
+        <result column="finish_time" property="finishTime" />
+        <result column="create_user" property="createUser" />
+        <result column="create_time" property="createTime" />
+        <result column="update_user" property="updateUser" />
+        <result column="update_time" property="updateTime" />
+        <result column="deleted" property="deleted" />
+    </resultMap>
+    <select id="listPage" resultType="com.ruoyi.approve.bean.vo.ApprovalInstanceVo">
+        select ai.*,su.nick_name as create_user_name from
+        approval_instance ai
+        left join sys_user su on ai.create_user = su.user_id
+        <where>
+            deleted = 0
+            <if test="ew.instanceNo != null">
+                and ai.instance_no like concat('%',#{ew.instanceNo},'%')
+            </if>
+            <if test="ew.templateName != null">
+                and ai.template_name like concat('%',#{ew.templateName},'%')
+            </if>
+            <if test="ew.templateId != null ">
+                and ai. template_id = #{ew.templateId}
+            </if>
+            <if test="ew.businessType != null ">
+                and ai.business_type = #{ew.businessType}
+            </if>
+            <if test="ew.createTimeStart != null and ew.createTimeEnd != null">
+                and ai.apply_time &gt;= #{ew.createTimeStart}
+                and ai.apply_time &lt;= #{ew.createTimeEnd}
+            </if>
+            <if test="ew.status != null">
+                and ai.status = #{ew.status}
+            </if>
+            <if test="ew.applicantName != null and ew.applicantName !=''">
+                and ai.applicant_name = #{ew.applicantName}
+            </if>
+        </where>
+        order by ai.create_time desc
+    </select>
+
+</mapper>
diff --git a/src/main/resources/mapper/approve/ApprovalInstanceNodeMapper.xml b/src/main/resources/mapper/approve/ApprovalInstanceNodeMapper.xml
new file mode 100644
index 0000000..560271b
--- /dev/null
+++ b/src/main/resources/mapper/approve/ApprovalInstanceNodeMapper.xml
@@ -0,0 +1,21 @@
+<?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.approve.mapper.ApprovalInstanceNodeMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.ApprovalInstanceNode">
+        <id column="id" property="id" />
+        <result column="instance_id" property="instanceId" />
+        <result column="level_no" property="levelNo" />
+        <result column="approve_type" property="approveType" />
+        <result column="status" property="status" />
+        <result column="start_time" property="startTime" />
+        <result column="finish_time" property="finishTime" />
+        <result column="create_user" property="createUser" />
+        <result column="create_time" property="createTime" />
+        <result column="update_user" property="updateUser" />
+        <result column="update_time" property="updateTime" />
+        <result column="deleted" property="deleted" />
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/mapper/approve/ApprovalRecordMapper.xml b/src/main/resources/mapper/approve/ApprovalRecordMapper.xml
new file mode 100644
index 0000000..db40692
--- /dev/null
+++ b/src/main/resources/mapper/approve/ApprovalRecordMapper.xml
@@ -0,0 +1,20 @@
+<?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.approve.mapper.ApprovalRecordMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.ApprovalRecord">
+        <id column="id" property="id" />
+        <result column="instance_id" property="instanceId" />
+        <result column="node_id" property="nodeId" />
+        <result column="task_id" property="taskId" />
+        <result column="operator_id" property="operatorId" />
+        <result column="operator_name" property="operatorName" />
+        <result column="action" property="action" />
+        <result column="comment" property="comment" />
+        <result column="create_user" property="createUser" />
+        <result column="create_time" property="createTime" />
+        <result column="deleted" property="deleted" />
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/mapper/approve/ApprovalTaskMapper.xml b/src/main/resources/mapper/approve/ApprovalTaskMapper.xml
new file mode 100644
index 0000000..c079440
--- /dev/null
+++ b/src/main/resources/mapper/approve/ApprovalTaskMapper.xml
@@ -0,0 +1,24 @@
+<?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.approve.mapper.ApprovalTaskMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.ApprovalTask">
+        <id column="id" property="id" />
+        <result column="instance_id" property="instanceId" />
+        <result column="node_id" property="nodeId" />
+        <result column="level_no" property="levelNo" />
+        <result column="approver_id" property="approverId" />
+        <result column="approver_name" property="approverName" />
+        <result column="task_status" property="taskStatus" />
+        <result column="approve_time" property="approveTime" />
+        <result column="comment" property="comment" />
+        <result column="is_read" property="isRead" />
+        <result column="create_user" property="createUser" />
+        <result column="create_time" property="createTime" />
+        <result column="update_user" property="updateUser" />
+        <result column="update_time" property="updateTime" />
+        <result column="deleted" property="deleted" />
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/mapper/approve/ApprovalTemplateMapper.xml b/src/main/resources/mapper/approve/ApprovalTemplateMapper.xml
new file mode 100644
index 0000000..a48432b
--- /dev/null
+++ b/src/main/resources/mapper/approve/ApprovalTemplateMapper.xml
@@ -0,0 +1,33 @@
+<?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.approve.mapper.ApprovalTemplateMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.ApprovalTemplate">
+        <id column="id" property="id" />
+        <result column="template_name" property="templateName" />
+        <result column="enabled" property="enabled" />
+        <result column="description" property="description" />
+        <result column="deleted" property="deleted" />
+        <result column="dept_id" property="deptId" />
+    </resultMap>
+    <select id="listPage" resultType="com.ruoyi.approve.bean.vo.ApprovalTemplateVo">
+        select at.*,su.nick_name as create_user_name  from
+        approval_template at
+        left join sys_user su on at.create_user = su.user_id
+        <where>
+            deleted = 0
+            <if test="ew.templateName != null">
+                and template_name like concat('%',#{ew.templateName},'%')
+            </if>
+            <if test="ew.templateType != null">
+                and template_type = #{ew.templateType}
+            </if>
+            <if test="ew.enabled != null">
+                and enabled = #{ew.enabled}
+            </if>
+        </where>
+        order by at.id desc
+    </select>
+
+</mapper>
diff --git a/src/main/resources/mapper/approve/ApprovalTemplateNodeApproverMapper.xml b/src/main/resources/mapper/approve/ApprovalTemplateNodeApproverMapper.xml
new file mode 100644
index 0000000..dac42cc
--- /dev/null
+++ b/src/main/resources/mapper/approve/ApprovalTemplateNodeApproverMapper.xml
@@ -0,0 +1,16 @@
+<?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.approve.mapper.ApprovalTemplateNodeApproverMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover">
+        <id column="id" property="id" />
+        <result column="node_id" property="nodeId" />
+        <result column="template_id" property="templateId" />
+        <result column="approver_id" property="approverId" />
+        <result column="approver_name" property="approverName" />
+        <result column="sort_no" property="sortNo" />
+        <result column="created_time" property="createdTime" />
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/mapper/approve/ApprovalTemplateNodeMapper.xml b/src/main/resources/mapper/approve/ApprovalTemplateNodeMapper.xml
new file mode 100644
index 0000000..7956787
--- /dev/null
+++ b/src/main/resources/mapper/approve/ApprovalTemplateNodeMapper.xml
@@ -0,0 +1,15 @@
+<?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.approve.mapper.ApprovalTemplateNodeMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.ApprovalTemplateNode">
+        <id column="id" property="id" />
+        <result column="template_id" property="templateId" />
+        <result column="level_no" property="levelNo" />
+        <result column="approve_type" property="approveType" />
+        <result column="created_time" property="createdTime" />
+        <result column="updated_time" property="updatedTime" />
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/mapper/approve/ApproveProcessMapper.xml b/src/main/resources/mapper/approve/ApproveProcessMapper.xml
index 057cefb..70d132b 100644
--- a/src/main/resources/mapper/approve/ApproveProcessMapper.xml
+++ b/src/main/resources/mapper/approve/ApproveProcessMapper.xml
@@ -41,7 +41,7 @@
         <if test="req.approveType != null ">
             and approve_type = #{req.approveType}
         </if>
-        order by approve_time desc
+        order by id desc
     </select>
 
 </mapper>
diff --git a/src/main/resources/mapper/approve/FinReimbursementDetailMapper.xml b/src/main/resources/mapper/approve/FinReimbursementDetailMapper.xml
new file mode 100644
index 0000000..f8a67bb
--- /dev/null
+++ b/src/main/resources/mapper/approve/FinReimbursementDetailMapper.xml
@@ -0,0 +1,29 @@
+<?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.approve.mapper.FinReimbursementDetailMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.FinReimbursementDetail">
+        <id column="id" property="id" />
+        <result column="reimbursement_id" property="reimbursementId" />
+        <result column="row_no" property="rowNo" />
+        <result column="invoice_date" property="invoiceDate" />
+        <result column="expense_category" property="expenseCategory" />
+        <result column="amount" property="amount" />
+        <result column="description" property="description" />
+        <result column="invoice_no" property="invoiceNo" />
+        <result column="invoice_type" property="invoiceType" />
+        <result column="invoice_amount" property="invoiceAmount" />
+        <result column="tax_rate" property="taxRate" />
+        <result column="tax_amount" property="taxAmount" />
+        <result column="remark" property="remark" />
+        <result column="tenant_id" property="tenantId" />
+        <result column="create_user" property="createUser" />
+        <result column="create_time" property="createTime" />
+        <result column="update_user" property="updateUser" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+        <result column="deleted" property="deleted" />
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/mapper/approve/FinReimbursementMapper.xml b/src/main/resources/mapper/approve/FinReimbursementMapper.xml
new file mode 100644
index 0000000..6b4931c
--- /dev/null
+++ b/src/main/resources/mapper/approve/FinReimbursementMapper.xml
@@ -0,0 +1,62 @@
+<?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.approve.mapper.FinReimbursementMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.FinReimbursement">
+        <id column="id" property="id" />
+        <result column="bill_no" property="billNo" />
+        <result column="reimbursement_type" property="reimbursementType" />
+        <result column="expense_type" property="expenseType" />
+        <result column="applicant_id" property="applicantId" />
+        <result column="applicant_code" property="applicantCode" />
+        <result column="applicant_name" property="applicantName" />
+        <result column="applicant_dept_id" property="applicantDeptId" />
+        <result column="applicant_dept_name" property="applicantDeptName" />
+        <result column="reason" property="reason" />
+        <result column="apply_amount" property="applyAmount" />
+        <result column="detail_total_amount" property="detailTotalAmount" />
+        <result column="payee_name" property="payeeName" />
+        <result column="payee_account" property="payeeAccount" />
+        <result column="payee_bank" property="payeeBank" />
+        <result column="approval_instance_id" property="approvalInstanceId" />
+        <result column="approve_process_id" property="approveProcessId" />
+        <result column="bill_status" property="billStatus" />
+        <result column="approved_time" property="approvedTime" />
+        <result column="paid_time" property="paidTime" />
+        <result column="account_expense_id" property="accountExpenseId" />
+        <result column="remark" property="remark" />
+        <result column="tenant_id" property="tenantId" />
+        <result column="create_user" property="createUser" />
+        <result column="create_time" property="createTime" />
+        <result column="update_user" property="updateUser" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+        <result column="deleted" property="deleted" />
+    </resultMap>
+    <select id="listPage" resultType="com.ruoyi.approve.bean.vo.FinReimbursementVo">
+        select fin_reimbursement.*,
+        fin_reimbursement_travel.start_time ,
+        fin_reimbursement_travel.end_time
+               from
+                fin_reimbursement
+            left join fin_reimbursement_travel on fin_reimbursement.id = fin_reimbursement_travel.reimbursement_id
+        <where>
+            <if test="ew.billNo != null and ew.billNo != ''">
+                bill_no like concat('%',#{ew.billNo},'%')
+            </if>
+            <if test="ew.applicantName != null and ew.applicantName != ''">
+                and applicant_name like concat('%',#{ew.applicantName},'%')
+            </if>
+            <if test="ew.applicantCode != null and ew.applicantCode != ''">
+                and applicant_code like concat('%',#{ew.applicantCode},'%')
+            </if>
+            <if test="ew.createTimeStart != null and ew.createTimeStart !='' and ew.createTimeEnd != null and ew.createTimeEnd != ''">
+                and create_time &gt;= #{ew.createTimeStart}
+                and create_time &lt;= #{ew.createTimeEnd}
+            </if>
+        </where>
+        order by create_time desc
+    </select>
+
+</mapper>
diff --git a/src/main/resources/mapper/approve/FinReimbursementTravelMapper.xml b/src/main/resources/mapper/approve/FinReimbursementTravelMapper.xml
new file mode 100644
index 0000000..dc42863
--- /dev/null
+++ b/src/main/resources/mapper/approve/FinReimbursementTravelMapper.xml
@@ -0,0 +1,29 @@
+<?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.approve.mapper.FinReimbursementTravelMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.FinReimbursementTravel">
+        <id column="id" property="id" />
+        <result column="reimbursement_id" property="reimbursementId" />
+        <result column="start_time" property="startTime" />
+        <result column="end_time" property="endTime" />
+        <result column="travel_days" property="travelDays" />
+        <result column="departure_city" property="departureCity" />
+        <result column="destination_city" property="destinationCity" />
+        <result column="hotel_standard" property="hotelStandard" />
+        <result column="lodging_days" property="lodgingDays" />
+        <result column="meal_allowance" property="mealAllowance" />
+        <result column="transport_allowance" property="transportAllowance" />
+        <result column="lodging_limit" property="lodgingLimit" />
+        <result column="standard_tag" property="standardTag" />
+        <result column="within_standard" property="withinStandard" />
+        <result column="tenant_id" property="tenantId" />
+        <result column="create_user" property="createUser" />
+        <result column="create_time" property="createTime" />
+        <result column="update_user" property="updateUser" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/mapper/approve/KnowledgeBaseMapper.xml b/src/main/resources/mapper/approve/KnowledgeBaseMapper.xml
index e4819c2..8d41ef1 100644
--- a/src/main/resources/mapper/approve/KnowledgeBaseMapper.xml
+++ b/src/main/resources/mapper/approve/KnowledgeBaseMapper.xml
@@ -16,6 +16,6 @@
                 and type = #{knowledgeBase.type}
             </if>
         </where>
-
+        order by id desc
     </select>
-</mapper>
\ No newline at end of file
+</mapper>
diff --git a/src/main/resources/mapper/basic/CustomerMapper.xml b/src/main/resources/mapper/basic/CustomerMapper.xml
index 99a5a28..ae92da9 100644
--- a/src/main/resources/mapper/basic/CustomerMapper.xml
+++ b/src/main/resources/mapper/basic/CustomerMapper.xml
@@ -52,6 +52,7 @@
                 )
             </if>
         </where>
+        order by c.id desc
     </select>
 
     <select id="list" resultType="com.ruoyi.basic.vo.CustomerVo">
@@ -106,6 +107,7 @@
                 )
             </if>
         </where>
+        order by c.id desc
     </select>
 
     <select id="customewTransactions" resultType="com.ruoyi.sales.vo.CustomerTransactionsVo">
@@ -134,6 +136,7 @@
                 AND c.customer_name LIKE CONCAT('%', #{customerName}, '%')
             </if>
         </where>
+        order by T1.customer_id desc
     </select>
 
     <select id="customewTransactionsDetails"
@@ -159,5 +162,6 @@
             group by  sl.id
         )T2 on T2.id = sl.id
         where sl.customer_id = #{customerId}
+        order by sl.id desc
     </select>
 </mapper>
diff --git a/src/main/resources/mapper/basic/SupplierManageMapper.xml b/src/main/resources/mapper/basic/SupplierManageMapper.xml
index 3e8397e..67e169c 100644
--- a/src/main/resources/mapper/basic/SupplierManageMapper.xml
+++ b/src/main/resources/mapper/basic/SupplierManageMapper.xml
@@ -35,6 +35,7 @@
                 AND T1.is_white = #{supplierManageDto.isWhite}
             </if>
         </where>
+        order by T1.id desc
     </select>
 
     <select id="supplierExportList" resultType="com.ruoyi.basic.excel.SupplierManageExcelDto">
@@ -115,6 +116,7 @@
                 AND sm.supplier_name LIKE CONCAT('%',#{supplierName},'%')
             </if>
         </where>
+        order by T1.supplier_id desc
     </select>
 
     <select id="supplierTransactionsDetails"
@@ -123,42 +125,51 @@
               pl.purchase_contract_number,
               pl.execution_date,
               pl.contract_amount,
-              IFNULL(T2.InboundAmount, 0) AS shippedAmount,
-              pl.contract_amount - IFNULL(T2.InboundAmount, 0) AS unshippedAmount
-       FROM purchase_ledger pl
-       LEFT JOIN (
-           SELECT t.sales_ledger_id,
-                  SUM(t.inbound_amount) AS InboundAmount
-           FROM (
-               SELECT sir.stock_in_num * slp.tax_inclusive_unit_price AS inbound_amount, slp.sales_ledger_id
-               FROM stock_in_record sir
-               INNER JOIN sales_ledger_product slp ON slp.id = sir.record_id
-               WHERE sir.approval_status = 1 AND sir.record_type = 7 AND slp.type = 2
-               UNION ALL
-               SELECT
-                   sir.stock_in_num * slp_agg.tax_inclusive_unit_price AS inbound_amount,
-                   slp_agg.sales_ledger_id
-               FROM stock_in_record sir
-                        INNER JOIN quality_inspect qi
-                                   ON qi.id = sir.record_id
-                        INNER JOIN purchase_ledger pl2
-                                   ON pl2.id = qi.purchase_ledger_id
-                        INNER JOIN (
-                   SELECT
-                       sales_ledger_id,
-                       product_model_id,
-                       MIN(tax_inclusive_unit_price) AS tax_inclusive_unit_price
-                   FROM sales_ledger_product
-                   WHERE type = 2
-                   GROUP BY sales_ledger_id, product_model_id
-               ) slp_agg
-                                   ON slp_agg.sales_ledger_id = pl2.id
-                                       AND slp_agg.product_model_id = sir.product_model_id
-               WHERE sir.approval_status = 1 AND sir.record_type = 10
-           ) t
-           GROUP BY t.sales_ledger_id
-       ) T2 ON T2.sales_ledger_id = pl.id
-       WHERE pl.supplier_id = #{supplierId}
+              IFNULL(T1.paymentAmount, 0) AS paymentAmount,
+              IFNULL(T2.InboundAmount, 0) - IFNULL(T3.returnAmount, 0) AS payableAmount
+       from purchase_ledger pl
+       left join (
+           select
+               pl.id,
+               sum(app.payment_amount) as paymentAmount
+           from account_purchase_payment app
+           left join account_payment_application apa on app.account_payment_application_id = apa.id
+           left join stock_in_record sir on FIND_IN_SET(sir.id, apa.stock_in_record_ids) > 0
+               -- 10 绫诲瀷鎵嶅叧鑱旇川妫�琛�
+           LEFT JOIN quality_inspect qi ON sir.record_type = 10 AND sir.record_id = qi.id
+               -- 鍔ㄦ�佸叧鑱旈噰璐紙鑷姩閫傞厤 7 鍜� 10锛�
+           LEFT JOIN purchase_ledger pl
+                     ON pl.id = IF(sir.record_type = 7, sir.record_id, qi.purchase_ledger_id)
+           WHERE sir.approval_status = 1
+             AND sir.record_type IN ('7','10')
+           group by pl.id
+       )T1 on T1.id = pl.id
+       left join (
+           SELECT
+               pl.id,
+               sum(sir.stock_in_num * slp.tax_inclusive_unit_price) AS InboundAmount
+           FROM stock_in_record sir
+                    -- 10 绫诲瀷鎵嶅叧鑱旇川妫�琛�
+                    LEFT JOIN quality_inspect qi ON sir.record_type = 10 AND sir.record_id = qi.id
+               -- 鍔ㄦ�佸叧鑱旈噰璐紙鑷姩閫傞厤 7 鍜� 10锛�
+                    LEFT JOIN purchase_ledger pl
+                              ON pl.id = IF(sir.record_type = 7, sir.record_id, qi.purchase_ledger_id)
+               -- 浜у搧鍏宠仈涓嶅姩
+                    LEFT JOIN sales_ledger_product slp ON pl.id = slp.sales_ledger_id
+           -- 鏉′欢
+           WHERE sir.approval_status = 1 AND slp.type = 2
+             AND sir.record_type IN ('7','10')
+           group by pl.id
+       )T2 on T2.id = pl.id
+       left join (
+           select pl.id,
+                  sum(pro.total_amount) as returnAmount
+           from purchase_return_orders pro
+                    left join purchase_ledger pl on pro.purchase_ledger_id = pl.id
+           group by pl.id
+       )T3 on T3.id = pl.id
+       where pl.supplier_id = #{supplierId}
+       order by pl.id desc
     </select>
 
 </mapper>
diff --git a/src/main/resources/mapper/collaborativeApproval/EnterpriseNewsMapper.xml b/src/main/resources/mapper/collaborativeApproval/EnterpriseNewsMapper.xml
new file mode 100644
index 0000000..830a858
--- /dev/null
+++ b/src/main/resources/mapper/collaborativeApproval/EnterpriseNewsMapper.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.collaborativeApproval.mapper.EnterpriseNewsMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.collaborativeApproval.pojo.EnterpriseNews">
+        <id column="id" property="id" />
+        <result column="title" property="title" />
+        <result column="summary" property="summary" />
+        <result column="content" property="content" />
+        <result column="category" property="category" />
+        <result column="read_scope" property="readScope" />
+        <result column="is_required" property="isRequired" />
+        <result column="status" property="status" />
+        <result column="required_read_count" property="requiredReadCount" />
+        <result column="read_count" property="readCount" />
+        <result column="create_user" property="createUser" />
+        <result column="create_time" property="createTime" />
+        <result column="update_user" property="updateUser" />
+        <result column="update_time" property="updateTime" />
+        <result column="dept_id" property="deptId" />
+    </resultMap>
+    <select id="listPage" resultType="com.ruoyi.collaborativeApproval.vo.EnterpriseNewsVo">
+        select en.*, u.nick_name as create_user_name from
+        enterprise_news en
+        left join sys_user u on en.create_user = u.user_id
+        <where>
+            <if test="enterpriseNewsDto.title != null and enterpriseNewsDto.title != ''">
+                and en.title like concat('%',#{enterpriseNewsDto.title},'%')
+            </if>
+            <if test="enterpriseNewsDto.category != null and enterpriseNewsDto.category != ''">
+                and en.category = #{enterpriseNewsDto.category}
+            </if>
+            <if test="enterpriseNewsDto.status != null ">
+                and en.status = #{enterpriseNewsDto.status}
+            </if>
+            <if test="enterpriseNewsDto.createUser != null and enterpriseNewsDto.createUser != ''">
+                and en.create_user = #{enterpriseNewsDto.createUser}
+            </if>
+            <if test="enterpriseNewsDto.createTimeStart != null and enterpriseNewsDto.createTimeEnd != null">
+                and en.create_time between #{enterpriseNewsDto.createTimeStart} and #{enterpriseNewsDto.createTimeEnd}
+            </if>
+        </where>
+        order by en.create_time desc
+    </select>
+
+</mapper>
diff --git a/src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeDeptMapper.xml b/src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeDeptMapper.xml
new file mode 100644
index 0000000..0b13f67
--- /dev/null
+++ b/src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeDeptMapper.xml
@@ -0,0 +1,13 @@
+<?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.collaborativeApproval.mapper.EnterpriseNewsScopeDeptMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept">
+        <id column="id" property="id" />
+        <result column="news_id" property="newsId" />
+        <result column="dept_id" property="deptId" />
+        <result column="create_time" property="createTime" />
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeUserMapper.xml b/src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeUserMapper.xml
new file mode 100644
index 0000000..7f4ac4b
--- /dev/null
+++ b/src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeUserMapper.xml
@@ -0,0 +1,13 @@
+<?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.collaborativeApproval.mapper.EnterpriseNewsScopeUserMapper">
+
+    <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+    <resultMap id="BaseResultMap" type="com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser">
+        <id column="id" property="id" />
+        <result column="news_id" property="newsId" />
+        <result column="user_id" property="userId" />
+        <result column="create_time" property="createTime" />
+    </resultMap>
+
+</mapper>
diff --git a/src/main/resources/mapper/collaborativeApproval/NoticeMapper.xml b/src/main/resources/mapper/collaborativeApproval/NoticeMapper.xml
index 08cf865..06acc33 100644
--- a/src/main/resources/mapper/collaborativeApproval/NoticeMapper.xml
+++ b/src/main/resources/mapper/collaborativeApproval/NoticeMapper.xml
@@ -17,5 +17,6 @@
                 and n.status = #{ew.status}
             </if>
         </where>
+        order by n.create_time desc
     </select>
 </mapper>
diff --git a/src/main/resources/mapper/collaborativeApproval/RulesRegulationsManagementMapper.xml b/src/main/resources/mapper/collaborativeApproval/RulesRegulationsManagementMapper.xml
index 03dc024..9ae4b8d 100644
--- a/src/main/resources/mapper/collaborativeApproval/RulesRegulationsManagementMapper.xml
+++ b/src/main/resources/mapper/collaborativeApproval/RulesRegulationsManagementMapper.xml
@@ -27,5 +27,6 @@
                 and rrm.category = #{ew.category}
             </if>
         </where>
+        order by rrm.id desc
     </select>
-</mapper>
\ No newline at end of file
+</mapper>
diff --git a/src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml b/src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml
index d8a5956..53b2f3c 100644
--- a/src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml
+++ b/src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml
@@ -37,5 +37,6 @@
             </if>
         </where>
         GROUP BY sam.id
+        order by sam.id desc
     </select>
 </mapper>
diff --git a/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml b/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
index 99b9bbc..9c1b6d5 100644
--- a/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
+++ b/src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
@@ -26,8 +26,8 @@
         left join device_ledger dl on dm.device_ledger_id = dl.id
         left join sys_user su on dm.create_user = su.user_id
         <where>
-            <if test="deviceMaintenanceDto.deviceName != null and deviceMaintenanceDto.deviceName != ''">
-                and dl.device_name like concat('%', #{deviceMaintenanceDto.deviceName}, '%')
+            <if test="deviceMaintenanceDto.deviceName != null">
+                and dl.device_name like concat('%',#{deviceMaintenanceDto.deviceName},'%')
             </if>
             <if test="deviceMaintenanceDto.deviceModel != null and deviceMaintenanceDto.deviceModel != ''">
                 and dl.device_model like concat('%', #{deviceMaintenanceDto.deviceModel}, '%')
@@ -48,17 +48,7 @@
                 date_add(str_to_date(#{deviceMaintenanceDto.maintenanceActuallyTime}, '%Y-%m-%d'), interval 1 day)
             </if>
         </where>
-        order by
-        <!--    寰呬繚鍏�(0)浼樺厛鎺掑湪涓婇潰锛屽凡瀹岀粨(1)鍦ㄤ笅闈� -->
-        dm.status asc,
-        case
-        <!-- 褰撶姸鎬佹槸 0锛堝緟淇濆吇锛夋椂锛屾寜璁″垝鏃堕棿鍗囧簭锛屽嵆鏃堕棿鏈�杩滅殑鍗曞瓙鍦ㄦ渶涓婇潰 -->
-        when dm.status = 0 then dm.maintenance_plan_time
-        end asc,
-        case
-        <!-- 褰撶姸鎬佹槸 1锛堝凡瀹岀粨锛夋椂锛屾寜瀹為檯淇濆吇鏃堕棿闄嶅簭锛屾渶杩戝垰淇濆吇瀹岀殑鍗曞瓙鍦ㄥ凡瀹岀粨閲屾帓鏈�鍓� -->
-        when dm.status = 1 then dm.maintenance_actually_time
-        end desc
+        order by dm.create_time desc
     </select>
 
     <select id="detailById" resultType="com.ruoyi.device.vo.DeviceMaintenanceVo">
diff --git a/src/main/resources/mapper/device/DeviceRepairMapper.xml b/src/main/resources/mapper/device/DeviceRepairMapper.xml
index 713e346..10a0abf 100644
--- a/src/main/resources/mapper/device/DeviceRepairMapper.xml
+++ b/src/main/resources/mapper/device/DeviceRepairMapper.xml
@@ -29,7 +29,6 @@
         from device_repair dr
         left join device_ledger dl on dr.device_ledger_id = dl.id
         <where>
-            1 = 1
             <if test="deviceRepairDto.deviceName != null">
                 and dl.device_name like concat('%',#{deviceRepairDto.deviceName},'%')
             </if>
@@ -53,6 +52,7 @@
                 and dr.maintenance_time like concat('%',#{deviceRepairDto.maintenanceTimeStr},'%')
             </if>
         </where>
+        order by dr.create_time desc
     </select>
     <select id="detailById" resultType="com.ruoyi.device.vo.DeviceRepairVo">
         select dr.id,
diff --git a/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml b/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml
index 65741f5..7de15bd 100644
--- a/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml
+++ b/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml
@@ -62,6 +62,6 @@
                 AND record_date = DATE_FORMAT(#{req.recordDate},'%Y-%m-%d')
             </if>
         </where>
-        ORDER BY update_time DESC
+        ORDER BY create_time DESC
     </select>
 </mapper>
diff --git a/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerRecordMapper.xml b/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerRecordMapper.xml
index 69446e0..e01ef1e 100644
--- a/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerRecordMapper.xml
+++ b/src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerRecordMapper.xml
@@ -32,7 +32,7 @@
                 AND t1.record_date = DATE_FORMAT(#{req.recordDate},'%Y-%m-%d')
             </if>
         </where>
-        ORDER BY t1.update_time DESC
+        ORDER BY t1.create_time DESC
     </select>
     <select id="list" resultType="com.ruoyi.measuringinstrumentledger.pojo.MeasuringInstrumentLedgerRecord">
         SELECT
@@ -65,4 +65,4 @@
         </where>
         ORDER BY t1.update_time DESC
     </select>
-</mapper>
\ No newline at end of file
+</mapper>
diff --git a/src/main/resources/mapper/measuringinstrumentledger/SparePartsMapper.xml b/src/main/resources/mapper/measuringinstrumentledger/SparePartsMapper.xml
index 611d51e..301ff6f 100644
--- a/src/main/resources/mapper/measuringinstrumentledger/SparePartsMapper.xml
+++ b/src/main/resources/mapper/measuringinstrumentledger/SparePartsMapper.xml
@@ -10,5 +10,6 @@
                 and sp.name like concat('%',#{spareParts.name},'%')
             </if>
         </where>
+        order by sp.create_time desc
     </select>
-</mapper>
\ No newline at end of file
+</mapper>
diff --git a/src/main/resources/mapper/measuringinstrumentledger/SparePartsRequisitionRecordMapper.xml b/src/main/resources/mapper/measuringinstrumentledger/SparePartsRequisitionRecordMapper.xml
index 9851e59..0f18391 100644
--- a/src/main/resources/mapper/measuringinstrumentledger/SparePartsRequisitionRecordMapper.xml
+++ b/src/main/resources/mapper/measuringinstrumentledger/SparePartsRequisitionRecordMapper.xml
@@ -32,5 +32,6 @@
                 and sprr.source_type = #{params.sourceType}
             </if>
         </where>
+        order by sprr.create_time desc
     </select>
 </mapper>
diff --git a/src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml b/src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml
index e069196..e15e4aa 100644
--- a/src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml
+++ b/src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml
@@ -39,6 +39,7 @@
                 and sl.sales_contract_no like concat('%',#{req.salesContractNo},'%')
             </if>
         </where>
+        order by rm.id desc
     </select>
     <select id="getReturnManagementDtoById" resultType="com.ruoyi.procurementrecord.bean.dto.ReturnManagementDto">
      select rm.*,
diff --git a/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml b/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
index 973e44e..519cbe2 100644
--- a/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
+++ b/src/main/resources/mapper/production/ProductionOperationTaskMapper.xml
@@ -71,7 +71,7 @@
                 and pot.work_order_no like concat('%', #{c.workOrderNo}, '%')
             </if>
         </where>
-        order by pot.id desc
+        order by pot.production_order_id desc, poro.drag_sort
     </select>
 
     <select id="selectTaskStatisticsByDate" resultType="com.ruoyi.home.dto.ProductionTaskStatisticsDto">
diff --git a/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml b/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
index a818160..a0a0a2d 100644
--- a/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
+++ b/src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml
@@ -120,6 +120,12 @@
                 <if test="c.supplierId != null">
                     AND pl.supplier_id = #{c.supplierId}
                 </if>
+                <if test="c.ids != null and c.ids.size() > 0">
+                    AND pl.id IN
+                    <foreach collection="c.ids" item="id" open="(" separator="," close=")">
+                        #{id}
+                    </foreach>
+                </if>
                 <if test="c.approvalStatus != null">
                     AND pl.approval_status = #{c.approvalStatus}
                 </if>
diff --git a/src/main/resources/mapper/quality/QualityInspectMapper.xml b/src/main/resources/mapper/quality/QualityInspectMapper.xml
index 72f166c..3e0abda 100644
--- a/src/main/resources/mapper/quality/QualityInspectMapper.xml
+++ b/src/main/resources/mapper/quality/QualityInspectMapper.xml
@@ -58,6 +58,15 @@
         <if test="qualityInspect.entryDateEnd != null and qualityInspect.entryDateEnd != '' ">
             AND qi.check_time &lt;= DATE_FORMAT(#{qualityInspect.entryDateEnd},'%Y-%m-%d')
         </if>
+        <if test="qualityInspect.purchaseContractNo != null and qualityInspect.purchaseContractNo != '' ">
+            AND pl.purchase_contract_number like concat('%',#{qualityInspect.purchaseContractNo},'%')
+        </if>
+        <if test="qualityInspect.workOrderNo != null and qualityInspect.workOrderNo != '' ">
+            AND pot.work_order_no like concat('%',#{qualityInspect.workOrderNo},'%')
+        </if>
+        <if test="qualityInspect.salesContractNo != null and qualityInspect.salesContractNo != '' ">
+            AND po_sales.sales_contract_no like concat('%',#{qualityInspect.salesContractNo},'%')
+        </if>
         ORDER BY qi.check_time DESC
     </select>
 
diff --git a/src/main/resources/mapper/quality/QualityTestStandardMapper.xml b/src/main/resources/mapper/quality/QualityTestStandardMapper.xml
index f326a66..53eef84 100644
--- a/src/main/resources/mapper/quality/QualityTestStandardMapper.xml
+++ b/src/main/resources/mapper/quality/QualityTestStandardMapper.xml
@@ -19,6 +19,7 @@
         <if test="c.inspectType != null ">
             AND inspect_type =#{c.inspectType}
         </if>
+        order by create_time desc
     </select>
     <select id="getQualityTestStandardByProductId" resultType="com.ruoyi.quality.pojo.QualityTestStandard">
         SELECT qts.*
diff --git a/src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml b/src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml
index 49380aa..d995258 100644
--- a/src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml
+++ b/src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml
@@ -26,23 +26,24 @@
         END AS method
         FROM quality_unqualified qu
         LEFT JOIN product_model pm ON qu.model = pm.id
-        where
-        1=1
-        <if test="qualityUnqualified.inspectType != null ">
-            AND inspect_type = #{qualityUnqualified.inspectType}
-        </if>
-        <if test="qualityUnqualified.inspectState != null ">
-            AND inspect_state = #{qualityUnqualified.inspectState}
-        </if>
-        <if test="qualityUnqualified.productName != null and qualityUnqualified.productName != '' ">
-            AND product_name = #{qualityUnqualified.productName}
-        </if>
-        <if test="qualityUnqualified.entryDateStart != null and qualityUnqualified.entryDateStart != '' ">
-            AND check_time &gt;= DATE_FORMAT(#{qualityUnqualified.entryDateStart},'%Y-%m-%d')
-        </if>
-        <if test="qualityUnqualified.entryDateEnd != null and qualityUnqualified.entryDateEnd != '' ">
-            AND  check_time &lt;= DATE_FORMAT(#{qualityUnqualified.entryDateEnd},'%Y-%m-%d')
-        </if>
+        <where>
+            <if test="qualityUnqualified.inspectType != null ">
+                AND inspect_type = #{qualityUnqualified.inspectType}
+            </if>
+            <if test="qualityUnqualified.inspectState != null ">
+                AND inspect_state = #{qualityUnqualified.inspectState}
+            </if>
+            <if test="qualityUnqualified.productName != null and qualityUnqualified.productName != '' ">
+                AND product_name = #{qualityUnqualified.productName}
+            </if>
+            <if test="qualityUnqualified.entryDateStart != null and qualityUnqualified.entryDateStart != '' ">
+                AND check_time &gt;= DATE_FORMAT(#{qualityUnqualified.entryDateStart},'%Y-%m-%d')
+            </if>
+            <if test="qualityUnqualified.entryDateEnd != null and qualityUnqualified.entryDateEnd != '' ">
+                AND  check_time &lt;= DATE_FORMAT(#{qualityUnqualified.entryDateEnd},'%Y-%m-%d')
+            </if>
+        </where>
+        order by qu.create_time desc
     </select>
     <select id="qualityUnqualifiedExport" resultType="com.ruoyi.quality.pojo.QualityUnqualified">
         SELECT
diff --git a/src/main/resources/mapper/sales/SalesLedgerMapper.xml b/src/main/resources/mapper/sales/SalesLedgerMapper.xml
index 1ea2510..6f368b2 100644
--- a/src/main/resources/mapper/sales/SalesLedgerMapper.xml
+++ b/src/main/resources/mapper/sales/SalesLedgerMapper.xml
@@ -134,10 +134,12 @@
         from sales_ledger sl
         left join purchase_ledger pl on sl.id = pl.sales_ledger_id
         left join customer c on sl.customer_id = c.id
-        where 1=1
-        <if test="customerName != null and customerName != '' ">
-            and c.customer_name like concat('%',#{customerName},'%')
-        </if>
+        <where>
+            <if test="customerName != null and customerName != '' ">
+                and c.customer_name like concat('%',#{customerName},'%')
+            </if>
+        </where>
+        order by sl.entry_date desc
     </select>
 
 </mapper>
diff --git a/src/main/resources/mapper/sales/SalesQuotationMapper.xml b/src/main/resources/mapper/sales/SalesQuotationMapper.xml
index 5448933..746e331 100644
--- a/src/main/resources/mapper/sales/SalesQuotationMapper.xml
+++ b/src/main/resources/mapper/sales/SalesQuotationMapper.xml
@@ -4,20 +4,19 @@
         "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <mapper namespace="com.ruoyi.sales.mapper.SalesQuotationMapper">
     <select id="listPage" resultType="com.ruoyi.sales.dto.SalesQuotationDto">
-        SELECT t1.*,
-               t2.approve_user_ids
+        SELECT t1.*
         FROM sales_quotation t1
-        LEFT JOIN approve_process t2 ON t1.quotation_no = t2.approve_reason and t2.approve_type = 6
-        WHERE 1=1
-          and t2.approve_delete = 0
-        <if test="salesQuotationDto.quotationNo != null and salesQuotationDto.quotationNo != '' ">
-            AND t1.quotation_no LIKE CONCAT('%',#{salesQuotationDto.quotationNo},'%')
-        </if>
-        <if test="salesQuotationDto.customer != null and salesQuotationDto.customer != '' ">
-            AND t1.customer = #{salesQuotationDto.customer}
-        </if>
-        <if test="salesQuotationDto.status != null and salesQuotationDto.status != '' ">
-            AND t1.status = #{salesQuotationDto.status}
-        </if>
+        <where>
+            <if test="salesQuotationDto.quotationNo != null and salesQuotationDto.quotationNo != '' ">
+                AND t1.quotation_no LIKE CONCAT('%',#{salesQuotationDto.quotationNo},'%')
+            </if>
+            <if test="salesQuotationDto.customer != null and salesQuotationDto.customer != '' ">
+                AND t1.customer = #{salesQuotationDto.customer}
+            </if>
+            <if test="salesQuotationDto.status != null and salesQuotationDto.status != '' ">
+                AND t1.status = #{salesQuotationDto.status}
+            </if>
+        </where>
+        order by t1.id desc
     </select>
 </mapper>
diff --git a/src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml b/src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml
index 7dbb388..9ae20d6 100644
--- a/src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml
+++ b/src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml
@@ -39,6 +39,7 @@
             and personal_attendance_records.date &gt;= #{params.date}
             and personal_attendance_records.date &lt; DATE_ADD(DATE(#{params.date}), INTERVAL 1 DAY)
         </if>
+        order by personal_attendance_records.id desc
     </select>
 
     <!-- 鏌ヨ鎸囧畾鏃ユ湡娌℃湁鑰冨嫟璁板綍鐨勫湪鑱屽憳宸ワ紙鍦ㄦ寚瀹氭椂闂翠箣鍓嶅叆鑱岀殑锛� -->
diff --git a/src/main/resources/mapper/staff/PersonalShiftMapper.xml b/src/main/resources/mapper/staff/PersonalShiftMapper.xml
index 69bcae3..7d80392 100644
--- a/src/main/resources/mapper/staff/PersonalShiftMapper.xml
+++ b/src/main/resources/mapper/staff/PersonalShiftMapper.xml
@@ -31,9 +31,9 @@
             </if>
         </where>
         GROUP BY u.id, u.staff_name
-        ORDER BY MAX(s.create_time)
+        ORDER BY MAX(s.create_time) desc
     </select>
-   
+
     <select id="performanceShiftYear" resultType="java.util.Map">
          SELECT
          u.staff_name name,
diff --git a/src/main/resources/mapper/staff/StaffLeaveMapper.xml b/src/main/resources/mapper/staff/StaffLeaveMapper.xml
index 4637ff0..fef5c81 100644
--- a/src/main/resources/mapper/staff/StaffLeaveMapper.xml
+++ b/src/main/resources/mapper/staff/StaffLeaveMapper.xml
@@ -18,18 +18,24 @@
         soj.emergency_contact as emergencyContact,
         soj.emergency_contact_phone as emergencyContactPhone,
         sp.post_name as postName,
-        sd.dept_name as deptName
+        sd.dept_name as deptName,
+        se.education as first_study,
+        se.major as profession
         FROM staff_leave
-        LEFT JOIN
-        staff_on_job soj ON soj.id = staff_leave.staff_on_job_id
-        LEFT JOIN
-        sys_post sp ON sp.post_id = soj.sys_post_id
-        LEFT JOIN
-        sys_dept sd ON sd.dept_id = soj.sys_dept_id
-        where 1=1
+        LEFT JOIN staff_on_job soj ON soj.id = staff_leave.staff_on_job_id
+        LEFT JOIN sys_post sp ON sp.post_id = soj.sys_post_id
+        LEFT JOIN sys_dept sd ON sd.dept_id = soj.sys_dept_id
+        LEFT JOIN staff_education se ON se.staff_on_job_id = staff_leave.staff_on_job_id
+        AND se.id = (
+        SELECT MAX(se2.id)
+        FROM staff_education se2
+        WHERE se2.staff_on_job_id = staff_leave.staff_on_job_id
+        )
+        WHERE 1=1
         <if test="c.staffName != null and c.staffName != '' ">
             AND soj.staff_name LIKE CONCAT('%',#{c.staffName},'%')
         </if>
+        order by staff_leave.create_time desc
     </select>
     <select id="staffLeaveList" resultType="com.ruoyi.staff.dto.StaffLeaveDto">
         SELECT
@@ -56,10 +62,12 @@
         sys_post sp ON sp.post_id = soj.sys_post_id
         LEFT JOIN
         sys_dept sd ON sd.dept_id = soj.sys_dept_id
-        where 1=1
-        <if test="c.staffName != null and c.staffName != '' ">
-            AND soj.staff_name LIKE CONCAT('%',#{c.staffName},'%')
-        </if>
+        <where>
+            <if test="c.staffName != null and c.staffName != '' ">
+                AND soj.staff_name LIKE CONCAT('%',#{c.staffName},'%')
+            </if>
+        </where>
+        order by staff_leave.create_time desc
     </select>
 
     <select id="staffLeaveReasonAnalytics" resultType="com.ruoyi.staff.dto.StaffLeaveDto">
diff --git a/src/main/resources/mapper/staff/StaffOnJobMapper.xml b/src/main/resources/mapper/staff/StaffOnJobMapper.xml
index a256ff8..90825e3 100644
--- a/src/main/resources/mapper/staff/StaffOnJobMapper.xml
+++ b/src/main/resources/mapper/staff/StaffOnJobMapper.xml
@@ -3,29 +3,83 @@
 <mapper namespace="com.ruoyi.staff.mapper.StaffOnJobMapper">
     <select id="staffOnJobListPage" resultType="com.ruoyi.staff.dto.StaffOnJobDto">
         SELECT
-        staff_on_job.*,
-        sp.post_name as postName,
-        sd.dept_name as deptName,
-        MIN(t1.contract_start_time) as contract_start_time,  -- 鍙栨渶鏃╁悎鍚屽紑濮嬫椂闂�
-        MAX(t1.contract_end_time) as contract_end_time
+        staff_on_job.id,
+        staff_on_job.staff_state,
+        staff_on_job.staff_no,
+        staff_on_job.staff_name,
+        staff_on_job.sex,
+        staff_on_job.native_place,
+        staff_on_job.sys_post_id,
+        staff_on_job.sys_dept_id,
+        staff_on_job.role_id,
+        staff_on_job.adress,
+        staff_on_job.first_study,
+        staff_on_job.profession,
+        staff_on_job.identity_card,
+        staff_on_job.age,
+        staff_on_job.phone,
+        staff_on_job.contract_term,
+        staff_on_job.contract_expire_time,
+        staff_on_job.trial_end_date,
+        staff_on_job.trial_start_date,
+        staff_on_job.sign_date,
+        staff_on_job.salary_select,
+        staff_on_job.pro_salary,
+        staff_on_job.date_select,
+        staff_on_job.remark,
+        staff_on_job.create_time,
+        staff_on_job.create_user,
+        staff_on_job.update_time,
+        staff_on_job.update_user,
+        staff_on_job.tenant_id,
+        staff_on_job.alias,
+        staff_on_job.birth_date,
+        staff_on_job.nation,
+        staff_on_job.marital_status,
+        staff_on_job.pro_term,
+        staff_on_job.positive_date,
+        staff_on_job.basic_salary,
+        staff_on_job.dept_id,
+        sp.post_name AS post_name,
+        sd.dept_name AS dept_name,
+        MIN(t1.contract_start_time) AS contract_start_time,
+        MAX(t1.contract_end_time) AS contract_end_time,
+        (
+        SELECT GROUP_CONCAT(sec.contact_name SEPARATOR ',')
+        FROM staff_emergency_contact sec
+        WHERE sec.staff_on_job_id = staff_on_job.id
+        ) AS emergency_contact,
+        (
+        SELECT GROUP_CONCAT(sec.contact_phone SEPARATOR ',')
+        FROM staff_emergency_contact sec
+        WHERE sec.staff_on_job_id = staff_on_job.id
+        ) AS emergency_contact_phone
         FROM staff_on_job
         LEFT JOIN sys_post sp ON sp.post_id = staff_on_job.sys_post_id
         LEFT JOIN sys_dept sd ON sd.dept_id = staff_on_job.sys_dept_id
-        LEFT JOIN staff_contract as t1 ON t1.staff_on_job_id = staff_on_job.id
-        WHERE 1=1
-        <if test="staffOnJob.staffState != null">
-            AND staff_state = #{staffOnJob.staffState}
-        </if>
-        <if test="staffOnJob.staffName != null and staffOnJob.staffName != '' ">
-            AND staff_name LIKE CONCAT('%',#{staffOnJob.staffName},'%')
-        </if>
-        <if test="staffOnJob.entryDateStart != null and staffOnJob.entryDateStart != '' ">
-            AND contract_expire_time &gt;= DATE_FORMAT(#{staffOnJob.entryDateStart},'%Y-%m-%d')
-        </if>
-        <if test="staffOnJob.entryDateEnd != null and staffOnJob.entryDateEnd != '' ">
-            AND contract_expire_time &lt;= DATE_FORMAT(#{staffOnJob.entryDateEnd},'%Y-%m-%d')
-        </if>
+        LEFT JOIN staff_contract AS t1 ON t1.staff_on_job_id = staff_on_job.id
+        <where>
+            <if test="staffOnJob.staffState != null">
+                AND staff_on_job.staff_state = #{staffOnJob.staffState}
+            </if>
+            <if test="staffOnJob.staffName != null and staffOnJob.staffName != '' ">
+                AND staff_on_job.staff_name LIKE CONCAT('%',#{staffOnJob.staffName},'%')
+            </if>
+            <if test="staffOnJob.entryDateStart != null and staffOnJob.entryDateStart != '' ">
+                AND staff_on_job.contract_expire_time &gt;= DATE_FORMAT(#{staffOnJob.entryDateStart},'%Y-%m-%d')
+            </if>
+            <if test="staffOnJob.entryDateEnd != null and staffOnJob.entryDateEnd != '' ">
+                AND staff_on_job.contract_expire_time &lt;= DATE_FORMAT(#{staffOnJob.entryDateEnd},'%Y-%m-%d')
+            </if>
+            <if test="staffOnJob.sysDeptId != null">
+                AND staff_on_job.sys_dept_id = #{staffOnJob.sysDeptId}
+            </if>
+        </where>
         GROUP BY staff_on_job.id
+        <if test="staffOnJob.contractStartTime != null">
+            HAVING MIN(t1.contract_start_time) = #{staffOnJob.contractStartTime}
+        </if>
+        order by staff_on_job.create_time desc
     </select>
     <select id="staffOnJobList" resultType="com.ruoyi.staff.dto.StaffOnJobDto">
         SELECT
@@ -37,13 +91,15 @@
         sys_post sp ON sp.post_id = staff_on_job.sys_post_id
         LEFT JOIN
         sys_dept sd ON sd.dept_id = staff_on_job.sys_dept_id
-        where 1=1
-        <if test="staffOnJob.staffState != null">
-            AND staff_state = #{staffOnJob.staffState}
-        </if>
-        <if test="staffOnJob.staffName != null and staffOnJob.staffName != '' ">
-            AND staff_name LIKE CONCAT('%',#{staffOnJob.staffName},'%')
-        </if>
+        <where>
+            <if test="staffOnJob.staffState != null">
+                AND staff_state = #{staffOnJob.staffState}
+            </if>
+            <if test="staffOnJob.staffName != null and staffOnJob.staffName != '' ">
+                AND staff_name LIKE CONCAT('%',#{staffOnJob.staffName},'%')
+            </if>
+        </where>
+        order by staff_on_job.create_time desc
     </select>
     <!-- 缁熻鎸囧畾鏃ユ湡鐨勫湪鑱屽憳宸ユ暟 -->
     <select id="countOnJobStaffByDate" resultType="java.lang.Integer">
diff --git a/src/main/resources/mapper/stock/StockInventoryMapper.xml b/src/main/resources/mapper/stock/StockInventoryMapper.xml
index 06c1863..0469a93 100644
--- a/src/main/resources/mapper/stock/StockInventoryMapper.xml
+++ b/src/main/resources/mapper/stock/StockInventoryMapper.xml
@@ -222,6 +222,7 @@
         unit,
         product_name,
         product_id
+        order by combined.create_time desc
     </select>
 
     <select id="listStockInventoryExportData" resultType="com.ruoyi.stock.execl.StockInventoryExportData">
@@ -367,6 +368,7 @@
                 and sir.create_time &lt;= #{ew.endMonth}
             </if>
         </where>
+        order by sir.id desc
     </select>
 
     <select id="stockInAndOutRecord" resultType="com.ruoyi.stock.dto.StockInventoryDto">
diff --git a/src/main/resources/mapper/system/SysDeptMapper.xml b/src/main/resources/mapper/system/SysDeptMapper.xml
index ee5256f..c71cd19 100644
--- a/src/main/resources/mapper/system/SysDeptMapper.xml
+++ b/src/main/resources/mapper/system/SysDeptMapper.xml
@@ -22,10 +22,11 @@
 		<result property="updateTime" column="update_time" />
 		<result property="staffCount" column="staff_count" />
 	</resultMap>
-	
+
 	<sql id="selectDeptVo">
-        select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time 
+        select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time
         from sys_dept d
+        order by d.create_time desc
     </sql>
 
 	<select id="selectDeptList" parameterType="com.ruoyi.project.system.domain.SysDept" resultMap="SysDeptResult">
@@ -49,9 +50,9 @@
 		<!-- 鏁版嵁鑼冨洿杩囨护 -->
 		${params.dataScope}
 		group by d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time
-		order by d.parent_id, d.order_num
+		order by d.parent_id asc, d.order_num asc, d.create_time desc
 	</select>
-    
+
     <select id="selectDeptListByRoleId" resultType="java.lang.Long">
 		select d.dept_id
 		from sys_dept d
@@ -60,39 +61,40 @@
             <if test="deptCheckStrictly">
               and d.dept_id not in (select d.parent_id from sys_dept d inner join sys_role_dept rd on d.dept_id = rd.dept_id and rd.role_id = #{roleId})
             </if>
-		order by d.parent_id, d.order_num
+		order by d.parent_id asc, d.order_num asc, d.create_time desc
 	</select>
-    
+
     <select id="selectDeptById" parameterType="Long" resultMap="SysDeptResult">
 		select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status,
 			(select dept_name from sys_dept where dept_id = d.parent_id) parent_name,
 			d.dept_nick
 		from sys_dept d
 		where d.dept_id = #{deptId}
+		order by d.create_time desc
 	</select>
-    
+
     <select id="checkDeptExistUser" parameterType="Long" resultType="int">
 		select count(1) from sys_user_dept where dept_id = #{deptId}
 	</select>
-	
+
 	<select id="hasChildByDeptId" parameterType="Long" resultType="int">
 		select count(1) from sys_dept
 		where del_flag = '0' and parent_id = #{deptId} limit 1
 	</select>
-	
+
 	<select id="selectChildrenDeptById" parameterType="Long" resultMap="SysDeptResult">
 		select * from sys_dept where find_in_set(#{deptId}, ancestors)
 	</select>
-	
+
 	<select id="selectNormalChildrenDeptById" parameterType="Long" resultType="int">
 		select count(*) from sys_dept where status = 0 and del_flag = '0' and find_in_set(#{deptId}, ancestors)
 	</select>
-	
+
 	<select id="checkDeptNameUnique" resultMap="SysDeptResult">
 	    <include refid="selectDeptVo"/>
 		where dept_name=#{deptName} and parent_id = #{parentId} and del_flag = '0' limit 1
 	</select>
-    
+
     <insert id="insertDept" parameterType="com.ruoyi.project.system.domain.SysDept">
  		insert into sys_dept(
  			<if test="deptId != null and deptId != 0">dept_id,</if>
@@ -122,7 +124,7 @@
  			sysdate()
  		)
 	</insert>
-	
+
 	<update id="updateDept" parameterType="com.ruoyi.project.system.domain.SysDept">
  		update sys_dept
  		<set>
@@ -140,7 +142,7 @@
  		</set>
  		where dept_id = #{deptId}
 	</update>
-	
+
 	<update id="updateDeptChildren" parameterType="java.util.List">
 	    update sys_dept set ancestors =
 	    <foreach collection="depts" item="item" index="index"
@@ -153,14 +155,14 @@
 	        #{item.deptId}
 	    </foreach>
 	</update>
-	 
+
 	<update id="updateDeptStatusNormal" parameterType="Long">
- 	    update sys_dept set status = '0' where dept_id in 
+ 	    update sys_dept set status = '0' where dept_id in
  	    <foreach collection="array" item="deptId" open="(" separator="," close=")">
         	#{deptId}
         </foreach>
 	</update>
-	
+
 	<delete id="deleteDeptById" parameterType="Long">
 		update sys_dept set del_flag = '2' where dept_id = #{deptId}
 	</delete>
@@ -184,4 +186,4 @@
 		WHERE parent_id = 100;
 	</select>
 
-</mapper> 
\ No newline at end of file
+</mapper>
diff --git a/src/main/resources/mapper/system/SysDictTypeMapper.xml b/src/main/resources/mapper/system/SysDictTypeMapper.xml
index 1cf1349..dd2342e 100644
--- a/src/main/resources/mapper/system/SysDictTypeMapper.xml
+++ b/src/main/resources/mapper/system/SysDictTypeMapper.xml
@@ -14,9 +14,9 @@
 		<result property="updateBy"   column="update_by"   />
 		<result property="updateTime" column="update_time" />
 	</resultMap>
-	
+
 	<sql id="selectDictTypeVo">
-        select dict_id, dict_name, dict_type, status, create_by, create_time, remark 
+        select dict_id, dict_name, dict_type, status, create_by, create_time, remark
 		from sys_dict_type
     </sql>
 
@@ -39,36 +39,37 @@
 				and date_format(create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
 			</if>
 	    </where>
+        order by create_time desc
 	</select>
-	
+
 	<select id="selectDictTypeAll" resultMap="SysDictTypeResult">
 		<include refid="selectDictTypeVo"/>
 	</select>
-	
+
 	<select id="selectDictTypeById" parameterType="Long" resultMap="SysDictTypeResult">
 		<include refid="selectDictTypeVo"/>
 		where dict_id = #{dictId}
 	</select>
-	
+
 	<select id="selectDictTypeByType" parameterType="String" resultMap="SysDictTypeResult">
 		<include refid="selectDictTypeVo"/>
 		where dict_type = #{dictType}
 	</select>
-	
+
 	<select id="checkDictTypeUnique" parameterType="String" resultMap="SysDictTypeResult">
 		<include refid="selectDictTypeVo"/>
 		where dict_type = #{dictType} limit 1
 	</select>
-	
+
 	<delete id="deleteDictTypeById" parameterType="Long">
  		delete from sys_dict_type where dict_id = #{dictId}
  	</delete>
- 	
+
  	<delete id="deleteDictTypeByIds" parameterType="Long">
  		delete from sys_dict_type where dict_id in
  		<foreach collection="array" item="dictId" open="(" separator="," close=")">
  			#{dictId}
-        </foreach> 
+        </foreach>
  	</delete>
 
  	<update id="updateDictType" parameterType="com.ruoyi.project.system.domain.SysDictType">
@@ -83,7 +84,7 @@
  		</set>
  		where dict_id = #{dictId}
 	</update>
- 	
+
  	<insert id="insertDictType" parameterType="com.ruoyi.project.system.domain.SysDictType">
  		insert into sys_dict_type(
  			<if test="dictName != null and dictName != ''">dict_name,</if>
@@ -101,5 +102,5 @@
  			sysdate()
  		)
 	</insert>
-	
-</mapper> 
\ No newline at end of file
+
+</mapper>
diff --git a/src/main/resources/mapper/system/SysNoticeMapper.xml b/src/main/resources/mapper/system/SysNoticeMapper.xml
index 3dd5735..14372d8 100644
--- a/src/main/resources/mapper/system/SysNoticeMapper.xml
+++ b/src/main/resources/mapper/system/SysNoticeMapper.xml
@@ -10,11 +10,16 @@
         <result property="noticeType"     column="notice_type"     />
         <result property="noticeContent"  column="notice_content"  />
         <result property="status"         column="status"          />
+        <result property="senderId"       column="sender_id"       />
+        <result property="consigneeId"    column="consignee_id"    />
+        <result property="jumpPath"       column="jump_path"       />
+        <result property="appJumpPath"    column="app_jump_path"   />
         <result property="createBy"       column="create_by"       />
         <result property="createTime"     column="create_time"     />
         <result property="updateBy"       column="update_by"       />
         <result property="updateTime"     column="update_time"     />
         <result property="remark"         column="remark"          />
+        <result property="tenantId"       column="tenant_id"       />
     </resultMap>
 
     <sql id="selectNoticeVo">
diff --git a/src/main/resources/mapper/system/SysPostMapper.xml b/src/main/resources/mapper/system/SysPostMapper.xml
index 177f74d..d62d78b 100644
--- a/src/main/resources/mapper/system/SysPostMapper.xml
+++ b/src/main/resources/mapper/system/SysPostMapper.xml
@@ -16,12 +16,12 @@
 		<result property="updateTime"    column="update_time"   />
 		<result property="remark"        column="remark"        />
 	</resultMap>
-	
+
 	<sql id="selectPostVo">
-        select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark 
+        select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark
 		from sys_post
     </sql>
-	
+
 	<select id="selectPostList" parameterType="com.ruoyi.project.system.domain.SysPost" resultMap="SysPostResult">
 	    <include refid="selectPostVo"/>
 		<where>
@@ -35,17 +35,18 @@
 				AND post_name like concat('%', #{postName}, '%')
 			</if>
 		</where>
+        order by create_time desc
 	</select>
-	
+
 	<select id="selectPostAll" resultMap="SysPostResult">
 		<include refid="selectPostVo"/>
 	</select>
-	
+
 	<select id="selectPostById" parameterType="Long" resultMap="SysPostResult">
 		<include refid="selectPostVo"/>
 		where post_id = #{postId}
 	</select>
-	
+
 	<select id="selectPostListByUserId" parameterType="Long" resultType="Long">
 		select p.post_id
         from sys_post p
@@ -53,7 +54,7 @@
 	        left join sys_user u on u.user_id = up.user_id
 	    where u.user_id = #{userId}
 	</select>
-	
+
 	<select id="selectPostsByUserName" parameterType="String" resultMap="SysPostResult">
 		select p.post_id, p.post_name, p.post_code
 		from sys_post p
@@ -61,17 +62,17 @@
 			 left join sys_user u on u.user_id = up.user_id
 		where u.user_name = #{userName}
 	</select>
-	
+
 	<select id="checkPostNameUnique" parameterType="String" resultMap="SysPostResult">
 		<include refid="selectPostVo"/>
 		 where post_name=#{postName} limit 1
 	</select>
-	
+
 	<select id="checkPostCodeUnique" parameterType="String" resultMap="SysPostResult">
 		<include refid="selectPostVo"/>
 		 where post_code=#{postCode} limit 1
 	</select>
-	
+
 	<update id="updatePost" parameterType="com.ruoyi.project.system.domain.SysPost">
  		update sys_post
  		<set>
@@ -85,7 +86,7 @@
  		</set>
  		where post_id = #{postId}
 	</update>
- 	
+
  	<insert id="insertPost" parameterType="com.ruoyi.project.system.domain.SysPost" useGeneratedKeys="true" keyProperty="postId">
  		insert into sys_post(
  			<if test="postId != null and postId != 0">post_id,</if>
@@ -107,16 +108,16 @@
  			sysdate()
  		)
 	</insert>
-	
+
 	<delete id="deletePostById" parameterType="Long">
 		delete from sys_post where post_id = #{postId}
 	</delete>
-	
+
 	<delete id="deletePostByIds" parameterType="Long">
  		delete from sys_post where post_id in
  		<foreach collection="array" item="postId" open="(" separator="," close=")">
  			#{postId}
-        </foreach> 
+        </foreach>
  	</delete>
 
-</mapper> 
\ No newline at end of file
+</mapper>
diff --git a/src/main/resources/mapper/system/SysRoleMapper.xml b/src/main/resources/mapper/system/SysRoleMapper.xml
index 9e16cf7..45617f3 100644
--- a/src/main/resources/mapper/system/SysRoleMapper.xml
+++ b/src/main/resources/mapper/system/SysRoleMapper.xml
@@ -20,10 +20,10 @@
 		<result property="updateTime"         column="update_time"           />
 		<result property="remark"             column="remark"                />
 	</resultMap>
-	
+
 	<sql id="selectRoleVo">
 	    select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly,
-            r.status, r.del_flag, r.create_time, r.remark 
+            r.status, r.del_flag, r.create_time, r.remark
         from sys_role r
 	        left join sys_user_role ur on ur.role_id = r.role_id
 	        left join sys_user u on u.user_id = ur.user_id
@@ -54,17 +54,17 @@
 		${params.dataScope}
 		order by r.role_sort
 	</select>
-    
+
 	<select id="selectRolePermissionByUserId" parameterType="Long" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		WHERE r.del_flag = '0' and ur.user_id = #{userId}
 	</select>
-	
+
 	<select id="selectRoleAll" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		WHERE r.del_flag = '0'
 	</select>
-	
+
 	<select id="selectRoleListByUserId" parameterType="Long" resultType="Long">
 		select r.role_id
         from sys_role r
@@ -72,27 +72,27 @@
 	        left join sys_user u on u.user_id = ur.user_id
 	    where u.user_id = #{userId}
 	</select>
-	
+
 	<select id="selectRoleById" parameterType="Long" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		where r.role_id = #{roleId}
 	</select>
-	
+
 	<select id="selectRolesByUserName" parameterType="String" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		WHERE r.del_flag = '0' and u.user_name = #{userName}
 	</select>
-	
+
 	<select id="checkRoleNameUnique" parameterType="String" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		 where r.role_name=#{roleName} and r.del_flag = '0' limit 1
 	</select>
-	
+
 	<select id="checkRoleKeyUnique" parameterType="String" resultMap="SysRoleResult">
 		<include refid="selectRoleVo"/>
 		 where r.role_key=#{roleKey} and r.del_flag = '0' limit 1
 	</select>
-	
+
  	<insert id="insertRole" parameterType="com.ruoyi.project.system.domain.SysRole" useGeneratedKeys="true" keyProperty="roleId">
  		insert into sys_role(
  			<if test="roleId != null and roleId != 0">role_id,</if>
@@ -120,7 +120,7 @@
  			sysdate()
  		)
 	</insert>
-	
+
 	<update id="updateRole" parameterType="com.ruoyi.project.system.domain.SysRole">
  		update sys_role
  		<set>
@@ -137,16 +137,16 @@
  		</set>
  		where role_id = #{roleId}
 	</update>
-	
+
 	<delete id="deleteRoleById" parameterType="Long">
  		update sys_role set del_flag = '2' where role_id = #{roleId}
  	</delete>
- 	
+
  	<delete id="deleteRoleByIds" parameterType="Long">
  	    update sys_role set del_flag = '2' where role_id in
  		<foreach collection="array" item="roleId" open="(" separator="," close=")">
  			#{roleId}
-        </foreach> 
+        </foreach>
  	</delete>
- 	
-</mapper> 
\ No newline at end of file
+
+</mapper>
diff --git a/src/main/resources/mapper/system/SysUserMapper.xml b/src/main/resources/mapper/system/SysUserMapper.xml
index b8f5aec..6719108 100644
--- a/src/main/resources/mapper/system/SysUserMapper.xml
+++ b/src/main/resources/mapper/system/SysUserMapper.xml
@@ -78,48 +78,48 @@
     </sql>
 
     <select id="selectUserList" parameterType="com.ruoyi.project.system.domain.SysUser" resultMap="SysUserResult">
-        select u.user_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag,
-        u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,T2.dept_names from sys_user u
-        left join
-        ( SELECT T1.user_id,GROUP_CONCAT(T2.dept_name SEPARATOR ', ') AS dept_names
-        FROM
-        sys_user_dept T1
-        LEFT JOIN sys_dept T2 ON T1.dept_id = T2.dept_id
-        <where>
-            <if test="tenantId != null and tenantId != 0">
-                T1.dept_id = #{tenantId}
-            </if>
-        </where>
-        GROUP BY T1.user_id
-        ) T2 on T2.user_id = u.user_id
-        where u.del_flag = '0'
-        <if test="userId != null and userId != 0">
-            AND u.user_id = #{userId}
-        </if>
-        <if test="userName != null and userName != ''">
-            AND u.user_name like concat('%', #{userName}, '%')
-        </if>
-        <if test="status != null and status != ''">
-            AND u.status = #{status}
-        </if>
-        <if test="phonenumber != null and phonenumber != ''">
-            AND u.phonenumber like concat('%', #{phonenumber}, '%')
-        </if>
-        <if test="params.beginTime != null and params.beginTime != ''"><!-- 寮�濮嬫椂闂存绱� -->
-            AND date_format(u.create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
-        </if>
-        <if test="params.endTime != null and params.endTime != ''"><!-- 缁撴潫鏃堕棿妫�绱� -->
-            AND date_format(u.create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
-        </if>
-        <if test="deptId != null">
-            AND u.user_id IN
-            (
-            SELECT user_id FROM sys_user_dept WHERE dept_id = #{deptId}
-            )
-        </if>
-        <!-- 鏁版嵁鑼冨洿杩囨护 -->
-        ${params.dataScope}
-    </select>
+		select u.user_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,T2.dept_names from sys_user u
+		left join
+		(	SELECT T1.user_id,GROUP_CONCAT(T2.dept_name SEPARATOR ', ') AS dept_names
+			FROM
+				sys_user_dept T1
+			LEFT JOIN sys_dept T2 ON T1.dept_id = T2.dept_id
+		<where>
+			<if test="tenantId != null and tenantId != 0">
+				T1.dept_id = #{tenantId}
+			</if>
+		</where>
+			GROUP BY T1.user_id
+		) T2 on T2.user_id = u.user_id
+		where u.del_flag = '0'
+		<if test="userId != null and userId != 0">
+			AND u.user_id = #{userId}
+		</if>
+		<if test="userName != null and userName != ''">
+			AND u.user_name like concat('%', #{userName}, '%')
+		</if>
+		<if test="status != null and status != ''">
+			AND u.status = #{status}
+		</if>
+		<if test="phonenumber != null and phonenumber != ''">
+			AND u.phonenumber like concat('%', #{phonenumber}, '%')
+		</if>
+		<if test="params.beginTime != null and params.beginTime != ''"><!-- 寮�濮嬫椂闂存绱� -->
+			AND date_format(u.create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
+		</if>
+		<if test="params.endTime != null and params.endTime != ''"><!-- 缁撴潫鏃堕棿妫�绱� -->
+			AND date_format(u.create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
+		</if>
+		<if test="deptId != null">
+			AND u.user_id IN
+			(
+			SELECT user_id FROM sys_user_dept WHERE dept_id = #{deptId}
+			)
+		</if>
+		<!-- 鏁版嵁鑼冨洿杩囨护 -->
+		${params.dataScope}
+		ORDER BY u.create_time DESC
+	</select>
 
     <select id="selectAllocatedList" parameterType="com.ruoyi.project.system.domain.SysUser" resultMap="SysUserResult">
         select distinct u.user_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
@@ -137,35 +137,36 @@
         ${params.dataScope}
     </select>
 
-    <select id="selectUnallocatedList" parameterType="com.ruoyi.project.system.domain.SysUser"
-            resultMap="SysUserResult">
-        select distinct u.user_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
-        from sys_user u
-        left join sys_user_role ur on u.user_id = ur.user_id
-        left join sys_role r on r.role_id = ur.role_id
-        where u.del_flag = '0' and (r.role_id != #{roleId} or r.role_id IS NULL)
-        and u.user_id not in (select u.user_id from sys_user u inner join sys_user_role ur on u.user_id = ur.user_id and
-        ur.role_id = #{roleId})
-        <if test="userName != null and userName != ''">
-            AND u.user_name like concat('%', #{userName}, '%')
-        </if>
-        <if test="phonenumber != null and phonenumber != ''">
-            AND u.phonenumber like concat('%', #{phonenumber}, '%')
-        </if>
-        <!-- 鏁版嵁鑼冨洿杩囨护 -->
-        ${params.dataScope}
-    </select>
+	<select id="selectUnallocatedList" parameterType="com.ruoyi.project.system.domain.SysUser" resultMap="SysUserResult">
+	    select distinct u.user_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
+	    from sys_user u
+			 left join sys_user_role ur on u.user_id = ur.user_id
+			 left join sys_role r on r.role_id = ur.role_id
+	    where u.del_flag = '0' and (r.role_id != #{roleId} or r.role_id IS NULL)
+	    and u.user_id not in (select u.user_id from sys_user u inner join sys_user_role ur on u.user_id = ur.user_id and ur.role_id = #{roleId})
+	    <if test="userName != null and userName != ''">
+			AND u.user_name like concat('%', #{userName}, '%')
+		</if>
+		<if test="phonenumber != null and phonenumber != ''">
+			AND u.phonenumber like concat('%', #{phonenumber}, '%')
+		</if>
+		<!-- 鏁版嵁鑼冨洿杩囨护 -->
+		${params.dataScope}
+		ORDER BY u.create_time DESC
+	</select>
 
-    <select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult">
-        <include refid="selectUserVo"/>
-        where u.user_name = #{userName} and u.del_flag = '0'
-    </select>
+	<select id="selectUserByUserName" parameterType="String" resultMap="SysUserResult">
+	    <include refid="selectUserVo"/>
+		where u.user_name = #{userName} and u.del_flag = '0'
+		ORDER BY u.create_time DESC
+	</select>
 
-    <select id="selectUserById" parameterType="Long" resultMap="SysUserResult">
-        <include refid="selectUserVo"/>
-        where u.user_id = #{userId}
-        and u.del_flag = '0'
-    </select>
+	<select id="selectUserById" parameterType="Long" resultMap="SysUserResult">
+		<include refid="selectUserVo"/>
+		where u.user_id = #{userId}
+		and u.del_flag = '0'
+		ORDER BY u.create_time DESC
+	</select>
 
     <select id="checkUserNameUnique" parameterType="String" resultMap="SysUserResult">
         select user_id, user_name
@@ -198,27 +199,112 @@
             </if>
             and u.del_flag = '0'
         </where>
+        ORDER BY u.create_time DESC
     </select>
-    <select id="selectRegistrantIds" resultType="com.ruoyi.project.system.domain.SysUser">
-        SELECT user_id, nick_name FROM sys_user
-        <where>
-            <if test="list != null and list.size() > 0">
-                user_id IN
-                <foreach item="id" collection="list" open="(" separator="," close=")">
-                    #{id}
-                </foreach>
-            </if>
-            <if test="list == null or list.size() == 0">
-                1=0  <!-- 绌哄垪琛ㄦ椂杩斿洖绌虹粨鏋� -->
-            </if>
-        </where>
-    </select>
-    <select id="selectUsersByIds" resultType="com.ruoyi.project.system.domain.SysUser">
-        SELECT user_id, nick_name
-        FROM sys_user
-        WHERE user_id IN
-        <foreach collection="userIds" item="id" open="(" separator="," close=")">
-            #{id}
+	<select id="selectRegistrantIds" resultType="com.ruoyi.project.system.domain.SysUser">
+		SELECT user_id, nick_name FROM sys_user
+		<where>
+			<if test="list != null and list.size() > 0">
+				user_id IN
+				<foreach item="id" collection="list" open="(" separator="," close=")">
+					#{id}
+				</foreach>
+			</if>
+			<if test="list == null or list.size() == 0">
+				1=0  <!-- 绌哄垪琛ㄦ椂杩斿洖绌虹粨鏋� -->
+			</if>
+		</where>
+		ORDER BY sys_user.create_time DESC
+	</select>
+	<select id="selectUsersByIds" resultType="com.ruoyi.project.system.domain.SysUser">
+		SELECT user_id, nick_name
+		FROM sys_user
+		WHERE user_id IN
+		<foreach collection="userIds" item="id" open="(" separator="," close=")">
+			#{id}
+		</foreach>
+		and del_flag = '0'
+		ORDER BY sys_user.create_time DESC
+	</select>
+	<select id="selectUserByNickName" resultType="com.ruoyi.project.system.domain.SysUser"
+			parameterType="java.lang.String">
+		<include refid="selectUserVo"/>
+		where u.nick_name = #{nickName} and u.del_flag = '0'
+		limit 1
+	</select>
+
+	<insert id="insertUser" parameterType="com.ruoyi.project.system.domain.SysUser" useGeneratedKeys="true" keyProperty="userId">
+ 		insert into sys_user(
+ 			<if test="userId != null and userId != 0">user_id,</if>
+ 			<if test="userName != null and userName != ''">user_name,</if>
+ 			<if test="nickName != null and nickName != ''">nick_name,</if>
+ 			<if test="email != null and email != ''">email,</if>
+ 			<if test="avatar != null and avatar != ''">avatar,</if>
+ 			<if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
+ 			<if test="sex != null and sex != ''">sex,</if>
+ 			<if test="password != null and password != ''">password,</if>
+ 			<if test="status != null and status != ''">status,</if>
+ 			<if test="createBy != null and createBy != ''">create_by,</if>
+ 			<if test="remark != null and remark != ''">remark,</if>
+		    <if test="tenantId != null ">tenant_id,</if>
+ 			create_time
+ 		)values(
+ 			<if test="userId != null and userId != ''">#{userId},</if>
+ 			<if test="userName != null and userName != ''">#{userName},</if>
+ 			<if test="nickName != null and nickName != ''">#{nickName},</if>
+ 			<if test="email != null and email != ''">#{email},</if>
+ 			<if test="avatar != null and avatar != ''">#{avatar},</if>
+ 			<if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
+ 			<if test="sex != null and sex != ''">#{sex},</if>
+ 			<if test="password != null and password != ''">#{password},</if>
+ 			<if test="status != null and status != ''">#{status},</if>
+ 			<if test="createBy != null and createBy != ''">#{createBy},</if>
+ 			<if test="remark != null and remark != ''">#{remark},</if>
+		    <if test="tenantId != null ">#{tenantId},</if>
+ 			sysdate()
+ 		)
+	</insert>
+
+	<update id="updateUser" parameterType="com.ruoyi.project.system.domain.SysUser">
+ 		update sys_user
+ 		<set>
+ 			<if test="nickName != null and nickName != ''">nick_name = #{nickName},</if>
+ 			<if test="email != null ">email = #{email},</if>
+ 			<if test="phonenumber != null ">phonenumber = #{phonenumber},</if>
+ 			<if test="sex != null and sex != ''">sex = #{sex},</if>
+ 			<if test="avatar != null and avatar != ''">avatar = #{avatar},</if>
+ 			<if test="password != null and password != ''">password = #{password},</if>
+ 			<if test="status != null and status != ''">status = #{status},</if>
+ 			<if test="loginIp != null and loginIp != ''">login_ip = #{loginIp},</if>
+ 			<if test="loginDate != null">login_date = #{loginDate},</if>
+ 			<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+ 			<if test="remark != null">remark = #{remark},</if>
+			<if test="tenantId != null">tenant_id = #{tenantId},</if>
+ 			update_time = sysdate()
+ 		</set>
+ 		where user_id = #{userId}
+	</update>
+
+	<update id="updateUserStatus" parameterType="com.ruoyi.project.system.domain.SysUser">
+ 		update sys_user set status = #{status} where user_id = #{userId}
+	</update>
+
+	<update id="updateUserAvatar" parameterType="com.ruoyi.project.system.domain.SysUser">
+ 		update sys_user set avatar = #{avatar} where user_name = #{userName}
+	</update>
+
+	<update id="resetUserPwd" parameterType="com.ruoyi.project.system.domain.SysUser">
+ 		update sys_user set password = #{password} where user_name = #{userName}
+	</update>
+
+	<delete id="deleteUserById" parameterType="Long">
+ 		update sys_user set del_flag = '2' where user_id = #{userId}
+ 	</delete>
+
+ 	<delete id="deleteUserByIds" parameterType="Long">
+ 		update sys_user set del_flag = '2' where user_id in
+ 		<foreach collection="array" item="userId" open="(" separator="," close=")">
+ 			#{userId}
         </foreach>
         and del_flag = '0'
     </select>
@@ -229,124 +315,41 @@
         limit 1
     </select>
 
-    <insert id="insertUser" parameterType="com.ruoyi.project.system.domain.SysUser" useGeneratedKeys="true"
-            keyProperty="userId">
-        insert into sys_user(
-        <if test="userId != null and userId != 0">user_id,</if>
-        <if test="userName != null and userName != ''">user_name,</if>
-        <if test="nickName != null and nickName != ''">nick_name,</if>
-        <if test="email != null and email != ''">email,</if>
-        <if test="avatar != null and avatar != ''">avatar,</if>
-        <if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
-        <if test="sex != null and sex != ''">sex,</if>
-        <if test="password != null and password != ''">password,</if>
-        <if test="status != null and status != ''">status,</if>
-        <if test="createBy != null and createBy != ''">create_by,</if>
-        <if test="remark != null and remark != ''">remark,</if>
-        <if test="tenantId != null ">tenant_id,</if>
-        create_time
-        )values(
-        <if test="userId != null and userId != ''">#{userId},</if>
-        <if test="userName != null and userName != ''">#{userName},</if>
-        <if test="nickName != null and nickName != ''">#{nickName},</if>
-        <if test="email != null and email != ''">#{email},</if>
-        <if test="avatar != null and avatar != ''">#{avatar},</if>
-        <if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
-        <if test="sex != null and sex != ''">#{sex},</if>
-        <if test="password != null and password != ''">#{password},</if>
-        <if test="status != null and status != ''">#{status},</if>
-        <if test="createBy != null and createBy != ''">#{createBy},</if>
-        <if test="remark != null and remark != ''">#{remark},</if>
-        <if test="tenantId != null ">#{tenantId},</if>
-        sysdate()
-        )
-    </insert>
-
-    <update id="updateUser" parameterType="com.ruoyi.project.system.domain.SysUser">
-        update sys_user
-        <set>
-            <if test="nickName != null and nickName != ''">nick_name = #{nickName},</if>
-            <if test="email != null ">email = #{email},</if>
-            <if test="phonenumber != null ">phonenumber = #{phonenumber},</if>
-            <if test="sex != null and sex != ''">sex = #{sex},</if>
-            <if test="avatar != null and avatar != ''">avatar = #{avatar},</if>
-            <if test="password != null and password != ''">password = #{password},</if>
-            <if test="status != null and status != ''">status = #{status},</if>
-            <if test="loginIp != null and loginIp != ''">login_ip = #{loginIp},</if>
-            <if test="loginDate != null">login_date = #{loginDate},</if>
-            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
-            <if test="remark != null">remark = #{remark},</if>
-            <if test="tenantId != null">tenant_id = #{tenantId},</if>
-            update_time = sysdate()
-        </set>
-        where user_id = #{userId}
-    </update>
-
-    <update id="updateUserStatus" parameterType="com.ruoyi.project.system.domain.SysUser">
-        update sys_user
-        set status = #{status}
-        where user_id = #{userId}
-    </update>
-
-    <update id="updateUserAvatar" parameterType="com.ruoyi.project.system.domain.SysUser">
-        update sys_user
-        set avatar = #{avatar}
-        where user_name = #{userName}
-    </update>
-
-    <update id="resetUserPwd" parameterType="com.ruoyi.project.system.domain.SysUser">
-        update sys_user
-        set password = #{password}
-        where user_name = #{userName}
-    </update>
-
-    <delete id="deleteUserById" parameterType="Long">
-        update sys_user
-        set del_flag = '2'
-        where user_id = #{userId}
-    </delete>
-
-    <delete id="deleteUserByIds" parameterType="Long">
-        update sys_user set del_flag = '2' where user_id in
-        <foreach collection="array" item="userId" open="(" separator="," close=")">
-            #{userId}
-        </foreach>
-    </delete>
-
-    <select id="getUserByRole" resultType="java.lang.Long">
-        select distinct su.user_id
-        from sys_user su
-                 left join sys_user_role sur on su.user_id = sur.user_id
-                 left join sys_role sr on sur.role_id = sr.role_id
-        where role_name like concat('%', #{role}, '%')
-          and sr.del_flag = '0'
-          and sr.status = '0'
-          and su.status = '0'
-          and su.del_flag = '0'
-    </select>
-    <select id="getUserByPerms" resultType="java.lang.Long">
-        select distinct t5.user_id
-        from sys_role_menu t1
-        left join sys_menu t2 on t1.menu_id = t2.menu_id
-        left join sys_role t3 on t1.role_id = t3.role_id
-        inner join sys_user_role t4 on t4.role_id = t3.role_id
-        inner join sys_user t5 on t5.user_id = t4.user_id
-        where t3.del_flag = '0'
-        and t2.perms is not null
-        and t2.perms &lt;&gt; ''
-        and t5.del_flag = '0'
-        and t5.status = '0'
-        and t3.status = '0'
-        <if test="perms != null and perms.size() > 0">
-            AND (
-            <foreach collection="perms" item="p" separator=" OR ">
-                t2.perms = #{p}
-                OR t2.perms = (split_part(#{p}, ':', 1) || ':' || split_part(#{p}, ':', 2) || ':*')
-                OR t2.perms = (split_part(#{p}, ':', 1) || ':*:*')
-            </foreach>
-            OR t2.perms = '*:*:*'
-            )
-        </if>
-    </select>
+	<select id="getUserByRole" resultType="java.lang.Long">
+		select distinct su.user_id
+		from sys_user su
+				 left join sys_user_role sur on su.user_id = sur.user_id
+				 left join sys_role sr on sur.role_id = sr.role_id
+		where role_name like concat('%', #{role}, '%')
+		  and sr.del_flag = '0'
+		  and sr.status = '0'
+		  and su.status = '0'
+		  and su.del_flag = '0'
+		ORDER BY su.create_time DESC
+	</select>
+	<select id="getUserByPerms" resultType="java.lang.Long">
+		select distinct t5.user_id
+		from sys_role_menu t1
+		left join sys_menu t2 on t1.menu_id = t2.menu_id
+		left join sys_role t3 on t1.role_id = t3.role_id
+		inner join sys_user_role t4 on t4.role_id = t3.role_id
+		inner join sys_user t5 on t5.user_id = t4.user_id
+		where t3.del_flag = '0'
+		and t2.perms is not null
+		and t2.perms &lt;&gt; ''
+		and t5.del_flag = '0'
+		and t5.status = '0'
+		and t3.status = '0'
+		<if test="perms != null and perms.size() > 0">
+			AND (
+			<foreach collection="perms" item="p" separator=" OR ">
+				t2.perms = #{p}
+				OR t2.perms = (split_part(#{p}, ':', 1) || ':' || split_part(#{p}, ':', 2) || ':*')
+				OR t2.perms = (split_part(#{p}, ':', 1) || ':*:*')
+			</foreach>
+			OR t2.perms = '*:*:*'
+			)
+		</if>
+	</select>
 
 </mapper>
diff --git a/src/main/resources/mapper/technology/TechnologyOperationMapper.xml b/src/main/resources/mapper/technology/TechnologyOperationMapper.xml
index f6adc74..b09fdc2 100644
--- a/src/main/resources/mapper/technology/TechnologyOperationMapper.xml
+++ b/src/main/resources/mapper/technology/TechnologyOperationMapper.xml
@@ -32,6 +32,6 @@
                 and t.type = #{c.type}
             </if>
         </where>
-        order by t.id asc
+        order by t.id desc
     </select>
 </mapper>
diff --git a/src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml b/src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml
index e1ccb2a..063f5aa 100644
--- a/src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml
+++ b/src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml
@@ -28,6 +28,6 @@
                 and top1.technology_param_id = #{paramId}
             </if>
         </where>
-        order by top1.id asc
+        order by top1.id desc
     </select>
 </mapper>
diff --git a/src/main/resources/mapper/warehouse/DocumentationBorrowManagementMapper.xml b/src/main/resources/mapper/warehouse/DocumentationBorrowManagementMapper.xml
index 18b2082..2c58276 100644
--- a/src/main/resources/mapper/warehouse/DocumentationBorrowManagementMapper.xml
+++ b/src/main/resources/mapper/warehouse/DocumentationBorrowManagementMapper.xml
@@ -51,6 +51,7 @@
         <if test="documentationBorrowManagement.entryDateEnd != null and documentationBorrowManagement.entryDateEnd != ''">
             and borrow_date &lt;= DATE_FORMAT(#{documentationBorrowManagement.entryDateEnd},'%Y-%m-%d')
         </if>
+        order by dbm.create_time desc
     </select>
     <select id="export" resultType="com.ruoyi.warehouse.dto.DocumentationBorrowManagementDto">
         select dbm.*,doc.doc_name
diff --git a/src/main/resources/mapper/warehouse/DocumentationMapper.xml b/src/main/resources/mapper/warehouse/DocumentationMapper.xml
index 91f17a9..503f175 100644
--- a/src/main/resources/mapper/warehouse/DocumentationMapper.xml
+++ b/src/main/resources/mapper/warehouse/DocumentationMapper.xml
@@ -32,6 +32,7 @@
         <if test="documentation.id != null">
             and doc.id = #{documentation.id}
         </if>
+        order by doc.create_time desc
     </select>
     <select id="listByDocumentClassificationId" resultType="com.ruoyi.warehouse.dto.DocumentationDto">
         SELECT
diff --git a/src/main/resources/mapper/warehouse/DocumentationReturnManagementMapper.xml b/src/main/resources/mapper/warehouse/DocumentationReturnManagementMapper.xml
index 06202f8..7545c55 100644
--- a/src/main/resources/mapper/warehouse/DocumentationReturnManagementMapper.xml
+++ b/src/main/resources/mapper/warehouse/DocumentationReturnManagementMapper.xml
@@ -28,6 +28,7 @@
         <if test="documentationReturnManagement.entryDateEnd != null and documentationReturnManagement.entryDateEnd != ''">
             and return_date &lt;= DATE_FORMAT(#{documentationReturnManagement.entryDateEnd},'%Y-%m-%d')
         </if>
+        order by dbm.create_time desc
     </select>
 
     <select id="exportrevent" resultType="com.ruoyi.warehouse.dto.ReturnExportDto">
diff --git a/src/main/resources/mapper/warehouse/WarehouseGoodsShelvesRowcolMapper.xml b/src/main/resources/mapper/warehouse/WarehouseGoodsShelvesRowcolMapper.xml
index 87dfa32..2b083a4 100644
--- a/src/main/resources/mapper/warehouse/WarehouseGoodsShelvesRowcolMapper.xml
+++ b/src/main/resources/mapper/warehouse/WarehouseGoodsShelvesRowcolMapper.xml
@@ -36,6 +36,7 @@
                 and wgsr.id = #{warehouseGoodsShelvesRowcol.id}
             </if>
         </where>
+        order by wgsr.create_time desc
     </select>
 
 </mapper>

--
Gitblit v1.9.3