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 >= #{ew.createTimeStart}
+ and ai.apply_time <= #{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 >= #{ew.createTimeStart}
+ and create_time <= #{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 <= 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 >= DATE_FORMAT(#{qualityUnqualified.entryDateStart},'%Y-%m-%d')
- </if>
- <if test="qualityUnqualified.entryDateEnd != null and qualityUnqualified.entryDateEnd != '' ">
- AND check_time <= 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 >= DATE_FORMAT(#{qualityUnqualified.entryDateStart},'%Y-%m-%d')
+ </if>
+ <if test="qualityUnqualified.entryDateEnd != null and qualityUnqualified.entryDateEnd != '' ">
+ AND check_time <= 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 >= #{params.date}
and personal_attendance_records.date < 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 >= DATE_FORMAT(#{staffOnJob.entryDateStart},'%Y-%m-%d')
- </if>
- <if test="staffOnJob.entryDateEnd != null and staffOnJob.entryDateEnd != '' ">
- AND contract_expire_time <= 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 >= DATE_FORMAT(#{staffOnJob.entryDateStart},'%Y-%m-%d')
+ </if>
+ <if test="staffOnJob.entryDateEnd != null and staffOnJob.entryDateEnd != '' ">
+ AND staff_on_job.contract_expire_time <= 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 <= #{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') <= 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') >= 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') <= 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') >= 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') <= 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 <> ''
- 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 <> ''
+ 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 <= 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 <= 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