liyong
4 小时以前 1ca5584d7e3200a9af65a099bd26d3593e2ba702
迁移pro
已添加272个文件
已修改733个文件
已删除109个文件
47190 ■■■■■ 文件已修改
.editorconfig 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
AGENTS.md 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
FILE_UPLOAD_README.md 734 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260428_create_table_ai_chat_session.sql 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260506_add_ai_enabled_to_sys_user.sql 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260506_用户AI功能字段前端联调说明.md 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/add.sql 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/create_table_customer_user.sql 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/encoding-rules.md 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/采购智能体多文件分析前端联调说明.md 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 193 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/CodeGenerator.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/RuoYiServletInitializer.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ScheduleTask.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountExpenseController.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountFileController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountIncomeController.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountingController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/BorrowInfoController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/SalesReceiptReturnController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/SalesRefundAmountOrderController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/dto/AccountDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/dto/DeviceTypeDistributionVO.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/dto/SalesRefundAmountOrderDto.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountExpense.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountFile.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountIncome.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/BorrowInfo.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/SalesReceiptReturn.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/SalesRefundAmountOrder.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountExpenseService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountIncomeService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountExpenseServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountIncomeServiceImpl.java 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountingServiceImpl.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/BorrowInfoServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/SalesRefundAmountOrderServiceImpl.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesNearExpiryController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesServiceController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesServiceFileController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/dto/AfterSalesServiceExeclDto.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/dto/AfterSalesServiceNewDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesNearExpiry.java 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesService.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesServiceFile.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceFileServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/ApproveTodoAgent.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/ApproveTodoIntentExecutor.java 296 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/Assistant.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/FileAnalyzeAgent.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/PurchaseAgent.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/PurchaseIntentExecutor.java 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/SeparateChatAssistant.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/bean/ChatForm.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/bean/PurchaseAiConfirmRequest.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/config/ApproveTodoAgentConfig.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/config/PurchaseAgentConfig.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/config/SeparateChatAssistantConfig.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/config/XiaozhiAgentConfig.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java 903 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/controller/XiaozhiController.java 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/dto/AiChatSessionDto.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/mapper/AiChatSessionMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/pojo/AiChatSession.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/service/AiChatSessionService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/service/AiFileTextExtractor.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java 996 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java 643 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/dto/ApproveNodeDto.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/dto/ApproveProcessConfigNodeDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/ApproveGetAndUpdateVo.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessConfigNodeVo.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessVO.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApproveNodeController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApproveProcessConfigNodeController.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/HolidaySettingsController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/NotificationManagementController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/RpaProcessAutomationController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApproveProcessConfigNodeMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApproveProcessMapper.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/AnnualLeaveSetting.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveLog.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveNode.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveProcessConfigNode.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/FileSharing.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/HolidaySettings.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/KnowledgeBase.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/NotificationManagement.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/OnlineMeeting.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/OvertimeSetting.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/RpaProcessAutomation.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/WorkingHoursSetting.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/ApproveProcessConfigNodeService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/IApproveProcessService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java 180 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessConfigNodeServiceImpl.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 204 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/HolidaySettingsServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/KnowledgeBaseServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/NotificationManagementServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/RpaProcessAutomationServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/utils/ApproveProcessConfigNodeUtils.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/utils/DailyRedisCounter.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/utils/StartAndEndDateDto.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/vo/ApproveGetAndUpdateVo.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/vo/ApproveProcessVO.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/vo/ApproveProcessVo.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerController.java 86 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerFollowUpController.java 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/EnumController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/ProductController.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/StorageAttachmentController.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/SupplierManageController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/SupplierManageFileController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/CustomerDto.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/ProductModelDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/StorageAttachmentDTO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/StorageAttachmentVO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/StorageBlobVO.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/SupplierManageDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/excel/SupplierManageExcelDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerUserMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/ProductModelMapper.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/Customer.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerFollowUp.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerFollowUpFile.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerReturnVisit.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/CustomerUser.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/Product.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/ProductModel.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/StorageAttachment.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/StorageBlob.java 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/SupplierManage.java 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/SupplierManageFile.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/CustomerFollowUpFileService.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/CustomerUserService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ICustomerService.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/IProductService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ISupplierService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/StorageBlobService.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerFollowUpFileServiceImpl.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerFollowUpServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerReturnVisitServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 267 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerUserServiceImpl.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ReturnVisitReminderService.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java 267 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/SupplierServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/task/ReturnVisitReminderTask.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/task/StorageBlobCleanupTask.java 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/utils/FileUtil.java 847 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/vo/CustomerVo.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/vo/ProductModelVo.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/DutyPlanController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/MeetingController.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/NoticeController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/NoticeTypeController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/RulesRegulationsManagementController.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/RulesRegulationsManagementFileController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/StaffContactsPersonalController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/dto/MeetSummaryDto.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/DutyPlan.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/MeetApplication.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/MeetDraft.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/MeetingMinutes.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/MeetingRoom.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/Notice.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/NoticeType.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/ReadingStatus.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/RulesRegulationsManagement.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/RulesRegulationsManagementFile.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/SealApplicationManagement.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/StaffContactsPersonal.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/DutyPlanService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/impl/DutyPlanServiceImpl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/impl/MeetingServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/impl/RulesRegulationsManagementFileServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/impl/RulesRegulationsManagementServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/impl/SealApplicationManagementServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/impl/StaffContactsPersonalServiceImpl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/aop/DataScopeAop.java 158 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/config/FileProperties.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/config/MinioConfig.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/config/MybatisHandler.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/constant/Constants.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/FileNameType.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/SparePartsRequisitionRecordSourceTypeEnum.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/filter/RepeatableFilter.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/filter/XssFilter.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/interceptor/DataScopeSqlInterceptor.java 257 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/MinioUtils.java 433 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/OrderUtils.java 102 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/ServletUtils.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/excel/ExcelUtils.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/file/FileUtils.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/http/HttpHelper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/http/HttpUtils.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/ip/IpUtils.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/vo/FileVo.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/vo/SimpleFileVo.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/xss/Xss.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/xss/XssValidator.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/controller/CompensationPerformanceController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/pojo/CompensationPerformance.java 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/service/impl/CompensationPerformanceServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customervisits/controller/CustomerVisitsController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customervisits/pojo/CustomerVisits.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceDefectRecordController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceLedgerController.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceMaintenanceFileController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceRepairController.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/MaintenanceTaskController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceDefectRecordDto.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceRepairDto.java 70 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/execl/DeviceLedgerExeclDto.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/execl/DeviceMaintenanceExeclDto.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/execl/DeviceRepairExeclDto.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/mapper/DeviceMaintenanceMapper.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/mapper/DeviceRepairMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceDefectRecord.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceLedger.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceMaintenanceFile.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceRepair.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/IDeviceLedgerService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/IDeviceRepairService.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceDefectRecordServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceLedgerServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java 75 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java 108 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskScheduler.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/vo/DeviceMaintenanceVo.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/vo/DeviceRepairVo.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/dto/DateQueryDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/dto/MapDto.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/ElectricityConsumptionAreaController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/EnergyPeriodController.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/EquipmentEnergyConsumptionController.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/dto/ElectricityConsumptionAreaTreeDto.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/dto/EquipmentEnergyConsumptionDto.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/pojo/ElectricityConsumptionArea.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/pojo/EnergyPeriod.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/pojo/EquipmentEnergyConsumption.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/service/impl/ElectricityConsumptionAreaServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/service/impl/EnergyPeriodServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/service/impl/EquipmentEnergyConsumptionServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/aspectj/LogAspect.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/DruidConfig.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/FilterConfig.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/ResourcesConfig.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/ScheduleConfig.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/SecurityConfig.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/ServerConfig.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/SwaggerConfig.java 177 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/manager/ShutdownManager.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/LoginUser.java 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/service/SysLoginService.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/service/SysPasswordService.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/service/SysPermissionService.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/service/SysRegisterService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/service/TokenService.java 121 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/security/service/UserDetailsServiceImpl.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/controller/HomeController.java 94 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/AnalysisCustomerContractAmountsDto.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/CustomerContributionRankingDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/CustomerRevenueAnalysisDto.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/DeptStaffDistributionDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/HomeBusinessDto.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/HomeSummaryDto.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/ProductCategoryDistributionDto.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/ProductionProgressDto.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/ProductionProgressOrderDto.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/ProductionTaskStatisticsDto.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/ProductionTurnoverDto.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/QualityStatisticsDto.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/QualityStatisticsItem.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/StatisticsReceivablePayableDto.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/SupplierPurchaseRankingDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/WorkOrderEfficiencyDto.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/QrCodeController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/QrCodeScanRecordController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/dto/QrCodeScanRecordDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/QrCode.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/QrCodeScanRecord.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java 49 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java 99 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/QuartzConfig.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduler.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/lavorissue/controller/LavorIssueController.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/lavorissue/dto/StatisticsLaborIssue.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/lavorissue/pojo/LaborIssue.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/lavorissue/service/LavorIssueService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/lavorissue/service/impl/LavorIssueServiceImpl.java 64 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/controller/MeasuringInstrumentLedgerController.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/controller/MeasuringInstrumentLedgerRecordController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsController.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsRequisitionRecordController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/dto/MeasuringInstrumentLedgerDto.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/dto/SparePartsRequisitionRecordDto.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/mapper/SparePartsRequisitionRecordMapper.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/pojo/MeasuringInstrumentLedger.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/pojo/MeasuringInstrumentLedgerRecord.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/pojo/SpareParts.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/pojo/SparePartsRequisitionRecord.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/service/MeasuringInstrumentLedgerRecordService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/service/MeasuringInstrumentLedgerService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/service/SparePartsRequisitionRecordService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/MeasuringInstrumentLedgerRecordServiceImpl.java 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/MeasuringInstrumentLedgerServiceImpl.java 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/SparePartsRequisitionRecordServiceImpl.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/SparePartsServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/controller/OaProjectController.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/controller/OaProjectPhaseController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/controller/OaProjectPhaseTaskController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/pojo/OaProject.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/pojo/OaProjectPhase.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/pojo/OaProjectPhaseTask.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/service/OaProjectService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/service/impl/OaProjectPhaseServiceImpl.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/oA/service/impl/OaProjectServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/officesupplies/controller/OfficeSuppliesController.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/officesupplies/pojo/OfficeSupplies.java 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/officesupplies/service/impl/OfficeSuppliesServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/controller/PdaVersionController.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/controller/TempFileController.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/dto/PdaVersionDTO.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/mapper/PdaVersionMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/pojo/PdaVersion.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/pojo/TempFile.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/service/PdaVersionService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/service/TempFileService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/service/impl/PdaVersionServiceImpl.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/other/service/impl/TempFileServiceImpl.java 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/GasTankWarningController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/InboundManagementController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementExceptionRecordController.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPlanController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPriceManagementController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordController.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordOutController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ReturnManagementController.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDto.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDtoCopy.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/dto/ReturnManagementDto.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/CustomStorage.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/GasTankWarning.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/InboundManagement.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementExceptionRecord.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementPlan.java 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementPriceManagement.java 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecordOut.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecordStorage.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnSaleProduct.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/GasTankWarningService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ProcurementPlanService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ProcurementPriceManagementService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordOutService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/GasTankWarningServiceImpl.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/InboundManagementServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementPlanServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementPriceManagementServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordOutServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnSaleProductServiceImpl.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/BomImportDto.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductStructureDto.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionAccountDto.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionBomStructureDto.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionOperationTaskDto.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionOrderDto.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionOrderPickDto.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionOrderPickRecordDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionOrderRoutingOperationParamDto.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionOrderRoutingOperationParamSyncDto.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionPlanImportDto.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionPlanSummaryDto.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionProductInputDto.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/ProductionProductOutputDto.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/SalesLedgerProductionAccountingDto.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/UserAccountDto.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/dto/UserProductionAccountingDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionAccountVo.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionBomStructureVo.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionOperationTaskVo.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionOrderPickRecordVo.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionOrderPickVo.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionOrderRoutingOperationParamVo.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionOrderRoutingOperationVo.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionOrderVo.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionOrderWorkOrderDetailVo.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/bean/vo/ProductionPlanVo.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProcessRouteController.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProcessRouteItemController.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductBomController.java 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductOrderController.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductProcessController.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductProcessRouteController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductProcessRouteItemController.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductStructureController.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductWorkOrderController.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductWorkOrderFileController.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionAccountController.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionBomStructureController.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOperationMainParamController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOperationTaskController.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOrderBomController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOrderController.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOrderPickController.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOrderPickRecordController.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingController.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingOperationController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingOperationParamController.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionPlanController.java 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionProductInputController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionProductMainController.java 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionProductOutputController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/SalesLedgerProductionAccountingController.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/SalesLedgerSchedulingController.java 207 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/SalesLedgerWorkController.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/BomImportDto.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/DaiDto.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProcessRouteDto.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProcessRouteItemDto.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProcessSchedulingDto.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductBomDto.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductOrderDto.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductProcessDto.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductProcessRouteItemDto.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductStructureDto.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionDispatchAddDto.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionProductInputDto.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionProductOutputDto.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionReportDto.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/SalesLedgerProductDto.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/SalesLedgerProductionAccountingDto.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/SalesLedgerSchedulingDto.java 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/SalesLedgerSchedulingProcessDto.java 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/SalesLedgerWorkDto.java 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/UserAccountDto.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/UserProductionAccountingDto.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/enums/ProductOrderStatusEnum.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProcessRouteItemMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProcessRouteMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductBomMapper.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductOrderMapper.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductProcessMapper.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductProcessRouteItemMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductProcessRouteMapper.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductStructureMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductWorkOrderFileMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductWorkOrderMapper.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionAccountMapper.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionBomStructureMapper.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOperationMainParamMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOperationTaskMapper.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOrderBomMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOrderMapper.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOrderPickMapper.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOrderPickRecordMapper.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOrderRoutingMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOrderRoutingOperationMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOrderRoutingOperationParamMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionPlanMapper.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionProductInputMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionProductMainMapper.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionProductOutputMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/SalesLedgerProductionAccountingMapper.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/SalesLedgerSchedulingMapper.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/SalesLedgerWorkMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/SpeculativeTradingInfoMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProcessRoute.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProcessRouteItem.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductBom.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductOrder.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductProcess.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductProcessRoute.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductProcessRouteItem.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductStructure.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductWorkOrder.java 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductWorkOrderFile.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionAccount.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionBomStructure.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOperationMainParam.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOperationTask.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrder.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderBom.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderPick.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderPickRecord.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderRouting.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperation.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperationParam.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionPlan.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionProductInput.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/SalesLedgerProductionAccounting.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/SalesLedgerScheduling.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/SalesLedgerWork.java 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/SpeculativeTradingInfo.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProcessRouteItemService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProcessRouteService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductBomService.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductOrderService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductProcessRouteItemService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductProcessRouteService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductProcessService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductStructureService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductWorkOrderFileService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductWorkOrderService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionAccountService.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionBomStructureService.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOperationMainParamService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOperationTaskService.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderBomService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderPickRecordService.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderPickService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationParamService.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderRoutingService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderService.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionPlanService.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionProductInputService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionProductMainService.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionProductOutputService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/SalesLedgerProductionAccountingService.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/SalesLedgerSchedulingService.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/SalesLedgerWorkService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProcessRouteItemServiceImpl.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProcessRouteServiceImpl.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductBomServiceImpl.java 318 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java 204 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteItemServiceImpl.java 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteServiceImpl.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductProcessServiceImpl.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductStructureServiceImpl.java 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderFileServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionAccountServiceImpl.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOperationMainParamServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java 355 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderBomServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickRecordServiceImpl.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java 919 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationParamServiceImpl.java 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingServiceImpl.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java 952 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java 350 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductInputServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 743 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductOutputServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/SalesLedgerProductionAccountingServiceImpl.java 88 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/SalesLedgerSchedulingServiceImpl.java 390 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/SalesLedgerWorkServiceImpl.java 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/common/CaptchaController.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/common/CommonController.java 226 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/CacheController.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/SysJobController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/SysJobLogController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/SysLogininforController.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/SysOperlogController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/SysUserOnlineController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/domain/SysJob.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/service/impl/SysJobLogServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/service/impl/SysJobServiceImpl.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/service/impl/SysLogininforServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/service/impl/SysOperLogServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysConfigController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysDeptController.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysDictDataController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysDictTypeController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysIndexController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysLoginController.java 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysMenuController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysNoticeController.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysPostController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysProfileController.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysRegisterController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysRoleController.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysUserClientController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysUserController.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysConfig.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysDept.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysDictData.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysDictType.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysMenu.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysNotice.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysPost.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysRole.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysUser.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysUserDept.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/vo/SysUserDeptVo.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/mapper/SysRoleMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysConfigServiceImpl.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysDeptServiceImpl.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysDictDataServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysDictTypeServiceImpl.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysMenuServiceImpl.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysPostServiceImpl.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysRoleServiceImpl.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysUserDeptServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysUserServiceImpl.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/tool/gen/controller/GenController.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/tool/gen/domain/GenTable.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/tool/gen/domain/GenTableColumn.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/tool/gen/service/GenTableColumnServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/tool/gen/service/GenTableServiceImpl.java 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/tool/swagger/TestController.java 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/controller/InfoController.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/controller/PlanController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/controller/RolesController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/dto/InfoStageDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/dto/RoleDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/dto/SaveInfoDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/dto/UpdateStateInfo.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/pojo/ContractInfo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/pojo/Info.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/pojo/InfoStage.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/pojo/Plan.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/pojo/PlanNode.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/pojo/Roles.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/pojo/ShippingAddress.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/service/InfoService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/service/PlanService.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/service/impl/PlanServiceImpl.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/service/impl/handle/ContractInfoHandleService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/service/impl/handle/InfoHandleService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/service/impl/handle/InfoStageHandleService.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/service/impl/handle/ShippingAddressHandleService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/vo/InfoStageVo.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/vo/PlanVo.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/vo/SaveInfoStageVo.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/vo/SaveInfoVo.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/vo/SavePlanNodeVo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/vo/SavePlanVo.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/vo/SearchPlanVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/AccountingReportController.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/InvoicePurchaseController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PaymentRegistrationController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/ProcurementBusinessSummaryController.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerTemplateController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrderProductsController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/ProcurementBusinessSummaryDto.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerImportDto.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderDto.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/SimpleReturnOrderGroupDto.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrderProductsMapper.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrdersMapper.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/InvoicePurchase.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PaymentRegistration.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseLedgerTemplate.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrderProducts.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/SalesLedgerProductTemplate.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/TicketRegistration.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/InvoicePurchaseServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PaymentRegistrationServiceImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/ProcurementBusinessSummaryServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 277 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerTemplateServiceImpl.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderVo.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityInspectController.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityInspectFileController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityInspectParamController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityReportController.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityTestStandardBindingController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityTestStandardController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityTestStandardParamController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityInspectDto.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityInspectStatDto.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityMonthlyDetailDto.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityMonthlyPassRateDto.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityMonthlyPassRateWrapperDto.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityParameterStatDto.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityPassRateDto.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/dto/QualityTopParameterDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/mapper/QualityInspectMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityInspect.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityInspectFile.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityInspectParam.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityTestStandard.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityTestStandardBinding.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityTestStandardParam.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/IQualityInspectService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/IQualityUnqualifiedService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityReportServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityTestStandardBindingServiceImpl.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityTestStandardServiceImpl.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeAccidentController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeCertificationController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeCertificationFileController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeContingencyPlanController.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeHazardController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeHazardRecordController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeHiddenController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeHiddenFileController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeTrainingController.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeTrainingDetailsController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/controller/SafeTrainingFileController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/dto/SafeHazardRecordDto.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/dto/SafeHiddenDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/dto/SafeTrainingDetailsDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/dto/SafeTrainingDto.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeAccident.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeCertification.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeCertificationFile.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeContingencyPlan.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeHazard.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeHazardRecord.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeHidden.java 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeHiddenFile.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeTraining.java 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeTrainingDetails.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/pojo/SafeTrainingFile.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/service/SafeTrainingDetailsService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/service/SafeTrainingService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/service/impl/SafeAccidentServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/service/impl/SafeCertificationServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/service/impl/SafeContingencyPlanServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/service/impl/SafeHazardRecordServiceImpl.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/service/impl/SafeHazardServiceImpl.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/service/impl/SafeHiddenServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/service/impl/SafeTrainingDetailsServiceImpl.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/safe/service/impl/SafeTrainingServiceImpl.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/InvoiceLedgerController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/InvoiceRegistrationController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/MetricStatisticsController.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/PaymentShippingController.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ReceiptPaymentController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesQuotationController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalespersonManagementController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ShipmentApprovalController.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ShippingProductDetailController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/InvoiceLedgerDto.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/InvoiceRegistrationDto.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/InvoiceRegistrationProductDto.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ReceiptPaymentDto.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ReceiptPaymentExeclDto.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ReceiptPaymentRecordDto.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerImportDto.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesLedgerProductDto.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ShippingProductDetailDto.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/StatisticsTableDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/excel/InvoiceLedgerExcelDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/excel/InvoiceRegisAndProductExcelDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/ShippingInfoMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/ShippingProductDetailMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/CommonFile.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/InvoiceLedger.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/InvoiceLedgerFile.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/InvoiceRegistration.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/InvoiceRegistrationProduct.java 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/Loss.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/PaymentShipping.java 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/PurchaseLedgerFile.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ReceiptPayment.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalespersonManagement.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ShipmentApproval.java 73 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ShippingProductDetail.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ICommonFileService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/InvoiceLedgerService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/InvoiceRegistrationService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ReceiptPaymentService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ShippingInfoService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ShippingProductDetailService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/CommonFileServiceImpl.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/InvoiceLedgerServiceImpl.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/InvoiceRegistrationServiceImpl.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/MetricStatisticsServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/PaymentShippingServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ReceiptPaymentServiceImpl.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 332 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 204 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalespersonManagementServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShipmentApprovalServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingProductDetailServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/SalesLedgerVo.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/AnalyticsController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/BankController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/HolidayApplicationController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalShiftController.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/SchemeApplicableStaffController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffContractController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffLeaveController.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffSalaryMainController.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffSchedulingController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/PerformanceShiftAddDto.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/SaveStaffSchedulingDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/StaffOnJobExcelDto.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/Bank.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/HolidayApplication.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceLocationConfig.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceRecords.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalShift.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/SchemeApplicableStaff.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/SchemeInsuranceDetail.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffContract.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffEducation.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffEmergencyContact.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffLeave.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffSalaryMain.java 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffScheduling.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffWorkExperience.java 32 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/IStaffOnJobService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/StaffLeaveService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/AnalyticsServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/HolidayApplicationServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalShiftServiceImpl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java 104 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffSchedulingServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/task/PersonalAttendanceRecordsTask.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/vo/MonthlyTurnoverRateVo.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/vo/TotalTurnoverRateVo.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockInRecordController.java 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockInventoryController.java 60 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/execl/StockInventoryExportData.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockInRecord.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockInventory.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockUninventory.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockInRecordService.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockInventoryService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockOutRecordService.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockUninventoryService.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java 283 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/BomImportDto.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyBomDto.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyBomStructureDto.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyOperationDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyOperationParamDto.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyParamDto.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingDto.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingOperationDto.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingOperationParamDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingOperationParamSyncDto.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomStructureVo.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomVo.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyOperationParamVo.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyOperationVo.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyParamVo.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyRoutingOperationParamVo.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyRoutingOperationVo.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/bean/vo/TechnologyRoutingVo.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyBomController.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyBomStructureController.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyParamController.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyRoutingController.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyRoutingOperationController.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyRoutingOperationParamController.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyBomMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyBomStructureMapper.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyOperationMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyOperationParamMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyParamMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyRoutingMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyRoutingOperationMapper.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/mapper/TechnologyRoutingOperationParamMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyBom.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyBomStructure.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyOperationParam.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyParam.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyRouting.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyRoutingOperation.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyRoutingOperationParam.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyBomService.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyBomStructureService.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyOperationParamService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyOperationService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyParamService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyRoutingOperationParamService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyRoutingOperationService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/TechnologyRoutingService.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java 485 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyBomStructureServiceImpl.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationParamServiceImpl.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationServiceImpl.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyParamServiceImpl.java 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingOperationParamServiceImpl.java 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingOperationServiceImpl.java 142 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java 224 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/DocumentClassificationController.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/DocumentationBorrowManagementController.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/DocumentationController.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/DocumentationFileController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/WarehouseController.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/WarehouseGoodsShelvesController.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/WarehouseGoodsShelvesRowcolController.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/dto/DocumentationBorrowManagementDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/dto/ReturnExportDto.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/mapper/DocumentationBorrowManagementMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/pojo/DocumentClassification.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/pojo/Documentation.java 42 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/pojo/DocumentationBorrowManagement.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/pojo/DocumentationFile.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/pojo/DocumentationReturnManagement.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/pojo/Warehouse.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/pojo/WarehouseGoodsShelves.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/pojo/WarehouseGoodsShelvesRowcol.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/service/DocumentationBorrowManagementService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/service/DocumentationService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/service/impl/DocumentClassificationServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/service/impl/DocumentationBorrowManagementServiceImpl.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/service/impl/DocumentationServiceImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/service/impl/WarehouseGoodsShelvesRowcolServiceImpl.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/service/impl/WarehouseGoodsShelvesServiceImpl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/service/impl/WarehouseServiceImpl.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/waterrecord/controller/WaterRecordController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/waterrecord/pojo/WaterRecord.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/waterrecord/service/impl/WaterRecordServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-demo.yml 115 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev-pro.yml 266 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 59 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-hbtmblc.yml 256 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-new-pro.yml 268 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application.yml 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/approve-todo-agent-prompt.txt 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/AccountExpenseMapper.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/AccountIncomeMapper.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/SalesRefundAmountOrderMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApproveProcessConfigNodeMapper.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApproveProcessMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerMapper.xml 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/ProductModelMapper.xml 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/StorageAttachmentMapper.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/StorageBlobMapper.xml 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/device/DeviceMaintenanceMapper.xml 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/device/DeviceRepairMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/measuringinstrumentledger/SparePartsRequisitionRecordMapper.xml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProcessRouteItemMapper.xml 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProcessRouteMapper.xml 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductBomMapper.xml 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductOrderMapper.xml 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductProcessMapper.xml 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductProcessRouteItemMapper.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductProcessRouteMapper.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductStructureMapper.xml 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductWorkOrderFileMapper.xml 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductWorkOrderMapper.xml 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionAccountMapper.xml 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionBomStructureMapper.xml 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOperationMainParamMapper.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOperationTaskMapper.xml 213 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderBomMapper.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderMapper.xml 215 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderPickMapper.xml 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderPickRecordMapper.xml 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderRoutingMapper.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderRoutingOperationMapper.xml 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderRoutingOperationParamMapper.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionPlanMapper.xml 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductInputMapper.xml 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductMainMapper.xml 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductOutputMapper.xml 92 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/SalesLedgerProductionAccountingMapper.xml 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/SalesLedgerSchedulingMapper.xml 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/SalesLedgerWorkMapper.xml 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PaymentRegistrationMapper.xml 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseReturnOrderProductsMapper.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityInspectMapper.xml 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityTestStandardMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/InvoiceLedgerMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerMapper.xml 53 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesQuotationMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/ShippingInfoMapper.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/ShippingProductDetailMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/PersonalShiftMapper.xml 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffOnJobMapper.xml 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInRecordMapper.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInventoryMapper.xml 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockOutRecordMapper.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockUninventoryMapper.xml 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysRoleMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysUserMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyBomMapper.xml 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyBomStructureMapper.xml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyOperationMapper.xml 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyParamMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyRoutingMapper.xml 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyRoutingOperationMapper.xml 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyRoutingOperationParamMapper.xml 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/my-prompt-template.txt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/my-prompt-template3.txt 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mybatis/mybatis-config.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/purchase-agent-prompt.txt 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/work-order-template.docx 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/vm/java/controller.java.vm 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.editorconfig
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.{java,xml,yml,yaml,md,properties,sql,js,ts,vue,html,css,scss,json}]
charset = utf-8
[*.md]
trim_trailing_whitespace = false
.gitignore
@@ -6,6 +6,7 @@
!gradle/wrapper/gradle-wrapper.jar
target/
/${project.build.directory}/
!.mvn/wrapper/maven-wrapper.jar
######################################################################
@@ -43,4 +44,4 @@
!*/build/*.java
!*/build/*.html
!*/build/*.xml
!*/build/*.xml
AGENTS.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
# ç¼–码与中文处理规则
本项目后续所有代码修改必须遵守以下规则:
1. æ–°å»ºæ–‡ä»¶ç»Ÿä¸€ä½¿ç”¨ `UTF-8` ç¼–码,且不带 `BOM`。
2. ä¿®æ”¹å·²æœ‰æ–‡ä»¶å‰ï¼Œå…ˆæ£€æŸ¥åŽŸæ–‡ä»¶ç¼–ç ï¼›å¦‚æžœä¸æ˜¯ `UTF-8`,默认先保持原编码,避免把中文写坏。
3. ä¸­æ–‡å†…容必须直接保留,不允许转义成 `\\uXXXX`。
4. è¾“出、提交、生成代码时不得出现乱码;如果终端显示异常,必须先校验文件真实字节编码,再继续修改。
5. æ¶‰åŠæ‰¹é‡æ›¿æ¢æ—¶ï¼Œåªèƒ½åœ¨ç¡®è®¤ç¼–码安全后执行;不能为了批量迁移把中文内容改坏。
推荐做法:
- ä¼˜å…ˆä¾èµ–项目根目录 `.editorconfig`。
- ä¿®æ”¹å‰å…ˆç¡®è®¤æ–‡ä»¶ç¼–码,再选择对应的写回方式。
- å¯¹åŒ…含大量中文注释、字符串的文件,优先小范围修改并及时编译验证。
FILE_UPLOAD_README.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,734 @@
# æ–‡ä»¶ä¸Šä¼ åŠŸèƒ½è¯´æ˜Ž
本文档基于以下代码整理:
- `src/main/java/com/ruoyi/basic/utils/FileUtil.java`
- `src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java`
- `src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java`
- `src/main/java/com/ruoyi/project/common/CommonController.java`
- `src/main/java/com/ruoyi/basic/controller/StorageAttachmentController.java`
用于说明本项目中文件上传、附件绑定、文件预览/下载的整体设计,以及 `FileUtil` ä¸­æ¯ä¸ªæ–¹æ³•的作用。
## 1. æ•´ä½“设计
本项目的文件体系分成两层:
- `storage_blob`:存文件实体信息
  - åŽŸå§‹æ–‡ä»¶å
  - å”¯ä¸€æ–‡ä»¶å `uidFilename`
  - æ–‡ä»¶è·¯å¾„ `path`
  - æ–‡ä»¶å¤§å° `byteSize`
  - æ–‡ä»¶ç±»åž‹ `contentType`
  - å…¬å…±è®¿é—®æ ‡è¯† `resourceKey`
- `storage_attachment`:存文件和业务记录的关联关系
  - `application`:文件用途
  - `recordType`:业务记录类型
  - `recordId`:业务记录主键
  - `storageBlobId`:关联的文件主表 id
可以理解为:
- `storage_blob` è´Ÿè´£â€œæ–‡ä»¶æœ¬èº«â€
- `storage_attachment` è´Ÿè´£â€œæ–‡ä»¶æŒ‚在哪条业务数据上”
## 2. ä¸Šä¼ æµç¨‹
### 2.1 æ™®é€šä¸Šä¼ 
接口:
- `POST /common/upload`
控制器位置:
- `src/main/java/com/ruoyi/project/common/CommonController.java`
入参:
- è¡¨å•字段名:`files`
- ç±»åž‹ï¼š`List<MultipartFile>`
代码逻辑:
1. å‰ç«¯å…ˆè°ƒç”¨ `/common/upload`
2. `CommonController.upload()` è°ƒç”¨ `storageBlobService.upload(files, false)`
3. æœåŠ¡å±‚ä¿å­˜æ–‡ä»¶å…ƒæ•°æ®åˆ° `storage_blob`
4. è¿”回 `StorageBlobVO` åˆ—表,里面通常会带:
   - æ–‡ä»¶ id
   - åŽŸå§‹æ–‡ä»¶å
   - å”¯ä¸€æ–‡ä»¶å
   - é¢„览地址 `previewURL`
   - ä¸‹è½½åœ°å€ `downloadURL`
说明:
- æ­¤æ—¶åªæ˜¯â€œä¸Šä¼ äº†æ–‡ä»¶â€
- è¿˜æ²¡æœ‰å’Œå…·ä½“业务单据建立关系
### 2.2 å…¬å…±ä¸Šä¼ 
接口:
- `POST /common/public/upload`
代码逻辑:
- `CommonController.publicUpload()` è°ƒç”¨ `storageBlobService.upload(files, true)`
说明:
- è¯¥æŽ¥å£ä¸Šä¼ çš„æ–‡ä»¶èµ°â€œå…¬å…±æ–‡ä»¶â€æ¨¡å¼
- æŽ§åˆ¶å™¨æ³¨é‡Šå·²æ˜Žç¡®è¯´æ˜Žï¼šæ°¸ä¹…有效,慎用
- å¯¹åº” URL æž„建时,可能走 `publicKey` å‚数,而不是临时 `token`
## 3. é™„件绑定流程
上传完成后,如果需要把文件绑定到某条业务记录,需要再调用附件接口。
接口:
- `POST /storageAttachment/add`
控制器位置:
- `src/main/java/com/ruoyi/basic/controller/StorageAttachmentController.java`
核心请求对象:
- `StorageAttachmentDTO`
其中继承了 `StorageAttachment`,并额外包含:
- `storageBlobDTOs`:待绑定的文件列表
常用字段含义:
- `application`:文件用途
- `recordType`:业务类型
- `recordId`:业务主键
- `storageBlobDTOs[].id`:上传成功后返回的文件 id
示例请求体:
```json
{
  "application": "file",
  "recordType": "common_file",
  "recordId": 1001,
  "storageBlobDTOs": [
    {
      "id": 12,
      "application": "file"
    },
    {
      "id": 13,
      "application": "file"
    }
  ]
}
```
绑定逻辑说明:
1. å…ˆä¸Šä¼ æ–‡ä»¶ï¼Œæ‹¿åˆ° `storage_blob.id`
2. å†è°ƒç”¨ `/storageAttachment/add`
3. æœåŠ¡å±‚æœ€ç»ˆä¼šé€šè¿‡ `FileUtil` ä¿å­˜ `storage_attachment`
4. åŽç»­å³å¯æŒ‰ä¸šåŠ¡è®°å½•æŸ¥è¯¢å‡ºè¯¥è®°å½•ä¸‹çš„é™„ä»¶
## 4. æŸ¥è¯¢ä¸Žåˆ é™¤é™„ä»¶
### 4.1 æŸ¥è¯¢é™„件列表
接口:
- `GET /storageAttachment/list`
说明:
- æŒ‰ `StorageAttachmentDTO` ä¸­çš„æ¡ä»¶æŸ¥è¯¢
- å¸¸è§æ¡ä»¶æ˜¯ `application`、`recordType`、`recordId`
- è¿”回结果本质上是和业务记录关联后的文件列表
### 4.2 åˆ é™¤é™„ä»¶
接口:
- `DELETE /storageAttachment/delete`
请求体:
- `List<Long> ids`
说明:
- è¿™é‡Œçš„ `ids` æ˜¯é™„件关联表 id,一般是 `storage_attachment.id`
- åˆ é™¤æ—¶é€šå¸¸ä¸ä»…会删关联关系,也会进一步删除对应文件记录
## 5. é¢„览与下载流程
### 5.1 ä¸‹è½½æŽ¥å£
接口:
- `GET /common/download/{fileName}`
支持两种访问方式:
- ä¸´æ—¶é“¾æŽ¥ï¼š`token`
- å…¬å…±é“¾æŽ¥ï¼š`publicKey`
代码逻辑:
1. å¦‚果请求里有 `publicKey`,走 `storageBlobService.getPublicFile(fileName, publicKey)`
2. å¦åˆ™èµ° `storageBlobService.getFileByToken(fileName, token)`
3. å–到实际文件后,调用 `fileUtil.compressFile(file)` åšå›¾ç‰‡åŽ‹ç¼©å¤„ç†
4. è®¾ç½®ä¸‹è½½å“åº”头,输出文件流
### 5.2 é¢„览接口
接口:
- `GET /common/preview/{fileName}`
支持两种访问方式:
- ä¸´æ—¶é“¾æŽ¥ï¼š`token`
- å…¬å…±é“¾æŽ¥ï¼š`publicKey`
代码逻辑:
1. æ ¡éªŒ `token` æˆ– `publicKey`
2. èŽ·å–æ–‡ä»¶
3. è°ƒç”¨ `fileUtil.compressFile(file)`
4. æ ¹æ®æ–‡ä»¶å†…容类型返回 inline é¢„览
## 6. æžšä¸¾å«ä¹‰
### 6.1 `ApplicationTypeEnum`
位置:
- `src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java`
当前定义值:
| æžšä¸¾ | type | è¯´æ˜Ž |
|---|---|---|
| `IMAGE` | `image` | å›¾ç‰‡ç±»æ–‡ä»¶ |
| `FILE` | `file` | æ™®é€šæ–‡ä»¶ |
| `AFTER_FILE` | `after_file` | å”®åŽç›¸å…³æ–‡ä»¶ |
| `BEFORE_FILE` | `before_file` | å”®å‰/前置相关文件 |
| `APK` | `apk` | å®‰è£…包文件 |
作用:
- ç”¨äºŽåŒºåˆ†åŒä¸€æ¡ä¸šåŠ¡è®°å½•ä¸‹ï¼Œä¸åŒç”¨é€”çš„æ–‡ä»¶
- `FileUtil` çš„很多查询、删除、保存方法都会用到该字段
### 6.2 `RecordTypeEnum`
位置:
- `src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java`
作用:
- ç”¨äºŽæ ‡è®°æ–‡ä»¶å±žäºŽå“ªç±»ä¸šåŠ¡è®°å½•
- ä¾‹å¦‚质检、采购、客户、售后、台账、通知、设备等模块
- ä¸Šä¼ å®ŒæˆåŽï¼Œé™„件最终通过 `recordType + recordId` å’Œä¸šåŠ¡æ•°æ®å…³è”
说明:
- è¯¥æžšä¸¾å€¼å¾ˆå¤šï¼Œæ–‡æ¡£ä¸é€ä¸ªå±•å¼€
- å®žé™…使用时必须传代码中已定义的 `type` å€¼
- å¦‚:
  - `common_file`
  - `after_sales_service`
  - `quality_inspect`
  - `product`
  - `notice`
## 7. `FileUtil` æ–¹æ³•说明
`FileUtil` æ˜¯æœ¬å¥—文件上传体系的核心工具类,主要负责:
- æ–‡ä»¶ä¸Žä¸šåŠ¡è®°å½•ç»‘å®š
- æ–‡ä»¶ä¸Žé™„件删除
- é™„件查询
- é¢„览/下载地址生成
- token ä½¿ç”¨æ¬¡æ•°æŽ§åˆ¶
- å›¾ç‰‡åŽ‹ç¼©
下面按功能分组说明每个方法。
### 7.1 ä¿å­˜é™„件关系
#### 1. `saveStorageAttachment(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, List<StorageBlobDTO> storageBlobDTOS)`
作用:
- æŒ‰â€œæ–‡ä»¶ç”¨é€” + è®°å½•类型 + è®°å½• id”保存附件关系
逻辑:
1. æ ¡éªŒ `application`、`recordType`、`recordId`
2. å…ˆåˆ é™¤è¿™ç»„业务记录下的旧附件
3. æŠŠæ–°çš„ `storageBlobDTOS` è½¬æˆ `storage_attachment` è®°å½•后批量插入
适用场景:
- æŸæ¡ä¸šåŠ¡æ•°æ®é‡æ–°ä¿å­˜é™„ä»¶ï¼Œæ—§é™„ä»¶æ•´ä½“æ›¿æ¢æˆæ–°é™„ä»¶
#### 2. `saveStorageAttachmentByRecordTypeAndRecordId(String application, RecordTypeEnum recordType, Long recordId, List<StorageBlobDTO> storageBlobDTOS)`
作用:
- æŒ‰ `recordType + recordId` ä¿å­˜é™„件关系,`application` å¯æŒ‡å®šï¼Œä¹Ÿå¯ä»Žæ¯ä¸ªæ–‡ä»¶å¯¹è±¡é‡Œè¯»å–
逻辑特点:
- å¦‚æžœ `application == null`,会根据 `storageBlobDTO.application` åˆ†åˆ«åˆ é™¤æ—§å…³ç³»
- å¦‚果附件列表为空,会直接删除该业务记录的附件关系
- æ’入时会自动回填 `application`
适用场景:
- ä¸€æ¬¡æäº¤é‡Œå¯èƒ½åŒ…含多种用途的附件
- æˆ–者调用方不方便直接传枚举类型
### 7.2 åˆ é™¤æ–‡ä»¶ä¸»è¡¨ `storage_blob`
#### 3. `deleteStorageBlobs(List<Long> storageBlobIds)`
作用:
- æŒ‰æ–‡ä»¶ä¸»è¡¨ id æ‰¹é‡åˆ é™¤æ–‡ä»¶è®°å½•
#### 4. `deleteStorageBlobsByStorageAttachmentIds(List<Long> storageAttachmentIds)`
作用:
- å…ˆæ ¹æ®é™„件关联 id æŸ¥åˆ° `storageBlobId`
- å†åˆ é™¤å¯¹åº”的文件主表记录
#### 5. `deleteStorageBlobsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum application, RecordTypeEnum recordType, List<Long> recordIds)`
作用:
- æ ¹æ®ç”¨é€”、记录类型、多个业务 id,批量删除对应的文件主表记录
适用场景:
- æ‰¹é‡åˆ é™¤æŸç±»ä¸šåŠ¡æ•°æ®æ—¶ï¼ŒåŒæ—¶æ¸…ç†é™„ä»¶
#### 6. `deleteStorageBlobsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId)`
作用:
- æ ¹æ® `recordType + recordId` åˆ é™¤è¯¥ä¸šåŠ¡è®°å½•ä¸‹æ‰€æœ‰æ–‡ä»¶ä¸»è¡¨è®°å½•
### 7.3 åˆ é™¤é™„件关系 `storage_attachment`
#### 7. `deleteStorageAttachmentsByStorageAttachmentIds(List<Long> storageAttachmentIds)`
作用:
- å…ˆåˆ é™¤é™„件对应的文件主表记录
- å†åˆ é™¤é™„件关系表记录
#### 8. `deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId)`
作用:
- åˆ é™¤æŒ‡å®šç”¨é€”、指定业务记录下的附件关系
特点:
- ä¼šå…ˆåˆ  blob,再删 attachment
#### 9. `deleteStorageAttachmentsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId)`
作用:
- åˆ é™¤æŒ‡å®šä¸šåŠ¡è®°å½•ä¸‹å…¨éƒ¨é™„ä»¶å…³ç³»ï¼Œä¸åŒºåˆ†ç”¨é€”
#### 10. `deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum application, RecordTypeEnum recordType, List<Long> recordIds)`
作用:
- æŒ‰å¤šä¸ªä¸šåŠ¡ id æ‰¹é‡åˆ é™¤é™„件关系
### 7.4 æŸ¥è¯¢é™„件关系
#### 11. `getStorageAttachmentsByStorageAttachmentIds(List<Long> storageAttachmentIds)`
作用:
- æ ¹æ®é™„件关系 id æŸ¥è¯¢ `storage_attachment` è®°å½•
#### 12. `getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId)`
作用:
- æŒ‰ç”¨é€”、业务类型、业务 id æŸ¥è¯¢é™„件关系
#### 13. `getStorageAttachmentsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId)`
作用:
- æŒ‰ä¸šåŠ¡ç±»åž‹ã€ä¸šåŠ¡ id æŸ¥è¯¢é™„件关系
### 7.5 æŸ¥è¯¢æ–‡ä»¶ä¿¡æ¯ `StorageBlobVO`
#### 14. `getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(StorageAttachmentDTO storageAttachmentDTO)`
作用:
- é€šè¿‡ `StorageAttachmentDTO` æ¡ä»¶æŸ¥è¯¢æ–‡ä»¶åˆ—表
特点:
- `application` å¯é€‰
- æœ€ç»ˆè¿”回的是带预览/下载地址的 `StorageBlobVO`
#### 15. `getStorageBlobVOsByStorageAttachmentIds(List<Long> storageAttachmentIds)`
作用:
- æ ¹æ®é™„件关系 id æŸ¥è¯¢æ–‡ä»¶åˆ—表
特点:
- ä¼šè‡ªåŠ¨æž„å»ºï¼š
  - `previewURL`
  - `downloadURL`
  - `storageAttachmentId`
#### 16. `getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId)`
作用:
- æŒ‰ç”¨é€”、业务类型、业务 id æŸ¥è¯¢æ–‡ä»¶åˆ—表
#### 17. `getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId)`
作用:
- æŒ‰ä¸šåŠ¡ç±»åž‹ã€ä¸šåŠ¡ id æŸ¥è¯¢æ–‡ä»¶åˆ—表
#### 18. `getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired)`
作用:
- å’Œç¬¬ 16 ä¸ªæ–¹æ³•类似,但可以自定义链接过期时间
说明:
- `expired` å•位是分钟
- è¿”回的预览/下载地址会按这个时间生成签名
#### 19. `getStorageBlobVOsByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired)`
作用:
- æ ¹æ®é™„件关系 id æŸ¥è¯¢æ–‡ä»¶åˆ—表,并自定义链接过期时间
### 7.6 æŸ¥è¯¢é™„件视图 `StorageAttachmentVO`
#### 20. `getStorageAttachmentVOSByStorageAttachmentIds(List<Long> storageAttachmentIds)`
作用:
- æŸ¥è¯¢é™„件视图对象
特点:
- æ¯æ¡é™„件记录里会嵌套自己的 `storageBlobVOS`
#### 21. `getStorageAttachmentVOSByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired)`
作用:
- æ ¹æ®é™„件关系 id æŸ¥è¯¢é™„件视图,并自定义链接过期时间
#### 22. `getStorageAttachmentVOSByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId)`
作用:
- æŒ‰ä¸šåŠ¡ç»´åº¦æŸ¥è¯¢é™„ä»¶è§†å›¾
#### 23. `getStorageAttachmentVOSByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired)`
作用:
- æŒ‰ä¸šåŠ¡ç»´åº¦æŸ¥è¯¢é™„ä»¶è§†å›¾ï¼Œå¹¶è‡ªå®šä¹‰é“¾æŽ¥è¿‡æœŸæ—¶é—´
### 7.7 ä»…获取预览地址
#### 24. `getFilePreviewURLByStorageAttachmentIds(List<Long> storageAttachmentIds)`
作用:
- æ ¹æ®é™„件关系 id åˆ—表,返回预览地址列表
#### 25. `getFilePreviewURLByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired)`
作用:
- æ ¹æ®é™„件关系 id åˆ—表,返回带自定义过期时间的预览地址列表
#### 26. `getFilePreviewURLByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId)`
作用:
- æŒ‰ä¸šåŠ¡ç»´åº¦è¿”å›žé¢„è§ˆåœ°å€åˆ—è¡¨
#### 27. `getFilePreviewURLByApplicationAndRecordTypeAndRecordIdAndExpired(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired)`
作用:
- æŒ‰ä¸šåŠ¡ç»´åº¦è¿”å›žå¸¦è‡ªå®šä¹‰è¿‡æœŸæ—¶é—´çš„é¢„è§ˆåœ°å€åˆ—è¡¨
### 7.8 ä»…获取下载地址
#### 28. `getFileDownloadURLByStorageAttachmentIds(List<Long> storageAttachmentIds)`
作用:
- æ ¹æ®é™„件关系 id åˆ—表,返回下载地址列表
#### 29. `getFileDownloadURLByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired)`
作用:
- æ ¹æ®é™„件关系 id åˆ—表,返回带自定义过期时间的下载地址列表
#### 30. `getFileDownloadURLByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId)`
作用:
- æŒ‰ä¸šåŠ¡ç»´åº¦è¿”å›žä¸‹è½½åœ°å€åˆ—è¡¨
#### 31. `getFileDownloadURLByApplicationAndRecordTypeAndRecordIdAndExpired(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired)`
作用:
- æŒ‰ä¸šåŠ¡ç»´åº¦è¿”å›žå¸¦è‡ªå®šä¹‰è¿‡æœŸæ—¶é—´çš„ä¸‹è½½åœ°å€åˆ—è¡¨
### 7.9 æž„建签名 URL
#### 32. `buildSignedPreviewUrl(StorageBlobVO storageBlob)`
作用:
- ä½¿ç”¨ç³»ç»Ÿé»˜è®¤è¿‡æœŸæ—¶é—´ï¼Œç”Ÿæˆé¢„览链接
实际调用:
- å†…部等价于调用 `buildSignedUrl(storageBlob, "/preview/", properties.getExpired())`
#### 33. `buildSignedDownloadUrl(StorageBlobVO storageBlob)`
作用:
- ä½¿ç”¨ç³»ç»Ÿé»˜è®¤è¿‡æœŸæ—¶é—´ï¼Œç”Ÿæˆä¸‹è½½é“¾æŽ¥
实际调用:
- å†…部等价于调用 `buildSignedUrl(storageBlob, "/download/", properties.getExpired())`
#### 34. `buildSignedUrl(StorageBlobVO storageBlob, String actionPath, BigDecimal expired)`
作用:
- æž„建统一的带签名预览/下载地址
支持:
- `actionPath = "/preview/"`
- `actionPath = "/download/"`
核心逻辑:
1. æ ¡éªŒè·¯å¾„参数和文件信息
2. æ‹¼æŽ¥åŸºç¡€è®¿é—®åœ°å€
3. å¦‚æžœ `expired == -1`,不生成 token,直接走 `publicKey`
4. å¦åˆ™ç”Ÿæˆå¸¦è¿‡æœŸæ—¶é—´çš„ JWT token
5. æŠŠ token çš„使用次数信息写入 Redis
6. è¿”回最终 URL
重要说明:
- `expired` å•位为分钟
- é»˜è®¤è¿‡æœŸæ—¶é—´ä¸º 120 åˆ†é’Ÿ
- éžæ°¸ä¹…链接会受“过期时间 + ä½¿ç”¨æ¬¡æ•°é™åˆ¶â€åŒé‡æŽ§åˆ¶
### 7.10 token ä½¿ç”¨æŽ§åˆ¶
#### 35. `cacheTokenUsage(String token, long expiredMillis)`
作用:
- æŠŠ token ä½¿ç”¨æ¬¡æ•°åˆå§‹åŒ–到 Redis
特点:
- åˆå§‹å€¼å†™å…¥ä¸º `0`
- TTL ä¸Ž token è¿‡æœŸæ—¶é—´ä¿æŒä¸€è‡´
说明:
- è¿™æ˜¯ç§æœ‰æ–¹æ³•,供 `buildSignedUrl()` å†…部调用
#### 36. `buildTokenUsageKey(String token)`
作用:
- ç»Ÿä¸€ç”Ÿæˆ Redis key
格式:
- `file:token:usage:{token}`
说明:
- è¿™æ˜¯ç§æœ‰æ–¹æ³•
#### 37. `validateTokenUsage(String token)`
作用:
- æ ¡éªŒ token æ˜¯å¦è¿˜èƒ½ç»§ç»­ä½¿ç”¨
核心逻辑:
1. ä»Ž Redis è¯»å–当前使用次数
2. å¦‚果没有值,认为链接已过期或已失效
3. å¦‚果达到上限,立即删除 Redis è®°å½•并报错
4. å¦åˆ™è‡ªå¢žä¸€æ¬¡ä½¿ç”¨æ¬¡æ•°
5. å¦‚果自增后达到上限,再删除 Redis è®°å½•
说明:
- è¯¥æ–¹æ³•通常会在实际访问文件时由服务层调用
#### 38. `resolveLimit()`
作用:
- è§£æž token å¯ä½¿ç”¨æ¬¡æ•°ä¸Šé™
规则:
- `properties.getUseLimit() <= 0` æ—¶ï¼Œé»˜è®¤è¿”回 `10`
说明:
- è¿™æ˜¯ç§æœ‰æ–¹æ³•
### 7.11 è·¯å¾„与压缩
#### 39. `buildRelativePath()`
作用:
- ç”Ÿæˆæ–‡ä»¶å­˜å‚¨ç›¸å¯¹è·¯å¾„
格式:
- `yyyy/MMdd`
例如:
- `2026/0430`
用途:
- ä¸€èˆ¬ç”¨äºŽæŒ‰æ—¥æœŸåˆ†ç›®å½•保存上传文件
#### 40. `compressFile(File file)`
作用:
- å¯¹å›¾ç‰‡è¿›è¡ŒåŽ‹ç¼©ï¼Œéžå›¾ç‰‡æˆ–ä¸æ»¡è¶³æ¡ä»¶æ—¶è¿”å›žåŽŸæ–‡ä»¶
压缩条件:
1. å¼€å¯äº† `properties.getCompress()`
2. æ–‡ä»¶æ˜¯å›¾ç‰‡
3. æ–‡ä»¶å¤§å°å¤§äºŽ `properties.getNeedCompressSize()`
处理逻辑:
1. ç›®æ ‡æ–‡ä»¶åä¸º `thumb_原文件名`
2. å¦‚果压缩文件已存在,直接复用
3. ä½¿ç”¨ `Thumbnailator` æŒ‰åŽŸå°ºå¯¸åŽ‹ç¼©ç”»è´¨
4. å¦‚果压缩失败,降级返回原文件
说明:
- å½“前下载和预览接口都会调用这个方法
#### 41. `isImage(String fileName)`
作用:
- ç®€å•判断文件是否是图片
支持后缀:
- `jpg`
- `jpeg`
- `png`
说明:
- è¿™æ˜¯ç§æœ‰æ–¹æ³•,供 `compressFile()` ä½¿ç”¨
## 8. æŽ¨èä½¿ç”¨é¡ºåº
业务上最常见的接入顺序如下:
1. å‰ç«¯ä¸Šä¼ æ–‡ä»¶åˆ° `/common/upload`
2. æ‹¿åˆ°è¿”回结果中的文件 id
3. ä¸šåŠ¡ä¿å­˜æ—¶è°ƒç”¨ `/storageAttachment/add`
4. ä¼ å…¥ `application + recordType + recordId + storageBlobDTOs`
5. åŽç»­é¡µé¢å›žæ˜¾æ—¶æŒ‰ä¸šåŠ¡æ¡ä»¶è°ƒç”¨é™„ä»¶æŸ¥è¯¢
6. å‰ç«¯ä½¿ç”¨è¿”回的 `previewURL` æˆ– `downloadURL`
## 9. å¸¸è§æ³¨æ„ç‚¹
### 9.1 å…ˆä¸Šä¼ ï¼Œå†ç»‘定
- `/common/upload` åªè´Ÿè´£æ–‡ä»¶å…¥åº“
- `/storageAttachment/add` æ‰æ˜¯å’Œä¸šåŠ¡æ•°æ®å»ºç«‹å…³ç³»
### 9.2 `application` å¾ˆé‡è¦
- åŒä¸€æ¡ `recordId` ä¸‹å¯èƒ½æœ‰å¤šç»„不同用途附件
- åˆ é™¤å’ŒæŸ¥è¯¢æ—¶ï¼Œç»å¸¸ä¾èµ– `application`
### 9.3 ä¸‹è½½é“¾æŽ¥ä¸æ˜¯æ°¸ä¹…有效
- æ™®é€šé“¾æŽ¥ä¸€èˆ¬é€šè¿‡ JWT token æŽ§åˆ¶
- åŒæ—¶å—过期时间和使用次数限制
### 9.4 å…¬å…±æ–‡ä»¶è¦æ…Žç”¨
- `public/upload` ä¸Šä¼ çš„æ–‡ä»¶å¯èµ°æ°¸ä¹…公开访问
- é€‚合公开资源,不适合敏感文件
### 9.5 å›¾ç‰‡é¢„览/下载可能返回压缩文件
- å½“前控制器在下载和预览前都会调用 `compressFile()`
- å¤§å›¾åœ¨è®¿é—®æ—¶å¯èƒ½ä½¿ç”¨åŽ‹ç¼©åŽçš„å‰¯æœ¬
## 10. ä¸€å¥è¯æ€»ç»“
本项目的文件上传方案是“两阶段模型”:
- ç¬¬ä¸€é˜¶æ®µä¸Šä¼ æ–‡ä»¶ï¼Œç”Ÿæˆ `storage_blob`
- ç¬¬äºŒé˜¶æ®µç»‘定业务,生成 `storage_attachment`
而 `FileUtil` åˆ™è´Ÿè´£æŠŠâ€œä¸Šä¼ åŽçš„æ–‡ä»¶â€å˜æˆâ€œå¯æŸ¥è¯¢ã€å¯é¢„览、可下载、可删除、可控时效”的完整附件能力。
doc/20260428_create_table_ai_chat_session.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS `ai_chat_session` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
  `memory_id` varchar(64) NOT NULL COMMENT '会话ID',
  `user_id` bigint NOT NULL COMMENT '用户ID',
  `tenant_id` bigint DEFAULT NULL COMMENT '租户ID',
  `title` varchar(128) DEFAULT NULL COMMENT '会话标题',
  `last_message` varchar(512) DEFAULT NULL COMMENT '最后一条消息',
  `message_count` int NOT NULL DEFAULT 0 COMMENT '消息数量',
  `last_chat_time` datetime DEFAULT NULL COMMENT '最后聊天时间',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_ai_chat_session_memory_id` (`memory_id`),
  KEY `idx_ai_chat_session_user_tenant` (`user_id`, `tenant_id`),
  KEY `idx_ai_chat_session_last_chat_time` (`last_chat_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='AI历史会话元数据表';
doc/20260506_add_ai_enabled_to_sys_user.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
alter table sys_user
    add ai_enabled tinyint(1) not null default 0 comment '是否开通AI功能(0否 1是)';
doc/20260506_Óû§AI¹¦ÄÜ×Ö¶Îǰ¶ËÁªµ÷˵Ã÷.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
# ç”¨æˆ· AI åŠŸèƒ½å­—æ®µå‰ç«¯è”è°ƒè¯´æ˜Ž
## èƒŒæ™¯
后端已在用户表增加隐藏字段 `ai_enabled`,用于标识用户是否开通 AI åŠŸèƒ½ã€‚
该字段不开放给用户资料维护接口(不通过用户编辑页面下发/回写)。
## å­—段定义
| å­—段 | ç±»åž‹ | é»˜è®¤å€¼ | è¯´æ˜Ž |
| --- | --- | --- | --- |
| ai_enabled | tinyint(1) | 0 | æ˜¯å¦å¼€é€š AI åŠŸèƒ½ï¼š`0`=未开通,`1`=已开通 |
## è”调接口
登录后调用:
```http
GET /getInfo
```
返回中新增顶层字段 `aiEnabled`:
```json
{
  "code": 200,
  "msg": "操作成功",
  "user": {
    "userId": 1,
    "userName": "admin"
  },
  "aiEnabled": 1,
  "roles": [
    "admin"
  ],
  "permissions": [
    "*:*:*"
  ]
}
```
## å‰ç«¯ä½¿ç”¨å»ºè®®
1. ç™»å½•成功后按现有流程调用 `/getInfo`。
2. ä»Žå“åº”顶层读取 `aiEnabled`。
3. åˆ¤å®šé€»è¾‘建议:
   - `aiEnabled === 1`:展示/放开 AI ç›¸å…³å…¥å£ã€‚
   - å…¶ä»–值(`0`、`null`、`undefined`):按未开通处理。
## ç¼“存说明
- `aiEnabled` å·²æ”¾å…¥ç™»å½•缓存对象(`LoginUser`)并随 token ç”Ÿå‘½å‘¨æœŸç®¡ç†ã€‚
- ç¼“å­˜ key å‰ç¼€ï¼š`login_tokens:`。
doc/add.sql
ÎļþÒÑɾ³ý
doc/create_table_customer_user.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
drop table if exists customer_user;
create table customer_user
(
    id          bigint auto_increment
        primary key,
    customer_id bigint not null default 0 comment '客户id',
    user_id     bigint not null default 0 comment '用户id',
    create_time datetime null comment '录入时间',
    tenant_id   bigint not null default 0 comment '租户id'
);
doc/encoding-rules.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
# ç¼–码与防乱码规范
本项目统一采用以下规则,避免中文乱码和编码冲突:
## å¿…须遵守
1. æ–°å»ºæ–‡ä»¶ç»Ÿä¸€ä½¿ç”¨ `UTF-8` ç¼–码,且不带 `BOM`
2. ä¿®æ”¹å·²æœ‰æ–‡ä»¶å‰å…ˆæ£€æŸ¥åŽŸæ–‡ä»¶ç¼–ç 
3. å¦‚果旧文件不是 `UTF-8`,默认先保持原编码,避免把中文写坏
4. ä¸­æ–‡å†…容直接保留,不要转义成 `\uXXXX`
5. è¾“出、生成、批量替换时不得出现乱码
6. å¦‚果终端显示异常,先校验文件真实字节和编码,再继续修改
## æŽ¨èåšæ³•
- ä¼˜å…ˆéµå¾ªé¡¹ç›®æ ¹ç›®å½• `.editorconfig`
- å¯¹åŒ…含大量中文注释、字符串的文件,优先小范围修改
- æ‰¹é‡æ›¿æ¢å‰å…ˆåšç¼–码抽样检查,再执行批量修改
- æ¯æ¬¡æ¶‰åŠä¸­æ–‡å†…容的批量改动后,及时编译或检查 diff
## é¡¹ç›®å†…对应位置
- ç¼–辑器默认规则:`/.editorconfig`
- AI ä¸Žåä½œçº¦æŸï¼š`/AGENTS.md`
doc/²É¹ºÖÇÄÜÌå¶àÎļþ·ÖÎöǰ¶ËÁªµ÷˵Ã÷.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,183 @@
# é‡‡è´­æ™ºèƒ½ä½“多文件分析前端联调说明
## æµç¨‹è¯´æ˜Ž
后端已新增采购智能体多文件分析确认流程:
1. å‰ç«¯ä¸Šä¼ å¤šä¸ªé‡‡è´­ç›¸å…³æ–‡ä»¶ï¼Œå¹¶é™„带用户要求。
2. åŽç«¯æå–文件内容,交给采购智能体分析。
3. æ™ºèƒ½ä½“返回待客户确认的结构化 JSON。
4. å‰ç«¯å±•示摘要、风险、缺失字段和待处理数据。
5. å®¢æˆ·ç¡®è®¤æˆ–补充数据后,前端调用确认接口。
6. åŽç«¯æ ¹æ®ç¡®è®¤åŽçš„æ•°æ®æ‰§è¡Œå¯¹åº”采购业务处理。
分析接口不会落库,只有确认接口会执行业务处理。
## æŽ¥å£ 1:采购多文件分析
```http
POST /purchase-ai/analyze-files
Content-Type: multipart/form-data
```
请求参数:
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
| --- | --- | --- | --- |
| files | file[] | æ˜¯ | å¤šæ–‡ä»¶ä¸Šä¼ å­—段,字段名必须是 `files` |
| message | string | å¦ | ç”¨æˆ·è¦æ±‚,例如:请根据这些采购合同和明细整理采购台账数据 |
| memoryId | string | å¦ | ä¼šè¯ ID,不传时后端会自动生成内部会话 |
返回:
```http
Content-Type: text/stream;charset=utf-8
```
前端需要拼接完整流式文本后再执行 `JSON.parse`。
返回 JSON ç»“构示例:
```json
{
  "success": true,
  "businessType": "purchase_ledger",
  "action": "confirm_required",
  "description": "已根据文件整理出采购台账草稿,请确认。",
  "confidence": 0.86,
  "missingFields": [],
  "warnings": [],
  "payload": {},
  "preview": []
}
```
字段说明:
| å­—段 | è¯´æ˜Ž |
| --- | --- |
| success | æ˜¯å¦åˆ†æžæˆåŠŸ |
| businessType | ä¸šåŠ¡ç±»åž‹ï¼š`purchase_ledger`、`payment_registration`、`purchase_return_order`、`unknown` |
| action | å›ºå®šä¸º `confirm_required` |
| description | ä¸­æ–‡è¯´æ˜Ž |
| confidence | ç½®ä¿¡åº¦ï¼Œ0 åˆ° 1 |
| missingFields | ç¼ºå¤±å­—段,前端需要提示用户补充 |
| warnings | é£Žé™©æç¤º |
| payload | å¾…客户确认并提交给确认接口的数据 |
| preview | ç»™å®¢æˆ·ç¡®è®¤ç”¨çš„中文摘要 |
## æŽ¥å£ 2:确认并执行业务处理
```http
POST /purchase-ai/analyze-files/confirm
Content-Type: application/json
```
请求体:
```json
{
  "businessType": "purchase_ledger",
  "payload": {
  }
}
```
当前支持的 `businessType`:
| businessType | è¯´æ˜Ž | åŽç«¯å¤„理 |
| --- | --- | --- |
| purchase_ledger | é‡‡è´­å°è´¦ | è°ƒç”¨é‡‡è´­å°è´¦æ–°å¢ž/编辑 |
| payment_registration | ä»˜æ¬¾ç™»è®° | è°ƒç”¨ä»˜æ¬¾ç™»è®°æ–°å¢ž |
| purchase_return_order | é‡‡è´­é€€è´§å• | è°ƒç”¨é‡‡è´­é€€è´§å•新增 |
确认接口返回普通 `AjaxResult`。
## é‡‡è´­å°è´¦ Payload çº¦å®š
采购台账确认推荐使用两个集合:
```json
{
 "businessType": "purchase_ledger",
  "payload": {
    "purchaseLedgers": []
  }
}
```
字段约定:
- `purchaseLedgers` æ”¾é‡‡è´­è®¢å•/采购台账主表数据,字段名必须与 `PurchaseLedgerDto` ä¿æŒä¸€è‡´ã€‚
- äº§å“æ˜Žç»†æ”¾åœ¨æ¯æ¡ `purchaseLedgers[i].productData` ä¸­ï¼Œå¯¹åº” `PurchaseLedgerDto` çš„ `private List<SalesLedgerProduct> productData;`。
- é¡¶å±‚ `payload.productData` ä»…作为旧格式兼容,不建议前端继续使用。
- æ–‡ä»¶ä¸­çš„“采购单号”就是“采购合同号”,前端可以统一映射成 `purchaseContractNumber`。
- æ–‡ä»¶ä¸­çš„“销售单号”就是“销售合同号”,前端可以统一映射成 `salesContractNo`。
- æ—¥æœŸå­—段统一使用 `yyyy-MM-dd`,例如 `2026-04-30`;不要提交 `4/30/26`、`2026/4/30`、`2026å¹´4月30日` æˆ–带时分秒的格式。
- é‡‡è´­å°è´¦ä¸éœ€è¦å‰ç«¯ä¼ å®¡æ‰¹äººï¼Œä¸è¦æäº¤ `approveUserIds`、`approverId`。
- `missingFields` é¢å‘客户展示,只放中文缺失项,例如 `供应商名称`、`含税单价`,不要展示英文字段名。
- é‡‡è´­å°è´¦ä¸šåŠ¡å¿…å¡«ï¼šé‡‡è´­åˆåŒå·ã€ä¾›åº”å•†åç§°æˆ–ä¾›åº”å•†ID。
- äº§å“æ˜Žç»†ä¸šåŠ¡å¿…å¡«ï¼šäº§å“åç§°ã€è§„æ ¼åž‹å·ã€å•ä½ã€æ•°é‡ã€å«ç¨Žå•ä»·ã€å«ç¨Žæ€»ä»·ï¼›å¦‚æžœåªæœ‰å«ç¨Žæ€»ä»·å’Œæ•°é‡ï¼ŒåŽç«¯ä¼šè‡ªåŠ¨è®¡ç®—å«ç¨Žå•ä»·ï¼›å¦‚æžœåªæœ‰å«ç¨Žå•ä»·å’Œæ•°é‡ï¼ŒåŽç«¯ä¼šè‡ªåŠ¨è®¡ç®—å«ç¨Žæ€»ä»·ã€‚
- äº§å“æ˜Žç»†å¯é€šè¿‡ `purchaseContractNumber`、`purchaseContractNo`、`采购合同号`、`采购单号`、`采购订单号` å…³è”对应采购订单;也可通过 `salesContractNo`、`salesContractNumber`、`销售合同号`、`销售单号`、`销售订单号` è¾…助匹配。
`purchaseLedgers` å•条记录允许使用的 `PurchaseLedgerDto` å­—段:
```text
entryDateStart, entryDateEnd, id, purchaseContractNumber,
supplierId, supplierName, isWhite, recorderId, recorderName, salesContractNo,
salesContractNoId, projectName, entryDate, executionDate, remarks,
attachmentMaterials, createdAt, updatedAt, salesLedgerId, hasChildren, Type,
productData, tempFileIds, SalesLedgerFiles, phoneNumber, businessPersonId,
productId, productModelId, invoiceNumber, invoiceAmount, ticketRegistrationId,
contractAmount, receiptPaymentAmount, unReceiptPaymentAmount, type,
paymentMethod, approvalStatus, templateName
```
示例:
```json
{
  "purchaseLedgers": [
    {
      "purchaseContractNumber": "CG-2026-001",
      "supplierName": "南通示例供应商",
      "salesContractNo": "XS-2026-001",
      "projectName": "示例项目",
      "entryDate": "2026-04-30",
      "executionDate": "2026-04-30",
      "contractAmount": 120000,
      "remarks": "由文件分析生成,待确认",
      "productData": [
        {
          "productCategory": "示例产品",
          "specificationModel": "型号A",
          "unit": "ä»¶",
          "quantity": 10,
          "taxInclusiveUnitPrice": 12000,
          "taxInclusiveTotalPrice": 120000,
          "type": 2
        }
      ]
    }
  ]
}
```
## å‰ç«¯å¤„理建议
1. ç”¨æˆ·é€‰æ‹©å¤šä¸ªæ–‡ä»¶ï¼Œå¡«å†™åˆ†æžè¦æ±‚。
2. ä½¿ç”¨ `multipart/form-data` è°ƒç”¨ `/purchase-ai/analyze-files`。
3. æ‹¼æŽ¥æµå¼è¿”回文本。
4. å¯¹å®Œæ•´æ–‡æœ¬æ‰§è¡Œ `JSON.parse`。
5. å±•示 `preview`、`warnings`、`missingFields` å’Œ `payload`。
6. å¦‚æžœ `missingFields` ä¸ä¸ºç©ºï¼Œå¼•导用户补充或编辑 `payload`。
7. ç”¨æˆ·ç¡®è®¤åŽï¼Œå°† `businessType` å’Œç¡®è®¤åŽçš„ `payload` æäº¤åˆ° `/purchase-ai/analyze-files/confirm`。
## æ³¨æ„äº‹é¡¹
- æ–‡ä»¶ä¸Šä¼ å­—段名必须是 `files`。
- åˆ†æžæŽ¥å£åªç”Ÿæˆå¾…确认数据,不会执行业务落库。
- ç¡®è®¤æŽ¥å£æ‰ä¼šæ‰§è¡Œä¸šåŠ¡å¤„ç†ã€‚
- å¦‚æžœ `payload` ç¼ºå°‘必要业务 ID,确认接口可能返回业务校验错误。
- å‰ç«¯éœ€è¦æŠŠ `missingFields` æ˜Žç¡®å±•示给用户。
- AI è¿”回内容按合法 JSON å¤„理,不要按普通自然语言展示。
pom.xml
@@ -15,44 +15,78 @@
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.15</version>
        <version>3.5.13</version>
        <relativePath/>
    </parent>
    <properties>
        <maven.compiler.source>25</maven.compiler.source>
        <maven.compiler.target>25</maven.compiler.target>
        <maven.compiler.release>25</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <java.version>25</java.version>
        <lombok.version>1.18.44</lombok.version>
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
        <pagehelper.spring.boot.starter.version>1.4.7</pagehelper.spring.boot.starter.version>
        <pagehelper.spring.boot.starter.version>2.1.1</pagehelper.spring.boot.starter.version>
        <fastjson.version>2.0.53</fastjson.version>
        <druid.version>1.2.23</druid.version>
        <commons.io.version>2.13.0</commons.io.version>
        <bitwalker.version>1.21</bitwalker.version>
        <jwt.version>0.9.1</jwt.version>
        <jwt.version>0.13.0</jwt.version>
        <kaptcha.version>2.3.3</kaptcha.version>
        <swagger.version>3.0.0</swagger.version>
        <knife4j.version>4.5.0</knife4j.version>
        <springdoc.version>2.8.17</springdoc.version>
        <swagger.annotations.version>1.6.15</swagger.annotations.version>
        <poi.version>5.2.3</poi.version>
        <oshi.version>6.6.5</oshi.version>
        <velocity.version>2.3</velocity.version>
        <!-- override dependency version -->
        <tomcat.version>9.0.102</tomcat.version>
        <!--        <tomcat.version>9.0.102</tomcat.version>-->
        <minio.version>8.4.3</minio.version>
        <okhttp.version>4.9.0</okhttp.version>
        <hutool.version>5.8.18</hutool.version>
        <logback.version>1.2.13</logback.version>
        <spring-security.version>5.7.12</spring-security.version>
        <spring-framework.version>5.3.39</spring-framework.version>
        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
        <hutool.version>5.8.43</hutool.version>
        <!--        <logback.version>1.2.13</logback.version>-->
        <!--        <spring-security.version>5.7.12</spring-security.version>-->
        <!--        <spring-framework.version>5.3.39</spring-framework.version>-->
        <mybatis-plus.version>3.5.16</mybatis-plus.version>
        <getui-sdk.version>1.0.7.0</getui-sdk.version>
        <jsqlparser.version>4.9</jsqlparser.version>
        <thumbnailator.version>0.4.20</thumbnailator.version>
        <langchain4j.version>1.0.0-beta3</langchain4j.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-bom</artifactId>
                <version>${langchain4j.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>dev.langchain4j</groupId>
                <artifactId>langchain4j-community-bom</artifactId>
                <version>${langchain4j.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <!-- ruoyi-springboot2 / swagger knife4j é…ç½® -->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>${knife4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springdoc</groupId>
            <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
            <version>${springdoc.version}</version>
        </dependency>
        <!-- SpringBoot æ ¸å¿ƒåŒ… -->
@@ -99,10 +133,48 @@
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <!-- pool å¯¹è±¡æ±  -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-pinecone</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-reactor</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-ollama-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-community-dashscope-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-mcp</artifactId>
        </dependency>
        <!-- Mysql驱动包 -->
@@ -110,13 +182,14 @@
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>8.0.33</version>
        </dependency>
        <!-- FreeMarker æ¨¡æ¿å¼•擎:处理变量占位符 -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.32</version>
            <version>2.3.33</version>
        </dependency>
        <!-- pagehelper åˆ†é¡µæ’ä»¶ -->
@@ -129,14 +202,26 @@
        <!-- é˜¿é‡Œæ•°æ®åº“连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <artifactId>druid-spring-boot-3-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-jsqlparser</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
@@ -145,6 +230,12 @@
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.github.jsqlparser</groupId>
            <artifactId>jsqlparser</artifactId>
            <version>${jsqlparser.version}</version>
        </dependency>
        <!-- è‡ªå®šä¹‰éªŒè¯æ³¨è§£ -->
@@ -189,35 +280,37 @@
        <!-- Token生成与解析-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <artifactId>jjwt-api</artifactId>
            <version>${jwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>${jwt.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>${jwt.version}</version>
            <scope>runtime</scope>
        </dependency>
        <!-- Jaxb -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <groupId>jakarta.xml.bind</groupId>
            <artifactId>jakarta.xml.bind-api</artifactId>
        </dependency>
        <!-- Swagger3依赖 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>${swagger.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>io.swagger</groupId>
                    <artifactId>swagger-models</artifactId>
                </exclusion>
            </exclusions>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>${swagger.annotations.version}</version>
        </dependency>
        <!-- é˜²æ­¢è¿›å…¥swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.6.2</version>
        </dependency>
        <!-- èŽ·å–ç³»ç»Ÿä¿¡æ¯ -->
        <dependency>
@@ -268,6 +361,7 @@
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
@@ -315,22 +409,22 @@
            <version>${getui-sdk.version}</version>
            <scope>compile</scope>
        </dependency>
        <!--hutool工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.43</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>
        <dependency>
            <groupId>net.coobird</groupId>
            <artifactId>thumbnailator</artifactId>
            <version>${thumbnailator.version}</version>
        </dependency>
    </dependencies>
@@ -339,11 +433,24 @@
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.14.1</version>
                <configuration>
                    <release>${maven.compiler.release}</release>
                    <proc>full</proc>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork> <!-- å¦‚果没有该配置,devtools不会生效 -->
                </configuration>
            </plugin>
        </plugins>
    </build>
src/main/java/com/ruoyi/CodeGenerator.java
@@ -5,7 +5,8 @@
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
@@ -19,11 +20,11 @@
// æ¼”示例子,执行 main æ–¹æ³•控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
    public static String database_url = "jdbc:mysql://1.15.17.182:9999/product-inventory-management-new";
    public static String database_url = "jdbc:mysql://localhost:3300/product-inventory-management-new-pro";
    public static String database_username = "root";
    public static String database_password= "xd@123456..";
    public static String database_password= "root";
    public static String author = "芯导软件(江苏)有限公司";
    public static String model = "staff"; // æ¨¡å—
    public static String model = "sales"; // æ¨¡å—
    public static String setParent = "com.ruoyi."+ model; // åŒ…路径
    public static String tablePrefix = ""; // è®¾ç½®è¿‡æ»¤è¡¨å‰ç¼€
    public static void main(String[] args) {
@@ -81,7 +82,7 @@
                                    new Column("update_time", FieldFill.INSERT_UPDATE),
                                    new Column("create_user", FieldFill.INSERT),
                                    new Column("update_user", FieldFill.INSERT_UPDATE),
                                    new Column("tenant_id", FieldFill.INSERT)
                                    new Column("dept_id", FieldFill.INSERT)
                            )
                            .idType(IdType.AUTO) // è‡ªå¢žä¸»é”®
@@ -118,9 +119,8 @@
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
        if (scanner.hasNextLine()) {
            String ipt = scanner.nextLine();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
src/main/java/com/ruoyi/RuoYiServletInitializer.java
@@ -3,6 +3,9 @@
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
/**
 * web容器中进行部署
 * 
@@ -15,4 +18,9 @@
    {
        return application.sources(RuoYiApplication.class);
    }
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
    }
}
src/main/java/com/ruoyi/ScheduleTask.java
@@ -5,7 +5,7 @@
import com.ruoyi.project.system.mapper.SysNoticeMapper;
import com.ruoyi.safe.mapper.SafeTrainingMapper;
import com.ruoyi.safe.pojo.SafeTraining;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@@ -14,14 +14,13 @@
import java.util.List;
@Component
@RequiredArgsConstructor
//定时任务汇总
public class ScheduleTask {
    @Autowired
    private SafeTrainingMapper safeTrainingMapper;
    private final SafeTrainingMapper safeTrainingMapper;
    @Autowired
    private SysNoticeMapper noticeMapper;
    private final SysNoticeMapper noticeMapper;
    //定时任务(15分钟执行一次--判断培训计划数据,状态做变更)
    @Scheduled(cron = "0 0/15 * * * ?")
src/main/java/com/ruoyi/account/controller/AccountExpenseController.java
@@ -2,23 +2,20 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.dto.ReportDateDto;
import com.ruoyi.account.mapper.AccountIncomeMapper;
import com.ruoyi.account.pojo.AccountExpense;
import com.ruoyi.account.pojo.AccountIncome;
import com.ruoyi.account.service.AccountExpenseService;
import com.ruoyi.account.service.AccountFileService;
import com.ruoyi.account.service.AccountIncomeService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.dto.DateQueryDto;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
@@ -27,7 +24,7 @@
 */
@RestController
@RequestMapping("/account/accountExpense")
@Api(tags = "财务管理--支出管理")
@Tag(name = "财务管理--支出管理")
public class AccountExpenseController {
    @Resource
@@ -43,7 +40,7 @@
     * @return
     */
    @PostMapping("/add")
    @ApiOperation("新增")
    @Operation(summary = "新增")
    public AjaxResult add(@RequestBody AccountExpense accountExpense) {
        accountExpense.setInputTime(new Date());
        LoginUser loginUser = SecurityUtils.getLoginUser();
@@ -57,7 +54,7 @@
     * @return
     */
    @DeleteMapping("/del")
    @ApiOperation("删除")
    @Operation(summary = "删除")
    public AjaxResult delQualityInspect(@RequestBody List<Integer> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return AjaxResult.error("请选择至少一条数据");
@@ -72,7 +69,7 @@
     * @return
     */
    @PostMapping("/update")
    @ApiOperation("修改")
    @Operation(summary = "修改")
    public AjaxResult update(@RequestBody AccountExpense accountExpense) {
        return AjaxResult.success(accountExpenseService.updateById(accountExpense));
    }
@@ -84,7 +81,7 @@
     * @return
     */
    @GetMapping("/listPage")
    @ApiOperation("分页查询")
    @Operation(summary = "分页查询")
    public AjaxResult accountExpenseListPage(Page page, AccountExpense accountExpense) {
        return AjaxResult.success(accountExpenseService.accountExpenseListPage(page, accountExpense));
    }
@@ -95,7 +92,7 @@
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("详情")
    @Operation(summary = "详情")
    public AjaxResult accountExpenseDetail(@PathVariable("id") Integer id) {
        return AjaxResult.success(accountExpenseService.getById(id));
    }
@@ -106,7 +103,7 @@
     * @param accountExpense
     */
    @PostMapping("/export")
    @ApiOperation("导出")
    @Operation(summary = "导出")
    public void accountExpenseExport(HttpServletResponse response,AccountExpense accountExpense) {
        accountExpenseService.accountExpenseExport(response, accountExpense);
    }
@@ -117,7 +114,7 @@
     * @return
     */
    @GetMapping("/report/forms")
    @ApiOperation("财务报表图表查询")
    @Operation(summary = "财务报表图表查询")
    public AjaxResult report(DateQueryDto dateQueryDto) {
        return AjaxResult.success(accountExpenseService.report(dateQueryDto));
    }
@@ -127,7 +124,7 @@
     * @return
     */
    @GetMapping("/report/analysis")
    @ApiOperation("财务报表-财务分析")
    @Operation(summary = "财务报表-财务分析")
    public AjaxResult analysis() {
        return AjaxResult.success(accountExpenseService.analysis());
    }
@@ -138,7 +135,7 @@
     * @return
     */
    @GetMapping("/report/income")
    @ApiOperation("财务报表图表收入年度查询")
    @Operation(summary = "财务报表图表收入年度查询")
    public AjaxResult reportIncome(ReportDateDto reportDateDto) {
        return AjaxResult.success(accountIncomeService.reportIncome(reportDateDto));
    }
@@ -149,7 +146,7 @@
     * @return
     */
    @GetMapping("/report/expense")
    @ApiOperation("财务报表图表支出年度查询")
    @Operation(summary = "财务报表图表支出年度查询")
    public AjaxResult reportExpense(ReportDateDto reportDateDto) {
        return AjaxResult.success(accountExpenseService.reportExpense(reportDateDto));
    }
src/main/java/com/ruoyi/account/controller/AccountFileController.java
@@ -6,12 +6,12 @@
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.quality.pojo.QualityInspectFile;
import com.ruoyi.quality.service.IQualityInspectFileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import java.util.List;
/**
@@ -19,7 +19,7 @@
 */
@RestController
@RequestMapping("/account/accountFile")
@Api(tags = "财务附件")
@Tag(name = "财务附件")
public class AccountFileController {
@@ -33,7 +33,7 @@
     * @return
     */
    @PostMapping("/add")
    @ApiOperation("新增")
    @Operation(summary = "新增")
    public AjaxResult add(@RequestBody AccountFile accountFile) {
        return AjaxResult.success(accountFileService.save(accountFile));
    }
@@ -44,7 +44,7 @@
     * @return
     */
    @DeleteMapping("/del")
    @ApiOperation("删除")
    @Operation(summary = "删除")
    public AjaxResult delAccountFile(@RequestBody List<Integer> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return AjaxResult.error("请选择至少一条数据");
@@ -60,7 +60,7 @@
     * @return
     */
    @GetMapping("/listPage")
    @ApiOperation("分页查询")
    @Operation(summary = "分页查询")
    public AjaxResult accountFileListPage(Page page, AccountFile accountFile) {
        return AjaxResult.success(accountFileService.accountFileListPage(page, accountFile));
    }
src/main/java/com/ruoyi/account/controller/AccountIncomeController.java
@@ -14,13 +14,13 @@
import com.ruoyi.quality.service.IQualityInspectFileService;
import com.ruoyi.quality.service.IQualityInspectParamService;
import com.ruoyi.quality.service.IQualityInspectService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
@@ -29,7 +29,7 @@
 */
@RestController
@RequestMapping("/account/accountIncome")
@Api(tags = "财务管理--收入管理")
@Tag(name = "财务管理--收入管理")
public class AccountIncomeController {
    @Resource
@@ -42,7 +42,7 @@
     * @return
     */
    @PostMapping("/add")
    @ApiOperation("新增")
    @Operation(summary = "新增")
    public AjaxResult add(@RequestBody AccountIncome accountIncome) {
        accountIncome.setInputTime(new Date());
        LoginUser loginUser = SecurityUtils.getLoginUser();
@@ -56,7 +56,7 @@
     * @return
     */
    @DeleteMapping("/del")
    @ApiOperation("删除")
    @Operation(summary = "删除")
    public AjaxResult delQualityInspect(@RequestBody List<Integer> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return AjaxResult.error("请选择至少一条数据");
@@ -71,7 +71,7 @@
     * @return
     */
    @PostMapping("/update")
    @ApiOperation("修改")
    @Operation(summary = "修改")
    public AjaxResult update(@RequestBody AccountIncome accountIncome) {
        return AjaxResult.success(accountIncomeService.updateById(accountIncome));
    }
@@ -83,7 +83,7 @@
     * @return
     */
    @GetMapping("/listPage")
    @ApiOperation("分页查询")
    @Operation(summary = "分页查询")
    public AjaxResult accountIncomeListPage(Page page, AccountIncome accountIncome) {
        return AjaxResult.success(accountIncomeService.accountIncomeListPage(page, accountIncome));
    }
@@ -94,7 +94,7 @@
     * @return
     */
    @GetMapping("/{id}")
    @ApiOperation("详情")
    @Operation(summary = "详情")
    public AjaxResult accountIncomeDetail(@PathVariable("id") Integer id) {
        return AjaxResult.success(accountIncomeService.getById(id));
    }
@@ -105,7 +105,7 @@
     * @param accountIncome
     */
    @PostMapping("/export")
    @ApiOperation("导出")
    @Operation(summary = "导出")
    public void accountIncomeExport(HttpServletResponse response,AccountIncome accountIncome) {
        accountIncomeService.accountIncomeExport(response, accountIncome);
    }
src/main/java/com/ruoyi/account/controller/AccountingController.java
@@ -4,9 +4,9 @@
import com.ruoyi.account.service.impl.AccountingServiceImpl;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -16,28 +16,28 @@
 * @author :yys
 * @date : 2026/1/17 10:40
 */
@Api(tags = "会计核算")
@Tag(name = "会计核算")
@RestController
@RequestMapping("/accounting")
@AllArgsConstructor
public class AccountingController extends BaseController {
    @Autowired
    private AccountingServiceImpl accountingService;
    @ApiOperation("总计")
    @Operation(summary = "总计")
    @GetMapping("/total")
    public AjaxResult total(@RequestParam Integer year) {
        return accountingService.total(year);
    }
    @ApiOperation("设备类型分布")
    @Operation(summary = "设备类型分布")
    @GetMapping("/deviceTypeDistribution")
    public AjaxResult deviceTypeDistribution(@RequestParam Integer year) {
        return accountingService.deviceTypeDistribution(year);
    }
    @ApiOperation("设备分页查询计算折旧")
    @Operation(summary = "设备分页查询计算折旧")
    @GetMapping("/calculateDepreciation")
    public AjaxResult calculateDepreciation(Page page, @RequestParam Integer year) {
        return accountingService.calculateDepreciation(page,year);
src/main/java/com/ruoyi/account/controller/BorrowInfoController.java
@@ -7,13 +7,14 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
@@ -24,16 +25,16 @@
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-01-15 02:57:29
 */
@Api(tags = "借款信息表")
@Tag(name = "借款信息表")
@RestController
@RequestMapping("/borrowInfo")
@AllArgsConstructor
public class BorrowInfoController {
    @Autowired
    private BorrowInfoService borrowInfoService;
    @GetMapping("/listPage")
    @ApiOperation("分页查询")
    @Operation(summary = "分页查询")
    public AjaxResult listPage(Page page, BorrowInfo borrowInfo) {
        return borrowInfoService.listPage(page,borrowInfo);
    }
@@ -73,7 +74,7 @@
     * å¯¼å‡º
     */
    @PostMapping("/export")
    @ApiOperation(value = "导出借款信息")
    @Operation(summary = "导出借款信息")
    public void export(HttpServletResponse response, BorrowInfo borrowInfo) {
        List<BorrowInfo> list = borrowInfoService.list();
        ExcelUtil<BorrowInfo> util = new ExcelUtil<>(BorrowInfo.class);
src/main/java/com/ruoyi/account/controller/SalesReceiptReturnController.java
@@ -6,7 +6,8 @@
import com.ruoyi.account.service.SalesReceiptReturnService;
import com.ruoyi.account.service.impl.SalesReceiptReturnServiceImpl;
import com.ruoyi.framework.web.domain.R;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -22,15 +23,15 @@
 */
@RestController
@RequestMapping("/salesReceiptReturn")
@AllArgsConstructor
public class SalesReceiptReturnController {
    @Autowired
    private  SalesReceiptReturnService salesReceiptReturnService;
    @GetMapping("/page")
    @ApiOperation("收款退货表-分页查询")
    @Operation(summary = "收款退货表-分页查询")
    public R<IPage<SalesReceiptReturnDto>> page(SalesReceiptReturnDto salesReceiptReturnDto) {
        return R.ok(salesReceiptReturnService.pageSalesReceiptReturnDto(salesReceiptReturnDto));
    }
src/main/java/com/ruoyi/account/controller/SalesRefundAmountOrderController.java
@@ -5,7 +5,8 @@
import com.ruoyi.account.pojo.SalesRefundAmountOrder;
import com.ruoyi.account.service.SalesRefundAmountOrderService;
import com.ruoyi.framework.web.domain.R;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -23,9 +24,9 @@
 */
@RestController
@RequestMapping("/salesRefundAmountOrder")
@AllArgsConstructor
public class SalesRefundAmountOrderController {
    @Autowired
    private SalesRefundAmountOrderService salesRefundAmountOrderService;
    @GetMapping("/page")
@@ -33,7 +34,7 @@
        return R.ok(salesRefundAmountOrderService.pageSalesRefundAmountOrderDto(page, salesRefundAmountOrder));
    }
    @ApiOperation("处理")
    @Operation(summary = "处理")
    @PostMapping("/dispose")
    public R dispose( SalesRefundAmountOrderDto salesRefundAmountOrderId) {
        return R.ok(salesRefundAmountOrderService.dispose(salesRefundAmountOrderId));
src/main/java/com/ruoyi/account/dto/AccountDto.java
@@ -6,11 +6,11 @@
import com.ruoyi.account.pojo.AccountIncome;
import com.ruoyi.dto.DateQueryDto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
src/main/java/com/ruoyi/account/dto/DeviceTypeDistributionVO.java
@@ -1,6 +1,5 @@
package com.ruoyi.account.dto;
import com.mchange.v1.util.ListUtils;
import lombok.Data;
import java.math.BigDecimal;
src/main/java/com/ruoyi/account/dto/SalesRefundAmountOrderDto.java
@@ -1,23 +1,23 @@
package com.ruoyi.account.dto;
import com.ruoyi.account.pojo.SalesRefundAmountOrder;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class SalesRefundAmountOrderDto extends SalesRefundAmountOrder {
    @ApiModelProperty("退货单号")
    @Schema(description = "退货单号")
    private String returnManagementNo;
    @ApiModelProperty("客户名称")
    @Schema(description = "客户名称")
    private String customerName;
    @ApiModelProperty("销售单号")
    @Schema(description = "销售单号")
    private String salesContractNo;
    @ApiModelProperty("创建人名称")
    @Schema(description = "创建人名称")
    private String createUserName;
src/main/java/com/ruoyi/account/pojo/AccountExpense.java
@@ -4,11 +4,11 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.dto.DateQueryDto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@@ -105,25 +105,28 @@
    private Date inputTime;
    @ApiModelProperty(value = "创建时间")
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty(value = "创建用户")
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "修改时间")
    @Schema(description = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "修改用户")
    @Schema(description = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "租户ID")
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/pojo/AccountFile.java
@@ -2,10 +2,10 @@
import com.baomidou.mybatisplus.annotation.*;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
import java.io.Serializable;
import java.time.LocalDateTime;
@@ -24,44 +24,47 @@
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "文件名称")
    @Schema(description = "文件名称")
    private String name;
    @ApiModelProperty(value = "文件路径")
    @Schema(description = "文件路径")
    private String url;
    @ApiModelProperty(value = "文件大小")
    @Schema(description = "文件大小")
    private int fileSize;
    @ApiModelProperty(value = "财务ID")
    @Schema(description = "财务ID")
    @NotBlank(message = "财务id不能为空!")
    private Long accountId;
    /**
     * ç±»åž‹(收入/支出)
     */
    @ApiModelProperty(value = "类型(收入/支出)")
    @Schema(description = "类型(收入/支出)")
    private String accountType;
    @ApiModelProperty(value = "创建时间")
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty(value = "修改时间")
    @Schema(description = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "创建用户")
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "修改用户")
    @Schema(description = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "租户ID")
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/pojo/AccountIncome.java
@@ -4,15 +4,16 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.dto.DateQueryDto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
/**
 * è´¢åŠ¡ç®¡ç†--收入管理
@@ -20,7 +21,7 @@
 */
@TableName(value = "account_income")
@Data
public class AccountIncome extends DateQueryDto  implements Serializable {
public class AccountIncome extends DateQueryDto implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
@@ -50,7 +51,7 @@
    /**
     * æ”¶å…¥ç±»åž‹(销售收入,服务收入,其他收入)
     */
    @Excel(name = "收入类型",readConverterExp = "0=销售收入,1=服务收入,2=其他收入,3=回款收入")
    @Excel(name = "收入类型", readConverterExp = "0=销售收入,1=服务收入,2=其他收入,3=回款收入")
    @NotBlank(message = "收入类型不能为空!!")
    private String incomeType;
@@ -75,8 +76,26 @@
    /**
     * æ”¶æ¬¾æ–¹å¼(现金,支票,银行转账,其他)
     */
    @Excel(name = "收款方式",readConverterExp = "0=现金,1=支票,2=银行转账,3=其他")
    @Excel(name = "收款方式", readConverterExp = "0=现金,1=支票,2=银行转账,3=其他")
    private String incomeMethod;
    /**
     * æ”¶æ¬¾æ–¹å¼æ ‡ç­¾
     */
    @TableField(exist = false)
    private String incomeMethodLabel;
    /**
     * payment_methods å­—典编码集合(business_type ä¸ºç©ºæ—¶ï¼‰
     */
    @TableField(exist = false)
    private List<String> paymentMethodList;
    /**
     * receipt_payment_type å­—典编码集合(business_type=1时)
     */
    @TableField(exist = false)
    private List<String> receiptPaymentMethodList;
    /**
     * å‘票号码
@@ -105,25 +124,28 @@
    private Date inputTime;
    @ApiModelProperty(value = "创建时间")
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty(value = "创建用户")
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "修改时间")
    @Schema(description = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "修改用户")
    @Schema(description = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "租户ID")
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/pojo/BorrowInfo.java
@@ -13,8 +13,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.dto.DateQueryDto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
@@ -30,64 +29,67 @@
@Getter
@Setter
@TableName("borrow_info")
@ApiModel(value = "BorrowInfo对象", description = "借款信息表")
@Schema(name = "BorrowInfo对象", description = "借款信息表")
public class BorrowInfo extends DateQueryDto implements Serializable{
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("借款记录主键ID")
    @Schema(description = "借款记录主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("借款人姓名")
    @Schema(description = "借款人姓名")
    @Excel(name = "借款人姓名")
    private String borrowerName;
    @ApiModelProperty("借款金额(元)")
    @Schema(description = "借款金额(元)")
    @Excel(name = "借款金额(元)")
    private BigDecimal borrowAmount;
    @ApiModelProperty("借款利率(如:5.85 ä»£è¡¨5.85%)")
    @Schema(description = "借款利率(如:5.85 ä»£è¡¨5.85%)")
    @Excel(name = "借款利率")
    private BigDecimal interestRate;
    @ApiModelProperty("借款日期")
    @Schema(description = "借款日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "借款日期", width = 30, dateFormat = "yyyy-MM-dd")
    private LocalDate borrowDate;
    @ApiModelProperty("实际还款日期(还款后填充)")
    @Schema(description = "实际还款日期(还款后填充)")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "实际还款日期", width = 30, dateFormat = "yyyy-MM-dd")
    private LocalDate repayDate;
    @ApiModelProperty("借款状态:1=待还款,2=已还款")
    @Schema(description = "借款状态:1=待还款,2=已还款")
    @Excel(name = "借款状态", readConverterExp = "1=待还款,2=已还款")
    private Integer status;
    @ApiModelProperty("备注(借款说明)")
    @Schema(description = "备注(借款说明)")
    @Excel(name = "备注")
    private String remark;
    @ApiModelProperty("创建时间")
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("创建者ID")
    @Schema(description = "创建者ID")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty("修改时间")
    @Schema(description = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("修改者ID")
    @Schema(description = "修改者ID")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty("租户id")
    @Schema(description = "租户id")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/pojo/SalesReceiptReturn.java
@@ -8,8 +8,7 @@
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@@ -24,48 +23,51 @@
@Getter
@Setter
@TableName("sales_receipt_return")
@ApiModel(value = "SalesReceiptReturn对象", description = "收款退货表")
@Schema(name = "SalesReceiptReturn对象", description = "收款退货表")
public class SalesReceiptReturn implements Serializable {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("主键 ID")
    @Schema(description = "主键 ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("退款单号")
    @Schema(description = "退款单号")
    private String refundId;
    @ApiModelProperty("付款账号")
    @Schema(description = "付款账号")
    private String paymentAccount;
    @ApiModelProperty("付款账号名称")
    @Schema(description = "付款账号名称")
    private String paymentAccountName;
    @ApiModelProperty("付款方式")
    @Schema(description = "付款方式")
    private Byte paymentMethod;
    @ApiModelProperty("实际付款金额")
    @Schema(description = "实际付款金额")
    private BigDecimal actualAmount;
    @ApiModelProperty("手续费")
    @Schema(description = "手续费")
    private BigDecimal fee;
    @ApiModelProperty("交易号")
    @Schema(description = "交易号")
    private String transactionNo;
    @ApiModelProperty("优惠金额")
    @Schema(description = "优惠金额")
    private BigDecimal discountAmount;
    @ApiModelProperty("创建时间")
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @Schema(description = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("创建者")
    @Schema(description = "创建者")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/pojo/SalesRefundAmountOrder.java
@@ -10,8 +10,7 @@
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
@@ -27,7 +26,7 @@
@Getter
@Setter
@TableName("sales_refund_amount_order")
@ApiModel(value = "SalesRefundAmountOrder对象", description = "销售管理--退款单")
@Schema(name = "SalesRefundAmountOrder对象", description = "销售管理--退款单")
public class SalesRefundAmountOrder implements Serializable {
    private static final long serialVersionUID = 1L;
@@ -35,36 +34,43 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("退货单号id")
    @Schema(description = "退货单号id")
    private Long returnManagementId;
    @ApiModelProperty("状态")
    @Schema(description = "状态")
    private Integer status;
    @ApiModelProperty("应退款金额")
    @Schema(description = "应退款金额")
    private BigDecimal refundAmount;
    @ApiModelProperty("已退款金额")
    @Schema(description = "已退款金额")
    private BigDecimal refundedAmount;
    @ApiModelProperty("未退款金额")
    @Schema(description = "未退款金额")
    private BigDecimal notRefundedAmount;
    @ApiModelProperty("创建时间")
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @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_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("创建人id")
    @Schema(description = "创建人id")
    private Long createUserId;
    @ApiModelProperty("更新人id")
    @Schema(description = "更新人id")
    private Long updateUserId;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/service/AccountExpenseService.java
@@ -11,7 +11,7 @@
import com.ruoyi.account.pojo.AccountIncome;
import com.ruoyi.dto.DateQueryDto;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
src/main/java/com/ruoyi/account/service/AccountIncomeService.java
@@ -8,7 +8,7 @@
import com.ruoyi.account.dto.ReportDateDto;
import com.ruoyi.account.pojo.AccountIncome;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
public interface AccountIncomeService extends IService<AccountIncome> {
src/main/java/com/ruoyi/account/service/impl/AccountExpenseServiceImpl.java
@@ -21,7 +21,7 @@
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.time.DayOfWeek;
import java.time.LocalDate;
src/main/java/com/ruoyi/account/service/impl/AccountIncomeServiceImpl.java
@@ -5,13 +5,11 @@
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.account.dto.AccountDto2;
import com.ruoyi.account.dto.AccountDto3;
import com.ruoyi.account.dto.ReportDateDto;
import com.ruoyi.account.mapper.AccountIncomeMapper;
import com.ruoyi.account.pojo.AccountIncome;
import com.ruoyi.account.service.AccountIncomeService;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.dto.DateQueryDto;
import com.ruoyi.project.system.domain.SysDictData;
@@ -19,14 +17,14 @@
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@AllArgsConstructor
@Service
@@ -40,9 +38,40 @@
    //分页查询
    @Override
    public IPage<AccountIncome> accountIncomeListPage(Page page, AccountIncome accountIncome) {
        resolveIncomeMethodLabelFilter(accountIncome);
        return accountIncomeMapper.accountIncomeListPage(page,accountIncome);
    }
    private void resolveIncomeMethodLabelFilter(AccountIncome accountIncome) {
        if (accountIncome == null) {
            return;
        }
        if (accountIncome.getIncomeMethodLabel() == null || accountIncome.getIncomeMethodLabel().trim().isEmpty()) {
            return;
        }
        String targetLabel = accountIncome.getIncomeMethodLabel().trim();
        List<String> paymentMethodList = selectDictValuesByLabel("payment_methods", targetLabel);
        List<String> receiptPaymentMethodList = selectDictValuesByLabel("receipt_payment_type", targetLabel);
        if (paymentMethodList.isEmpty()) {
            paymentMethodList = Collections.singletonList("__NO_MATCH__");
        }
        if (receiptPaymentMethodList.isEmpty()) {
            receiptPaymentMethodList = Collections.singletonList("__NO_MATCH__");
        }
        accountIncome.setPaymentMethodList(paymentMethodList);
        accountIncome.setReceiptPaymentMethodList(receiptPaymentMethodList);
        accountIncome.setIncomeMethod(null);
    }
    private List<String> selectDictValuesByLabel(String dictType, String label) {
        return sysDictDataMapper.selectDictDataByType(dictType).stream()
                .filter(item -> label.equals(item.getDictLabel()))
                .map(SysDictData::getDictValue)
                .filter(v -> v != null && !v.trim().isEmpty())
                .distinct()
                .collect(Collectors.toList());
    }
    //导出
    @Override
    public void accountIncomeExport(HttpServletResponse response, AccountIncome accountIncome) {
src/main/java/com/ruoyi/account/service/impl/AccountingServiceImpl.java
@@ -17,10 +17,8 @@
import com.ruoyi.procurementrecord.pojo.CustomStorage;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
import com.ruoyi.procurementrecord.service.impl.ProcurementRecordOutServiceImpl;
import com.ruoyi.procurementrecord.service.impl.ProcurementRecordServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@@ -36,22 +34,14 @@
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class AccountingServiceImpl {
    @Autowired
    private DeviceLedgerMapper deviceLedgerMapper;
    @Autowired
    private BorrowInfoMapper borrowInfoMapper;
    @Autowired
    private CustomStorageMapper customStorageMapper;
    @Autowired
    private ProcurementRecordMapper procurementRecordMapper;
    @Autowired
    private ProcurementRecordOutMapper procurementRecordOutMapper;
    private final DeviceLedgerMapper deviceLedgerMapper;
    private final BorrowInfoMapper borrowInfoMapper;
    private final CustomStorageMapper customStorageMapper;
    private final ProcurementRecordMapper procurementRecordMapper;
    private final ProcurementRecordOutMapper procurementRecordOutMapper;
    public AjaxResult total(Integer year) {
        Map<String,Object> map = new HashMap<>();
src/main/java/com/ruoyi/account/service/impl/BorrowInfoServiceImpl.java
@@ -2,20 +2,19 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.account.mapper.BorrowInfoMapper;
import com.ruoyi.account.pojo.AccountExpense;
import com.ruoyi.account.pojo.AccountIncome;
import com.ruoyi.account.pojo.BorrowInfo;
import com.ruoyi.account.mapper.BorrowInfoMapper;
import com.ruoyi.account.service.AccountExpenseService;
import com.ruoyi.account.service.AccountIncomeService;
import com.ruoyi.account.service.BorrowInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.service.ReceiptPaymentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -30,16 +29,12 @@
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class BorrowInfoServiceImpl extends ServiceImpl<BorrowInfoMapper, BorrowInfo> implements BorrowInfoService {
    @Autowired
    private BorrowInfoMapper borrowInfoMapper;
    @Autowired
    private AccountIncomeService accountIncomeService;
    @Autowired
    private AccountExpenseService accountExpenseService;
    private final BorrowInfoMapper borrowInfoMapper;
    private final AccountIncomeService accountIncomeService;
    private final AccountExpenseService accountExpenseService;
    @Override
    public AjaxResult listPage(Page page, BorrowInfo borrowInfo) {
src/main/java/com/ruoyi/account/service/impl/SalesRefundAmountOrderServiceImpl.java
@@ -2,12 +2,12 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.dto.SalesRefundAmountOrderDto;
import com.ruoyi.account.pojo.SalesRefundAmountOrder;
import com.ruoyi.account.mapper.SalesRefundAmountOrderMapper;
import com.ruoyi.account.service.SalesRefundAmountOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import com.ruoyi.account.dto.SalesRefundAmountOrderDto;
import com.ruoyi.account.mapper.SalesRefundAmountOrderMapper;
import com.ruoyi.account.pojo.SalesRefundAmountOrder;
import com.ruoyi.account.service.SalesRefundAmountOrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
@@ -19,11 +19,11 @@
 * @since 2026-03-07 10:16:47
 */
@Service
@RequiredArgsConstructor
public class SalesRefundAmountOrderServiceImpl extends ServiceImpl<SalesRefundAmountOrderMapper, SalesRefundAmountOrder> implements SalesRefundAmountOrderService {
    @Autowired
    private SalesRefundAmountOrderMapper salesRefundAmountOrderMapper;
    private final SalesRefundAmountOrderMapper salesRefundAmountOrderMapper;
    @Override
    public IPage<SalesRefundAmountOrderDto> pageSalesRefundAmountOrderDto(Page<SalesRefundAmountOrderDto> page, SalesRefundAmountOrderDto salesRefundAmountOrder) {
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesNearExpiryController.java
@@ -8,9 +8,9 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
/**
@@ -23,18 +23,18 @@
 * @since 2026/03/02 14:40
 */
@RestController
@Api(tags = "临期售后管理")
@Tag(name = "临期售后管理")
@RequestMapping("/afterSalesNearExpiryService")
@AllArgsConstructor
public class AfterSalesNearExpiryController extends BaseController {
    @Autowired
    private AfterSalesNearExpiryService afterSalesNearExpiryService;
    /**
     * æ–°å¢žä¸´æœŸå”®åŽ
     */
    @PostMapping("/add")
    @ApiOperation("新增临期售后")
    @Operation(summary = "新增临期售后")
    @Log(title = "新增临期售后", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody AfterSalesNearExpiry entity) {
        afterSalesNearExpiryService.add(entity);
@@ -45,7 +45,7 @@
     * æ›´æ–°ä¸´æœŸå”®åŽ
     */
    @PostMapping("/update")
    @ApiOperation("更新临期售后")
    @Operation(summary = "更新临期售后")
    @Log(title = "更新临期售后", businessType = BusinessType.UPDATE)
    public AjaxResult update(@RequestBody AfterSalesNearExpiry entity) {
        afterSalesNearExpiryService.update(entity);
@@ -56,7 +56,7 @@
     * åˆ é™¤ä¸´æœŸå”®åŽ
     */
    @DeleteMapping("/delete")
    @ApiOperation("删除临期售后")
    @Operation(summary = "删除临期售后")
    @Log(title = "删除临期售后", businessType = BusinessType.DELETE)
    public AjaxResult delete(Long[] ids) {
        afterSalesNearExpiryService.delete(ids);
@@ -67,7 +67,7 @@
     * åˆ†é¡µæŸ¥è¯¢ä¸´æœŸå”®åŽ
     */
    @GetMapping("/listPage")
    @ApiOperation("分页查询临期售后")
    @Operation(summary = "分页查询临期售后")
    @Log(title = "分页查询临期售后", businessType = BusinessType.OTHER)
    public AjaxResult listPage(Page<AfterSalesNearExpiry> page, AfterSalesNearExpiry entity) {
        IPage<AfterSalesNearExpiry> listPage = afterSalesNearExpiryService.listPage(page, entity);
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesServiceController.java
@@ -15,14 +15,14 @@
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.service.ISalesLedgerService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@@ -32,22 +32,18 @@
 * @date : 2025/7/30 9:27
 */
@RestController
@Api(tags = "售后服务")
@Tag(name = "售后服务")
@RequestMapping("/afterSalesService")
@AllArgsConstructor
public class AfterSalesServiceController extends BaseController {
    @Autowired
    private AfterSalesServiceService afterSalesServiceService;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private ISalesLedgerService salesLedgerService;
    @GetMapping("/listPage")
    @ApiOperation("售后服务-分页查询")
    @Operation(summary = "售后服务-分页查询")
    @Log(title = "售后服务-分页查询", businessType = BusinessType.OTHER)
    public AjaxResult listPage(Page page, AfterSalesServiceNewDto afterSalesService) {
        IPage<AfterSalesServiceNewDto> listPage = afterSalesServiceService.listPage(page, afterSalesService);
@@ -56,7 +52,7 @@
    @Log(title = "售后服务-反馈登记", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    @ApiOperation("售后服务-反馈登记")
    @Operation(summary = "售后服务-反馈登记")
    public void export(HttpServletResponse response) {
        Page page = new Page(-1,-1);
        AfterSalesServiceNewDto afterSalesService = new AfterSalesServiceNewDto();
@@ -74,7 +70,7 @@
    @Log(title = "售后服务-售后处理", businessType = BusinessType.EXPORT)
    @PostMapping("/exportTwo")
    @ApiOperation("售后服务-售后处理")
    @Operation(summary = "售后服务-售后处理")
    public void exportTwo(HttpServletResponse response) {
        Page page = new Page(-1,-1);
        AfterSalesServiceNewDto afterSalesService = new AfterSalesServiceNewDto();
@@ -87,14 +83,14 @@
    }
    @PostMapping("/add")
    @ApiOperation("售后服务-新增")
    @Operation(summary = "售后服务-新增")
    @Log(title = "售后服务-新增", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody AfterSalesServiceNewDto afterSalesServiceNewDto) {
        return afterSalesServiceService.addAfterSalesServiceDto(afterSalesServiceNewDto) ? AjaxResult.success() : AjaxResult.error();
    }
    @PostMapping("/update")
    @ApiOperation("售后服务-修改")
    @Operation(summary = "售后服务-修改")
    @Log(title = "售后服务-修改", businessType = BusinessType.UPDATE)
    public AjaxResult update(@RequestBody AfterSalesServiceNewDto afterSalesServiceNewDto) {
        if (afterSalesServiceNewDto.getProductModelIdList() != null && afterSalesServiceNewDto.getProductModelIdList().isEmpty() ) {
@@ -108,7 +104,7 @@
    }
    @DeleteMapping("/delete")
    @ApiOperation("售后服务-删除")
    @Operation(summary = "售后服务-删除")
    @Log(title = "售后服务-删除", businessType = BusinessType.DELETE)
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
@@ -119,7 +115,7 @@
    }
    @PostMapping("/dispose")
    @ApiOperation("售后服务-处理")
    @Operation(summary = "售后服务-处理")
    @Log(title = "售后服务-处理", businessType = BusinessType.UPDATE)
    public AjaxResult dispose(@RequestBody AfterSalesService afterSalesService) {
        AfterSalesService byId = afterSalesServiceService.getById(afterSalesService.getId());
@@ -135,7 +131,7 @@
    @GetMapping("listSalesLedger")
    @ApiOperation("售后服务-获取销售台账")
    @Operation(summary = "售后服务-获取销售台账")
    public AjaxResult listSalesLedger(SalesLedgerDto salesLedgerDto, Page page) {
        IPage<SalesLedgerDto> list = salesLedgerService.listSalesLedger(salesLedgerDto,page);
        return AjaxResult.success(list);
@@ -143,12 +139,12 @@
    @GetMapping("getById")
    @ApiOperation("售后服务-根据id获取详情")
    @Operation(summary = "售后服务-根据id获取详情")
    public AjaxResult getById(Long id) {
        return AjaxResult.success(afterSalesServiceService.getAfterSalesServiceNewDtoById(id));
    }
    @ApiOperation("售后服务-统计工单情况")
    @Operation(summary = "售后服务-统计工单情况")
    @GetMapping("count")
    public AjaxResult count() {
        return AjaxResult.success(afterSalesServiceService.countAfterSalesService());
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesServiceFileController.java
@@ -7,8 +7,9 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -23,15 +24,15 @@
 * @since 2026/03/02 11:20
 */
@RestController
@Api(tags = "售后服务附件表")
@Tag(name = "售后服务附件表")
@RequestMapping("/afterSalesService/file")
@AllArgsConstructor
public class AfterSalesServiceFileController extends BaseController {
    @Autowired
    private AfterSalesServiceFileService afterSalesServiceFileService;
    @PostMapping("/upload")
    @ApiOperation("售后服务-文件上传")
    @Operation(summary = "售后服务-文件上传")
    @Log(title = "售后服务-文件上传", businessType = BusinessType.INSERT)
    public AjaxResult fileUpload(@RequestParam("file") MultipartFile file,
                                 @RequestParam("id") Long afterSalesServiceId) {
@@ -40,14 +41,14 @@
    }
    @GetMapping("/listPage")
    @ApiOperation("售后处理-售后附件列表")
    @Operation(summary = "售后处理-售后附件列表")
    @Log(title = "售后处理-售后附件列表", businessType = BusinessType.OTHER)
    public AjaxResult fileList(Page<AfterSalesServiceFile> page, Long afterSalesServiceId) {
        return AjaxResult.success(afterSalesServiceFileService.fileList(page, afterSalesServiceId));
    }
    @DeleteMapping("/del/{fileId}")
    @ApiOperation("售后处理-删除附件")
    @Operation(summary = "售后处理-删除附件")
    @Log(title = "售后处理-删除附件", businessType = BusinessType.DELETE)
    public AjaxResult delFile(@PathVariable Long fileId) {
        afterSalesServiceFileService.delFile(fileId);
src/main/java/com/ruoyi/aftersalesservice/dto/AfterSalesServiceExeclDto.java
@@ -2,7 +2,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -18,7 +18,7 @@
    /**
     * çŠ¶æ€ 1-待处理 2-已处理
     */
    @ApiModelProperty("状态 1-待处理 2-已处理")
    @Schema(description = "状态 1-待处理 2-已处理")
    private Integer status;
@@ -28,7 +28,7 @@
    /**
     * ç™»è®°äººåç§°
     */
    @ApiModelProperty("登记人名称")
    @Schema(description = "登记人名称")
    @Excel(name = "登记人名称")
    private String checkNickName;
@@ -36,14 +36,14 @@
    /**
     * å®¢æˆ·åç§°
     */
    @ApiModelProperty("客户名称")
    @Schema(description = "客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    /**
     * é—®é¢˜æè¿°
     */
    @ApiModelProperty("问题描述")
    @Schema(description = "问题描述")
    @Excel(name = "问题描述")
    private String proDesc;
@@ -51,14 +51,14 @@
    /**
     * åé¦ˆæ—¥æœŸ
     */
    @ApiModelProperty("反馈日期")
    @Schema(description = "反馈日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "反馈日期", width = 30, dateFormat = "yyyy-MM-dd")
    private Date feedbackDate;
    @ApiModelProperty("关联部门")
    @Schema(description = "关联部门")
    @Excel(name = "关联部门")
    private String deptName;
src/main/java/com/ruoyi/aftersalesservice/dto/AfterSalesServiceNewDto.java
@@ -4,7 +4,7 @@
import com.ruoyi.aftersalesservice.pojo.AfterSalesService;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@@ -12,7 +12,7 @@
@Data
public class AfterSalesServiceNewDto extends AfterSalesService {
    @ApiModelProperty("产品型号ID数组")
    @Schema(description = "产品型号ID数组")
    private List<Long> productModelIdList;
    private SalesLedgerDto salesLedgerDto;
src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesNearExpiry.java
@@ -1,11 +1,12 @@
package com.ruoyi.aftersalesservice.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.IdType;
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.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -23,69 +24,72 @@
 */
@Data
@TableName("after_sales_near_expiry")
@ApiModel("临期售后管理表")
@Schema(name = "临期售后管理表")
public class AfterSalesNearExpiry {
    @TableId(type = IdType.AUTO)
    @ApiModelProperty("主键")
    @Schema(description = "主键")
    private Long id;
    @ApiModelProperty("临期产品名称")
    @Schema(description = "临期产品名称")
    private String productName;
    @ApiModelProperty("产品批号")
    @Schema(description = "产品批号")
    private String batchNumber;
    @ApiModelProperty("临期日期")
    @Schema(description = "临期日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate expireDate;
    @ApiModelProperty("库存数量")
    @Schema(description = "库存数量")
    private Integer stockQuantity;
    @ApiModelProperty("客户名称")
    @Schema(description = "客户名称")
    private String customerName;
    @ApiModelProperty("联系电话")
    @Schema(description = "联系电话")
    private String contactPhone;
    @ApiModelProperty("问题描述")
    @Schema(description = "问题描述")
    private String disRes;
    @ApiModelProperty("处理结果")
    @Schema(description = "处理结果")
    private String disposeResult;
    @ApiModelProperty("处理状态 (1-待处理 2-已处理)")
    @Schema(description = "处理状态 (1-待处理 2-已处理)")
    private Integer status;
    @ApiModelProperty("处理人ID")
    @Schema(description = "处理人ID")
    private Long disposeUserId;
    @ApiModelProperty("处理人名称")
    @Schema(description = "处理人名称")
    private String disposeNickName;
    @ApiModelProperty("处理日期")
    @Schema(description = "处理日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate disDate;
    @ApiModelProperty("创建者")
    @Schema(description = "创建者")
    private Long createUser;
    @ApiModelProperty("创建时间")
    @Schema(description = "创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty("修改者")
    @Schema(description = "修改者")
    private Long updateUser;
    @ApiModelProperty("修改时间")
    @Schema(description = "修改时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    @ApiModelProperty("租户id")
    @Schema(description = "租户id")
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesService.java
@@ -3,8 +3,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -19,7 +18,7 @@
 */
@Data
@TableName("after_sales_service")
@ApiModel
@Schema
public class AfterSalesService {
    private static final long serialVersionUID = 1L;
@@ -30,10 +29,10 @@
    /**
     * çŠ¶æ€ 1-待处理 2-已处理
     */
    @ApiModelProperty("状态 1-待处理 2-已处理")
    @Schema(description = "状态 1-待处理 2-已处理")
    private Integer status;
    @ApiModelProperty("售后单号")
    @Schema(description = "售后单号")
    private String afterSalesServiceNo;
@@ -44,54 +43,54 @@
    /**
     * ç™»è®°äºº
     */
    @ApiModelProperty("登记人")
    @Schema(description = "登记人")
    private Long checkUserId;
    /**
     * ç™»è®°äººåç§°
     */
    @ApiModelProperty("登记人名称")
    @Schema(description = "登记人名称")
    @Excel(name = "登记人名称")
    private String checkNickName;
    /**
     * å¤„理人
     */
    @ApiModelProperty("处理人")
    @Schema(description = "处理人")
    private Long disposeUserId;
    /**
     * å¤„理人名称
     */
    @ApiModelProperty("处理人名称")
    @Schema(description = "处理人名称")
    @Excel(name = "处理人名称")
    private String disposeNickName;
    /**
     * å®¢æˆ·åç§°
     */
    @ApiModelProperty("客户名称")
    @Schema(description = "客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    /**
     * é—®é¢˜æè¿°
     */
    @ApiModelProperty("问题描述")
    @Schema(description = "问题描述")
    @Excel(name = "问题描述")
    private String proDesc;
    /**
     * å¤„理结果
     */
    @ApiModelProperty("处理结果")
    @Schema(description = "处理结果")
    @Excel(name = "处理结果")
    private String disRes;
    /**
     * åé¦ˆæ—¥æœŸ
     */
    @ApiModelProperty("反馈日期")
    @Schema(description = "反馈日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "反馈日期", width = 30, dateFormat = "yyyy-MM-dd")
@@ -100,7 +99,7 @@
    /**
     * å¤„理日期
     */
    @ApiModelProperty("处理日期")
    @Schema(description = "处理日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "处理日期", width = 30, dateFormat = "yyyy-MM-dd")
@@ -138,26 +137,29 @@
    private Long tenantId;
    @TableField(exist = false)
    @ApiModelProperty("部门名称")
    @Schema(description = "部门名称")
    private String deptName;
    @ApiModelProperty("售后类型")
    @Schema(description = "售后类型")
    private String serviceType;
    @ApiModelProperty("紧急程度")
    @Schema(description = "紧急程度")
    private String urgency;
    @ApiModelProperty("销售台账ID")
    @Schema(description = "销售台账ID")
    private Long salesLedgerId;
    @ApiModelProperty("分配人ID")
    @Schema(description = "分配人ID")
    private Long distributionUserId;
    @ApiModelProperty("产品型号IDs")
    @Schema(description = "产品型号IDs")
    private String productModelIds;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesServiceFile.java
@@ -1,10 +1,11 @@
package com.ruoyi.aftersalesservice.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@@ -20,43 +21,46 @@
 */
@Data
@TableName("after_sales_service_file")
@ApiModel("售后服务附件表")
@Schema(name = "售后服务附件表")
public class AfterSalesServiceFile {
    @TableId(type = IdType.AUTO)
    @ApiModelProperty("主键")
    @Schema(description = "主键")
    private Long id;
    @ApiModelProperty("售后服务记录ID")
    @Schema(description = "售后服务记录ID")
    private Long serviceId;
    @ApiModelProperty("文件名称")
    @Schema(description = "文件名称")
    private String fileName;
    @ApiModelProperty("文件访问地址")
    @Schema(description = "文件访问地址")
    private String fileUrl;
    @ApiModelProperty("文件大小")
    @Schema(description = "文件大小")
    private Long fileSize;
    @ApiModelProperty("文件后缀")
    @Schema(description = "文件后缀")
    private String fileSuffix;
    @ApiModelProperty("删除标志(0代表存在 1代表删除)")
    @Schema(description = "删除标志(0代表存在 1代表删除)")
    private String delFlag;
    @ApiModelProperty("上传者")
    @Schema(description = "上传者")
    private Long createUser;
    @ApiModelProperty("创建时间")
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty("修改者")
    @Schema(description = "修改者")
    private Long updateUser;
    @ApiModelProperty("修改时间")
    @Schema(description = "修改时间")
    private LocalDateTime updateTime;
    @ApiModelProperty("租户id")
    @Schema(description = "租户id")
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceFileServiceImpl.java
@@ -13,8 +13,8 @@
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@@ -37,10 +37,10 @@
 * @since 2026/03/02 11:19
 */
@Service
@RequiredArgsConstructor
public class AfterSalesServiceFileServiceImpl extends ServiceImpl<AfterSalesServiceFileMapper, AfterSalesServiceFile> implements AfterSalesServiceFileService {
    @Autowired
    private AfterSalesServiceService afterSalesServiceService;
    private final AfterSalesServiceService afterSalesServiceService;
    @Value("${file.upload-dir}")
    private String uploadDir;
src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java
@@ -21,9 +21,9 @@
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerProductService;
import com.ruoyi.sales.service.ISalesLedgerService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
@@ -36,20 +36,14 @@
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class AfterSalesServiceServiceImpl extends ServiceImpl<AfterSalesServiceMapper, AfterSalesService> implements AfterSalesServiceService {
    @Autowired
    private AfterSalesServiceMapper afterSalesServiceMapper;
    @Autowired
    private SysDeptMapper sysDeptMapper;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private ISalesLedgerProductService salesLedgerProductService;
    @Autowired
    private ISalesLedgerService salesLedgerService;
    private final AfterSalesServiceMapper afterSalesServiceMapper;
    private final SysDeptMapper sysDeptMapper;
    private final SysUserMapper sysUserMapper;
    private final ISalesLedgerProductService salesLedgerProductService;
    private final ISalesLedgerService salesLedgerService;
    @Override
    public IPage<AfterSalesServiceNewDto> listPage(Page page, AfterSalesServiceNewDto afterSalesService) {
src/main/java/com/ruoyi/ai/assistant/ApproveTodoAgent.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.ai.assistant;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
@AiService(
        wiringMode = EXPLICIT,
        streamingChatModel = "qwenStreamingChatModel",
        chatMemoryProvider = "chatMemoryProviderApproveTodo",
        tools = "approveTodoTools")
public interface ApproveTodoAgent {
    @SystemMessage(fromResource = "approve-todo-agent-prompt.txt")
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
}
src/main/java/com/ruoyi/ai/assistant/ApproveTodoIntentExecutor.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,296 @@
package com.ruoyi.ai.assistant;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.ai.tools.ApproveTodoTools;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
public class ApproveTodoIntentExecutor {
    private static final Pattern APPROVE_ID_PATTERN = Pattern.compile("\\b[A-Za-z]*\\d{8,}\\b");
    private static final Pattern LIMIT_PATTERN = Pattern.compile("(前|最近)?(\\d{1,2})条");
    private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
    private static final Pattern NUMBER_PATTERN = Pattern.compile("(\\d+(?:\\.\\d+)?)");
    private static final Pattern RECENT_RANGE_PATTERN = Pattern.compile("近\\d+(天|周|个月|月|å¹´)");
    private static final Pattern HALF_RANGE_PATTERN = Pattern.compile("(最近|近)?半(个)?(月|å¹´)");
    private static final Pattern EXPLICIT_RANGE_PATTERN = Pattern.compile(".*(到|至).*");
    private final ApproveTodoTools approveTodoTools;
    public ApproveTodoIntentExecutor(ApproveTodoTools approveTodoTools) {
        this.approveTodoTools = approveTodoTools;
    }
    public String tryExecute(String memoryId, String message) {
        if (!StringUtils.hasText(message)) {
            return null;
        }
        String text = message.trim();
        String approveId = extractApproveId(text);
        if (isStatsIntent(text)) {
            return approveTodoTools.getTodoStats(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    extractTimeRange(text)
            );
        }
        if (containsAny(text, "流转", "进度", "节点", "日志", "卡在", "卡到", "当前审批人", "处理记录")) {
            return StringUtils.hasText(approveId)
                    ? approveTodoTools.getTodoProgress(memoryId, approveId)
                    : missingApproveId("todo_progress", "查询审批进度需要提供流程编号。");
        }
        if (containsAny(text, "详情", "明细") && !containsAny(text, "列表")) {
            return StringUtils.hasText(approveId)
                    ? approveTodoTools.getTodoDetail(memoryId, approveId)
                    : missingApproveId("todo_detail", "查询审批详情需要提供流程编号。");
        }
        if (containsAny(text, "取消审核", "撤销审核", "回退审核", "撤销审批", "撤回审批")
                || (containsAny(text, "撤销", "撤回") && containsAny(text, "审批操作", "审核操作"))) {
            return StringUtils.hasText(approveId)
                    ? approveTodoTools.cancelReviewTodo(memoryId, approveId, firstNonBlank(extractTail(text, "原因"), extractTail(text, "备注")))
                    : missingApproveId("cancel_review_action", "取消审核需要提供流程编号。");
        }
        if (containsAny(text, "删除", "移除")) {
            return StringUtils.hasText(approveId)
                    ? approveTodoTools.deleteTodo(memoryId, approveId)
                    : missingApproveId("delete_action", "删除审批单需要提供流程编号。");
        }
        if (containsAny(text, "驳回", "拒绝")) {
            return StringUtils.hasText(approveId)
                    ? approveTodoTools.reviewTodo(memoryId, approveId, "reject", firstNonBlank(extractTail(text, "原因"), extractTail(text, "备注")))
                    : missingApproveId("review_action", "驳回审批需要提供流程编号。");
        }
        if (containsAny(text, "审核通过", "审批通过", "通过审批", "同意审批", "审批同意")) {
            return StringUtils.hasText(approveId)
                    ? approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractTail(text, "备注"))
                    : missingApproveId("review_action", "审批通过需要提供流程编号。");
        }
        if (StringUtils.hasText(approveId)
                && containsAny(text, "通过", "同意")
                && !containsAny(text, "未通过", "通过率", "审批通过率", "审核通过率")) {
            return approveTodoTools.reviewTodo(memoryId, approveId, "approve", extractTail(text, "备注"));
        }
        if (containsAny(text, "修改", "更新", "变更")) {
            return StringUtils.hasText(approveId)
                    ? approveTodoTools.updateTodo(
                    memoryId,
                    approveId,
                    extractValue(text, "标题"),
                    extractDateValue(text, "开始日期"),
                    extractDateValue(text, "结束日期"),
                    extractBigDecimalValue(text, "金额"),
                    extractValue(text, "地点"),
                    extractIntegerValue(text, "类型"),
                    extractValue(text, "备注"))
                    : missingApproveId("update_action", "修改审批单需要提供流程编号。");
        }
        if (containsAny(text, "列表", "待办", "查询审批", "单据", "流程", "审批批")) {
            return approveTodoTools.listTodos(
                    memoryId,
                    extractStatus(text),
                    extractApproveType(text),
                    extractKeyword(text),
                    extractLimit(text),
                    extractScope(text));
        }
        return null;
    }
    private boolean isStatsIntent(String text) {
        if (containsAny(text, "统计", "分析", "图表", "趋势", "占比", "汇总", "总量", "分布", "各有多少", "有多少")) {
            return true;
        }
        boolean hasQueryWord = containsAny(text, "查询", "查看", "看下", "看看", "获取");
        boolean hasDataWord = containsAny(text, "数据", "报表", "情况", "数量", "金额");
        boolean hasTimeWord = containsAny(text, "今天", "昨日", "昨天", "本周", "上周", "本月", "上月", "本年", "今年", "去年")
                || DATE_PATTERN.matcher(text).find()
                || RECENT_RANGE_PATTERN.matcher(text).find()
                || HALF_RANGE_PATTERN.matcher(text).find()
                || EXPLICIT_RANGE_PATTERN.matcher(text).matches();
        return hasQueryWord && hasDataWord && hasTimeWord;
    }
    private boolean containsAny(String text, String... keywords) {
        for (String keyword : keywords) {
            if (text.contains(keyword)) {
                return true;
            }
        }
        return false;
    }
    private String extractApproveId(String text) {
        Matcher matcher = APPROVE_ID_PATTERN.matcher(text);
        return matcher.find() ? matcher.group() : null;
    }
    private Integer extractLimit(String text) {
        Matcher matcher = LIMIT_PATTERN.matcher(text);
        return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10;
    }
    private String extractStatus(String text) {
        if (containsAny(text, "待审核", "待审批")) {
            return "pending";
        }
        if (containsAny(text, "审核中", "处理中", "处理中的", "办理中")) {
            return "processing";
        }
        if (containsAny(text, "已通过", "通过", "审核完成", "审批完成")) {
            return "approved";
        }
        if (containsAny(text, "未通过", "驳回", "已驳回", "拒绝")) {
            return "rejected";
        }
        if (containsAny(text, "重新提交")) {
            return "resubmitted";
        }
        return "all";
    }
    private Integer extractApproveType(String text) {
        if (text.contains("公出")) {
            return 1;
        }
        if (text.contains("请假")) {
            return 2;
        }
        if (text.contains("出差")) {
            return 3;
        }
        if (text.contains("报销")) {
            return 4;
        }
        if (text.contains("采购")) {
            return 5;
        }
        if (text.contains("报价")) {
            return 6;
        }
        if (text.contains("发货")) {
            return 7;
        }
        if (text.contains("危险作业")) {
            return 8;
        }
        return null;
    }
    private String extractKeyword(String text) {
        String cleaned = text
                .replace("查询", "")
                .replace("查看", "")
                .replace("列出", "")
                .replace("帮我", "")
                .replace("审批", "")
                .replace("单据", "")
                .replace("待办", "")
                .replace("列表", "")
                .replace("前10条", "")
                .replace("最近10条", "")
                .trim();
        return cleaned.length() >= 2 ? cleaned : null;
    }
    private String extractValue(String text, String fieldName) {
        Pattern pattern = Pattern.compile(fieldName + "(改为|修改为|是)[::]?[\\s]*([^,,。;;\\s]+)");
        Matcher matcher = pattern.matcher(text);
        return matcher.find() ? matcher.group(2) : null;
    }
    private String extractDateValue(String text, String fieldName) {
        int index = text.indexOf(fieldName);
        if (index < 0) {
            return null;
        }
        Matcher matcher = DATE_PATTERN.matcher(text.substring(index));
        return matcher.find() ? matcher.group(1) : null;
    }
    private String extractStartDate(String text) {
        Matcher matcher = DATE_PATTERN.matcher(text);
        return matcher.find() ? matcher.group(1) : null;
    }
    private String extractEndDate(String text) {
        Matcher matcher = DATE_PATTERN.matcher(text);
        if (!matcher.find()) {
            return null;
        }
        return matcher.find() ? matcher.group(1) : null;
    }
    private String extractTimeRange(String text) {
        if (containsAny(text, "今天", "昨日", "昨天", "本周", "上周", "本月", "上月", "本年", "今年", "去年")) {
            return text;
        }
        if (RECENT_RANGE_PATTERN.matcher(text).find()) {
            return text;
        }
        if (HALF_RANGE_PATTERN.matcher(text).find()) {
            return text;
        }
        if (EXPLICIT_RANGE_PATTERN.matcher(text).matches()) {
            return text;
        }
        return null;
    }
    private Integer extractIntegerValue(String text, String fieldName) {
        if (!text.contains(fieldName)) {
            return null;
        }
        Matcher matcher = Pattern.compile(fieldName + "(改为|修改为|是)[::]?[\\s]*(\\d{1,2})").matcher(text);
        return matcher.find() ? Integer.parseInt(matcher.group(2)) : null;
    }
    private BigDecimal extractBigDecimalValue(String text, String fieldName) {
        int index = text.indexOf(fieldName);
        if (index < 0) {
            return null;
        }
        Matcher matcher = NUMBER_PATTERN.matcher(text.substring(index));
        return matcher.find() ? new BigDecimal(matcher.group(1)) : null;
    }
    private String extractTail(String text, String key) {
        Pattern pattern = Pattern.compile(key + "(是|为)?[::]?[\\s]*(.+)");
        Matcher matcher = pattern.matcher(text);
        return matcher.find() ? matcher.group(2).trim() : null;
    }
    private String extractScope(String text) {
        if (containsAny(text, "我发起", "我提交", "我申请", "申请人是我")) {
            return "applicant";
        }
        if (containsAny(text, "待我审批", "待我审核", "我处理", "我审批", "当前待我", "需要我处理")) {
            return "approver";
        }
        return "related";
    }
    private String firstNonBlank(String first, String second) {
        return StringUtils.hasText(first) ? first : second;
    }
    private String missingApproveId(String type, String description) {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("success", false);
        result.put("type", type);
        result.put("description", description);
        result.put("summary", Map.of());
        result.put("data", Map.of());
        result.put("charts", Map.of());
        return JSON.toJSONString(result);
    }
}
src/main/java/com/ruoyi/ai/assistant/Assistant.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.ai.assistant;
import dev.langchain4j.service.spring.AiService;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
/**
 * @author :yys
 * @date : 2025/5/2 18:26
 */
//因为我们在配置文件中同时配置了多个大语言模型,所以需要在这里明确指定(EXPLICIT)模型的beanName
@AiService(wiringMode = EXPLICIT, chatModel = "qwenChatModel")
public interface Assistant {
    String chat(String userMessage);
}
src/main/java/com/ruoyi/ai/assistant/FileAnalyzeAgent.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.ai.assistant;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
@AiService(
        wiringMode = EXPLICIT,
        streamingChatModel = "qwenStreamingChatModel",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface FileAnalyzeAgent {
    @SystemMessage("""
            ä½ æ˜¯ä¼ä¸šæ–‡æ¡£åˆ†æžåŠ©æ‰‹ã€‚
            è¯·ä¸¥æ ¼åŸºäºŽç”¨æˆ·æä¾›çš„æ–‡ä»¶å†…容进行分析,输出要结构化、准确、简洁。
            è‹¥æ–‡ä»¶å†…容不足以支持结论,明确指出“不足信息”并给出需要补充的数据项。
            """)
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
}
src/main/java/com/ruoyi/ai/assistant/PurchaseAgent.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.ruoyi.ai.assistant;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
@AiService(
        wiringMode = EXPLICIT,
        streamingChatModel = "qwenStreamingChatModel",
        chatMemoryProvider = "chatMemoryProviderPurchase",
        tools = "purchaseAgentTools"
)
public interface PurchaseAgent {
    @SystemMessage(fromResource = "purchase-agent-prompt.txt")
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
}
src/main/java/com/ruoyi/ai/assistant/PurchaseIntentExecutor.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,161 @@
package com.ruoyi.ai.assistant;
import com.ruoyi.ai.tools.PurchaseAgentTools;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
public class PurchaseIntentExecutor {
    private static final Pattern ID_PATTERN = Pattern.compile("\\b\\d{1,12}\\b");
    private static final Pattern LIMIT_PATTERN = Pattern.compile("(前|最近)?(\\d{1,2})条");
    private static final Pattern DATE_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
    private final PurchaseAgentTools purchaseAgentTools;
    public PurchaseIntentExecutor(PurchaseAgentTools purchaseAgentTools) {
        this.purchaseAgentTools = purchaseAgentTools;
    }
    public String tryExecute(String memoryId, String message) {
        if (!StringUtils.hasText(message)) {
            return null;
        }
        String text = message.trim();
        if (containsAny(text, "排行", "排名", "前几", "前五", "前十") && containsAny(text, "物料", "产品", "原材料", "采购金额", "金额")) {
            return purchaseAgentTools.rankPurchaseMaterials(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    text,
                    extractLimit(text)
            );
        }
        if (containsAny(text, "未入库", "待入库", "没有入库", "还未入库")) {
            return purchaseAgentTools.listUnstockedPurchaseOrders(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    extractKeyword(text),
                    extractLimit(text)
            );
        }
        if (containsAny(text, "到货异常", "到货有异常", "异常到货", "到货问题", "供应商到货异常")) {
            return purchaseAgentTools.listArrivalExceptions(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    text,
                    extractLimit(text)
            );
        }
        if (containsAny(text, "待付款", "未付款", "未付清", "待支付", "应付")) {
            return purchaseAgentTools.listPendingPaymentOrders(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    extractKeyword(text),
                    extractLimit(text)
            );
        }
        if (containsAny(text, "退货", "退料", "拒收")) {
            return purchaseAgentTools.listPurchaseReturns(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    extractKeyword(text),
                    extractLimit(text)
            );
        }
        if (isStatsIntent(text)) {
            return purchaseAgentTools.getPurchaseStats(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    text
            );
        }
        if (containsAny(text, "详情", "明细") && extractId(text) != null) {
            return purchaseAgentTools.getPurchaseLedgerDetail(memoryId, extractId(text));
        }
        if (containsAny(text, "台账", "采购单", "采购订单", "订单", "合同", "列表", "查询")) {
            return purchaseAgentTools.listPurchaseLedgers(
                    memoryId,
                    extractKeyword(text),
                    extractStartDate(text),
                    extractEndDate(text),
                    extractLimit(text)
            );
        }
        return null;
    }
    private boolean isStatsIntent(String text) {
        if (containsAny(text, "统计", "分析", "报表", "汇总", "趋势", "数据看板", "情况", "有多少")) {
            return true;
        }
        boolean queryWord = containsAny(text, "查询", "查看", "看下", "看看", "获取");
        boolean dataWord = containsAny(text, "数据", "金额", "数量", "合同额", "付款额", "发票额");
        boolean timeWord = containsAny(text, "今天", "本周", "本月", "上月", "今年", "去年", "近半年", "最近半个月", "半个月")
                || DATE_PATTERN.matcher(text).find();
        return queryWord && dataWord && timeWord;
    }
    private boolean containsAny(String text, String... keywords) {
        for (String keyword : keywords) {
            if (text.contains(keyword)) {
                return true;
            }
        }
        return false;
    }
    private Long extractId(String text) {
        Matcher matcher = ID_PATTERN.matcher(text);
        if (!matcher.find()) {
            return null;
        }
        return Long.parseLong(matcher.group());
    }
    private Integer extractLimit(String text) {
        Matcher matcher = LIMIT_PATTERN.matcher(text);
        return matcher.find() ? Integer.parseInt(matcher.group(2)) : 10;
    }
    private String extractStartDate(String text) {
        Matcher matcher = DATE_PATTERN.matcher(text);
        return matcher.find() ? matcher.group() : null;
    }
    private String extractEndDate(String text) {
        Matcher matcher = DATE_PATTERN.matcher(text);
        if (!matcher.find()) {
            return null;
        }
        return matcher.find() ? matcher.group() : null;
    }
    private String extractKeyword(String text) {
        String cleaned = text
                .replace("查询", "")
                .replace("查看", "")
                .replace("采购", "")
                .replace("采购单", "")
                .replace("采购订单", "")
                .replace("订单", "")
                .replace("台账", "")
                .replace("列表", "")
                .replace("哪些", "")
                .replace("列出", "")
                .replace("帮我", "")
                .replace("最近10条", "")
                .replace("前10条", "")
                .trim();
        return cleaned.length() >= 2 ? cleaned : null;
    }
}
src/main/java/com/ruoyi/ai/assistant/SeparateChatAssistant.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.ruoyi.ai.assistant;
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService;
import dev.langchain4j.service.spring.AiServiceWiringMode;
/**
 * @author :yys
 * @date : 2025/5/2 19:35
 */
@AiService(
        wiringMode = AiServiceWiringMode.EXPLICIT,
        chatModel = "qwenChatModel",
        chatMemoryProvider = "chatMemoryProvider"
)
public interface SeparateChatAssistant {
    @SystemMessage(fromResource = "my-prompt-template.txt")//系统消息提示词
    String chat(@MemoryId String memoryId, @UserMessage String userMessage);
    @UserMessage("你是我的好朋友,请用粤语回答问题。{{message}}")
    String chat2(@MemoryId String memoryId, @V("message") String userMessage);
    @SystemMessage(fromResource = "my-prompt-template3.txt")
    String chat3(
            @MemoryId String memoryId,
            @UserMessage String userMessage,
            @V("username") String username,
            @V("age") int age
    );
}
src/main/java/com/ruoyi/ai/bean/ChatForm.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.ai.bean;
import lombok.Data;
/**
 * @author :yys
 * @date : 2025/5/2 20:03
 */
@Data
public class ChatForm {
    private String memoryId;//对话id
    private String message;//用户问题
}
src/main/java/com/ruoyi/ai/bean/PurchaseAiConfirmRequest.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.ruoyi.ai.bean;
import java.util.Map;
public class PurchaseAiConfirmRequest {
    private String businessType;
    private Map<String, Object> payload;
    public String getBusinessType() {
        return businessType;
    }
    public void setBusinessType(String businessType) {
        this.businessType = businessType;
    }
    public Map<String, Object> getPayload() {
        return payload;
    }
    public void setPayload(Map<String, Object> payload) {
        this.payload = payload;
    }
}
src/main/java/com/ruoyi/ai/config/ApproveTodoAgentConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.ai.config;
import com.ruoyi.ai.store.MongoChatMemoryStore;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApproveTodoAgentConfig {
    @Bean
    ChatMemoryProvider chatMemoryProviderApproveTodo(MongoChatMemoryStore mongoChatMemoryStore) {
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(30)
                .chatMemoryStore(mongoChatMemoryStore)
                .build();
    }
}
src/main/java/com/ruoyi/ai/config/EmbeddingStoreConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package com.ruoyi.ai.config;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.pinecone.PineconeEmbeddingStore;
import dev.langchain4j.store.embedding.pinecone.PineconeServerlessIndexConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @author :yys
 * @date : 2025/5/2 21:07
 */
@Configuration
public class EmbeddingStoreConfig {
    @Autowired
    private EmbeddingModel embeddingModel;
    @Bean
    public EmbeddingStore<TextSegment> embeddingStore() {
        //创建向量存储
        return PineconeEmbeddingStore.builder()
                .apiKey("pcsk_4SJLnh_tNB3wSLJU8tc4E5P28PcXX8eCLdURqZpVhg1FMV8CRYxjneWdzqRdB5Ftqooi9")
                .index("xiaozhi-index")//如果指定的索引不存在,将创建一个新的索引
                .nameSpace("xiaozhi-namespace") //如果指定的名称空间不存在,将创建一个新的名称 ç©ºé—´
                .createIndex(PineconeServerlessIndexConfig.builder()
                        .cloud("AWS") //指定索引部署在 AWS äº‘服务上。
                        .region("us-east-1") //指定索引所在的 AWS åŒºåŸŸä¸º us-east-1。
                        .dimension(embeddingModel.dimension()) //指定索引的向量维度,该维度与 embeddedModel ç”Ÿæˆçš„向量维度相同。
                        .build())
                .build();
    }
}
src/main/java/com/ruoyi/ai/config/PurchaseAgentConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.ai.config;
import com.ruoyi.ai.store.MongoChatMemoryStore;
import dev.langchain4j.community.model.dashscope.QwenStreamingChatModel;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class PurchaseAgentConfig {
    @Bean
    ChatMemoryProvider chatMemoryProviderPurchase(MongoChatMemoryStore mongoChatMemoryStore) {
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(30)
                .chatMemoryStore(mongoChatMemoryStore)
                .build();
    }
    @Bean("purchaseVisionStreamingChatModel")
    QwenStreamingChatModel purchaseVisionStreamingChatModel(
            @Value("${langchain4j.community.dashscope.streaming-chat-model.api-key}") String apiKey) {
        return QwenStreamingChatModel.builder()
                .apiKey(apiKey)
                .modelName("qwen-vl-max")
                .isMultimodalModel(true)
                .build();
    }
}
src/main/java/com/ruoyi/ai/config/SeparateChatAssistantConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.ai.config;
import com.ruoyi.ai.store.MongoChatMemoryStore;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @author :yys
 * @date : 2025/5/2 19:20
 */
@Configuration
public class SeparateChatAssistantConfig {
    //注入持久化对象
    @Autowired
    private MongoChatMemoryStore mongoChatMemoryStore;
    @Bean
    ChatMemoryProvider chatMemoryProvider() {
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(10)
                .chatMemoryStore(mongoChatMemoryStore)//配置持久化对象
                .build();
    }
}
src/main/java/com/ruoyi/ai/config/XiaozhiAgentConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,91 @@
package com.ruoyi.ai.config;
import com.ruoyi.ai.store.MongoChatMemoryStore;
import dev.langchain4j.memory.chat.ChatMemoryProvider;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @author :yys
 * @date : 2025/5/2 20:01
 */
@Configuration
public class XiaozhiAgentConfig {
    @Autowired
    private MongoChatMemoryStore mongoChatMemoryStore;
    @Autowired
    private EmbeddingStore embeddingStore;
    @Autowired
    private EmbeddingModel embeddingModel;
//    @Value("${knowledge.one}")
//    private String one;
//
//    @Value("${knowledge.two}")
//    private String two;
//
//    @Value("${knowledge.three}")
//    private String three;
    @Bean
    ChatMemoryProvider chatMemoryProviderXiaozhi() {
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(20)
                .chatMemoryStore(mongoChatMemoryStore)
                .build();
    }
//    @Bean
//    ContentRetriever contentRetrieverXiaozhi() {
//        //使用FileSystemDocumentLoader读取指定目录下的知识库文档
//        //并使用默认的文档解析器对文档进行解析
//        Document document1 = FileSystemDocumentLoader.loadDocument(one);
////        Document document2 = FileSystemDocumentLoader.loadDocument(two);
////        Document document3 = FileSystemDocumentLoader.loadDocument(three);
////        List<Document> documents = Arrays.asList(document1, document2, document3);
//
//        List<Document> documents = Collections.singletonList(document1);
////         2. å°†æ•°æ®åº“数据转为LangChain4j的Document对象
////        List<Document> documents = new ArrayList<>();
//
//        //使用内存向量存储
//        InMemoryEmbeddingStore<TextSegment> inMemoryEmbeddingStore = new InMemoryEmbeddingStore<>();
//        //使用默认的文档分割器
//        EmbeddingStoreIngestor.builder()
//                .embeddingModel(embeddingModel)
//                .embeddingStore(inMemoryEmbeddingStore)
//                .build()
//                .ingest(documents);
//        //从嵌入存储(EmbeddingStore)里检索和查询内容相关的信息
//        return EmbeddingStoreContentRetriever.builder()
//                .embeddingModel(embeddingModel)
//                .embeddingStore(inMemoryEmbeddingStore)
//                .build();
//    }
    @Bean
    ContentRetriever contentRetrieverXiaozhiPincone() {
        // åˆ›å»ºä¸€ä¸ª EmbeddingStoreContentRetriever å¯¹è±¡ï¼Œç”¨äºŽä»ŽåµŒå…¥å­˜å‚¨ä¸­æ£€ç´¢å†…容
        return EmbeddingStoreContentRetriever
                .builder()
                // è®¾ç½®ç”¨äºŽç”ŸæˆåµŒå…¥å‘量的嵌入模型
                .embeddingModel(embeddingModel)
                // æŒ‡å®šè¦ä½¿ç”¨çš„嵌入存储
                .embeddingStore(embeddingStore)
                // è®¾ç½®æœ€å¤§æ£€ç´¢ç»“果数量,这里表示最多返回 1 æ¡åŒ¹é…ç»“æžœ
                .maxResults(1)
                // è®¾ç½®æœ€å°å¾—分阈值,只有得分大于等于 0.8 çš„结果才会被返回
                .minScore(0.8)
                // æž„建最终的 EmbeddingStoreContentRetriever å®žä¾‹
                .build();
    }
}
src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.ruoyi.ai.context;
import com.ruoyi.framework.security.LoginUser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class AiSessionUserContext {
    private final Map<String, LoginUser> loginUserByMemoryId = new ConcurrentHashMap<>();
    public void bind(String memoryId, LoginUser loginUser) {
        if (!StringUtils.hasText(memoryId) || loginUser == null) {
            return;
        }
        loginUserByMemoryId.put(memoryId, loginUser);
    }
    public LoginUser get(String memoryId) {
        if (!StringUtils.hasText(memoryId)) {
            return null;
        }
        return loginUserByMemoryId.get(memoryId);
    }
    public void remove(String memoryId) {
        if (!StringUtils.hasText(memoryId)) {
            return;
        }
        loginUserByMemoryId.remove(memoryId);
    }
}
src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,903 @@
package com.ruoyi.ai.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.ai.assistant.PurchaseAgent;
import com.ruoyi.ai.assistant.PurchaseIntentExecutor;
import com.ruoyi.ai.bean.ChatForm;
import com.ruoyi.ai.bean.PurchaseAiConfirmRequest;
import com.ruoyi.ai.context.AiSessionUserContext;
import com.ruoyi.ai.service.AiChatSessionService;
import com.ruoyi.ai.service.AiFileTextExtractor;
import com.ruoyi.ai.store.MongoChatMemoryStore;
import com.ruoyi.basic.mapper.SupplierManageMapper;
import com.ruoyi.basic.pojo.SupplierManage;
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 com.ruoyi.purchase.dto.PurchaseLedgerDto;
import com.ruoyi.purchase.dto.PurchaseReturnOrderDto;
import com.ruoyi.purchase.pojo.PaymentRegistration;
import com.ruoyi.purchase.service.IPaymentRegistrationService;
import com.ruoyi.purchase.service.IPurchaseLedgerService;
import com.ruoyi.purchase.service.PurchaseReturnOrdersService;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import dev.langchain4j.data.image.Image;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.Content;
import dev.langchain4j.data.message.ImageContent;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.TextContent;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.model.chat.StreamingChatLanguageModel;
import dev.langchain4j.model.chat.response.ChatResponse;
import dev.langchain4j.model.chat.response.StreamingChatResponseHandler;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestParam;
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 org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Base64;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
@Tag(name = "采购智能体")
@RestController
@RequestMapping("/purchase-ai")
public class PurchaseAiController extends BaseController {
    private static final String PURCHASE_FILE_ANALYZE_MEMORY_PREFIX = "purchase-file-analyze::";
    private static final int MAX_FILE_COUNT = 10;
    private static final int MAX_SINGLE_FILE_TEXT_LENGTH = 8000;
    private static final int MAX_TOTAL_FILE_TEXT_LENGTH = 30000;
    private final PurchaseAgent purchaseAgent;
    private final PurchaseIntentExecutor purchaseIntentExecutor;
    private final AiSessionUserContext aiSessionUserContext;
    private final MongoChatMemoryStore mongoChatMemoryStore;
    private final AiChatSessionService aiChatSessionService;
    private final AiFileTextExtractor aiFileTextExtractor;
    private final ObjectMapper objectMapper;
    private final IPurchaseLedgerService purchaseLedgerService;
    private final IPaymentRegistrationService paymentRegistrationService;
    private final PurchaseReturnOrdersService purchaseReturnOrdersService;
    private final SupplierManageMapper supplierManageMapper;
    private final StreamingChatLanguageModel purchaseVisionStreamingChatModel;
    public PurchaseAiController(PurchaseAgent purchaseAgent,
                                PurchaseIntentExecutor purchaseIntentExecutor,
                                AiSessionUserContext aiSessionUserContext,
                                MongoChatMemoryStore mongoChatMemoryStore,
                                AiChatSessionService aiChatSessionService,
                                AiFileTextExtractor aiFileTextExtractor,
                                ObjectMapper objectMapper,
                                IPurchaseLedgerService purchaseLedgerService,
                                IPaymentRegistrationService paymentRegistrationService,
                                PurchaseReturnOrdersService purchaseReturnOrdersService,
                                SupplierManageMapper supplierManageMapper,
                                @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) {
        this.purchaseAgent = purchaseAgent;
        this.purchaseIntentExecutor = purchaseIntentExecutor;
        this.aiSessionUserContext = aiSessionUserContext;
        this.mongoChatMemoryStore = mongoChatMemoryStore;
        this.aiChatSessionService = aiChatSessionService;
        this.aiFileTextExtractor = aiFileTextExtractor;
        this.objectMapper = objectMapper;
        this.purchaseLedgerService = purchaseLedgerService;
        this.paymentRegistrationService = paymentRegistrationService;
        this.purchaseReturnOrdersService = purchaseReturnOrdersService;
        this.supplierManageMapper = supplierManageMapper;
        this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel;
    }
    @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 = purchaseIntentExecutor.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);
        }
        return purchaseAgent.chat(memoryId, userMessage)
                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
    }
    @Operation(summary = "采购多文件分析")
    @PostMapping(value = "/analyze-files", consumes = "multipart/form-data", produces = "text/stream;charset=utf-8")
    public Flux<String> analyzeFiles(@RequestParam("files") MultipartFile[] files,
                                     @RequestParam(value = "message", required = false) String message,
                                     @RequestParam(value = "memoryId", required = false) String memoryId) {
        if (files == null || files.length == 0) {
            return Flux.just("files不能为空");
        }
        if (files.length > MAX_FILE_COUNT) {
            return Flux.just("一次最多分析" + MAX_FILE_COUNT + "个文件");
        }
        String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString();
        String finalMemoryId = rawMemoryId.startsWith(PURCHASE_FILE_ANALYZE_MEMORY_PREFIX)
                ? rawMemoryId
                : PURCHASE_FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId;
        LoginUser loginUser = SecurityUtils.getLoginUser();
        aiSessionUserContext.bind(finalMemoryId, loginUser);
        String finalMessage = StringUtils.hasText(message)
                ? message
                : "请分析这些采购文件,提取可用于业务处理的数据,并整理成待客户确认的格式";
        String fileContent;
        try {
            fileContent = buildMultiFileContent(files);
        } catch (IllegalArgumentException ex) {
            return Flux.just(ex.getMessage());
        } catch (IOException ex) {
            return Flux.just("文件读取失败");
        }
        if (!StringUtils.hasText(fileContent)) {
            return Flux.just("未提取到有效文件内容");
        }
        String userPrompt = buildPurchaseFileAnalyzePrompt(finalMessage, fileContent);
        aiChatSessionService.touchSession(finalMemoryId, loginUser, "采购多文件分析: " + finalMessage);
        if (containsImageFile(files)) {
            return chatWithPurchaseVisionModel(finalMemoryId, userPrompt, files)
                    .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
                    .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
        }
        return Flux.defer(() -> purchaseAgent.chat(finalMemoryId, userPrompt))
                .onErrorResume(NoSuchElementException.class, ex -> {
                    mongoChatMemoryStore.deleteMessages(finalMemoryId);
                    return purchaseAgent.chat(finalMemoryId, userPrompt);
                })
                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
    }
    @Operation(summary = "采购多文件分析确认处理")
    @PostMapping("/analyze-files/confirm")
    public AjaxResult confirmAnalyzeResult(@RequestBody PurchaseAiConfirmRequest request) {
        if (request == null || !StringUtils.hasText(request.getBusinessType())) {
            return AjaxResult.error("businessType不能为空");
        }
        if (request.getPayload() == null || request.getPayload().isEmpty()) {
            return AjaxResult.error("payload不能为空");
        }
        try {
            String businessType = request.getBusinessType().trim();
            return switch (businessType) {
                case "purchase_ledger" -> processPurchaseLedger(request.getPayload());
                case "payment_registration" -> processPaymentRegistration(request.getPayload());
                case "purchase_return_order" -> processPurchaseReturnOrder(request.getPayload());
                default -> AjaxResult.error("暂不支持该业务类型: " + businessType);
            };
        } catch (Exception ex) {
            return AjaxResult.error(toCustomerMessage(ex));
        }
    }
    @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 buildMultiFileContent(MultipartFile[] files) throws IOException {
        StringBuilder builder = new StringBuilder();
        int totalLength = 0;
        for (MultipartFile file : files) {
            String text = aiFileTextExtractor.extractText(file);
            if (!StringUtils.hasText(text)) {
                continue;
            }
            String limitedText = text.length() > MAX_SINGLE_FILE_TEXT_LENGTH
                    ? text.substring(0, MAX_SINGLE_FILE_TEXT_LENGTH)
                    : text;
            if (totalLength + limitedText.length() > MAX_TOTAL_FILE_TEXT_LENGTH) {
                int remain = MAX_TOTAL_FILE_TEXT_LENGTH - totalLength;
                if (remain <= 0) {
                    break;
                }
                limitedText = limitedText.substring(0, remain);
            }
            builder.append("\n--- æ–‡ä»¶: ")
                    .append(file.getOriginalFilename())
                    .append(" ---\n")
                    .append(limitedText)
                    .append('\n');
            totalLength += limitedText.length();
        }
        return builder.toString();
    }
    private boolean containsImageFile(MultipartFile[] files) {
        for (MultipartFile file : files) {
            if (aiFileTextExtractor.isImageFile(file)) {
                return true;
            }
        }
        return false;
    }
    private Flux<String> chatWithPurchaseVisionModel(String memoryId, String userPrompt, MultipartFile[] files) {
        return Flux.create(sink -> {
            try {
                List<Content> contents = new ArrayList<>();
                contents.add(TextContent.from(userPrompt));
                for (MultipartFile file : files) {
                    if (!aiFileTextExtractor.isImageFile(file)) {
                        continue;
                    }
                    contents.add(TextContent.from("下面这张图片文件名:" + file.getOriginalFilename()));
                    contents.add(ImageContent.from(Image.builder()
                            .base64Data(Base64.getEncoder().encodeToString(file.getBytes()))
                            .mimeType(resolveImageMimeType(file))
                            .build()));
                }
                List<ChatMessage> messages = List.of(
                        SystemMessage.from("你是采购业务文件分析助手。请从文本和图片中识别采购台账、采购产品明细、付款或退货信息,只输出合法 JSON。"),
                        UserMessage.from(contents)
                );
                purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() {
                    @Override
                    public void onPartialResponse(String partialResponse) {
                        sink.next(partialResponse);
                    }
                    @Override
                    public void onCompleteResponse(ChatResponse completeResponse) {
                        sink.complete();
                    }
                    @Override
                    public void onError(Throwable error) {
                        sink.error(error);
                    }
                });
            } catch (Exception ex) {
                sink.next("图片文件读取失败,请确认图片格式为 png、jpg、jpeg、webp æˆ– bmp,且大小不超过10MB");
                sink.complete();
            }
        });
    }
    private String resolveImageMimeType(MultipartFile file) {
        String contentType = file.getContentType();
        if (StringUtils.hasText(contentType) && contentType.startsWith("image/")) {
            return contentType;
        }
        String filename = file.getOriginalFilename();
        String ext = "";
        if (StringUtils.hasText(filename) && filename.contains(".")) {
            ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
        }
        return switch (ext) {
            case "jpg", "jpeg" -> "image/jpeg";
            case "webp" -> "image/webp";
            case "bmp" -> "image/bmp";
            default -> "image/png";
        };
    }
    private String buildPurchaseFileAnalyzePrompt(String message, String fileContent) {
        return """
                ä½ æ˜¯é‡‡è´­ä¸šåŠ¡æ–‡ä»¶åˆ†æžåŠ©æ‰‹ã€‚è¯·ä¸¥æ ¼æ ¹æ®ç”¨æˆ·ä¸Šä¼ çš„å¤šä¸ªæ–‡ä»¶å’Œç”¨æˆ·è¦æ±‚æå–é‡‡è´­ä¸šåŠ¡æ•°æ®ã€‚
                ç”¨æˆ·è¦æ±‚:
                %s
                è¾“出要求:
                1. åªè¾“出合法 JSON,不要 Markdown,不要额外解释。
                2. JSON é¡¶å±‚字段固定为:
                   - success: boolean
                   - businessType: purchase_ledger | payment_registration | purchase_return_order | unknown
                   - action: confirm_required
                   - description: ä¸­æ–‡è¯´æ˜Ž
                   - confidence: 0到1的小数
                   - missingFields: ç¼ºå¤±å­—段中文名称数组,面向客户展示,不要输出英文字段名
                   - warnings: é£Žé™©æç¤ºæ•°ç»„
                   - payload: å¾…客户确认的数据,字段名必须使用后端 DTO å­—段名
                   - preview: ç»™å®¢æˆ·ç¡®è®¤ç”¨çš„中文摘要数组
                3. å¦‚果可判断为采购台账,businessType ä½¿ç”¨ purchase_ledger,payload.purchaseLedgers ä¸ºé‡‡è´­è®¢å•/采购台账数组:
                   - purchaseLedgers: é‡‡è´­è®¢å•/采购台账数组,每条记录字段名必须与 PurchaseLedgerDto ä¿æŒä¸€è‡´
                   - äº§å“æ˜Žç»†å¿…须放在每条采购台账记录的 productData å­—段中,productData ç±»åž‹ä¸º List<SalesLedgerProduct>
                   - ä¸è¦ä¼˜å…ˆä½¿ç”¨ payload é¡¶å±‚ productData;顶层 productData ä»…作为旧格式兼容
                   - æ–‡ä»¶é‡Œçš„“采购单号”就是“采购合同号”,统一映射为 purchaseContractNumber
                   - æ–‡ä»¶é‡Œçš„“销售单号”就是“销售合同号”,统一映射为 salesContractNo
                   - æ‰€æœ‰æ—¥æœŸå­—段必须使用 yyyy-MM-dd,例如 2026-04-30;不要输出 4/30/26、2026/4/30、2026å¹´4月30日 æˆ–带时分秒的格式
                   - é‡‡è´­å°è´¦ä¸éœ€è¦åœ¨ payload ä¸­ä¼ å®¡æ‰¹äººï¼Œä¸è¦è¾“出 approveUserIds、approverId
                   - missingFields åªå¡«å†™ä¸šåŠ¡å¿…å¡«ä½†æ— æ³•è¯†åˆ«çš„å­—æ®µï¼Œä¸è¦æŠŠ PurchaseLedgerDto çš„æ‰€æœ‰ç©ºå­—段都列为缺失;缺失项必须写中文,例如“供应商名称”“含税单价”,不要写 supplierId、taxInclusiveUnitPrice
                   - é‡‡è´­å°è´¦ä¸»è¡¨å¿…填字段仅按这些判断: purchaseContractNumber、supplierName æˆ– supplierId
                   - productData æ¯æ¡äº§å“å¿…填字段: productCategory、specificationModel、unit、quantity、taxInclusiveUnitPrice æˆ– taxInclusiveTotalPrice;如果只有含税总价和数量,必须计算 taxInclusiveUnitPrice;如果只有含税单价和数量,必须计算 taxInclusiveTotalPrice
                   - äº§å“å­—段按采购导入接口 PurchaseLedgerProductImportDto å¯¹é½: é‡‡è´­å•号、产品大类、规格型号、单位、数量、税率、含税单价、含税总价、发票类型、是否质检
                   - é‡‡è´­äº§å“ type å›ºå®šä¸º 2
                   - purchaseLedgers æ¯æ¡è®°å½•只使用这些 PurchaseLedgerDto å­—段名:
                     entryDateStart, entryDateEnd, id, purchaseContractNumber, supplierId, supplierName, isWhite, recorderId, recorderName, salesContractNo, salesContractNoId, projectName, entryDate, executionDate, remarks, attachmentMaterials, createdAt, updatedAt, salesLedgerId, hasChildren, Type, productData, tempFileIds, SalesLedgerFiles, phoneNumber, businessPersonId, productId, productModelId, invoiceNumber, invoiceAmount, ticketRegistrationId, contractAmount, receiptPaymentAmount, unReceiptPaymentAmount, type, paymentMethod, approvalStatus, templateName
                   - productData æ¯æ¡äº§å“åªä½¿ç”¨è¿™äº› SalesLedgerProduct å­—段名:
                     productCategory, specificationModel, unit, quantity, taxRate, taxInclusiveUnitPrice, taxInclusiveTotalPrice, taxExclusiveTotalPrice, invoiceType, productId, productModelId, isChecked, type
                4. å¦‚果可判断为付款登记,businessType ä½¿ç”¨ payment_registration,payload.records ä¸ºä»˜æ¬¾ç™»è®°æ•°ç»„,字段尽量包含 purchaseLedgerId、salesLedgerProductId、currentPaymentAmount、paymentMethod、paymentDate。
                5. å¦‚果可判断为采购退货,businessType ä½¿ç”¨ purchase_return_order,payload æŒ‰ PurchaseReturnOrderDto ç»„织,明细放 purchaseReturnOrderProductsDtos。
                6. ç¼ºå°‘业务处理必须字段时,不要编造 ID,把字段放入 missingFields,并仍返回可确认的草稿数据。
                7. æ‰€æœ‰ä¸­æ–‡å†…容直接保留,不要转义成 Unicode。
                æ–‡ä»¶å†…容:
                %s
                """.formatted(message, fileContent);
    }
    private AjaxResult processPurchaseLedger(Map<String, Object> payload) throws Exception {
        if (payload.containsKey("purchaseLedgers")) {
            return processPurchaseLedgerBatch(payload);
        }
        Map<String, Object> normalizedPayload = normalizePurchaseLedgerMap(payload);
        PurchaseLedgerDto dto = objectMapper.convertValue(normalizedPayload, PurchaseLedgerDto.class);
        AjaxResult ledgerResult = validatePurchaseLedger(dto, 0);
        if (ledgerResult != null) {
            return ledgerResult;
        }
        AjaxResult supplierResult = fillSupplierIdByName(dto);
        if (supplierResult != null) {
            return supplierResult;
        }
        AjaxResult productResult = validatePurchaseProducts(dto.getProductData(), 0);
        if (productResult != null) {
            return productResult;
        }
        int result = purchaseLedgerService.addOrEditPurchase(dto);
        return AjaxResult.success("采购台账已处理", result);
    }
    private AjaxResult processPurchaseLedgerBatch(Map<String, Object> payload) throws Exception {
        List<Map<String, Object>> purchaseLedgers = toMapList(payload.get("purchaseLedgers"));
        if (purchaseLedgers.isEmpty()) {
            return AjaxResult.error("purchaseLedgers不能为空");
        }
        List<Map<String, Object>> topLevelProductData = toMapList(payload.get("productData"));
        List<Map<String, Object>> results = new ArrayList<>();
        for (int i = 0; i < purchaseLedgers.size(); i++) {
            Map<String, Object> ledgerMap = normalizePurchaseLedgerMap(purchaseLedgers.get(i));
            PurchaseLedgerDto dto = objectMapper.convertValue(ledgerMap, PurchaseLedgerDto.class);
            AjaxResult ledgerResult = validatePurchaseLedger(dto, i);
            if (ledgerResult != null) {
                return ledgerResult;
            }
            AjaxResult supplierResult = fillSupplierIdByName(dto);
            if (supplierResult != null) {
                return supplierResult;
            }
            List<SalesLedgerProduct> products = dto.getProductData();
            if (products == null || products.isEmpty()) {
                products = matchProductsForLedger(ledgerMap, dto, topLevelProductData, purchaseLedgers.size() == 1);
                dto.setProductData(products);
            }
            AjaxResult productResult = validatePurchaseProducts(products, i);
            if (productResult != null) {
                return productResult;
            }
            int result = purchaseLedgerService.addOrEditPurchase(dto);
            Map<String, Object> item = new LinkedHashMap<>();
            item.put("index", i);
            item.put("purchaseContractNumber", dto.getPurchaseContractNumber());
            item.put("supplierId", dto.getSupplierId());
            item.put("supplierName", dto.getSupplierName());
            item.put("productCount", products.size());
            item.put("result", result);
            results.add(item);
        }
        return AjaxResult.success("采购台账已批量处理", results);
    }
    private List<SalesLedgerProduct> matchProductsForLedger(Map<String, Object> ledgerMap,
                                                            PurchaseLedgerDto dto,
                                                            List<Map<String, Object>> productData,
                                                            boolean onlyOneLedger) {
        List<SalesLedgerProduct> products = new ArrayList<>();
        for (Map<String, Object> productMap : productData) {
            if (onlyOneLedger || productBelongsToLedger(productMap, ledgerMap, dto)) {
                products.add(objectMapper.convertValue(normalizeSalesLedgerProductMap(productMap), SalesLedgerProduct.class));
            }
        }
        return products;
    }
    private boolean productBelongsToLedger(Map<String, Object> productMap, Map<String, Object> ledgerMap, PurchaseLedgerDto dto) {
        Long productPurchaseLedgerId = longValue(productMap, "purchaseLedgerId", "purchaseId", "采购订单id", "采购台账id");
        if (productPurchaseLedgerId != null && dto.getId() != null && productPurchaseLedgerId.equals(dto.getId())) {
            return true;
        }
        Long productSalesLedgerId = longValue(productMap, "salesLedgerId");
        if (productSalesLedgerId != null && dto.getId() != null && productSalesLedgerId.equals(dto.getId())) {
            return true;
        }
        String productContractNo = stringValue(productMap, "purchaseContractNumber", "purchaseContractNo", "采购合同号", "采购单号", "采购订单号");
        if (StringUtils.hasText(productContractNo)
                && StringUtils.hasText(dto.getPurchaseContractNumber())
                && productContractNo.trim().equals(dto.getPurchaseContractNumber().trim())) {
            return true;
        }
        String ledgerContractNo = stringValue(ledgerMap, "purchaseContractNumber", "purchaseContractNo", "采购合同号", "采购单号", "采购订单号");
        if (StringUtils.hasText(productContractNo)
                && StringUtils.hasText(ledgerContractNo)
                && productContractNo.trim().equals(ledgerContractNo.trim())) {
            return true;
        }
        String productSalesContractNo = stringValue(productMap, "salesContractNo", "salesContractNumber", "销售合同号", "销售单号", "销售订单号");
        if (StringUtils.hasText(productSalesContractNo)
                && StringUtils.hasText(dto.getSalesContractNo())
                && productSalesContractNo.trim().equals(dto.getSalesContractNo().trim())) {
            return true;
        }
        String ledgerSalesContractNo = stringValue(ledgerMap, "salesContractNo", "salesContractNumber", "销售合同号", "销售单号", "销售订单号");
        if (StringUtils.hasText(productSalesContractNo)
                && StringUtils.hasText(ledgerSalesContractNo)
                && productSalesContractNo.trim().equals(ledgerSalesContractNo.trim())) {
            return true;
        }
        String productSupplierName = stringValue(productMap, "supplierName", "供应商名称");
        return StringUtils.hasText(productSupplierName)
                && StringUtils.hasText(dto.getSupplierName())
                && productSupplierName.trim().equals(dto.getSupplierName().trim());
    }
    private Map<String, Object> normalizePurchaseLedgerMap(Map<String, Object> source) {
        Map<String, Object> target = new LinkedHashMap<>();
        copyPurchaseLedgerDtoFields(source, target);
        putDtoFieldIfPresent(source, target, "entryDateStart", "录入开始日期", "录入日期开始");
        putDtoFieldIfPresent(source, target, "entryDateEnd", "录入结束日期", "录入日期结束");
        putDtoFieldIfPresent(source, target, "id", "采购台账id", "采购订单id", "主键");
        putDtoFieldIfPresent(source, target, "purchaseContractNumber", "purchaseContractNo", "采购合同号", "采购单号", "采购订单号");
        putDtoFieldIfPresent(source, target, "supplierId", "供应商id", "供应商ID", "供应商名称id", "供应商名称ID");
        putDtoFieldIfPresent(source, target, "supplierName", "供应商", "供应商名称");
        putDtoFieldIfPresent(source, target, "isWhite", "是否白名单");
        putDtoFieldIfPresent(source, target, "recorderId", "录入人id", "录入人ID", "录入人姓名id", "录入人姓名ID");
        putDtoFieldIfPresent(source, target, "recorderName", "录入人", "录入人姓名");
        putDtoFieldIfPresent(source, target, "salesContractNo", "salesContractNumber", "销售合同号", "销售单号", "销售订单号");
        putDtoFieldIfPresent(source, target, "salesContractNoId", "销售合同号id", "销售合同号ID", "销售单号id", "销售单号ID");
        putDtoFieldIfPresent(source, target, "projectName", "项目", "项目名称");
        putDtoFieldIfPresent(source, target, "entryDate", "录入日期");
        putDtoFieldIfPresent(source, target, "executionDate", "签订日期", "合同签订日期");
        putDtoFieldIfPresent(source, target, "remarks", "备注", "说明");
        putDtoFieldIfPresent(source, target, "attachmentMaterials", "附件材料", "附件材料路径或名称");
        putDtoFieldIfPresent(source, target, "createdAt", "创建时间", "记录创建时间");
        putDtoFieldIfPresent(source, target, "updatedAt", "更新时间", "记录最后更新时间");
        putDtoFieldIfPresent(source, target, "salesLedgerId", "销售台账id", "销售台账ID", "关联销售台账主表主键");
        putDtoFieldIfPresent(source, target, "hasChildren", "是否有子级", "是否有明细");
        putDtoFieldIfPresent(source, target, "Type", "台账类型", "业务类型");
        putDtoFieldIfPresent(source, target, "productData", "products", "产品明细", "采购产品明细");
        putDtoFieldIfPresent(source, target, "tempFileIds", "临时文件id", "临时文件ID", "临时文件ids");
        putDtoFieldIfPresent(source, target, "SalesLedgerFiles", "附件列表", "销售台账附件");
        putDtoFieldIfPresent(source, target, "phoneNumber", "业务员手机号", "手机号");
        putDtoFieldIfPresent(source, target, "businessPersonId", "业务员id", "业务员ID");
        putDtoFieldIfPresent(source, target, "productId", "产品id", "产品ID");
        putDtoFieldIfPresent(source, target, "productModelId", "产品规格id", "产品规格ID");
        putDtoFieldIfPresent(source, target, "invoiceNumber", "发票号", "发票号码");
        putDtoFieldIfPresent(source, target, "invoiceAmount", "发票金额", "发票金额(元)");
        putDtoFieldIfPresent(source, target, "ticketRegistrationId", "来票登记id", "来票登记ID");
        putDtoFieldIfPresent(source, target, "contractAmount", "合同金额", "合同金额(产品含税总价)");
        putDtoFieldIfPresent(source, target, "receiptPaymentAmount", "来票金额", "已来票金额", "已来票金额(元)");
        putDtoFieldIfPresent(source, target, "unReceiptPaymentAmount", "未来票金额", "未来票金额(元)");
        putDtoFieldIfPresent(source, target, "type", "文件类型");
        putDtoFieldIfPresent(source, target, "paymentMethod", "付款方式");
        putDtoFieldIfPresent(source, target, "approvalStatus", "审批状态");
        putDtoFieldIfPresent(source, target, "templateName", "模板名称");
        target.remove("approveUserIds");
        target.remove("approverId");
        normalizeNestedProductData(target);
        attachImportStyleProductData(source, target);
        if (target.get("type") == null) {
            target.put("type", 2);
        }
        target.putIfAbsent("entryDate", LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE));
        normalizePurchaseLedgerDateFields(target);
        return target;
    }
    private void attachImportStyleProductData(Map<String, Object> source, Map<String, Object> target) {
        if (target.get("productData") != null) {
            return;
        }
        Map<String, Object> productMap = normalizeSalesLedgerProductMap(source);
        if (hasImportStyleProductData(productMap)) {
            target.put("productData", List.of(productMap));
        }
    }
    private boolean hasImportStyleProductData(Map<String, Object> productMap) {
        return hasMapText(productMap, "productCategory")
                || hasMapText(productMap, "specificationModel")
                || productMap.get("quantity") != null
                || productMap.get("taxInclusiveUnitPrice") != null
                || productMap.get("taxInclusiveTotalPrice") != null;
    }
    private boolean hasMapText(Map<String, Object> map, String key) {
        Object value = map.get(key);
        return value != null && StringUtils.hasText(String.valueOf(value));
    }
    private void normalizeNestedProductData(Map<String, Object> target) {
        Object productDataValue = target.get("productData");
        if (productDataValue == null) {
            return;
        }
        List<Map<String, Object>> productMaps = toMapList(productDataValue);
        List<Map<String, Object>> normalizedProducts = new ArrayList<>();
        for (Map<String, Object> productMap : productMaps) {
            normalizedProducts.add(normalizeSalesLedgerProductMap(productMap));
        }
        target.put("productData", normalizedProducts);
    }
    private Map<String, Object> normalizeSalesLedgerProductMap(Map<String, Object> source) {
        Map<String, Object> target = new LinkedHashMap<>();
        copySalesLedgerProductFields(source, target);
        putDtoFieldIfPresent(source, target, "productCategory", "产品大类", "产品名称", "产品", "品名", "物料名称");
        putDtoFieldIfPresent(source, target, "specificationModel", "规格型号", "型号", "规格", "产品规格");
        putDtoFieldIfPresent(source, target, "unit", "单位");
        putDtoFieldIfPresent(source, target, "quantity", "数量", "采购数量");
        putDtoFieldIfPresent(source, target, "taxRate", "税率");
        putDtoFieldIfPresent(source, target, "taxInclusiveUnitPrice", "含税单价", "单价", "采购单价", "含税价格");
        putDtoFieldIfPresent(source, target, "taxInclusiveTotalPrice", "含税总价", "总价", "采购金额", "金额", "合同金额");
        putDtoFieldIfPresent(source, target, "taxExclusiveTotalPrice", "不含税总价");
        putDtoFieldIfPresent(source, target, "invoiceType", "发票类型", "发票类别");
        putDtoFieldIfPresent(source, target, "productId", "产品id", "产品ID");
        putDtoFieldIfPresent(source, target, "productModelId", "产品规格id", "产品规格ID", "型号id", "型号ID");
        putDtoFieldIfPresent(source, target, "isChecked", "是否质检", "是否质检验", "质检");
        putDtoFieldIfPresent(source, target, "type", "台账类型");
        normalizeProductAmounts(target);
        target.putIfAbsent("type", 2);
        return target;
    }
    private void copySalesLedgerProductFields(Map<String, Object> source, Map<String, Object> target) {
        String[] productFields = {
                "id", "salesLedgerId", "warnNum", "productCategory", "specificationModel", "unit",
                "speculativeTradingName", "quantity", "minStock", "taxRate", "taxInclusiveUnitPrice",
                "taxInclusiveTotalPrice", "taxExclusiveTotalPrice", "invoiceType", "type", "ticketsNum",
                "ticketsAmount", "futureTickets", "futureTicketsAmount", "invoiceNum", "noInvoiceNum",
                "invoiceAmount", "noInvoiceAmount", "productId", "productModelId", "register", "registerDate",
                "approveStatus", "pendingInvoiceTotal", "invoiceTotal", "pendingTicketsTotal", "ticketsTotal",
                "isChecked", "isProduction"
        };
        for (String field : productFields) {
            if (source.containsKey(field)) {
                target.put(field, source.get(field));
            }
        }
    }
    private void normalizeProductAmounts(Map<String, Object> target) {
        BigDecimal quantity = decimalValue(target.get("quantity"));
        BigDecimal unitPrice = decimalValue(target.get("taxInclusiveUnitPrice"));
        BigDecimal totalPrice = decimalValue(target.get("taxInclusiveTotalPrice"));
        if (unitPrice == null && totalPrice != null && quantity != null && quantity.compareTo(BigDecimal.ZERO) != 0) {
            target.put("taxInclusiveUnitPrice", totalPrice.divide(quantity, 6, RoundingMode.HALF_UP));
        }
        if (totalPrice == null && unitPrice != null && quantity != null) {
            target.put("taxInclusiveTotalPrice", unitPrice.multiply(quantity));
        }
        BigDecimal taxRate = decimalValue(target.get("taxRate"));
        totalPrice = decimalValue(target.get("taxInclusiveTotalPrice"));
        if (target.get("taxExclusiveTotalPrice") == null && totalPrice != null && taxRate != null) {
            BigDecimal divisor = BigDecimal.ONE.add(taxRate.divide(new BigDecimal("100"), 6, RoundingMode.HALF_UP));
            target.put("taxExclusiveTotalPrice", totalPrice.divide(divisor, 2, RoundingMode.HALF_UP));
        }
    }
    private AjaxResult validatePurchaseProducts(List<SalesLedgerProduct> products, int ledgerIndex) {
        if (products == null || products.isEmpty()) {
            return null;
        }
        for (int i = 0; i < products.size(); i++) {
            SalesLedgerProduct product = products.get(i);
            String prefix = "第" + (ledgerIndex + 1) + "个采购台账的第" + (i + 1) + "条产品";
            if (!StringUtils.hasText(product.getProductCategory())) {
                return AjaxResult.error(prefix + "缺少产品名称,请补充后再确认");
            }
            if (!StringUtils.hasText(product.getSpecificationModel())) {
                return AjaxResult.error(prefix + "缺少规格型号,请补充后再确认");
            }
            if (!StringUtils.hasText(product.getUnit())) {
                return AjaxResult.error(prefix + "缺少单位,请补充后再确认");
            }
            if (product.getQuantity() == null) {
                return AjaxResult.error(prefix + "缺少数量");
            }
            if (product.getTaxInclusiveUnitPrice() == null) {
                return AjaxResult.error(prefix + "缺少含税单价,请补充后再确认");
            }
            if (product.getTaxInclusiveTotalPrice() == null) {
                return AjaxResult.error(prefix + "缺少含税总价,请补充后再确认");
            }
        }
        return null;
    }
    private AjaxResult validatePurchaseLedger(PurchaseLedgerDto dto, int ledgerIndex) {
        String prefix = "第" + (ledgerIndex + 1) + "个采购台账";
        if (!StringUtils.hasText(dto.getPurchaseContractNumber())) {
            return AjaxResult.error(prefix + "缺少采购合同号,请补充后再确认");
        }
        if (dto.getSupplierId() == null && !StringUtils.hasText(dto.getSupplierName())) {
            return AjaxResult.error(prefix + "缺少供应商名称,请补充后再确认");
        }
        return null;
    }
    private void normalizePurchaseLedgerDateFields(Map<String, Object> target) {
        normalizeDateField(target, "entryDate");
        normalizeDateField(target, "executionDate");
        normalizeDateField(target, "createdAt");
        normalizeDateField(target, "updatedAt");
    }
    private void normalizeDateField(Map<String, Object> target, String fieldName) {
        Object value = target.get(fieldName);
        if (value == null) {
            return;
        }
        String normalizedDate = normalizeDateValue(value);
        if (StringUtils.hasText(normalizedDate)) {
            target.put(fieldName, normalizedDate);
        }
    }
    private String normalizeDateValue(Object value) {
        if (value instanceof Date date) {
            return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DateTimeFormatter.ISO_LOCAL_DATE);
        }
        if (value instanceof Number number) {
            return LocalDate.of(1899, 12, 30)
                    .plusDays(number.longValue())
                    .format(DateTimeFormatter.ISO_LOCAL_DATE);
        }
        String text = String.valueOf(value).trim();
        if (!StringUtils.hasText(text)) {
            return null;
        }
        if (text.length() >= 10 && text.charAt(4) == '-' && text.charAt(7) == '-') {
            return text.substring(0, 10);
        }
        String normalizedText = text.replace("å¹´", "-")
                .replace("月", "-")
                .replace("日", "")
                .replace(".", "-")
                .replace("/", "-")
                .trim();
        DateTimeFormatter[] formatters = {
                DateTimeFormatter.ofPattern("yyyy-M-d"),
                DateTimeFormatter.ofPattern("M-d-yyyy"),
                DateTimeFormatter.ofPattern("M-d-yy")
        };
        for (DateTimeFormatter formatter : formatters) {
            try {
                return LocalDate.parse(normalizedText, formatter).format(DateTimeFormatter.ISO_LOCAL_DATE);
            } catch (DateTimeParseException ignored) {
                // Try the next supported input pattern.
            }
        }
        return text;
    }
    private void copyPurchaseLedgerDtoFields(Map<String, Object> source, Map<String, Object> target) {
        String[] dtoFields = {
                "entryDateStart", "entryDateEnd", "id", "purchaseContractNumber",
                "supplierId", "supplierName", "isWhite", "recorderId", "recorderName", "salesContractNo",
                "salesContractNoId", "projectName", "entryDate", "executionDate", "remarks", "attachmentMaterials",
                "createdAt", "updatedAt", "salesLedgerId", "hasChildren", "Type", "productData", "tempFileIds",
                "SalesLedgerFiles", "phoneNumber", "businessPersonId", "productId", "productModelId", "invoiceNumber",
                "invoiceAmount", "ticketRegistrationId", "contractAmount", "receiptPaymentAmount",
                "unReceiptPaymentAmount", "type", "paymentMethod", "approvalStatus", "templateName"
        };
        for (String field : dtoFields) {
            if (source.containsKey(field)) {
                target.put(field, source.get(field));
            }
        }
    }
    private void putDtoFieldIfPresent(Map<String, Object> source, Map<String, Object> target, String dtoField, String... aliases) {
        if (target.containsKey(dtoField) && target.get(dtoField) != null) {
            return;
        }
        for (String alias : aliases) {
            Object value = source.get(alias);
            if (value != null && StringUtils.hasText(String.valueOf(value))) {
                target.put(dtoField, value);
                return;
            }
        }
    }
    private List<Map<String, Object>> toMapList(Object value) {
        if (value == null) {
            return List.of();
        }
        return objectMapper.convertValue(value, new TypeReference<List<Map<String, Object>>>() {
        });
    }
    private String stringValue(Map<String, Object> map, String... keys) {
        for (String key : keys) {
            Object value = map.get(key);
            if (value != null && StringUtils.hasText(String.valueOf(value))) {
                return String.valueOf(value);
            }
        }
        return null;
    }
    private Long longValue(Map<String, Object> map, String... keys) {
        String value = stringValue(map, keys);
        if (!StringUtils.hasText(value)) {
            return null;
        }
        try {
            return Long.parseLong(value.trim());
        } catch (NumberFormatException ignored) {
            return null;
        }
    }
    private BigDecimal decimalValue(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof BigDecimal decimal) {
            return decimal;
        }
        if (value instanceof Number number) {
            return new BigDecimal(String.valueOf(number));
        }
        String text = String.valueOf(value)
                .replace(",", "")
                .replace(",", "")
                .replace("元", "")
                .replace("ï¿¥", "")
                .trim();
        if (!StringUtils.hasText(text)) {
            return null;
        }
        try {
            return new BigDecimal(text);
        } catch (NumberFormatException ignored) {
            return null;
        }
    }
    private String toCustomerMessage(Exception ex) {
        String message = ex.getMessage();
        if (!StringUtils.hasText(message)) {
            return "处理失败,请检查确认数据后重试";
        }
        if (message.contains("tax_inclusive_unit_price")) {
            return "处理失败:产品明细缺少含税单价,请补充后再确认";
        }
        if (message.contains("tax_inclusive_total_price")) {
            return "处理失败:产品明细缺少含税总价,请补充后再确认";
        }
        if (message.contains("entryDate")) {
            return "处理失败:录入日期格式不正确,请使用 yyyy-MM-dd,例如 2026-04-30";
        }
        if (message.contains("supplier")) {
            return "处理失败:供应商信息不完整,请确认供应商名称或供应商ID";
        }
        if (message.contains("SQL") || message.contains("java.") || message.contains("Exception")) {
            return "处理失败:确认数据不完整或格式不正确,请检查必填字段后重试";
        }
        return "处理失败:" + message;
    }
    private AjaxResult fillSupplierIdByName(PurchaseLedgerDto dto) {
        if (dto.getSupplierId() != null) {
            return null;
        }
        if (!StringUtils.hasText(dto.getSupplierName())) {
            return AjaxResult.error("供应商ID不能为空;未识别到供应商名称,无法自动匹配供应商ID");
        }
        SupplierManage supplier = supplierManageMapper.selectOne(new LambdaQueryWrapper<SupplierManage>()
                .eq(SupplierManage::getSupplierName, dto.getSupplierName().trim())
                .last("limit 1"));
        if (supplier == null) {
            return AjaxResult.error("未找到供应商:" + dto.getSupplierName() + ",请先维护供应商或手动选择供应商ID");
        }
        dto.setSupplierId(supplier.getId());
        return null;
    }
    private AjaxResult processPaymentRegistration(Map<String, Object> payload) {
        Object recordsValue = payload.get("records");
        List<PaymentRegistration> records;
        if (recordsValue == null) {
            records = Collections.singletonList(objectMapper.convertValue(payload, PaymentRegistration.class));
        } else {
            records = objectMapper.convertValue(recordsValue, new TypeReference<List<PaymentRegistration>>() {
            });
        }
        int result = paymentRegistrationService.insertPaymentRegistration(records);
        return AjaxResult.success("付款登记已处理", result);
    }
    private AjaxResult processPurchaseReturnOrder(Map<String, Object> payload) {
        PurchaseReturnOrderDto dto = objectMapper.convertValue(payload, PurchaseReturnOrderDto.class);
        Boolean result = purchaseReturnOrdersService.add(dto);
        return AjaxResult.success("采购退货单已处理", result);
    }
}
src/main/java/com/ruoyi/ai/controller/XiaozhiController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,162 @@
package com.ruoyi.ai.controller;
import com.ruoyi.ai.assistant.ApproveTodoAgent;
import com.ruoyi.ai.assistant.ApproveTodoIntentExecutor;
import com.ruoyi.ai.assistant.FileAnalyzeAgent;
import com.ruoyi.ai.bean.ChatForm;
import com.ruoyi.ai.context.AiSessionUserContext;
import com.ruoyi.ai.service.AiChatSessionService;
import com.ruoyi.ai.service.AiFileTextExtractor;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
@Tag(name = "协同办公助手")
@RestController
@RequestMapping("/xiaozhi")
public class XiaozhiController extends BaseController {
    private static final String FILE_ANALYZE_MEMORY_PREFIX = "file-analyze::";
    private final ApproveTodoAgent approveTodoAgent;
    private final ApproveTodoIntentExecutor approveTodoIntentExecutor;
    private final FileAnalyzeAgent fileAnalyzeAgent;
    private final AiSessionUserContext aiSessionUserContext;
    private final MongoChatMemoryStore mongoChatMemoryStore;
    private final AiFileTextExtractor aiFileTextExtractor;
    private final AiChatSessionService aiChatSessionService;
    public XiaozhiController(ApproveTodoAgent approveTodoAgent,
                             ApproveTodoIntentExecutor approveTodoIntentExecutor,
                             FileAnalyzeAgent fileAnalyzeAgent,
                             AiSessionUserContext aiSessionUserContext,
                             MongoChatMemoryStore mongoChatMemoryStore,
                             AiFileTextExtractor aiFileTextExtractor,
                             AiChatSessionService aiChatSessionService) {
        this.approveTodoAgent = approveTodoAgent;
        this.approveTodoIntentExecutor = approveTodoIntentExecutor;
        this.fileAnalyzeAgent = fileAnalyzeAgent;
        this.aiSessionUserContext = aiSessionUserContext;
        this.mongoChatMemoryStore = mongoChatMemoryStore;
        this.aiFileTextExtractor = aiFileTextExtractor;
        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 = approveTodoIntentExecutor.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);
        }
        return approveTodoAgent.chat(memoryId, userMessage)
                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
    }
    @Operation(summary = "上传文件分析")
    @PostMapping(value = "/analyze-file", consumes = "multipart/form-data", produces = "text/stream;charset=utf-8")
    public Flux<String> analyzeFile(@RequestParam("file") MultipartFile file,
                                    @RequestParam(value = "message", required = false) String message,
                                    @RequestParam(value = "memoryId", required = false) String memoryId) {
        String rawMemoryId = StringUtils.hasText(memoryId) ? memoryId : UUID.randomUUID().toString();
        String finalMemoryId = rawMemoryId.startsWith(FILE_ANALYZE_MEMORY_PREFIX)
                ? rawMemoryId
                : FILE_ANALYZE_MEMORY_PREFIX + rawMemoryId;
        LoginUser loginUser = SecurityUtils.getLoginUser();
        aiSessionUserContext.bind(finalMemoryId, loginUser);
        String finalMessage = StringUtils.hasText(message) ? message : "请分析这个文件的核心内容并给出总结";
        String fileText;
        try {
            fileText = aiFileTextExtractor.extractText(file);
        } catch (IllegalArgumentException ex) {
            return Flux.just(ex.getMessage());
        } catch (IOException ex) {
            return Flux.just("文件读取失败");
        }
        if (!StringUtils.hasText(fileText)) {
            return Flux.just("未提取到有效文件内容");
        }
        String limitedContent = fileText.length() > 12000 ? fileText.substring(0, 12000) : fileText;
        String userPrompt = "你将分析用户上传的文件内容。\n"
                + "文件名: " + file.getOriginalFilename() + "\n"
                + "用户问题: " + finalMessage + "\n"
                + "文件内容如下:\n"
                + limitedContent;
        aiChatSessionService.touchSession(finalMemoryId, loginUser, "文件分析: " + finalMessage);
        return Flux.defer(() -> fileAnalyzeAgent.chat(finalMemoryId, userPrompt))
                .onErrorResume(NoSuchElementException.class, ex -> {
                    // DashScope åœ¨åŽ†å²æ¶ˆæ¯å…¼å®¹åœºæ™¯ä¸‹å¯èƒ½æŠ› NoSuchElementException,清空后重试一次
                    mongoChatMemoryStore.deleteMessages(finalMemoryId);
                    return fileAnalyzeAgent.chat(finalMemoryId, userPrompt);
                })
                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, 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()));
    }
}
src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.ai.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AiChatMessageDto {
    private String role;
    private String content;
}
src/main/java/com/ruoyi/ai/dto/AiChatSessionDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.ai.dto;
import lombok.Data;
import java.util.Date;
@Data
public class AiChatSessionDto {
    private String memoryId;
    private String title;
    private String lastMessage;
    private Integer messageCount;
    private Date lastChatTime;
}
src/main/java/com/ruoyi/ai/mapper/AiChatSessionMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.ruoyi.ai.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.ai.pojo.AiChatSession;
public interface AiChatSessionMapper extends BaseMapper<AiChatSession> {
}
src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.ruoyi.ai.mongodbBean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document("chat_messages")
public class ChatMessages {
    @Id
    private ObjectId id;
    @Indexed(unique = true)
    private String memoryId;
    private String content;
    private Date createTime;
    private Date updateTime;
}
src/main/java/com/ruoyi/ai/pojo/AiChatSession.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.ai.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.util.Date;
@Data
@TableName("ai_chat_session")
public class AiChatSession {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String memoryId;
    private Long userId;
    private Long tenantId;
    private String title;
    private String lastMessage;
    private Integer messageCount;
    private Date lastChatTime;
    private Date createTime;
    private Date updateTime;
}
src/main/java/com/ruoyi/ai/service/AiChatSessionService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.ai.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.ai.dto.AiChatMessageDto;
import com.ruoyi.ai.dto.AiChatSessionDto;
import com.ruoyi.ai.pojo.AiChatSession;
import com.ruoyi.framework.security.LoginUser;
import java.util.List;
public interface AiChatSessionService extends IService<AiChatSession> {
    void touchSession(String memoryId, LoginUser loginUser, String userMessage);
    void refreshSessionStats(String memoryId, LoginUser loginUser);
    List<AiChatSessionDto> listCurrentUserSessions(LoginUser loginUser);
    List<AiChatMessageDto> listCurrentUserMessages(String memoryId, LoginUser loginUser);
    boolean deleteCurrentUserSession(String memoryId, LoginUser loginUser);
}
src/main/java/com/ruoyi/ai/service/AiFileTextExtractor.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,131 @@
package com.ruoyi.ai.service;
import com.ruoyi.common.utils.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Component
public class AiFileTextExtractor {
    private static final long MAX_FILE_SIZE = 10L * 1024 * 1024;
    public String extractText(MultipartFile file) throws IOException {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("文件不能为空");
        }
        if (file.getSize() > MAX_FILE_SIZE) {
            throw new IllegalArgumentException("文件过大,请控制在10MB以内");
        }
        String filename = file.getOriginalFilename();
        String ext = getExtension(filename);
        byte[] bytes = file.getBytes();
        if (isPlainText(ext)) {
            return decodeText(bytes);
        }
        if ("docx".equals(ext)) {
            return extractDocx(bytes);
        }
        if ("xlsx".equals(ext)) {
            return extractXlsx(bytes);
        }
        if ("xls".equals(ext)) {
            return extractXls(bytes);
        }
        if (isImage(ext)) {
            return "图片文件:" + filename + ",已上传,请结合图片内容识别采购单据、表格和产品明细。";
        }
        throw new IllegalArgumentException("暂不支持该文件类型: " + ext);
    }
    public boolean isImageFile(MultipartFile file) {
        if (file == null) {
            return false;
        }
        return isImage(getExtension(file.getOriginalFilename()));
    }
    private String extractDocx(byte[] bytes) throws IOException {
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
             XWPFDocument document = new XWPFDocument(inputStream);
             XWPFWordExtractor extractor = new XWPFWordExtractor(document)) {
            return extractor.getText();
        }
    }
    private String extractXlsx(byte[] bytes) throws IOException {
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
             XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
            return extractWorkbook(workbook);
        }
    }
    private String extractXls(byte[] bytes) throws IOException {
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
             HSSFWorkbook workbook = new HSSFWorkbook(inputStream)) {
            return extractWorkbook(workbook);
        }
    }
    private String extractWorkbook(org.apache.poi.ss.usermodel.Workbook workbook) {
        StringBuilder text = new StringBuilder();
        DataFormatter formatter = new DataFormatter();
        for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
            Sheet sheet = workbook.getSheetAt(i);
            text.append("Sheet: ").append(sheet.getSheetName()).append("\n");
            for (Row row : sheet) {
                short lastCellNum = row.getLastCellNum();
                if (lastCellNum <= 0) {
                    text.append("\n");
                    continue;
                }
                for (int c = 0; c < lastCellNum; c++) {
                    String cellText = formatter.formatCellValue(row.getCell(c));
                    text.append(cellText);
                    if (c < lastCellNum - 1) {
                        text.append('\t');
                    }
                }
                text.append('\n');
            }
        }
        return text.toString();
    }
    private String decodeText(byte[] bytes) {
        String utf8 = new String(bytes, StandardCharsets.UTF_8);
        if (utf8.contains("�")) {
            return new String(bytes, java.nio.charset.Charset.forName("GBK"));
        }
        return utf8;
    }
    private String getExtension(String filename) {
        if (!StringUtils.hasText(filename) || !filename.contains(".")) {
            return "";
        }
        return filename.substring(filename.lastIndexOf('.') + 1).toLowerCase();
    }
    private boolean isPlainText(String ext) {
        return StringUtils.inStringIgnoreCase(ext,
                "txt", "md", "markdown", "json", "xml", "yaml", "yml", "csv", "log", "properties",
                "java", "js", "ts", "vue", "html", "css", "sql", "py", "go", "sh", "bat");
    }
    private boolean isImage(String ext) {
        return StringUtils.inStringIgnoreCase(ext, "png", "jpg", "jpeg", "webp", "bmp");
    }
}
src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,191 @@
package com.ruoyi.ai.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.ai.dto.AiChatMessageDto;
import com.ruoyi.ai.dto.AiChatSessionDto;
import com.ruoyi.ai.mapper.AiChatSessionMapper;
import com.ruoyi.ai.pojo.AiChatSession;
import com.ruoyi.ai.service.AiChatSessionService;
import com.ruoyi.ai.store.MongoChatMemoryStore;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.LoginUser;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.ToolExecutionResultMessage;
import dev.langchain4j.data.message.UserMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class AiChatSessionServiceImpl extends ServiceImpl<AiChatSessionMapper, AiChatSession> implements AiChatSessionService {
    private static final int TITLE_MAX_LENGTH = 40;
    private static final int LAST_MESSAGE_MAX_LENGTH = 300;
    private final MongoChatMemoryStore mongoChatMemoryStore;
    @Override
    public void touchSession(String memoryId, LoginUser loginUser, String userMessage) {
        if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) {
            return;
        }
        Date now = new Date();
        AiChatSession session = getSession(memoryId, loginUser.getUserId(), loginUser.getTenantId());
        if (session == null) {
            AiChatSession add = new AiChatSession();
            add.setMemoryId(memoryId);
            add.setUserId(loginUser.getUserId());
            add.setTenantId(loginUser.getTenantId());
            add.setTitle(buildTitle(userMessage));
            add.setLastMessage(trimText(userMessage, LAST_MESSAGE_MAX_LENGTH));
            add.setMessageCount(0);
            add.setLastChatTime(now);
            add.setCreateTime(now);
            add.setUpdateTime(now);
            save(add);
            return;
        }
        AiChatSession update = new AiChatSession();
        update.setId(session.getId());
        if (!StringUtils.hasText(session.getTitle())) {
            update.setTitle(buildTitle(userMessage));
        }
        update.setLastMessage(trimText(userMessage, LAST_MESSAGE_MAX_LENGTH));
        update.setLastChatTime(now);
        update.setUpdateTime(now);
        updateById(update);
    }
    @Override
    public void refreshSessionStats(String memoryId, LoginUser loginUser) {
        if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) {
            return;
        }
        AiChatSession session = getSession(memoryId, loginUser.getUserId(), loginUser.getTenantId());
        if (session == null) {
            return;
        }
        List<ChatMessage> messages = mongoChatMemoryStore.getMessages(memoryId);
        AiChatSession update = new AiChatSession();
        update.setId(session.getId());
        update.setMessageCount(messages.size());
        update.setLastMessage(trimText(lastMessageText(messages), LAST_MESSAGE_MAX_LENGTH));
        update.setLastChatTime(new Date());
        update.setUpdateTime(new Date());
        updateById(update);
    }
    @Override
    public List<AiChatSessionDto> listCurrentUserSessions(LoginUser loginUser) {
        if (loginUser == null || loginUser.getUserId() == null) {
            return new LinkedList<>();
        }
        LambdaQueryWrapper<AiChatSession> queryWrapper = new LambdaQueryWrapper<AiChatSession>()
                .eq(AiChatSession::getUserId, loginUser.getUserId())
                .orderByDesc(AiChatSession::getLastChatTime);
        applyTenantCondition(queryWrapper, loginUser.getTenantId());
        return list(queryWrapper).stream().map(item -> {
            AiChatSessionDto dto = new AiChatSessionDto();
            dto.setMemoryId(item.getMemoryId());
            dto.setTitle(item.getTitle());
            dto.setLastMessage(item.getLastMessage());
            dto.setMessageCount(item.getMessageCount());
            dto.setLastChatTime(item.getLastChatTime());
            return dto;
        }).collect(Collectors.toList());
    }
    @Override
    public List<AiChatMessageDto> listCurrentUserMessages(String memoryId, LoginUser loginUser) {
        if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) {
            return new LinkedList<>();
        }
        AiChatSession session = getSession(memoryId, loginUser.getUserId(), loginUser.getTenantId());
        if (session == null) {
            return new LinkedList<>();
        }
        List<ChatMessage> messages = mongoChatMemoryStore.getMessages(memoryId);
        return messages.stream().map(this::convertMessage).collect(Collectors.toList());
    }
    @Override
    public boolean deleteCurrentUserSession(String memoryId, LoginUser loginUser) {
        if (!StringUtils.hasText(memoryId) || loginUser == null || loginUser.getUserId() == null) {
            return false;
        }
        LambdaQueryWrapper<AiChatSession> queryWrapper = new LambdaQueryWrapper<AiChatSession>()
                .eq(AiChatSession::getMemoryId, memoryId)
                .eq(AiChatSession::getUserId, loginUser.getUserId());
        applyTenantCondition(queryWrapper, loginUser.getTenantId());
        boolean removed = remove(queryWrapper);
        mongoChatMemoryStore.deleteMessages(memoryId);
        return removed;
    }
    private AiChatSession getSession(String memoryId, Long userId, Long tenantId) {
        LambdaQueryWrapper<AiChatSession> queryWrapper = new LambdaQueryWrapper<AiChatSession>()
                .eq(AiChatSession::getMemoryId, memoryId)
                .eq(AiChatSession::getUserId, userId);
        applyTenantCondition(queryWrapper, tenantId);
        return getOne(queryWrapper, false);
    }
    private void applyTenantCondition(LambdaQueryWrapper<AiChatSession> queryWrapper, Long tenantId) {
        if (tenantId == null) {
            queryWrapper.isNull(AiChatSession::getTenantId);
            return;
        }
        queryWrapper.eq(AiChatSession::getTenantId, tenantId);
    }
    private String buildTitle(String userMessage) {
        if (!StringUtils.hasText(userMessage)) {
            return "新会话";
        }
        return trimText(userMessage, TITLE_MAX_LENGTH);
    }
    private String trimText(String text, int maxLength) {
        if (!StringUtils.hasText(text)) {
            return "";
        }
        String source = text.trim();
        if (source.length() <= maxLength) {
            return source;
        }
        return source.substring(0, maxLength) + "...";
    }
    private String lastMessageText(List<ChatMessage> messages) {
        if (messages == null || messages.isEmpty()) {
            return "";
        }
        ChatMessage lastMessage = messages.get(messages.size() - 1);
        return convertMessage(lastMessage).getContent();
    }
    private AiChatMessageDto convertMessage(ChatMessage message) {
        if (message instanceof UserMessage userMessage) {
            return new AiChatMessageDto("user", userMessage.singleText());
        }
        if (message instanceof AiMessage aiMessage) {
            return new AiChatMessageDto("assistant", aiMessage.text());
        }
        if (message instanceof SystemMessage systemMessage) {
            return new AiChatMessageDto("system", systemMessage.text());
        }
        if (message instanceof ToolExecutionResultMessage toolExecutionResultMessage) {
            return new AiChatMessageDto("tool", toolExecutionResultMessage.text());
        }
        return new AiChatMessageDto("unknown", String.valueOf(message));
    }
}
src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
package com.ruoyi.ai.store;
import com.ruoyi.ai.mongodbBean.ChatMessages;
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.ChatMessageDeserializer;
import dev.langchain4j.data.message.ChatMessageSerializer;
import dev.langchain4j.store.memory.chat.ChatMemoryStore;
import lombok.RequiredArgsConstructor;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
@Component
@RequiredArgsConstructor
public class MongoChatMemoryStore implements ChatMemoryStore {
    private final MongoTemplate mongoTemplate;
    @Override
    public List<ChatMessage> getMessages(Object memoryId) {
        Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId)));
        ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class);
        if (chatMessages == null || chatMessages.getContent() == null) {
            return new LinkedList<>();
        }
        return ChatMessageDeserializer.messagesFromJson(chatMessages.getContent());
    }
    @Override
    public void updateMessages(Object memoryId, List<ChatMessage> messages) {
        String memoryIdValue = memoryIdString(memoryId);
        Query query = Query.query(Criteria.where("memoryId").is(memoryIdValue));
        Update update = new Update();
        update.set("memoryId", memoryIdValue);
        update.set("content", ChatMessageSerializer.messagesToJson(messages));
        update.set("updateTime", new Date());
        update.setOnInsert("createTime", new Date());
        mongoTemplate.upsert(query, update, ChatMessages.class);
    }
    @Override
    public void deleteMessages(Object memoryId) {
        Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId)));
        mongoTemplate.remove(query, ChatMessages.class);
    }
    public void appendMessages(Object memoryId, List<ChatMessage> appendList) {
        List<ChatMessage> messages = new LinkedList<>(getMessages(memoryId));
        messages.addAll(appendList);
        updateMessages(memoryId, messages);
    }
    private String memoryIdString(Object memoryId) {
        return memoryId == null ? "" : memoryId.toString();
    }
}
src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,996 @@
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.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
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 SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("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、pending、processing、approved、rejected、resubmitted", 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、applicant、approver;related è¡¨ç¤ºå½“前用户相关,applicant è¡¨ç¤ºæˆ‘发起的,approver è¡¨ç¤ºå¾…我处理的", required = false) String scope) {
        LoginUser loginUser = currentLoginUser(memoryId);
        Long userId = loginUser.getUserId();
        Integer statusCode = parseStatus(status);
        String normalizedScope = normalizeScope(scope);
        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);
            }
        }
        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
                ),
                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 = "执行审批动作,action ä»…支持 approve æˆ– reject,且只能处理当前登录人自己的待审节点。")
    public String reviewTodo(@ToolMemoryId String memoryId,
                             @P("流程编号 approveId") String approveId,
                             @P("动作,approve=通过,reject=驳回") 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) {
        return value == null ? "" : DATE_FORMAT.format(value);
    }
    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 {
            return DATE_FORMAT.parse(dateText);
        } catch (ParseException 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) {
    }
}
src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,643 @@
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.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.purchase.mapper.InvoicePurchaseMapper;
import com.ruoyi.purchase.mapper.PaymentRegistrationMapper;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
import com.ruoyi.purchase.pojo.InvoicePurchase;
import com.ruoyi.purchase.pojo.PaymentRegistration;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import com.ruoyi.procurementrecord.mapper.InboundManagementMapper;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
import com.ruoyi.procurementrecord.pojo.InboundManagement;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
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.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Comparator;
import java.util.stream.Collectors;
@Component
public class PurchaseAgentTools {
    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final int DEFAULT_LIMIT = 10;
    private static final int MAX_LIMIT = 30;
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    private final PaymentRegistrationMapper paymentRegistrationMapper;
    private final InvoicePurchaseMapper invoicePurchaseMapper;
    private final PurchaseReturnOrdersMapper purchaseReturnOrdersMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final ProcurementRecordMapper procurementRecordMapper;
    private final InboundManagementMapper inboundManagementMapper;
    private final AiSessionUserContext aiSessionUserContext;
    public PurchaseAgentTools(PurchaseLedgerMapper purchaseLedgerMapper,
                              PaymentRegistrationMapper paymentRegistrationMapper,
                              InvoicePurchaseMapper invoicePurchaseMapper,
                              PurchaseReturnOrdersMapper purchaseReturnOrdersMapper,
                              SalesLedgerProductMapper salesLedgerProductMapper,
                              ProcurementRecordMapper procurementRecordMapper,
                              InboundManagementMapper inboundManagementMapper,
                              AiSessionUserContext aiSessionUserContext) {
        this.purchaseLedgerMapper = purchaseLedgerMapper;
        this.paymentRegistrationMapper = paymentRegistrationMapper;
        this.invoicePurchaseMapper = invoicePurchaseMapper;
        this.purchaseReturnOrdersMapper = purchaseReturnOrdersMapper;
        this.salesLedgerProductMapper = salesLedgerProductMapper;
        this.procurementRecordMapper = procurementRecordMapper;
        this.inboundManagementMapper = inboundManagementMapper;
        this.aiSessionUserContext = aiSessionUserContext;
    }
    @Tool(name = "查询采购台账列表", value = "按关键字和时间范围查询采购台账,支持返回最近N条")
    public String listPurchaseLedgers(@ToolMemoryId String memoryId,
                                      @P(value = "关键字,可匹配采购合同号/供应商/项目名", required = false) String keyword,
                                      @P(value = "开始日期 yyyy-MM-dd", required = false) String startDate,
                                      @P(value = "结束日期 yyyy-MM-dd", required = false) String endDate,
                                      @P(value = "返回条数,默认10,最大30", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        LocalDate start = parseLocalDate(startDate);
        LocalDate end = parseLocalDate(endDate);
        int finalLimit = normalizeLimit(limit);
        LambdaQueryWrapper<PurchaseLedger> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), PurchaseLedger::getTenantId);
        if (StringUtils.hasText(keyword)) {
            wrapper.and(w -> w.like(PurchaseLedger::getPurchaseContractNumber, keyword)
                    .or().like(PurchaseLedger::getSupplierName, keyword)
                    .or().like(PurchaseLedger::getProjectName, keyword));
        }
        if (start != null) {
            wrapper.ge(PurchaseLedger::getEntryDate, toDate(start));
        }
        if (end != null) {
            wrapper.lt(PurchaseLedger::getEntryDate, toExclusiveEndDate(end));
        }
        wrapper.orderByDesc(PurchaseLedger::getEntryDate, PurchaseLedger::getId).last("limit " + finalLimit);
        List<PurchaseLedger> rows = defaultList(purchaseLedgerMapper.selectList(wrapper));
        List<Map<String, Object>> items = rows.stream().map(this::toLedgerItem).collect(Collectors.toList());
        return jsonResponse(true, "purchase_ledger_list", "已返回采购台账列表",
                Map.of("count", items.size(), "limit", finalLimit, "keyword", safe(keyword)),
                Map.of("items", items), Map.of());
    }
    @Tool(name = "查询采购台账详情", value = "按采购台账ID查询详情")
    public String getPurchaseLedgerDetail(@ToolMemoryId String memoryId, @P("采购台账ID") Long ledgerId) {
        if (ledgerId == null) {
            return jsonResponse(false, "purchase_ledger_detail", "采购台账ID不能为空", Map.of(), Map.of(), Map.of());
        }
        LoginUser loginUser = currentLoginUser(memoryId);
        PurchaseLedger ledger = purchaseLedgerMapper.selectById(ledgerId);
        if (ledger == null || !tenantMatched(ledger.getTenantId(), loginUser.getTenantId())) {
            return jsonResponse(false, "purchase_ledger_detail", "未找到该采购台账或无权限访问", Map.of("ledgerId", ledgerId), Map.of(), Map.of());
        }
        return jsonResponse(true, "purchase_ledger_detail", "已返回采购台账详情",
                Map.of("ledgerId", ledgerId),
                Map.of("detail", toLedgerItem(ledger)),
                Map.of());
    }
    @Tool(name = "统计采购数据", value = "统计时间范围内采购合同数、合同金额、付款金额、发票金额、退货金额")
    public String getPurchaseStats(@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);
        List<PurchaseLedger> ledgers = queryLedgers(loginUser, range);
        List<PaymentRegistration> payments = queryPayments(loginUser, range);
        List<InvoicePurchase> invoices = queryInvoices(loginUser, range);
        List<PurchaseReturnOrders> returns = queryReturns(loginUser, range);
        BigDecimal contractAmount = ledgers.stream()
                .map(PurchaseLedger::getContractAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal paymentAmount = payments.stream()
                .map(PaymentRegistration::getCurrentPaymentAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal invoiceAmount = invoices.stream()
                .map(InvoicePurchase::getInvoiceAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal returnAmount = returns.stream()
                .map(PurchaseReturnOrders::getTotalAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", range.start().toString());
        summary.put("endDate", range.end().toString());
        summary.put("ledgerCount", ledgers.size());
        summary.put("paymentCount", payments.size());
        summary.put("invoiceCount", invoices.size());
        summary.put("returnCount", returns.size());
        summary.put("contractAmount", contractAmount);
        summary.put("paymentAmount", paymentAmount);
        summary.put("invoiceAmount", invoiceAmount);
        summary.put("returnAmount", returnAmount);
        return jsonResponse(true, "purchase_stats", "已返回采购统计数据", summary, Map.of(), Map.of());
    }
    @Tool(name = "采购物料金额排行", value = "按时间范围统计采购物料金额排行,可回答本月采购金额排名靠前的物料。")
    public String rankPurchaseMaterials(@ToolMemoryId String memoryId,
                                        @P(value = "开始日期 yyyy-MM-dd", required = false) String startDate,
                                        @P(value = "结束日期 yyyy-MM-dd", required = false) String endDate,
                                        @P(value = "时间范围描述,例如本月、近7天、近30天", required = false) String timeRange,
                                        @P(value = "返回条数,默认10,最大30", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, timeRange);
        List<Long> ledgerIds = queryLedgers(loginUser, range).stream()
                .map(PurchaseLedger::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        if (ledgerIds.isEmpty()) {
            return jsonResponse(true, "purchase_material_rank", "当前时间范围内没有采购物料数据。",
                    rangeSummary(range, 0), Map.of("items", List.of()), Map.of());
        }
        List<SalesLedgerProduct> products = defaultList(salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>()
                .eq(SalesLedgerProduct::getType, 2)
                .in(SalesLedgerProduct::getSalesLedgerId, ledgerIds)));
        Map<String, MaterialRankItem> grouped = new LinkedHashMap<>();
        for (SalesLedgerProduct product : products) {
            String name = safe(product.getProductCategory());
            String model = safe(product.getSpecificationModel());
            String key = name + "|" + model;
            MaterialRankItem item = grouped.computeIfAbsent(key, ignored -> new MaterialRankItem(name, model, safe(product.getUnit())));
            item.quantity = item.quantity.add(defaultDecimal(product.getQuantity()));
            item.amount = item.amount.add(defaultDecimal(product.getTaxInclusiveTotalPrice()));
        }
        List<Map<String, Object>> items = grouped.values().stream()
                .sorted(Comparator.comparing((MaterialRankItem item) -> item.amount).reversed())
                .limit(normalizeLimit(limit))
                .map(MaterialRankItem::toMap)
                .collect(Collectors.toList());
        return jsonResponse(true, "purchase_material_rank", "已返回采购物料金额排行。",
                rangeSummary(range, items.size()), Map.of("items", items), Map.of());
    }
    @Tool(name = "查询未入库采购订单", value = "查询采购订单下仍有待入库数量的物料明细。")
    public String listUnstockedPurchaseOrders(@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 keyword,
                                              @P(value = "返回条数,默认10,最大30", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, null);
        List<PurchaseLedger> ledgers = queryLedgers(loginUser, range).stream()
                .filter(ledger -> matchLedgerKeyword(ledger, keyword))
                .collect(Collectors.toList());
        Map<Long, PurchaseLedger> ledgerMap = ledgers.stream()
                .filter(ledger -> ledger.getId() != null)
                .collect(Collectors.toMap(PurchaseLedger::getId, ledger -> ledger, (a, b) -> a, LinkedHashMap::new));
        if (ledgerMap.isEmpty()) {
            return jsonResponse(true, "purchase_unstocked_list", "未查询到符合条件的采购订单。",
                    rangeSummary(range, 0), Map.of("items", List.of()), Map.of());
        }
        List<SalesLedgerProduct> products = defaultList(salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>()
                .eq(SalesLedgerProduct::getType, 2)
                .in(SalesLedgerProduct::getSalesLedgerId, ledgerMap.keySet())));
        List<Map<String, Object>> items = products.stream()
                .filter(product -> matchProductKeyword(product, keyword))
                .map(product -> toUnstockedItem(product, ledgerMap.get(product.getSalesLedgerId())))
                .filter(Objects::nonNull)
                .limit(normalizeLimit(limit))
                .collect(Collectors.toList());
        return jsonResponse(true, "purchase_unstocked_list", "已返回未入库采购订单。",
                rangeSummary(range, items.size()), Map.of("items", items), Map.of());
    }
    @Tool(name = "查询采购到货异常", value = "查询到货状态异常或备注包含异常信息的到货记录。")
    public String listArrivalExceptions(@ToolMemoryId String memoryId,
                                        @P(value = "开始日期 yyyy-MM-dd", required = false) String startDate,
                                        @P(value = "结束日期 yyyy-MM-dd", required = false) String endDate,
                                        @P(value = "时间范围描述,例如近7天、本月", required = false) String timeRange,
                                        @P(value = "返回条数,默认10,最大30", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, timeRange);
        LambdaQueryWrapper<InboundManagement> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), InboundManagement::getTenantId);
        wrapper.ge(InboundManagement::getArrivalTime, toDate(range.start()))
                .lt(InboundManagement::getArrivalTime, toExclusiveEndDate(range.end()))
                .and(w -> w.notLike(InboundManagement::getStatus, "正常")
                        .notLike(InboundManagement::getStatus, "完成")
                        .notLike(InboundManagement::getStatus, "已到货")
                        .or().like(InboundManagement::getStatus, "异常")
                        .or().like(InboundManagement::getRemark, "异常")
                        .or().like(InboundManagement::getRemark, "问题")
                        .or().like(InboundManagement::getRemark, "延迟")
                        .or().like(InboundManagement::getRemark, "短缺"));
        wrapper.orderByDesc(InboundManagement::getArrivalTime).last("limit " + normalizeLimit(limit));
        List<Map<String, Object>> items = defaultList(inboundManagementMapper.selectList(wrapper)).stream()
                .map(this::toArrivalItem)
                .collect(Collectors.toList());
        return jsonResponse(true, "purchase_arrival_exception_list", "已返回采购到货异常记录。",
                rangeSummary(range, items.size()), Map.of("items", items), Map.of());
    }
    @Tool(name = "查询待付款采购单", value = "查询合同金额大于已付款金额的采购单。")
    public String listPendingPaymentOrders(@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 keyword,
                                           @P(value = "返回条数,默认10,最大30", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, null);
        List<Map<String, Object>> items = queryLedgers(loginUser, range).stream()
                .filter(ledger -> matchLedgerKeyword(ledger, keyword))
                .map(ledger -> toPendingPaymentItem(loginUser, ledger))
                .filter(Objects::nonNull)
                .sorted(Comparator.comparing(item -> (BigDecimal) item.get("pendingAmount"), Comparator.reverseOrder()))
                .limit(normalizeLimit(limit))
                .collect(Collectors.toList());
        return jsonResponse(true, "purchase_pending_payment_list", "已返回待付款采购单。",
                rangeSummary(range, items.size()), Map.of("items", items), Map.of());
    }
    @Tool(name = "查询采购退货情况", value = "按时间范围查询采购退货单列表和退货金额。")
    public String listPurchaseReturns(@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 keyword,
                                      @P(value = "返回条数,默认10,最大30", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, null);
        LambdaQueryWrapper<PurchaseReturnOrders> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), PurchaseReturnOrders::getDeptId);
        wrapper.ge(PurchaseReturnOrders::getPreparedAt, range.start())
                .le(PurchaseReturnOrders::getPreparedAt, range.end());
        if (StringUtils.hasText(keyword)) {
            wrapper.and(w -> w.like(PurchaseReturnOrders::getNo, keyword)
                    .or().like(PurchaseReturnOrders::getRemark, keyword)
                    .or().like(PurchaseReturnOrders::getReturnUserName, keyword));
        }
        wrapper.orderByDesc(PurchaseReturnOrders::getPreparedAt).last("limit " + normalizeLimit(limit));
        List<PurchaseReturnOrders> returns = defaultList(purchaseReturnOrdersMapper.selectList(wrapper));
        BigDecimal totalAmount = returns.stream()
                .map(PurchaseReturnOrders::getTotalAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        Map<String, Object> summary = rangeSummary(range, returns.size());
        summary.put("returnAmount", totalAmount);
        return jsonResponse(true, "purchase_return_list", "已返回采购退货情况。",
                summary,
                Map.of("items", returns.stream().map(this::toReturnItem).collect(Collectors.toList())),
                Map.of());
    }
    private List<PurchaseLedger> queryLedgers(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<PurchaseLedger> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), PurchaseLedger::getTenantId);
        wrapper.ge(PurchaseLedger::getEntryDate, toDate(range.start()))
                .lt(PurchaseLedger::getEntryDate, toExclusiveEndDate(range.end()));
        return defaultList(purchaseLedgerMapper.selectList(wrapper));
    }
    private Map<String, Object> rangeSummary(DateRange range, int count) {
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", range.start().toString());
        summary.put("endDate", range.end().toString());
        summary.put("count", count);
        return summary;
    }
    private boolean matchLedgerKeyword(PurchaseLedger ledger, String keyword) {
        if (!StringUtils.hasText(keyword)) {
            return true;
        }
        String text = keyword.trim();
        return safe(ledger.getPurchaseContractNumber()).contains(text)
                || safe(ledger.getSupplierName()).contains(text)
                || safe(ledger.getProjectName()).contains(text);
    }
    private boolean matchProductKeyword(SalesLedgerProduct product, String keyword) {
        if (!StringUtils.hasText(keyword)) {
            return true;
        }
        String text = keyword.trim();
        return safe(product.getProductCategory()).contains(text)
                || safe(product.getSpecificationModel()).contains(text);
    }
    private Map<String, Object> toUnstockedItem(SalesLedgerProduct product, PurchaseLedger ledger) {
        if (product == null || ledger == null || product.getId() == null) {
            return null;
        }
        BigDecimal orderedQuantity = defaultDecimal(product.getQuantity());
        BigDecimal inboundQuantity = sumInboundQuantity(product.getId());
        BigDecimal pendingQuantity = orderedQuantity.subtract(inboundQuantity);
        if (pendingQuantity.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }
        Map<String, Object> item = new LinkedHashMap<>();
        item.put("purchaseLedgerId", ledger.getId());
        item.put("purchaseContractNumber", safe(ledger.getPurchaseContractNumber()));
        item.put("supplierName", safe(ledger.getSupplierName()));
        item.put("productCategory", safe(product.getProductCategory()));
        item.put("specificationModel", safe(product.getSpecificationModel()));
        item.put("unit", safe(product.getUnit()));
        item.put("orderedQuantity", orderedQuantity);
        item.put("inboundQuantity", inboundQuantity);
        item.put("pendingInboundQuantity", pendingQuantity);
        item.put("entryDate", formatDate(ledger.getEntryDate()));
        return item;
    }
    private BigDecimal sumInboundQuantity(Long salesLedgerProductId) {
        List<ProcurementRecordStorage> records = defaultList(procurementRecordMapper.selectList(new LambdaQueryWrapper<ProcurementRecordStorage>()
                .eq(ProcurementRecordStorage::getType, 1)
                .eq(ProcurementRecordStorage::getSalesLedgerProductId, salesLedgerProductId)));
        return records.stream()
                .map(ProcurementRecordStorage::getInboundNum)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    private Map<String, Object> toArrivalItem(InboundManagement item) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("id", item.getId());
        map.put("orderNo", safe(item.getOrderNo()));
        map.put("arrivalNo", safe(item.getArrivalNo()));
        map.put("supplierName", safe(item.getSupplierName()));
        map.put("status", safe(item.getStatus()));
        map.put("arrivalTime", formatDate(item.getArrivalTime()));
        map.put("arrivalQuantity", safe(item.getArrivalQuantity()));
        map.put("remark", safe(item.getRemark()));
        return map;
    }
    private Map<String, Object> toPendingPaymentItem(LoginUser loginUser, PurchaseLedger ledger) {
        BigDecimal contractAmount = defaultDecimal(ledger.getContractAmount());
        BigDecimal paidAmount = sumPaymentAmount(loginUser, ledger.getId());
        BigDecimal pendingAmount = contractAmount.subtract(paidAmount);
        if (pendingAmount.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }
        Map<String, Object> item = toLedgerItem(ledger);
        item.put("paidAmount", paidAmount);
        item.put("pendingAmount", pendingAmount);
        return item;
    }
    private BigDecimal sumPaymentAmount(LoginUser loginUser, Long purchaseLedgerId) {
        LambdaQueryWrapper<PaymentRegistration> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), PaymentRegistration::getTenantId);
        wrapper.eq(PaymentRegistration::getPurchaseLedgerId, purchaseLedgerId);
        return defaultList(paymentRegistrationMapper.selectList(wrapper)).stream()
                .map(PaymentRegistration::getCurrentPaymentAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
    private Map<String, Object> toReturnItem(PurchaseReturnOrders item) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("id", item.getId());
        map.put("no", safe(item.getNo()));
        map.put("returnType", item.getReturnType());
        map.put("purchaseLedgerId", item.getPurchaseLedgerId());
        map.put("preparedAt", item.getPreparedAt() == null ? "" : item.getPreparedAt().toString());
        map.put("returnUserName", safe(item.getReturnUserName()));
        map.put("totalAmount", item.getTotalAmount());
        map.put("remark", safe(item.getRemark()));
        return map;
    }
    private BigDecimal defaultDecimal(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
    private List<PaymentRegistration> queryPayments(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<PaymentRegistration> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), PaymentRegistration::getTenantId);
        wrapper.ge(PaymentRegistration::getPaymentDate, toDate(range.start()))
                .lt(PaymentRegistration::getPaymentDate, toExclusiveEndDate(range.end()));
        return defaultList(paymentRegistrationMapper.selectList(wrapper));
    }
    private List<InvoicePurchase> queryInvoices(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<InvoicePurchase> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), InvoicePurchase::getTenantId);
        wrapper.ge(InvoicePurchase::getIssueDate, range.start())
                .le(InvoicePurchase::getIssueDate, range.end());
        return defaultList(invoicePurchaseMapper.selectList(wrapper));
    }
    private List<PurchaseReturnOrders> queryReturns(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<PurchaseReturnOrders> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), PurchaseReturnOrders::getDeptId);
        wrapper.ge(PurchaseReturnOrders::getPreparedAt, range.start())
                .le(PurchaseReturnOrders::getPreparedAt, range.end());
        return defaultList(purchaseReturnOrdersMapper.selectList(wrapper));
    }
    private Map<String, Object> toLedgerItem(PurchaseLedger item) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("id", item.getId());
        map.put("purchaseContractNumber", safe(item.getPurchaseContractNumber()));
        map.put("supplierName", safe(item.getSupplierName()));
        map.put("projectName", safe(item.getProjectName()));
        map.put("entryDate", formatDate(item.getEntryDate()));
        map.put("contractAmount", item.getContractAmount());
        map.put("approvalStatus", item.getApprovalStatus());
        map.put("paymentMethod", safe(item.getPaymentMethod()));
        return map;
    }
    private DateRange resolveDateRange(String startDate, String endDate, String timeRange) {
        LocalDate today = LocalDate.now();
        LocalDate start = parseLocalDate(startDate);
        LocalDate end = parseLocalDate(endDate);
        if (start != null || end != null) {
            LocalDate s = start != null ? start : end;
            LocalDate e = end != null ? end : start;
            if (s.isAfter(e)) {
                LocalDate temp = s;
                s = e;
                e = temp;
            }
            return new DateRange(s, e, s + "至" + e);
        }
        if (!StringUtils.hasText(timeRange)) {
            return new DateRange(today.minusDays(29), today, "近30天");
        }
        String text = timeRange.trim();
        if (text.contains("今年") || text.contains("本年")) {
            return new DateRange(today.withDayOfYear(1), today, "今年");
        }
        if (text.contains("本月")) {
            return new DateRange(today.withDayOfMonth(1), today, "本月");
        }
        if (text.contains("上月")) {
            LocalDate first = today.minusMonths(1).withDayOfMonth(1);
            LocalDate last = first.withDayOfMonth(first.lengthOfMonth());
            return new DateRange(first, last, "上月");
        }
        if (text.contains("近半年") || text.contains("最近半年")) {
            return new DateRange(today.minusMonths(6).plusDays(1), today, "近半年");
        }
        if (text.contains("近半个月") || text.contains("最近半个月") || text.contains("半个月")) {
            return new DateRange(today.minusDays(14), today, "近半个月");
        }
        java.util.regex.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 relativeStart = 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(relativeStart, today, "近" + amount + unit);
        }
        return new DateRange(today.minusDays(29), today, "近30天");
    }
    private LocalDate parseLocalDate(String text) {
        if (!StringUtils.hasText(text)) {
            return null;
        }
        return LocalDate.parse(text.trim(), DATE_FMT);
    }
    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 String formatDate(Date date) {
        if (date == null) {
            return "";
        }
        return DATE_FMT.format(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
    }
    private boolean tenantMatched(Long dataTenantId, Long userTenantId) {
        if (userTenantId == null) {
            return true;
        }
        return Objects.equals(dataTenantId, userTenantId);
    }
    private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, com.baomidou.mybatisplus.core.toolkit.support.SFunction<T, Long> field) {
        if (tenantId != null) {
            wrapper.eq(field, tenantId);
        }
    }
    private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, com.baomidou.mybatisplus.core.toolkit.support.SFunction<T, Long> field) {
        if (deptId != null) {
            wrapper.eq(field, deptId);
        }
    }
    private LoginUser currentLoginUser(String memoryId) {
        LoginUser loginUser = aiSessionUserContext.get(memoryId);
        if (loginUser != null) {
            return loginUser;
        }
        return SecurityUtils.getLoginUser();
    }
    private int normalizeLimit(Integer limit) {
        if (limit == null || limit <= 0) {
            return DEFAULT_LIMIT;
        }
        return Math.min(limit, MAX_LIMIT);
    }
    private String safe(Object value) {
        return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ');
    }
    private <T> List<T> defaultList(List<T> list) {
        return list == null ? List.of() : list;
    }
    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 static class MaterialRankItem {
        private final String productCategory;
        private final String specificationModel;
        private final String unit;
        private BigDecimal quantity = BigDecimal.ZERO;
        private BigDecimal amount = BigDecimal.ZERO;
        private MaterialRankItem(String productCategory, String specificationModel, String unit) {
            this.productCategory = productCategory;
            this.specificationModel = specificationModel;
            this.unit = unit;
        }
        private Map<String, Object> toMap() {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("productCategory", productCategory);
            map.put("specificationModel", specificationModel);
            map.put("unit", unit);
            map.put("quantity", quantity);
            map.put("amount", amount);
            return map;
        }
    }
}
src/main/java/com/ruoyi/approve/bean/dto/ApproveNodeDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.approve.bean.dto;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.basic.dto.StorageBlobDTO;
import lombok.Data;
import java.util.List;
@Data
public class ApproveNodeDto extends ApproveNode {
}
src/main/java/com/ruoyi/approve/bean/dto/ApproveProcessConfigNodeDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.approve.bean.dto;
import com.ruoyi.approve.pojo.ApproveProcessConfigNode;
import lombok.Data;
import java.util.List;
@Data
public class ApproveProcessConfigNodeDto  extends ApproveProcessConfigNode {
    private List<ApproveProcessConfigNode>  approveProcessConfigNodes;
}
src/main/java/com/ruoyi/approve/bean/vo/ApproveGetAndUpdateVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
package com.ruoyi.approve.bean.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Data
public class ApproveGetAndUpdateVo {
    private List<String> tempFileIds;
    //审批id
    @NotBlank(message = "流程编号不能为空")
    private String id;
    //申请事由
    @NotBlank(message = "申请事由不能为空")
    private String approveReason;
    private String approveUserIds;
    private String approveDeptName;
    private Long approveUser;
    private String approveTime;
    private Integer approveStatus;
    @Excel(name = "开始时间", dateFormat = "yyyy-MM-dd",width = 30)
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Schema(description = "开始时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    @Excel(name = "结束时间", dateFormat = "yyyy-MM-dd",width = 30)
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Schema(description = "结束时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    private BigDecimal price;
    private String location;
    /**
     * å®¡æ‰¹ç±»åž‹
     */
    private Integer approveType;
    private List<StorageBlobDTO> storageBlobDTOS;
}
src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessConfigNodeVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
package com.ruoyi.approve.bean.vo;
import com.ruoyi.approve.pojo.ApproveProcessConfigNode;
import lombok.Data;
@Data
public class ApproveProcessConfigNodeVo extends ApproveProcessConfigNode {
}
src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
package com.ruoyi.approve.bean.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Data
public class ApproveProcessVO {
    /**
     * ä¸´æ—¶æ–‡ä»¶id列表
     */
    private List<String> tempFileIds;
    /**
     * å®¡æ‰¹æµç¨‹id
     */
    private Long id;
    private String approveId;
    /**
     * å®¡æ‰¹éƒ¨é—¨id
     */
    private Long approveDeptId;
    /**
     * å®¡æ‰¹æ—¶é—´
     */
    private String approveTime;
    /**
     * ç”³è¯·äººid
     */
    // ç”³è¯·äºº
    private Long approveUser;
    /**
     * å®¡æ‰¹äººid列表
     */
    // å®¡æ‰¹äºº
    private String approveUserIds;
    /**
     * å®¡æ‰¹ç†ç”±
     */
    private String approveReason;
    @Excel(name = "开始时间", dateFormat = "yyyy-MM-dd",width = 30)
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Schema(description = "开始时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    @Excel(name = "结束时间", dateFormat = "yyyy-MM-dd",width = 30)
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Schema(description = "结束时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    private BigDecimal price;
    private String location;
    /**
     * å®¡æ‰¹ç±»åž‹
     */
    private Integer approveType;
     /**
     * è®¾å¤‡æŠ¥ä¿®id
     */
    private Long deviceRepairId;
     /**
     * æŠ¥ä¿®é‡‘额
     */
    private BigDecimal maintenancePrice;
    private List<StorageBlobDTO> storageBlobDTOList;
}
src/main/java/com/ruoyi/approve/controller/ApproveNodeController.java
@@ -3,19 +3,20 @@
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.approve.service.IApproveNodeService;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@Api(tags = "审批记录")
@Tag(name = "审批记录")
@RestController
@RequestMapping("/approveNode")
@AllArgsConstructor
public class ApproveNodeController {
    @Autowired
    private IApproveNodeService approveNodeService;
    /**
@@ -24,7 +25,7 @@
     * @return
     */
    @GetMapping("/details/{id}")
    @ApiOperation(value = "流程状态详情")
    @Operation(summary = "流程状态详情")
    public AjaxResult details(@PathVariable String id) {
        return AjaxResult.success(approveNodeService.details(id));
    }
@@ -36,7 +37,7 @@
     */
    @PostMapping("/updateApproveNode")
    @Transactional(rollbackFor = Exception.class)
    @ApiOperation(value = "审批节点")
    @Operation(summary = "审批节点")
    public AjaxResult updateApproveNode(@RequestBody ApproveNode approveNode) throws IOException {
        approveNodeService.updateApproveNode(approveNode);
        return AjaxResult.success();
src/main/java/com/ruoyi/approve/controller/ApproveProcessConfigNodeController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package com.ruoyi.approve.controller;
import com.ruoyi.approve.pojo.ApproveProcessConfigNode;
import com.ruoyi.approve.service.ApproveProcessConfigNodeService;
import com.ruoyi.framework.web.domain.R;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * å®¡æ‰¹æµç¨‹é…ç½®èŠ‚ç‚¹è¡¨ å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-23 10:21:41
 */
@RestController
@RequestMapping("/approveProcessConfigNode")
@AllArgsConstructor
public class ApproveProcessConfigNodeController {
    private final ApproveProcessConfigNodeService approveProcessConfigNodeService;
    /**
     *  æŸ¥è¯¢å®¡æ‰¹ä¸‹é¢çš„节点
     * @param type
     * @return
     */
    @GetMapping("/list")
    public R listNode(Integer type) {
        return R.ok(approveProcessConfigNodeService.listNode(type));
    }
    /**
     * æ·»åŠ å®¡æ‰¹èŠ‚ç‚¹
     * @param approveProcessConfigNodes
     * @return
     */
    @ApiOperation("添加审批节点")
    @PostMapping("/add")
    public R addApproveProcessConfigNodes(@RequestBody List<ApproveProcessConfigNode> approveProcessConfigNodes) {
        return R.ok(approveProcessConfigNodeService.addApproveProcessConfigNodes(approveProcessConfigNodes));
    }
}
src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java
@@ -2,40 +2,31 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.pojo.AccountExpense;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.service.IApproveProcessService;
import com.ruoyi.approve.vo.ApproveGetAndUpdateVo;
import com.ruoyi.approve.vo.ApproveProcessVO;
import com.ruoyi.approve.bean.vo.ApproveGetAndUpdateVo;
import com.ruoyi.approve.bean.vo.ApproveProcessVO;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.domain.SysDept;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.ParseException;
import java.util.List;
@RestController
@RequestMapping("/approveProcess")
@Api(tags = "审批")
@AllArgsConstructor
@Tag(name = "审批")
public class ApproveProcessController {
    @GetMapping("/test")
    public AjaxResult test() {
        System.out.println(1111);
        return AjaxResult.success("测试");
    }
    @Autowired
    private IApproveProcessService approveProcessService;
    /**、
     * èŽ·å–éƒ¨é—¨åˆ—è¡¨
@@ -58,7 +49,7 @@
     */
    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
    @ApiOperation(value = "添加审批")
    @Operation(summary = "添加审批")
    public AjaxResult add(@RequestBody ApproveProcessVO approveProcessVO) throws Exception {
        if (approveProcessVO == null) {
            return AjaxResult.warn("参数不能为空");
@@ -74,7 +65,7 @@
     * @return
     */
    @GetMapping("/get")
    @ApiOperation(value = "审批详情")
    @Operation(summary = "审批详情")
    public AjaxResult get(ApproveGetAndUpdateVo approveGetAndUpdateVo){
        if (approveGetAndUpdateVo.getId() == null || approveGetAndUpdateVo.getId().isEmpty()) {
            return AjaxResult.warn("参数不能为空");
@@ -89,7 +80,7 @@
     */
    @PostMapping("/update")
    @Transactional(rollbackFor = Exception.class)
    @ApiOperation(value = "更新审批")
    @Operation(summary = "更新审批")
    public AjaxResult update(@RequestBody ApproveGetAndUpdateVo approveGetAndUpdateVo) throws IOException {
        if (approveGetAndUpdateVo == null) {
            return AjaxResult.warn("参数不能为空");
@@ -102,7 +93,7 @@
     * @return
     */
    @GetMapping("/list")
    @ApiOperation(value = "获取审批列表")
    @Operation(summary = "获取审批列表")
    public AjaxResult list(Page page, ApproveProcess approveProcess) {
        return AjaxResult.success(approveProcessService.listAll(page, approveProcess));
    }
@@ -113,7 +104,7 @@
     * @return
     */
    @DeleteMapping("/deleteIds")
    @ApiOperation(value = "删除审批")
    @Operation(summary = "删除审批")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult deleteIds(@RequestBody List<Long> ids) {
        if (ids == null || ids.size() == 0) {
@@ -123,7 +114,7 @@
        return AjaxResult.success("操作成功");
    }
    @ApiOperation(value = "公出管理导出")
    @Operation(summary = "公出管理导出")
    @PostMapping("/exportOne")
    public void exportOne(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
@@ -133,7 +124,7 @@
        util.exportExcel(response, accountExpenses, "公出管理导出");
    }
    @ApiOperation(value = "请假管理导出")
    @Operation(summary = "请假管理导出")
    @PostMapping("/exportTwo")
    public void exportTwo(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
@@ -143,7 +134,7 @@
        util.exportExcel(response, accountExpenses, "请假管理导出");
    }
    @ApiOperation(value = "出差管理导出")
    @Operation(summary = "出差管理导出")
    @PostMapping("/exportThree")
    public void exportThree(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
@@ -153,7 +144,7 @@
        util.exportExcel(response, accountExpenses, "出差管理导出");
    }
    @ApiOperation(value = "报销管理导出")
    @Operation(summary = "报销管理导出")
    @PostMapping("/exportFour")
    public void exportFour(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
@@ -163,7 +154,7 @@
        util.exportExcel(response, accountExpenses, "报销管理导出");
    }
    @ApiOperation(value = "采购申请导出")
    @Operation(summary = "采购申请导出")
    @PostMapping("/exportFive")
    public void exportFive(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
@@ -173,7 +164,7 @@
        util.exportExcel(response, accountExpenses, "采购申请导出");
    }
    @ApiOperation(value = "协同审批导出")
    @Operation(summary = "协同审批导出")
    @PostMapping("/exportZero")
    public void exportZero(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
@@ -183,7 +174,7 @@
        util.exportExcel(response, accountExpenses, "协同审批导出");
    }
    @ApiOperation(value = "危险作业审批导出")
    @Operation(summary = "危险作业审批导出")
    @PostMapping("/exportEight")
    public void exportEight(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
src/main/java/com/ruoyi/approve/controller/HolidaySettingsController.java
@@ -18,13 +18,9 @@
@RequestMapping("/holidaySettings")
@AllArgsConstructor
public class HolidaySettingsController {
    @Autowired
    private HolidaySettingsService holidaySettingsService;
    @Autowired
    private AnnualLeaveSettingMapper annualLeaveSettingMapper;
    @Autowired
    private OvertimeSettingMapper overtimeSettingMapper;
    @Autowired
    private WorkingHoursSettingMapper workingHoursSettingMapper;
    /**、
src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java
@@ -2,27 +2,23 @@
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.mapper.KnowledgeBaseMapper;
import com.ruoyi.approve.pojo.KnowledgeBase;
import com.ruoyi.approve.pojo.RpaProcessAutomation;
import com.ruoyi.approve.service.KnowledgeBaseService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@RequestMapping("/knowledgeBase")
@AllArgsConstructor
@Api(tags = "知识库管理")
@Tag(name = "知识库管理")
public class KnowledgeBaseController {
    @Autowired
    private KnowledgeBaseService knowledgeBaseService;
    /**、
@@ -61,7 +57,7 @@
        return AjaxResult.success(knowledgeBaseService.removeByIds(ids));
    }
    @ApiOperation(value = "知识库管理导出")
    @Operation(summary = "知识库管理导出")
    @PostMapping("/export")
    public void export(HttpServletResponse response) {
        List<KnowledgeBase> accountExpenses = knowledgeBaseService.list();
src/main/java/com/ruoyi/approve/controller/NotificationManagementController.java
@@ -19,11 +19,8 @@
@RequestMapping("/notificationManagement")
@AllArgsConstructor
public class NotificationManagementController {
    @Autowired
    private NotificationManagementService notificationManagementService ;
    @Autowired
    private OnlineMeetingMapper onlineMeetingMapper;
    @Autowired
    private FileSharingMapper fileSharingMapper;
    /**、
     * èŽ·å–åˆ—è¡¨
src/main/java/com/ruoyi/approve/controller/RpaProcessAutomationController.java
@@ -1,31 +1,24 @@
package com.ruoyi.approve.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.pojo.RpaProcessAutomation;
import com.ruoyi.approve.service.RpaProcessAutomationService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.domain.SysDept;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@RequestMapping("/rpaProcessAutomation")
@AllArgsConstructor
@Api(tags = "RPA流程自动化")
@Tag(name = "RPA流程自动化")
public class RpaProcessAutomationController {
    @Autowired
    private RpaProcessAutomationService rpaProcessAutomationService;
    /**、
     * èŽ·å–åˆ—è¡¨
@@ -63,7 +56,7 @@
        return AjaxResult.success(rpaProcessAutomationService.removeByIds(ids));
    }
    @ApiOperation(value = "RPA流程自动化导出")
    @Operation(summary = "RPA流程自动化导出")
    @PostMapping("/export")
    public void export(HttpServletResponse response) {
        List<RpaProcessAutomation> accountExpenses = rpaProcessAutomationService.list();
src/main/java/com/ruoyi/approve/mapper/ApproveProcessConfigNodeMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.approve.pojo.ApproveProcessConfigNode;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * å®¡æ‰¹æµç¨‹é…ç½®èŠ‚ç‚¹è¡¨ Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-23 10:21:41
 */
@Mapper
public interface ApproveProcessConfigNodeMapper extends BaseMapper<ApproveProcessConfigNode> {
}
src/main/java/com/ruoyi/approve/mapper/ApproveProcessMapper.java
@@ -3,6 +3,7 @@
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.vo.ApproveProcessVo;
import com.ruoyi.approve.pojo.ApproveProcess;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -16,7 +17,7 @@
@Mapper
public interface ApproveProcessMapper extends BaseMapper<ApproveProcess> {
    IPage<ApproveProcess> listPage(Page page,@Param("req") ApproveProcess approveProcess);
    IPage<ApproveProcessVo> listPage(Page page, @Param("req") ApproveProcess approveProcess);
}
src/main/java/com/ruoyi/approve/pojo/AnnualLeaveSetting.java
@@ -67,4 +67,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/ApproveLog.java
@@ -1,5 +1,8 @@
package com.ruoyi.approve.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -54,4 +57,11 @@
     */
    private String approveRemark;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/ApproveNode.java
@@ -1,18 +1,16 @@
package com.ruoyi.approve.pojo;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.sales.pojo.CommonFile;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import com.ruoyi.basic.dto.StorageBlobDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
/**
 * å®¡æ‰¹èŠ‚ç‚¹è¡¨
@@ -20,20 +18,20 @@
 */
@Data
@TableName("approve_node")
@ApiModel
@Schema
public class ApproveNode{
    @ApiModelProperty("附件id")
    @Schema(description = "附件id")
    @TableField(exist = false)
    private List<String> tempFileIds;
    @TableField(exist = false)
    @ApiModelProperty("附件列表")
    @Schema(description = "附件列表")
    private String url;
    /**
     *
     *
     */
    private Long id;
@@ -128,4 +126,10 @@
    private static final long serialVersionUID = 1L;
}
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @TableField(exist = false)
    private List<StorageBlobDTO> storageBlobDTOS;
}
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java
@@ -1,19 +1,18 @@
package com.ruoyi.approve.pojo;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.sales.pojo.CommonFile;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.sales.pojo.CommonFile;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
/**
 * å®¡æ‰¹æµç¨‹è¡¨
@@ -21,7 +20,7 @@
 */
@Data
@TableName("approve_process")
@ApiModel
@Schema
public class ApproveProcess{
    /**
     *
@@ -35,7 +34,7 @@
    /**
     * æµç¨‹ç¼–号
     */
    @ApiModelProperty(value = "流程编号")
    @Schema(description = "流程编号")
    @Excel(name = "流程编号")
    private String approveId;
@@ -47,7 +46,7 @@
    /**
     * ç”³è¯·äººåç§°
     */
    @ApiModelProperty(value = "申请人名称")
    @Schema(description = "申请人名称")
    @Excel(name = "申请人")
    private String approveUserName;
@@ -59,7 +58,7 @@
    /**
     * ç”³è¯·éƒ¨é—¨åç§°
     */
    @ApiModelProperty(value = "申请部门名称")
    @Schema(description = "申请部门名称")
    @Excel(name = "申请部门")
    private String approveDeptName;
@@ -76,7 +75,7 @@
    /**
     * ç”³è¯·åŽŸå› 
     */
    @ApiModelProperty(value = "申请原因")
    @Schema(description = "申请原因")
    @Excel(name = "审批事由")
    private String approveReason;
@@ -88,7 +87,7 @@
    /**
     * å½“前审批用户名称
     */
    @ApiModelProperty(value = "当前审批人")
    @Schema(description = "当前审批人")
    @Excel(name = "当前审批人")
    private String approveUserCurrentName;
@@ -96,7 +95,7 @@
     * ç”³è¯·æ—¥æœŸ
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @ApiModelProperty(value = "申请日期")
    @Schema(description = "申请日期")
    @Excel(name = "申请日期" ,dateFormat = "yyyy-MM-dd")
    private Date approveTime;
@@ -104,14 +103,14 @@
     * å®¡æ‰¹å®Œæˆæ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @ApiModelProperty(value = "审批完成时间")
    @Schema(description = "审批完成时间")
    @Excel(name = "结束日期" ,dateFormat = "yyyy-MM-dd")
    private Date approveOverTime;
    /**
     * å®¡æ‰¹çŠ¶æ€ï¼š0待审核,1审核中,2审核完成 3审核未通过 4已重新提交
     */
    @ApiModelProperty(value = "审批状态:0待审核,1审核中,2审核完成 3审核未通过 4已重新提交")
    @Schema(description = "审批状态:0待审核,1审核中,2审核完成 3审核未通过 4已重新提交")
    @Excel(name = "审批状态", readConverterExp = "0=待审核,1=审核中,2=审核完成,3=审核未通过,4=已重新提交")
    private Integer approveStatus;
@@ -137,18 +136,18 @@
    /**
     * å®¡æ‰¹å¤‡æ³¨
     */
    @ApiModelProperty(value = "审批备注")
    @Schema(description = "审批备注")
    private String approveRemark;
    @Excel(name = "开始时间", dateFormat = "yyyy-MM-dd",width = 30)
    @JsonFormat(pattern = "yyyy-MM-dd")
    @ApiModelProperty(value = "开始时间")
    @Schema(description = "开始时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    @Excel(name = "结束时间", dateFormat = "yyyy-MM-dd",width = 30)
    @JsonFormat(pattern = "yyyy-MM-dd")
    @ApiModelProperty(value = "结束时间")
    @Schema(description = "结束时间")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
@@ -171,4 +170,14 @@
    private static final long serialVersionUID = 1L;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @TableField(exist = false)
    private List<StorageBlobVO> storageBlobVOS;
}
src/main/java/com/ruoyi/approve/pojo/ApproveProcessConfigNode.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package com.ruoyi.approve.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-04-23 10:21:41
 */
@Getter
@Setter
@ToString
@TableName("approve_process_config_node")
@ApiModel(value = "ApproveProcessConfigNode对象", description = "审批流程配置节点表")
public class ApproveProcessConfigNode implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®ID
     */
    @ApiModelProperty("主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    //  '审批类型:1公出管理,2请假管理,3出差管理,4报销管理,5采购审批,6报价审批,7发货审批,8危险作业审批',
    @ApiModelProperty("审批类型:1公出管理,2请假管理,3出差管理,4报销管理,5采购审批,6报价审批,7发货审批,8危险作业审批")
    private Integer approveType;
    /**
     * èŠ‚ç‚¹é¡ºåº
     */
    @ApiModelProperty("节点顺序")
    private Integer nodeOrder;
    /**
     * å®¡æ‰¹äººIDs(多个用逗号分隔)
     */
    @ApiModelProperty("审批人ID")
    private Long approverId;
    /**
     * å®¡æ‰¹äººåç§°
     */
    @ApiModelProperty("审批人名称")
    private String approverName;
    /**
     * è¶…时时长(小时)
     */
    @ApiModelProperty("超时时长(小时)")
    private Integer timeoutHours;
    /**
     * ç§Ÿæˆ·ID
     */
    @ApiModelProperty("租户ID")
    private Long tenantId;
    /**
     * åˆ›å»ºç”¨æˆ·ID
     */
    @ApiModelProperty("创建用户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹ç”¨æˆ·ID
     */
    @ApiModelProperty("修改用户ID")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    /**
     * åˆ é™¤æ ‡è®°ï¼š0正常,1删除
     */
    @ApiModelProperty("删除标记:0正常,1删除")
    private Boolean deleteFlag;
    /**
     * éƒ¨é—¨ID
     */
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/FileSharing.java
@@ -66,4 +66,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/HolidaySettings.java
@@ -77,4 +77,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/KnowledgeBase.java
@@ -1,5 +1,6 @@
package com.ruoyi.approve.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
@@ -83,4 +84,11 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/NotificationManagement.java
@@ -85,4 +85,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/OnlineMeeting.java
@@ -77,4 +77,7 @@
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/OvertimeSetting.java
@@ -76,4 +76,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/RpaProcessAutomation.java
@@ -62,4 +62,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/WorkingHoursSetting.java
@@ -76,4 +76,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/service/ApproveProcessConfigNodeService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.approve.service;
import com.ruoyi.approve.bean.vo.ApproveProcessConfigNodeVo;
import com.ruoyi.approve.pojo.ApproveProcessConfigNode;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
 * <p>
 * å®¡æ‰¹æµç¨‹é…ç½®èŠ‚ç‚¹è¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-23 10:21:41
 */
public interface ApproveProcessConfigNodeService extends IService<ApproveProcessConfigNode> {
    List<ApproveProcessConfigNodeVo> listNode(Integer type);
    Boolean addApproveProcessConfigNodes(List<ApproveProcessConfigNode> approveProcessConfigNodes);
}
src/main/java/com/ruoyi/approve/service/IApproveProcessService.java
@@ -3,14 +3,13 @@
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.approve.pojo.ApproveNode;
import com.ruoyi.approve.vo.ApproveProcessVo;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.vo.ApproveGetAndUpdateVo;
import com.ruoyi.approve.vo.ApproveProcessVO;
import com.ruoyi.approve.bean.vo.ApproveGetAndUpdateVo;
import com.ruoyi.approve.bean.vo.ApproveProcessVO;
import com.ruoyi.project.system.domain.SysDept;
import java.io.IOException;
import java.text.ParseException;
import java.util.List;
public interface IApproveProcessService extends IService<ApproveProcess> {
@@ -25,7 +24,7 @@
     */
    List<SysDept> selectDeptListByDeptIds(Long[] deptIds);
    IPage<ApproveProcess> listAll(Page page, ApproveProcess approveProcess);
    IPage<ApproveProcessVo> listAll(Page page, ApproveProcess approveProcess);
    void delApprove(List<Long> ids);
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -3,7 +3,6 @@
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.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.mapper.ApproveNodeMapper;
@@ -11,27 +10,37 @@
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.service.IApproveNodeService;
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.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.device.mapper.DeviceRepairMapper;
import com.ruoyi.device.pojo.DeviceRepair;
import com.ruoyi.other.service.impl.TempFileServiceImpl;
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.purchase.service.impl.PurchaseLedgerServiceImpl;
import com.ruoyi.sales.mapper.*;
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.CommonFileMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.SalesQuotationMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
@@ -43,44 +52,26 @@
import java.util.List;
@Service
//@RequiredArgsConstructor
@RequiredArgsConstructor
public class ApproveNodeServiceImpl extends ServiceImpl<ApproveNodeMapper, ApproveNode> implements IApproveNodeService {
    @Autowired
    private  ApproveNodeMapper approveNodeMapper;
    @Autowired
    private ApproveProcessMapper approveProcessMapper;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private TempFileServiceImpl tempFileService;
    @Autowired
    private  ISysNoticeService sysNoticeService;
    @Autowired
    private CommonFileMapper fileMapper;
    @Autowired
    private DeviceRepairMapper deviceRepairMapper;
    @Autowired
    private PurchaseLedgerMapper purchaseLedgerMapper;
    @Autowired
    private SalesQuotationMapper salesQuotationMapper;
    @Autowired
    private ShippingInfoMapper shippingInfoMapper;
    @Autowired
    private CommonFileServiceImpl commonFileService;
    @Autowired
    private StockUtils stockUtils;
    @Autowired
    private SalesLedgerProductMapper salesLedgerProductMapper;
    @Autowired
    private PurchaseLedgerServiceImpl purchaseLedgerServiceImpl;
    private final ApproveNodeMapper approveNodeMapper;
    private final ApproveProcessMapper approveProcessMapper;
    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 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 FileUtil fileUtil;
    public ApproveProcess getApproveById(String id) {
@@ -94,7 +85,7 @@
    }
    @Override
    public void initApproveNodes(String approveUserIds,String approveID,Long tenantId) {
    public void initApproveNodes(String approveUserIds, String approveID, Long tenantId) {
        Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
        String[] names = approveUserIds.split(",");
        for (int i = 0; i < names.length; i++) {
@@ -102,7 +93,7 @@
            if (sysUser == null) continue;
            ApproveNode approveNode = new ApproveNode();
            approveNode.setApproveProcessId(approveID);
            approveNode.setApproveNodeOrder(i +1);
            approveNode.setApproveNodeOrder(i + 1);
            approveNode.setApproveNodeUser(sysUser.getNickName());
            approveNode.setApproveNodeUserId(sysUser.getUserId());
            approveNode.setApproveNodeTime(new Date());
@@ -132,17 +123,17 @@
//                .eq(ApproveProcess::getApproveStatus, 0)
                .last("limit 1");
        ApproveProcess approveProcess = approveProcessMapper.selectOne(approveProcessLambdaQueryWrapper);
        if(approveProcess != null && approveProcess.getApproveStatus() == 3){
        if (approveProcess != null && approveProcess.getApproveStatus() == 3) {
            return list;
        }
        for (ApproveNode approveNode : list) {
            List<CommonFile> commonFiles = fileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                    .eq(CommonFile::getCommonId, approveNode.getId())
                    .eq(CommonFile::getType, FileNameType.ApproveNode.getValue()));
            if(!CollectionUtils.isEmpty(commonFiles)){
            if (!CollectionUtils.isEmpty(commonFiles)) {
                approveNode.setUrl(commonFiles.get(0).getUrl());
            }
            if(approveNode.getApproveNodeStatus() == 1){
            if (approveNode.getApproveNodeStatus() == 1) {
                continue;
            }
            approveNode.setIsShen(true);
@@ -151,13 +142,13 @@
        return list;
    }
    public void updateApproveProcessStatus(ApproveNode approveNode,Integer status) throws IOException {
    public void updateApproveProcessStatus(ApproveNode approveNode, Integer status) throws IOException {
        LambdaQueryWrapper<ApproveProcess> approveProcessLambdaQueryWrapper = new LambdaQueryWrapper<>();
        approveProcessLambdaQueryWrapper.eq(ApproveProcess::getApproveId, approveNode.getApproveProcessId())
                .eq(ApproveProcess::getApproveDelete, 0)
                .last("limit 1");
        ApproveProcess approveProcess = approveProcessMapper.selectOne(approveProcessLambdaQueryWrapper);
        if(approveProcess == null) throw new RuntimeException("审批不存在");
        if (approveProcess == null) throw new RuntimeException("审批不存在");
        LambdaQueryWrapper<ApproveNode> approveNodeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        approveNodeLambdaQueryWrapper.eq(ApproveNode::getApproveProcessId, approveNode.getApproveProcessId())
                .eq(ApproveNode::getApproveNodeOrder, approveNode.getApproveNodeOrder() + 1)
@@ -166,33 +157,21 @@
                .last("limit 1");
        ApproveNode approveNode1 = approveNodeMapper.selectOne(approveNodeLambdaQueryWrapper);
        approveProcess.setApproveStatus(status);
        if(approveNode1 != null){
        if (approveNode1 != null) {
            approveProcess.setApproveUserCurrentId(approveNode1.getApproveNodeUserId());
            approveProcess.setApproveUserCurrentName(approveNode1.getApproveNodeUser());
        }
        if(approveProcess.getApproveStatus().equals(2) || approveProcess.getApproveStatus().equals(3) || approveProcess.getApproveStatus().equals(4)){
        if (approveProcess.getApproveStatus().equals(2) || approveProcess.getApproveStatus().equals(3) || approveProcess.getApproveStatus().equals(4)) {
            approveProcess.setApproveOverTime(new Date());
        }
        approveProcessMapper.updateById(approveProcess);
        DeviceRepair deviceRepair = deviceRepairMapper.selectById(approveProcess.getDeviceRepairId());
        if(ObjectUtils.isNotNull(deviceRepair)) {
            if(approveProcess.getApproveStatus().equals(2)){
                // åŒæ„
                deviceRepair.setStatus(1);
            }else if(approveProcess.getApproveStatus().equals(3)){
                // æ‹’绝
                deviceRepair.setStatus(2);
            }
            deviceRepairMapper.updateById(deviceRepair);
        }
        //采购审核
        if(approveProcess.getApproveType().equals(5)){
        if (approveProcess.getApproveType().equals(5)) {
            PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                    .eq(PurchaseLedger::getPurchaseContractNumber, approveProcess.getApproveReason())
                    .last("limit 1"));
            if(purchaseLedger != null) {
            if (purchaseLedger != null) {
                if (status.equals(2)) {
                    // åŒæ„
                    purchaseLedger.setApprovalStatus(3);
@@ -201,8 +180,8 @@
                    for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
                        // è´¨æ£€
                        if (salesLedgerProduct.getIsChecked()) {
                            purchaseLedgerServiceImpl.addQualityInspect(purchaseLedger, salesLedgerProduct);
                        }else {
                            addQualityInspect(purchaseLedger, salesLedgerProduct);
                        } else {
                            //直接入库
                            stockUtils.addStock(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId());
                        }
@@ -218,63 +197,60 @@
            }
        }
        // é”€å”®æŠ¥ä»·çŠ¶æ€ä¿®æ”¹
        if(approveProcess.getApproveType().equals(6)){
        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){
            if (status.equals(2) && salesQuote != null) {
                salesQuote.setStatus("通过");
            }else if(status.equals(3) && salesQuote != null){
            } else if (status.equals(3) && salesQuote != null) {
                salesQuote.setStatus("拒绝");
            }else if(status.equals(1) && salesQuote != null){
            } else if (status.equals(1) && salesQuote != null) {
                salesQuote.setStatus("审核中");
            }
            salesQuotationMapper.updateById(salesQuote);
        }
        // å‡ºåº“审批修改
        if(approveProcess.getApproveType().equals(7)){
        if (approveProcess.getApproveType().equals(7)) {
            String[] split = approveProcess.getApproveReason().split(":");
            ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
                    .eq(ShippingInfo::getShippingNo, split[1])
                    .orderByDesc(ShippingInfo::getCreateTime)
                    .last("limit 1"));
            if(shippingInfo != null){
                if(status.equals(2)){
            if (shippingInfo != null) {
                if (status.equals(2)) {
                    shippingInfo.setStatus("审核通过");
                }else if(status.equals(3)){
                } else if (status.equals(3)) {
                    shippingInfo.setStatus("审核拒绝");
                }else if(status.equals(1)){
                } else if (status.equals(1)) {
                    shippingInfo.setStatus("审核中");
                }
                shippingInfoMapper.updateById(shippingInfo);
            }
        }
        // ç»‘定附件
        if(!CollectionUtils.isEmpty(approveNode.getTempFileIds()) && approveNode.getApproveNodeStatus() == 1){
            tempFileService.migrateTempFilesToFormal(approveNode.getId(), approveNode.getTempFileIds(), FileNameType.ApproveNode.getValue());
        }
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_NODE, approveNode.getId(), approveNode.getStorageBlobDTOS());
    }
    @Override
    public void updateApproveNode(ApproveNode approveNode) throws IOException {
        // å®¡æ‰¹èŠ‚ç‚¹çŠ¶æ€:1同意,2拒绝,0尚未审核
        switch (approveNode.getApproveNodeStatus()){
        switch (approveNode.getApproveNodeStatus()) {
            case 1:
                updateApproveProcessStatus(approveNode, Boolean.TRUE.equals(approveNode.getIsLast()) ? 2 : 1);
                /*消息通知*/
                Integer nodeOrder = approveNode.getApproveNodeOrder();
                ApproveProcess approveProcess = approveProcessMapper.selectList(Wrappers.<ApproveProcess>lambdaQuery()
                        .eq(ApproveProcess::getApproveId, approveNode.getApproveProcessId())).get(0);
                if (approveProcess.getApproveUserIds().split(",").length > nodeOrder){
                if (approveProcess.getApproveUserIds().split(",").length > nodeOrder) {
                    String id = approveProcess.getApproveUserIds().split(",")[nodeOrder];
                    if (approveProcess.getApproveType()==8){
                    if (approveProcess.getApproveType() == 8) {
                        sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
                                approveProcess.getApproveId() + "流程编号的审批需要您审核!!!!!",
                                Arrays.asList(Long.valueOf(id)),
                                "/safeProduction/safeWorkApproval?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
                    }else {
                    } else {
                        sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
                                approveProcess.getApproveId() + "流程编号的审批需要您审核!!!!!",
                                Arrays.asList(Long.valueOf(id)),
@@ -307,8 +283,8 @@
    }
    //审批类型获取(与前端页面对应)
    private String approveProcessType(Integer approveType){
        switch (approveType){
    private String approveProcessType(Integer approveType) {
        switch (approveType) {
            case 1:
                return "公出管理";
            case 2:
@@ -325,8 +301,38 @@
                return "发货审批";
            case 8:
                return "危险作业审批";
            case 9:
                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);
                    });
        }
    }
}
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessConfigNodeServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.bean.vo.ApproveProcessConfigNodeVo;
import com.ruoyi.approve.mapper.ApproveProcessConfigNodeMapper;
import com.ruoyi.approve.pojo.ApproveProcessConfigNode;
import com.ruoyi.approve.service.ApproveProcessConfigNodeService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
 * å®¡æ‰¹æµç¨‹é…ç½®èŠ‚ç‚¹è¡¨ æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-04-23 10:21:41
 */
@Service
@RequiredArgsConstructor
public class ApproveProcessConfigNodeServiceImpl extends ServiceImpl<ApproveProcessConfigNodeMapper, ApproveProcessConfigNode> implements ApproveProcessConfigNodeService {
    private final ApproveProcessConfigNodeMapper approveProcessConfigNodeMapper;
    @Override
    public List<ApproveProcessConfigNodeVo> listNode(Integer type) {
        List<ApproveProcessConfigNode> approveProcessConfigNodes = approveProcessConfigNodeMapper.selectList(new QueryWrapper<ApproveProcessConfigNode>().lambda().eq(ApproveProcessConfigNode::getApproveType, type).orderByAsc(ApproveProcessConfigNode::getNodeOrder));
        return approveProcessConfigNodes.stream()
                .map(node -> {
                    ApproveProcessConfigNodeVo vo = new ApproveProcessConfigNodeVo();
                    BeanUtils.copyProperties(node, vo);
                    return vo;
                })
                .collect(java.util.stream.Collectors.toList());
    }
    @Override
    public Boolean addApproveProcessConfigNodes(List<ApproveProcessConfigNode> approveProcessConfigNodes) {
        //删除旧数据
        approveProcessConfigNodeMapper.delete(new QueryWrapper<ApproveProcessConfigNode>().lambda().eq(ApproveProcessConfigNode::getApproveType, approveProcessConfigNodes.get(0).getApproveType()));
        //新增 æ•°æ®
        approveProcessConfigNodeMapper.insert(approveProcessConfigNodes);
        return true;
    }
}
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -1,24 +1,31 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
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;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.bean.vo.ApproveGetAndUpdateVo;
import com.ruoyi.approve.bean.vo.ApproveProcessConfigNodeVo;
import com.ruoyi.approve.bean.vo.ApproveProcessVO;
import com.ruoyi.approve.mapper.ApproveNodeMapper;
import com.ruoyi.approve.mapper.ApproveProcessConfigNodeMapper;
import com.ruoyi.approve.mapper.ApproveProcessMapper;
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.pojo.ApproveProcessConfigNode;
import com.ruoyi.approve.service.ApproveProcessConfigNodeService;
import com.ruoyi.approve.service.IApproveNodeService;
import com.ruoyi.approve.service.IApproveProcessService;
import com.ruoyi.approve.utils.DailyRedisCounter;
import com.ruoyi.approve.vo.ApproveGetAndUpdateVo;
import com.ruoyi.approve.vo.ApproveProcessVO;
import com.ruoyi.approve.vo.ApproveProcessVo;
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.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.other.service.impl.TempFileServiceImpl;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysNotice;
import com.ruoyi.project.system.domain.SysUser;
@@ -32,80 +39,74 @@
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
@Service
//@RequiredArgsConstructor
@RequiredArgsConstructor
public class ApproveProcessServiceImpl extends ServiceImpl<ApproveProcessMapper, ApproveProcess> implements IApproveProcessService {
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
    @Autowired
    private  StringRedisTemplate redisTemplate;
    @Autowired
    private  DailyRedisCounter dailyRedisCounter;
    @Autowired
    private  SysDeptMapper sysDeptMapper;
    @Autowired
    private  IApproveNodeService approveNodeService;
    @Autowired
    private  SysUserMapper sysUserMapper;
    @Autowired
    private  ApproveProcessMapper approveProcessMapper;
    @Autowired
    private  TempFileServiceImpl tempFileService;
    @Autowired
    private  CommonFileMapper commonFileMapper;
    @Autowired
    private  CommonFileServiceImpl commonFileService;
    @Autowired
    private  ISysNoticeService sysNoticeService;
    private final SysDeptMapper sysDeptMapper;
    private final IApproveNodeService approveNodeService;
    private final SysUserMapper sysUserMapper;
    private final ApproveProcessMapper approveProcessMapper;
    private final CommonFileMapper commonFileMapper;
    private final CommonFileServiceImpl commonFileService;
    private final ISysNoticeService sysNoticeService;
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    private final ShippingInfoMapper shippingInfoMapper;
    private final ApproveNodeMapper approveNodeMapper;
    private final ApproveProcessConfigNodeService approveProcessConfigNodeService;
    private final FileUtil fileUtil;
    private final ApproveProcessConfigNodeMapper approveProcessConfigNodeMapper;
    @Override
    public void addApprove(ApproveProcessVO approveProcessVO) throws Exception {
        SysUser sysUser = sysUserMapper.selectUserById(approveProcessVO.getApproveUser());
        SysDept sysDept = sysDeptMapper.selectDeptById(approveProcessVO.getApproveDeptId());
        String[] split = approveProcessVO.getApproveUserIds().split(",");
        List<Long> longList = Arrays.stream(split)
                .map(Long::valueOf)  // å°†æ¯ä¸ª String è½¬æ¢ä¸º Long
        SysUser sysUser = SecurityUtils.getLoginUser().getUser();
        SysDept sysDept = sysDeptMapper.selectDeptById(SecurityUtils.getLoginUser().getCurrentDeptId());
        List<ApproveProcessConfigNodeVo> list = approveProcessConfigNodeService.listNode(approveProcessVO.getApproveType());
        List<Long> nodeIds = list.stream()
                .map(ApproveProcessConfigNodeVo::getApproverId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(longList);
        if (list.isEmpty()) {
            throw new RuntimeException("流程不存在");
        }
        if (CollectionUtils.isEmpty(nodeIds)) {
            autoPassPurchaseApproveIfNoApprover(approveProcessVO);
            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("申请人不存在");
        String today = LocalDate.now().format(DATE_FORMAT);
        Long approveId = dailyRedisCounter.incrementAndGetByDb();
        String formattedCount = String.format("%03d", approveId);
        //流程 ID
        String approveID = today + formattedCount;
//        String today = LocalDate.now().format(DATE_FORMAT);
//        Long approveId = dailyRedisCounter.incrementAndGetByDb();
//        String formattedCount = String.format("%03d", approveId);
//        //流程 ID
//        String approveID = today + formattedCount;
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        ApproveProcess approveProcess = new ApproveProcess();
        approveProcess.setApproveId(approveID);
        approveProcess.setApproveUser(approveProcessVO.getApproveUser());
        String no = OrderUtils.countTodayByCreateTime(approveProcessMapper, "", "approve_id");
        approveProcess.setApproveId(no);
        approveProcess.setApproveUser(sysUser.getUserId());
        approveProcess.setApproveUserName(sysUser.getNickName());
        approveProcess.setApproveDeptId(approveProcessVO.getApproveDeptId());
        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()) ? null : dateFormat.parse(approveProcessVO.getApproveTime()));
        approveProcess.setApproveTime(StringUtils.isEmpty(approveProcessVO.getApproveTime()) ? new Date() : dateFormat.parse(approveProcessVO.getApproveTime()));
        approveProcess.setApproveReason(approveProcessVO.getApproveReason());
        approveProcess.setDeviceRepairId(approveProcessVO.getDeviceRepairId());
        approveProcess.setMaintenancePrice(approveProcessVO.getMaintenancePrice());
@@ -117,11 +118,10 @@
        approveProcess.setApproveType(approveProcessVO.getApproveType());
        approveProcess.setCreateTime(LocalDateTime.now());
        approveProcess.setTenantId(approveProcessVO.getApproveDeptId());
        approveProcess.setApproveUserIds(approveProcessVO.getApproveUserIds());
        approveProcess.setApproveUserCurrentId(longList.get(0));
        approveProcess.setApproveUserCurrentId(nodeIds.get(0));
        approveProcess.setApproveUserCurrentName(sysUsers
                .stream()
                .filter(SysUser -> SysUser.getUserId().equals(longList.get(0)))
                .filter(SysUser -> SysUser.getUserId().equals(nodeIds.get(0)))
                .collect(Collectors.toList())
                .get(0)
                .getNickName());
@@ -133,22 +133,35 @@
        }
        save(approveProcess);
        //初始化审批节点
        approveNodeService.initApproveNodes(approveProcessVO.getApproveUserIds(), approveID, approveProcessVO.getApproveDeptId());
        String nodeIdStr = nodeIds.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        approveNodeService.initApproveNodes(nodeIdStr, no, approveProcessVO.getApproveDeptId());
        // é™„件绑定
        tempFileService.migrateTempFilesToFormal(approveProcess.getId(), approveProcessVO.getTempFileIds(), FileNameType.ApproveProcess.getValue());
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_PROCESS, approveProcess.getId(), approveProcessVO.getStorageBlobDTOList());
        /*消息通知*/
        String id = approveProcessVO.getApproveUserIds().split(",")[0];
        if (approveProcess.getApproveType()==8){
        Long id = nodeIds.getFirst();
        if (approveProcess.getApproveType() == 8) {
            sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
                    approveProcess.getApproveId() + "流程编号的审批需要您审核!!!!!",
                    Arrays.asList(Long.valueOf(id)),
                    Collections.singletonList(id),
                    "/safeProduction/safeWorkApproval?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
        }else {
        } else {
            sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
                    approveProcess.getApproveId() + "流程编号的审批需要您审核!!!!!",
                    Arrays.asList(Long.valueOf(id)),
                    Collections.singletonList(id),
                    "/collaborativeApproval/approvalProcess?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
        }
    }
    private void autoPassPurchaseApproveIfNoApprover(ApproveProcessVO approveProcessVO) {
        if (!Objects.equals(approveProcessVO.getApproveType(), 5)
                || !StringUtils.hasText(approveProcessVO.getApproveReason())) {
            throw new RuntimeException("审核用户不存在");
        }
        purchaseLedgerMapper.update(null, new LambdaUpdateWrapper<PurchaseLedger>()
                .eq(PurchaseLedger::getPurchaseContractNumber, approveProcessVO.getApproveReason())
                .set(PurchaseLedger::getApprovalStatus, 3));
    }
    @Override
@@ -161,18 +174,12 @@
        return sysDeptList;
    }
    @Autowired
    private PurchaseLedgerMapper purchaseLedgerMapper;
    @Autowired
    private ShippingInfoMapper shippingInfoMapper;
    @Override
    public IPage<ApproveProcess> listAll(Page page, ApproveProcess approveProcess) {
        IPage<ApproveProcess> approveProcessIPage = approveProcessMapper.listPage(page, approveProcess);
        List<ApproveProcess> records = approveProcessIPage.getRecords();
    public IPage<ApproveProcessVo> listAll(Page page, ApproveProcess approveProcess) {
        IPage<ApproveProcessVo> approveProcessIPage = approveProcessMapper.listPage(page, approveProcess);
        List<ApproveProcessVo> records = approveProcessIPage.getRecords();
        for (ApproveProcess record : records) {
        for (ApproveProcessVo record : records) {
            List<CommonFile> allFiles = new ArrayList<>();
            //  é‡‡è´­å®¡æ‰¹æŸ¥è¯¢
@@ -216,6 +223,7 @@
            }
            record.setCommonFileList(allFiles);
            record.setStorageBlobVOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.APPROVE_PROCESS, record.getId()));
        }
        return approveProcessIPage;
    }
@@ -286,9 +294,9 @@
            //  åˆ é™¤å¯¹åº”的消息通知
            sysNoticeService.remove(new LambdaQueryWrapper<SysNotice>()
                            .eq(SysNotice::getNoticeTitle, approveProcessType(latestProcess.getApproveType()))
                            .eq(SysNotice::getSenderId, latestProcess.getApproveUser())
                            .apply("CAST(notice_content AS CHAR) LIKE CONCAT('%', {0}, '%')", latestProcess.getApproveId()));
                    .eq(SysNotice::getNoticeTitle, approveProcessType(latestProcess.getApproveType()))
                    .eq(SysNotice::getSenderId, latestProcess.getApproveUser())
                    .apply("CAST(notice_content AS CHAR) LIKE CONCAT('%', {0}, '%')", latestProcess.getApproveId()));
        }
    }
@@ -296,16 +304,12 @@
    @Override
    public ApproveProcess getApproveById(String id) {
        ApproveProcess one = approveProcessMapper.selectList(Wrappers.<ApproveProcess>lambdaQuery()
                .eq(ApproveProcess::getApproveId,id)
                .eq(ApproveProcess::getApproveDelete,0)).get(0);
        one.setCommonFileList(commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                .eq(CommonFile::getCommonId, one.getId())
                .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue())));
                .eq(ApproveProcess::getApproveId, id)
                .eq(ApproveProcess::getApproveDelete, 0)).get(0);
        one.setStorageBlobVOS(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.APPROVE_PROCESS, one.getId()));
        return one;
    }
    @Autowired
    private ApproveNodeMapper approveNodeMapper;
    // æŠ¥ä»·å®¡æ‰¹ç¼–辑审核人
    public void updateApproveUser(ApproveGetAndUpdateVo approveGetAndUpdateVo) {
@@ -314,17 +318,20 @@
                .eq(ApproveProcess::getApproveReason, approveGetAndUpdateVo.getApproveReason())
                .last("limit 1");
        ApproveProcess approveProcess = approveProcessMapper.selectOne(approveProcessLambdaQueryWrapper);
        if (approveProcess == null) throw new RuntimeException("请选择审批人");
        String[] split = approveGetAndUpdateVo.getApproveUserIds().split(",");
        if (split.length == 0) {
            throw new RuntimeException("请选择审批人");
        }
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(Arrays.asList(split).stream().map(Long::parseLong).collect(Collectors.toList()));
        //查询审批配置
        List<ApproveProcessConfigNode> approveProcessConfig = approveProcessConfigNodeMapper.selectList(new LambdaQueryWrapper<ApproveProcessConfigNode>().eq(ApproveProcessConfigNode::getApproveType, approveGetAndUpdateVo.getApproveType()));
        List<Long> configNodeIds = approveProcessConfig.stream()
                .sorted(Comparator.comparing(ApproveProcessConfigNode::getNodeOrder))
                .map(ApproveProcessConfigNode::getApproverId)
                .collect(Collectors.toList());
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(configNodeIds);
        if (CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("请选择审批人");
        //审核中不可以编辑审核人
        if (approveProcess.getApproveStatus() != 1) {
            approveProcess.setApproveUserCurrentId(Long.parseLong(split[0]));
            approveProcess.setApproveUserCurrentName(sysUsers.stream().filter(user -> user.getUserId().equals(Long.parseLong(split[0]))).collect(Collectors.toList()).get(0).getNickName());
            approveProcess.setApproveUserCurrentId(configNodeIds.get(0));
            approveProcess.setApproveUserCurrentName(sysUsers.stream().filter(user -> user.getUserId().equals(configNodeIds.get(0))).collect(Collectors.toList()).get(0).getNickName());
        }
        if (approveGetAndUpdateVo.getApproveStatus() != null) {
            approveProcess.setApproveStatus(approveGetAndUpdateVo.getApproveStatus());
@@ -336,21 +343,24 @@
        LambdaQueryWrapper<ApproveNode> approveNodeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        approveNodeLambdaQueryWrapper.eq(ApproveNode::getApproveProcessId, approveProcess.getApproveId())
                .eq(ApproveNode::getDeleteFlag, 0)
//                .eq(ApproveNode::getTenantId, SecurityUtils.getLoginUser().getTenantId())
                .orderByAsc(ApproveNode::getApproveNodeOrder);
        approveNodeMapper.delete(approveNodeLambdaQueryWrapper);
        //查询审批配置
        approveGetAndUpdateVo.setApproveUserIds(configNodeIds.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(",")));
        approveNodeService.initApproveNodes(approveGetAndUpdateVo.getApproveUserIds(), approveProcess.getApproveId(), approveProcess.getTenantId());
        /*消息通知*/
        String id = approveProcess.getApproveUserIds().split(",")[0];
        if (approveProcess.getApproveType()==8){
        Long id = configNodeIds.get(0);
        if (approveProcess.getApproveType() == 8) {
            sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
                    approveProcess.getApproveId() + "流程编号的审批需要您审核!!!!!",
                    Arrays.asList(Long.valueOf(id)),
                    Collections.singletonList(id),
                    "/safeProduction/safeWorkApproval?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
        }else {
        } else {
            sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
                    approveProcess.getApproveId() + "流程编号的审批需要您审核!!!!!",
                    Arrays.asList(Long.valueOf(id)),
                    Collections.singletonList(id),
                    "/collaborativeApproval/approvalProcess?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
        }
    }
@@ -404,15 +414,15 @@
//            approveNodeMapper.updateById(approveNode);
//            i++;
//        }
        tempFileService.migrateTempFilesToFormal(approve.getId(), approveGetAndUpdateVo.getTempFileIds(), FileNameType.ApproveProcess.getValue());
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_PROCESS, approve.getId(), approveGetAndUpdateVo.getStorageBlobDTOS());
        /*消息通知*/
        String id = approve.getApproveUserIds().split(",")[0];
        if (approve.getApproveType()==8){
        if (approve.getApproveType() == 8) {
            sysNoticeService.simpleNoticeByUser(approveProcessType(approve.getApproveType()),
                    approve.getApproveId() + "流程编号的审批需要您审核!!!!!",
                    Arrays.asList(Long.valueOf(id)),
                    "/safeProduction/safeWorkApproval?approveType=" + approve.getApproveType() + "&approveId=" + approve.getApproveId());
        }else {
        } else {
            sysNoticeService.simpleNoticeByUser(approveProcessType(approve.getApproveType()),
                    approve.getApproveId() + "流程编号的审批需要您审核!!!!!",
                    Arrays.asList(Long.valueOf(id)),
@@ -441,6 +451,8 @@
                return "发货审批";
            case 8:
                return "危险作业审批";
            case 9:
                return "办公用品审批";
        }
        return null;
    }
src/main/java/com/ruoyi/approve/service/impl/HolidaySettingsServiceImpl.java
@@ -6,13 +6,13 @@
import com.ruoyi.approve.mapper.HolidaySettingsMapper;
import com.ruoyi.approve.pojo.HolidaySettings;
import com.ruoyi.approve.service.HolidaySettingsService;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class HolidaySettingsServiceImpl extends ServiceImpl<HolidaySettingsMapper, HolidaySettings> implements HolidaySettingsService {
    @Autowired
    private HolidaySettingsMapper holidaySettingsMapper;
    private final HolidaySettingsMapper holidaySettingsMapper;
    @Override
    public IPage<HolidaySettings> listpage(Page page, HolidaySettings holidaySettings) {
src/main/java/com/ruoyi/approve/service/impl/KnowledgeBaseServiceImpl.java
@@ -6,13 +6,13 @@
import com.ruoyi.approve.mapper.KnowledgeBaseMapper;
import com.ruoyi.approve.pojo.KnowledgeBase;
import com.ruoyi.approve.service.KnowledgeBaseService;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class KnowledgeBaseServiceImpl extends ServiceImpl<KnowledgeBaseMapper, KnowledgeBase> implements KnowledgeBaseService {
    @Autowired
    private KnowledgeBaseMapper knowledgeBaseMapper;
    private final KnowledgeBaseMapper knowledgeBaseMapper;
    @Override
    public IPage<KnowledgeBase> listpage(Page page, KnowledgeBase knowledgeBase) {
src/main/java/com/ruoyi/approve/service/impl/NotificationManagementServiceImpl.java
@@ -2,18 +2,17 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.mapper.NotificationManagementMapper;
import com.ruoyi.approve.pojo.NotificationManagement;
import com.ruoyi.approve.service.NotificationManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class NotificationManagementServiceImpl extends ServiceImpl<NotificationManagementMapper, NotificationManagement> implements NotificationManagementService {
    @Autowired
    private NotificationManagementMapper notificationManagementMapper;
    private final NotificationManagementMapper notificationManagementMapper;
    @Override
    public IPage<NotificationManagement> listpage(Page page, NotificationManagement notificationManagement) {
src/main/java/com/ruoyi/approve/service/impl/RpaProcessAutomationServiceImpl.java
@@ -1,19 +1,18 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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.RpaProcessAutomationMapper;
import com.ruoyi.approve.pojo.RpaProcessAutomation;
import com.ruoyi.approve.service.RpaProcessAutomationService;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class RpaProcessAutomationServiceImpl extends ServiceImpl<RpaProcessAutomationMapper, RpaProcessAutomation> implements RpaProcessAutomationService {
    @Autowired
    private RpaProcessAutomationMapper rpaProcessAutomationMapper;
    private final RpaProcessAutomationMapper rpaProcessAutomationMapper;
    @Override
    public IPage<RpaProcessAutomation> listpage(Page page, RpaProcessAutomation rpaProcessAutomation) {
        return rpaProcessAutomationMapper.listpage(page,rpaProcessAutomation);
src/main/java/com/ruoyi/approve/utils/ApproveProcessConfigNodeUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
package com.ruoyi.approve.utils;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class ApproveProcessConfigNodeUtils {
}
src/main/java/com/ruoyi/approve/utils/DailyRedisCounter.java
@@ -3,12 +3,11 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.approve.mapper.ApproveProcessMapper;
import com.ruoyi.approve.pojo.ApproveProcess;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
@@ -19,14 +18,11 @@
//基于redis的一个每日计数器
@Component
@RequiredArgsConstructor
public class DailyRedisCounter {
    private static final String KEY_PREFIX = "daily_counter:";
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
    private final StringRedisTemplate redisTemplate;
    public DailyRedisCounter(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    @Value("${ruoyi.approvalNumberPrefix}")
    private String approvalNumberPrefix;
@@ -49,8 +45,7 @@
        return count;
    }
    @Autowired
    private ApproveProcessMapper approveProcessMapper;
    private final ApproveProcessMapper approveProcessMapper;
    /**
     * èŽ·å–å½“å‰æ—¶é—´çš„  å¼€å§‹æ—¥æœŸ  ï¼Œç»“束日期
src/main/java/com/ruoyi/approve/utils/StartAndEndDateDto.java
@@ -1,8 +1,7 @@
package com.ruoyi.approve.utils;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
@@ -10,26 +9,26 @@
 * @date : 2023/9/19 10:58
 */
@Data
@ApiModel
@Schema
public class StartAndEndDateDto {
    @ApiModelProperty("开始时间")
    @Schema(description = "开始时间")
    @TableField(exist = false)
    private String startDate;
    @ApiModelProperty("结束时间")
    @Schema(description = "结束时间")
    @TableField(exist = false)
    private String endDate;
    @ApiModelProperty("开始月份")
    @Schema(description = "开始月份")
    @TableField(exist = false)
    private Integer startMonth;
    @ApiModelProperty("结束月份")
    @Schema(description = "结束月份")
    @TableField(exist = false)
    private Integer endMonth;
    @ApiModelProperty("年份")
    @Schema(description = "年份")
    @TableField(exist = false)
    private Integer year;
src/main/java/com/ruoyi/approve/vo/ApproveGetAndUpdateVo.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/approve/vo/ApproveProcessVO.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/approve/vo/ApproveProcessVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.approve.vo;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.basic.dto.StorageBlobVO;
import lombok.Data;
import java.util.List;
@Data
public class ApproveProcessVo extends ApproveProcess {
    private List<StorageBlobVO> storageBlobVOs;
}
src/main/java/com/ruoyi/basic/controller/CustomerController.java
@@ -2,18 +2,20 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.CustomerDto;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.service.ICustomerService;
import com.ruoyi.basic.vo.CustomerVo;
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.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.domain.R;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
@@ -32,8 +34,9 @@
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆåˆ—表
     */
    @GetMapping("/list")
    public IPage<Customer> list(Page<Customer> page, Customer customer) {
        return customerService.selectCustomerList(page, customer);
    public R list(Page<CustomerDto> page, CustomerDto customer) {
        IPage<CustomerVo> customerDtoIPage = customerService.selectCustomerList(page, customer);
        return R.ok(customerDtoIPage);
    }
    /**
@@ -41,16 +44,9 @@
     */
    @Log(title = "客户档案", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, Customer customer) {
        Long[] ids = customer.getIds();
        List<Customer> list;
        if (ids != null && ids.length > 0) {
            list = customerService.selectCustomerListByIds(ids);
        } else {
            list = customerService.selectCustomerLists(customer);
        }
        ExcelUtil<Customer> util = new ExcelUtil<Customer>(Customer.class);
        util.exportExcel(response, list, "客户档案数据");
    public void export(HttpServletResponse response, CustomerDto customer) {
        ExcelUtil<CustomerVo> util = new ExcelUtil<CustomerVo>(CustomerVo.class);
        util.exportExcel(response, customerService.selectCustomerLists(customer), "客户档案数据");
    }
    @PostMapping("/downloadTemplate")
@@ -66,17 +62,17 @@
     */
    @Log(title = "客户档案", businessType = BusinessType.IMPORT)
    @PostMapping("/importData")
    public AjaxResult importData(MultipartFile file) throws Exception {
    public R importData(MultipartFile file, Integer type) throws Exception {
        return customerService.importData(file);
        return customerService.importData(file, type);
    }
    /**
     * èŽ·å–å®¢æˆ·æ¡£æ¡ˆè¯¦ç»†ä¿¡æ¯
     */
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Long id) {
        return success(customerService.selectCustomerDetailById(id));
    public R getInfo(@PathVariable("id") Long id) {
        return R.ok(customerService.selectCustomerDetailById(id));
    }
    /**
@@ -84,8 +80,8 @@
     */
    @Log(title = "客户档案", businessType = BusinessType.INSERT)
    @PostMapping("/addCustomer")
    public AjaxResult add(@RequestBody Customer customer) {
        return toAjax(customerService.insertCustomer(customer));
    public R add(@RequestBody Customer customer) {
        return R.ok(customerService.insertCustomer(customer));
    }
    /**
@@ -93,8 +89,8 @@
     */
    @Log(title = "客户档案", businessType = BusinessType.UPDATE)
    @PostMapping("/updateCustomer")
    public AjaxResult edit(@RequestBody Customer customer) {
        return toAjax(customerService.updateCustomer(customer));
    public R edit(@RequestBody Customer customer) {
        return R.ok(customerService.updateCustomer(customer));
    }
    /**
@@ -102,11 +98,11 @@
     */
    @Log(title = "客户档案", businessType = BusinessType.DELETE)
    @DeleteMapping("/delCustomer")
    public AjaxResult remove(@RequestBody Long[] ids) {
    public R remove(@RequestBody Long[] ids) {
        if (ids == null || ids.length == 0) {
            return AjaxResult.error("请传入要删除的ID");
            return R.fail("请传入要删除的ID");
        }
        return toAjax(customerService.deleteCustomerByIds(ids));
        return R.ok(customerService.deleteCustomerByIds(ids));
    }
    /**
@@ -116,4 +112,44 @@
    public List customerList(Customer customer) {
        return customerService.customerList(customer);
    }
    /**
     * åˆ†é…å®¢æˆ·
     */
    @Log(title = "客户档案", businessType = BusinessType.OTHER)
    @PostMapping("/assignCustomer")
    public R assignCustomer(@RequestBody CustomerDto customer) {
        customerService.assignCustomer(customer);
        return R.ok();
    }
    /**
     * å›žæ”¶å®¢æˆ·
     */
    @Log(title = "客户档案", businessType = BusinessType.OTHER)
    @PostMapping("/recycleCustomer")
    public R recycleCustomer(@RequestBody CustomerDto customer) {
        customerService.recycleCustomer(customer);
        return R.ok();
    }
    /**
     * å…±äº«å®¢æˆ·
     */
    @Log(title = "客户档案", businessType = BusinessType.OTHER)
    @PostMapping("/together")
    public R together(@RequestBody CustomerDto customer) {
        customerService.together(customer);
        return R.ok();
    }
    /**
     * ç§æµ·å®¢æˆ·æµå›žå…¬æµ·
     */
    @Log(title = "客户档案", businessType = BusinessType.OTHER)
    @PostMapping("/back")
    public R back(Long id) {
        return R.ok(customerService.back(id));
    }
}
src/main/java/com/ruoyi/basic/controller/CustomerFollowUpController.java
@@ -3,6 +3,7 @@
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.ruoyi.basic.dto.CustomerFollowUpFileDto;
import com.ruoyi.basic.pojo.CustomerFollowUp;
import com.ruoyi.basic.pojo.CustomerReturnVisit;
import com.ruoyi.basic.service.CustomerFollowUpService;
@@ -11,12 +12,10 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.basic.dto.CustomerFollowUpFileDto;
import com.ruoyi.common.utils.SecurityUtils;
import java.util.List;
@@ -31,19 +30,18 @@
 */
@RestController
@RequestMapping("/basic/customer-follow")
@AllArgsConstructor
public class CustomerFollowUpController extends BaseController {
    @Autowired
    private CustomerFollowUpService customerFollowUpService;
    private final CustomerFollowUpService customerFollowUpService;
    @Autowired
    private CustomerReturnVisitService customerReturnVisitService;
    private final CustomerReturnVisitService customerReturnVisitService;
    /**
     * æŸ¥è¯¢å®¢æˆ·è·Ÿè¿›åˆ—表
     */
    @GetMapping("/list")
    @ApiOperation("查询客户跟进列表")
    @Operation(summary = "查询客户跟进列表")
    public IPage<CustomerFollowUp> list(Page<CustomerFollowUp> page, CustomerFollowUp customerFollowUp) {
        LambdaQueryWrapper<CustomerFollowUp> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(customerFollowUp.getCustomerId() != null, CustomerFollowUp::getCustomerId, customerFollowUp.getCustomerId())
@@ -55,7 +53,7 @@
    /**
     * èŽ·å–å®¢æˆ·è·Ÿè¿›è¯¦ç»†ä¿¡æ¯
     */
    @ApiOperation("获取客户跟进详细信息")
    @Operation(summary = "获取客户跟进详细信息")
    @GetMapping(value = "/{id}")
    public AjaxResult getInfo(@PathVariable("id") Integer id) {
        return AjaxResult.success(customerFollowUpService.getFollowUpWithFiles(id));
@@ -65,7 +63,7 @@
     * æ–°å¢žå®¢æˆ·è·Ÿè¿›
     */
    @PostMapping("/add")
    @ApiOperation("新增客户跟进")
    @Operation(summary = "新增客户跟进")
    @Log(title = "客户跟进-新增", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody CustomerFollowUp customerFollowUp) {
        return toAjax(customerFollowUpService.insertCustomerFollowUp(customerFollowUp));
@@ -75,7 +73,7 @@
     * ä¿®æ”¹å®¢æˆ·è·Ÿè¿›
     */
    @PutMapping("/edit")
    @ApiOperation("修改客户跟进")
    @Operation(summary = "修改客户跟进")
    @Log(title = "客户跟进-修改", businessType = BusinessType.UPDATE)
    public AjaxResult edit(@RequestBody CustomerFollowUp customerFollowUp) {
        return toAjax(customerFollowUpService.updateCustomerFollowUp(customerFollowUp));
@@ -84,7 +82,7 @@
    /**
     * ä¸Šä¼ è·Ÿè¿›é™„ä»¶
     */
    @ApiOperation("上传跟进附件")
    @Operation(summary = "上传跟进附件")
    @PostMapping("/upload/{followUpId}")
    @Log(title = "客户跟进-上传附件", businessType = BusinessType.INSERT)
    public AjaxResult uploadFiles(@RequestParam("files") List<MultipartFile> files, @PathVariable Integer followUpId) {
@@ -94,7 +92,7 @@
    /**
     * ä¸Šä¼ è·Ÿè¿›é™„件(复用,无ID)
     */
    @ApiOperation("上传附件(复用)")
    @Operation(summary = "上传附件(复用)")
    @PostMapping("/upload")
    @Log(title = "上传附件(复用)", businessType = BusinessType.INSERT)
    public AjaxResult uploadFiles(@RequestParam("files") List<MultipartFile> files, @RequestParam(required = false) String name) {
@@ -105,7 +103,7 @@
    /**
     * æ‰¹é‡æŸ¥è¯¢é™„件列表
     */
    @ApiOperation("批量查询附件列表")
    @Operation(summary = "批量查询附件列表")
    @PostMapping("/file/list")
    public AjaxResult getFileList(@RequestBody List<Long> ids) {
        return AjaxResult.success(customerFollowUpService.getFollowUpFilesByIds(ids));
@@ -114,7 +112,7 @@
    /**
     * åˆ é™¤è·Ÿè¿›é™„ä»¶
     */
    @ApiOperation("删除跟进附件")
    @Operation(summary = "删除跟进附件")
    @DeleteMapping("/file/{fileId}")
    @Log(title = "客户跟进-删除附件", businessType = BusinessType.DELETE)
    public AjaxResult deleteFile(@PathVariable Integer fileId) {
@@ -125,7 +123,7 @@
    /**
     * åˆ é™¤å®¢æˆ·è·Ÿè¿›
     */
    @ApiOperation("删除客户跟进")
    @Operation(summary = "删除客户跟进")
    @DeleteMapping("/{id}")
    @Log(title = "客户跟进-删除", businessType = BusinessType.DELETE)
    public AjaxResult remove(@PathVariable Integer id) {
@@ -135,7 +133,7 @@
    /**
     * æ–°å¢ž/更新回访提醒
     */
    @ApiOperation("新增/更新回访提醒")
    @Operation(summary = "新增/更新回访提醒")
    @PostMapping("/return-visit")
    @Log(title = "回访提醒-新增/更新", businessType = BusinessType.UPDATE)
    public AjaxResult saveReturnVisit(@RequestBody CustomerReturnVisit customerReturnVisit) {
@@ -145,7 +143,7 @@
    /**
     * èŽ·å–å›žè®¿æé†’è¯¦æƒ…
     */
    @ApiOperation("获取回访提醒详情")
    @Operation(summary = "获取回访提醒详情")
    @GetMapping("/return-visit/{customerId}")
    public AjaxResult getReturnVisit(@PathVariable Integer customerId) {
        return AjaxResult.success(customerReturnVisitService.getByCustomerId(customerId));
@@ -154,7 +152,7 @@
    /**
     * æ ‡è®°å›žè®¿æé†’已读
     */
    @ApiOperation("标记回访提醒已读")
    @Operation(summary = "标记回访提醒已读")
    @PutMapping("/return-visit/read/{id}")
    @Log(title = "回访提醒-标记已读", businessType = BusinessType.UPDATE)
    public AjaxResult markAsRead(@PathVariable Long id) {
src/main/java/com/ruoyi/basic/controller/EnumController.java
@@ -5,7 +5,7 @@
import com.ruoyi.common.utils.EnumUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Anonymous;
import com.ruoyi.framework.web.domain.R;
import io.swagger.annotations.Api;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -15,7 +15,7 @@
import java.util.Map;
@RestController
@Api(tags = "枚举接口")
@Tag(name = "枚举接口")
@RequestMapping("/basic/enum")
public class EnumController {
src/main/java/com/ruoyi/basic/controller/ProductController.java
@@ -10,6 +10,7 @@
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.basic.service.IProductService;
import com.ruoyi.basic.vo.ProductModelVo;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
@@ -17,15 +18,12 @@
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerProductService;
import com.ruoyi.sales.service.ISalesLedgerService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@@ -34,9 +32,7 @@
public class ProductController extends BaseController {
    private IProductService productService;
    private IProductModelService productModelService;
    @Autowired
    private ISalesLedgerProductService salesLedgerProductService;
    /**
     * æŸ¥è¯¢äº§å“
@@ -118,9 +114,9 @@
        return productModelService.modelListPage(page, productDto);
    }
    @ApiOperation("分页查询所有产品型号")
    @Operation(summary = "分页查询所有产品型号")
    @GetMapping("/pageModel")
    public IPage<ProductModel> listPageProductModel(Page<ProductModel> page, ProductModel productModel) {
    public IPage<ProductModelVo> listPageProductModel(Page<ProductModelVo> page, ProductModel productModel) {
        return productService.listPageProductModel(page, productModel);
    }
@@ -137,7 +133,7 @@
     * äº§å“å¯¼å…¥æ¨¡æ¿
     */
    @GetMapping("/export")
    @ApiOperation("产品导入模板")
    @Operation(summary = "产品导入模板")
    @Log(title = "产品导入模板", businessType = BusinessType.EXPORT)
    public void importProduct(HttpServletResponse response) {
        ExcelUtil<ProductModelExportDto> excelUtil = new ExcelUtil<>(ProductModelExportDto.class);
src/main/java/com/ruoyi/basic/controller/StorageAttachmentController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package com.ruoyi.basic.controller;
import com.ruoyi.basic.dto.StorageAttachmentDTO;
import com.ruoyi.basic.service.StorageAttachmentService;
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;
@RestController
@AllArgsConstructor
@Tag(name = "通用上传")
@RequestMapping("/storageAttachment")
public class StorageAttachmentController {
    private StorageAttachmentService storageAttachmentService;
    /**
     * åˆ†é¡µæŸ¥è¯¢é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     *
     * @param storageAttachmentDTO å…³è”记录信息
     * @return åˆ†é¡µç»“æžœ
     */
    @GetMapping("/list")
    @Operation(summary = "分页查询通用文件上传的附件信息")
    public R list(StorageAttachmentDTO storageAttachmentDTO) {
        return R.ok(storageAttachmentService.list(storageAttachmentDTO));
    }
    /**
     * åˆ é™¤é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     *
     * @param ids æ–‡ä»¶id列表
     * @return åˆ é™¤ç»“æžœ
     */
    @DeleteMapping("/delete")
    @Operation(summary = "删除通用文件上传的附件信息")
    public R batchDelete(@RequestBody List<Long> ids) {
        return R.ok(storageAttachmentService.batchDeleteStorageAttachment(ids));
    }
    /**
     * ä¿å­˜é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     */
    @PostMapping("/add")
    @Operation(summary = "保存通用文件上传的附件信息")
    public R add(@RequestBody StorageAttachmentDTO storageAttachmentDTO) {
        storageAttachmentService.saveStorageAttachment(storageAttachmentDTO);
        return R.ok();
    }
}
src/main/java/com/ruoyi/basic/controller/SupplierManageController.java
@@ -2,26 +2,25 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.SupplierManageDto;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.SupplierManage;
import com.ruoyi.basic.service.ISupplierService;
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.web.domain.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@RequestMapping("/system/supplier")
@AllArgsConstructor
public class SupplierManageController {
    @Autowired
    private ISupplierService supplierService;
    /**
src/main/java/com/ruoyi/basic/controller/SupplierManageFileController.java
@@ -7,7 +7,7 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import java.util.List;
/**
src/main/java/com/ruoyi/basic/dto/CustomerDto.java
@@ -21,4 +21,12 @@
    private List<CustomerFollowUpDto> followUpList;
}
    private String usageUserName;
    private String togetherUserNames;
    /**
     * å…±äº«ç”¨æˆ·ID列表
     */
    private List<Long> userIds;
}
src/main/java/com/ruoyi/basic/dto/ProductModelDto.java
@@ -1,7 +1,7 @@
package com.ruoyi.basic.dto;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.production.dto.ProductStructureDto;
import com.ruoyi.production.bean.dto.ProductStructureDto;
import lombok.Data;
import java.util.List;
src/main/java/com/ruoyi/basic/dto/StorageAttachmentDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.ruoyi.basic.dto;
import com.ruoyi.basic.pojo.StorageAttachment;
import lombok.Data;
import java.util.List;
@Data
public class StorageAttachmentDTO extends StorageAttachment {
    /**
     * å­˜å‚¨æ–‡ä»¶åˆ—表
     */
    private List<StorageBlobDTO> storageBlobDTOs;
}
src/main/java/com/ruoyi/basic/dto/StorageAttachmentVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.ruoyi.basic.dto;
import com.ruoyi.basic.pojo.StorageAttachment;
import lombok.Data;
import java.util.List;
@Data
public class StorageAttachmentVO extends StorageAttachment {
    /**
     * å­˜å‚¨æ–‡ä»¶åˆ—表
     */
    private List<StorageBlobVO> storageBlobVOS;
}
src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java
@@ -5,7 +5,18 @@
@Data
public class StorageBlobDTO extends StorageBlob {
    private String url;
    /**
     * é¢„览地址
     */
    private String previewURL;
    private String downloadUrl;
    /**
     * ä¸‹è½½åœ°å€
     */
    private String downloadURL;
    /**
     * æ–‡ä»¶ç±»åž‹
     */
    private String application;
}
src/main/java/com/ruoyi/basic/dto/StorageBlobVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.basic.dto;
import com.ruoyi.basic.pojo.StorageBlob;
import lombok.Data;
@Data
public class StorageBlobVO extends StorageBlob {
    /**
     * é¢„览地址
     */
    private String previewURL;
    /**
     * ä¸‹è½½åœ°å€
     */
    private String downloadURL;
    private Long storageAttachmentId;
}
src/main/java/com/ruoyi/basic/dto/SupplierManageDto.java
@@ -1,12 +1,12 @@
package com.ruoyi.basic.dto;
import com.ruoyi.basic.pojo.SupplierManage;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class SupplierManageDto extends SupplierManage {
    @ApiModelProperty(value = "维护人员名称")
    @Schema(description = "维护人员名称")
    private String maintainUserName;
}
src/main/java/com/ruoyi/basic/enums/ApplicationTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.basic.enums;
public enum ApplicationTypeEnum {
    IMAGE("image"),
    FILE("file"),
    AFTER_FILE("after_file"),
    BEFORE_FILE("before_file"),
    APK("apk");
    private final String type;
    ApplicationTypeEnum(String type) { this.type = type; }
    public String getType() { return type; }
    /**
     * æ ¹æ® type å€¼èŽ·å–å¯¹åº”çš„æžšä¸¾å®žä¾‹
     * @param type åº”用类型字符串
     * @return å¯¹åº”çš„ ApplicationTypeEnum æžšä¸¾å®žä¾‹
     * @throws RuntimeException å¦‚æžœ type æ— æ•ˆ
     */
    public static ApplicationTypeEnum getByType(String type) {
        for (ApplicationTypeEnum enumValue : ApplicationTypeEnum.values()) {
            if (enumValue.getType().equals(type)) {
                return enumValue;
            }
        }
        throw new RuntimeException("无效的应用类型: " + type);
    }
}
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,231 @@
package com.ruoyi.basic.enums;
public enum RecordTypeEnum {
    SHIPPING_INFO("shipping_info"),
    INSPECTION_TASK("inspection_task"),
    PDA_VERSION("pda_version"),
    SALES_LEDGER("sales_ledger"),
    SUPPLIER_MANAGE("supplier_manage"),
    APPROVAL_PROCESS("approval_process"),
    QR_CODE_SCAN_RECORDS("qr_code_scan_records"),
    // Water Record
    WATER_RECORD("water_record"),
    // Warehouse
    WAREHOUSE_GOODS_SHELVES_ROWCOL("warehouse_goods_shelves_rowcol"),
    WAREHOUSE_GOODS_SHELVES("warehouse_goods_shelves"),
    DOCUMENTATION_FILE("documentation_file"),
    DOCUMENTATION_RETURN_MANAGEMENT("documentation_return_management"),
    DOCUMENTATION_BORROW_MANAGEMENT("documentation_borrow_management"),
    DOCUMENTATION("documentation"),
    WAREHOUSE("warehouse"),
    DOCUMENT_CLASSIFICATION("document_classification"),
    // Technology
    TECHNOLOGY_ROUTING_OPERATION_PARAM("technology_routing_operation_param"),
    TECHNOLOGY_ROUTING("technology_routing"),
    TECHNOLOGY_ROUTING_OPERATION("technology_routing_operation"),
    TECHNOLOGY_BOM("technology_bom"),
    TECHNOLOGY_PARAM("technology_param"),
    TECHNOLOGY_OPERATION_PARAM("technology_operation_param"),
    TECHNOLOGY_OPERATION("technology_operation"),
    TECHNOLOGY_BOM_STRUCTURE("technology_bom_structure"),
    // Stock
    STOCK_OUT_RECORD("stock_out_record"),
    STOCK_UNINVENTORY("stock_uninventory"),
    STOCK_INVENTORY("stock_inventory"),
    STOCK_IN_RECORD("stock_in_record"),
    // Staff
    STAFF_WORK_EXPERIENCE("staff_work_experience"),
    STAFF_SALARY_MAIN("staff_salary_main"),
    STAFF_SCHEDULING("staff_scheduling"),
    STAFF_SALARY_DETAIL("staff_salary_detail"),
    STAFF_ON_JOB("staff_on_job"),
    STAFF_LEAVE("staff_leave"),
    STAFF_CONTRACT("staff_contract"),
    STAFF_EMERGENCY_CONTACT("staff_emergency_contact"),
    STAFF_EDUCATION("staff_education"),
    SCHEME_APPLICABLE_STAFF("scheme_applicable_staff"),
    PERSONAL_SHIFT("personal_shift"),
    SCHEME_INSURANCE_DETAIL("scheme_insurance_detail"),
    PERSONAL_ATTENDANCE_RECORDS("personal_attendance_records"),
    PERSONAL_ATTENDANCE_LOCATION_CONFIG("personal_attendance_location_config"),
    BANK("bank"),
    HOLIDAY_APPLICATION("holiday_application"),
    // Sales
    SHIPMENT_APPROVAL("shipment_approval"),
    SALESPERSON_MANAGEMENT("salesperson_management"),
    SALES_QUOTATION_PRODUCT("sales_quotation_product"),
    SALES_QUOTATION("sales_quotation"),
    SALES_LEDGER_PRODUCT("sales_ledger_product"),
    PURCHASE_LEDGER_FILE("purchase_ledger_file"),
    RECEIPT_PAYMENT("receipt_payment"),
    PAYMENT_SHIPPING("payment_shipping"),
    INVOICE_REGISTRATION_PRODUCT("invoice_registration_product"),
    LOSS("loss"),
    INVOICE_REGISTRATION("invoice_registration"),
    INVOICE_LEDGER_FILE("invoice_ledger_file"),
    INVOICE_LEDGER("invoice_ledger"),
    COMMON_FILE("common_file"),
    // Safe
    SAFE_TRAINING_FILE("safe_training_file"),
    SAFE_TRAINING_DETAILS("safe_training_details"),
    SAFE_TRAINING("safe_training"),
    SAFE_HIDDEN_FILE("safe_hidden_file"),
    SAFE_HAZARD_RECORD("safe_hazard_record"),
    SAFE_HIDDEN("safe_hidden"),
    SAFE_HAZARD("safe_hazard"),
    SAFE_CONTINGENCY_PLAN("safe_contingency_plan"),
    SAFE_CERTIFICATION_FILE("safe_certification_file"),
    SAFE_CERTIFICATION("safe_certification"),
    SAFE_ACCIDENT("safe_accident"),
    // Quality
    QUALITY_UNQUALIFIED("quality_unqualified"),
    QUALITY_TEST_STANDARD_PARAM("quality_test_standard_param"),
    QUALITY_TEST_STANDARD_BINDING("quality_test_standard_binding"),
    QUALITY_TEST_STANDARD("quality_test_standard"),
    QUALITY_INSPECT_FILE("quality_inspect_file"),
    QUALITY_INSPECT_PARAM("quality_inspect_param"),
    QUALITY_INSPECT("quality_inspect"),
    // Purchase
    TICKET_REGISTRATION("ticket_registration"),
    PURCHASE_RETURN_ORDER_PRODUCTS("purchase_return_order_products"),
    PURCHASE_RETURN_ORDERS("purchase_return_orders"),
    SALES_LEDGER_PRODUCT_TEMPLATE("sales_ledger_product_template"),
    PURCHASE_LEDGER("purchase_ledger"),
    PURCHASE_LEDGER_TEMPLATE("purchase_ledger_template"),
    PRODUCT_RECORD("product_record"),
    PAYMENT_REGISTRATION("payment_registration"),
    INVOICE_PURCHASE("invoice_purchase"),
    // Project Management
    SHIPPING_ADDRESS("shipping_address"),
    ROLES("roles"),
    PLAN("plan"),
    PLAN_NODE("plan_node"),
    INFO_STAGE("info_stage"),
    CONTRACT_INFO("contract_info"),
    INFO("info"),
    // Production
    PRODUCTION_PRODUCT_OUTPUT("production_product_output"),
    PRODUCTION_ORDER_ROUTING_OPERATION("production_order_routing_operation"),
    PRODUCTION_PRODUCT_INPUT("production_product_input"),
    PRODUCTION_PRODUCT_MAIN("production_product_main"),
    PRODUCTION_PLAN("production_plan"),
    PRODUCTION_ORDER_ROUTING_OPERATION_PARAM("production_order_routing_operation_param"),
    PRODUCTION_ORDER_ROUTING("production_order_routing"),
    PRODUCTION_ORDER_PICK_RECORD("production_order_pick_record"),
    PRODUCTION_ORDER_PICK("production_order_pick"),
    PRODUCTION_ORDER_BOM("production_order_bom"),
    PRODUCTION_OPERATION_TASK("production_operation_task"),
    PRODUCTION_ORDER("production_order"),
    PRODUCTION_ACCOUNT("production_account"),
    PRODUCTION_BOM_STRUCTURE("production_bom_structure"),
    PRODUCTION_OPERATION_MAIN_PARAM("production_operation_main_param"),
    // Procurement Record
    RETURN_SALE_PRODUCT("return_sale_product"),
    PROCUREMENT_PLAN("procurement_plan"),
    PROCUREMENT_RECORD_OUT("procurement_record_out"),
    PROCUREMENT_RECORD_STORAGE("procurement_record_storage"),
    RETURN_MANAGEMENT("return_management"),
    PROCUREMENT_PRICE_MANAGEMENT("procurement_price_management"),
    GAS_TANK_WARNING("gas_tank_warning"),
    CUSTOM_STORAGE("custom_storage"),
    PROCUREMENT_EXCEPTION_RECORD("procurement_exception_record"),
    INBOUND_MANAGEMENT("inbound_management"),
    // Office Supplies
    OFFICE_SUPPLIES("office_supplies"),
    // OA
    OA_PROJECT_PHASE_TASK("oa_project_phase_task"),
    OA_PROJECT("oa_project"),
    OA_PROJECT_PHASE("oa_project_phase"),
    // Measuring Instrument Ledger
    SPARE_PARTS("spare_parts"),
    MEASURING_INSTRUMENT_LEDGER_RECORD("measuring_instrument_ledger_record"),
    MEASURING_INSTRUMENT_LEDGER("measuring_instrument_ledger"),
    SPARE_PARTS_REQUISITION_RECORD("spare_parts_requisition_record"),
    // Labor Issue
    LABOR_ISSUE("labor_issue"),
    // Inspection Task
    TIMING_TASK("timing_task"),
    QR_CODE("qr_code"),
    // Equipment Energy Consumption
    ENERGY_PERIOD("energy_period"),
    EQUIPMENT_ENERGY_CONSUMPTION("equipment_energy_consumption"),
    ELECTRICITY_CONSUMPTION_AREA("electricity_consumption_area"),
    // Device
    MAINTENANCE_TASK("maintenance_task"),
    DEVICE_REPAIR("device_repair"),
    DEVICE_MAINTENANCE_FILE("device_maintenance_file"),
    DEVICE_DEFECT_RECORD("device_defect_record"),
    DEVICE_MAINTENANCE("device_maintenance"),
    DEVICE_LEDGER("device_ledger"),
    // Customer Visits
    CUSTOMER_VISITS("customer_visits"),
    // Compensation Performance
    COMPENSATION_PERFORMANCE("compensation_performance"),
    // Collaborative Approval
    STAFF_CONTACTS_PERSONAL("staff_contacts_personal"),
    SEAL_APPLICATION_MANAGEMENT("seal_application_management"),
    RULES_REGULATIONS_MANAGEMENT_FILE("rules_regulations_management_file"),
    RULES_REGULATIONS_MANAGEMENT("rules_regulations_management"),
    READING_STATUS("reading_status"),
    NOTICE("notice"),
    NOTICE_TYPE("notice_type"),
    MEET_DRAFT("meet_draft"),
    MEETING_ROOM("meeting_room"),
    MEETING_MINUTES("meeting_minutes"),
    MEET_APPLICATION("meet_application"),
    DUTY_PLAN("duty_plan"),
    // Basic
    SUPPLIER_MANAGE_FILE("supplier_manage_file"),
    PRODUCT_MODEL("product_model"),
    CUSTOMER_RETURN_VISIT("customer_return_visit"),
    PRODUCT("product"),
    CUSTOMER("customer"),
    CUSTOMER_PRIVATE_POOL("customer_private_pool"),
    CUSTOMER_FOLLOW_UP_FILE("customer_follow_up_file"),
    CUSTOMER_FOLLOW_UP("customer_follow_up"),
    CUSTOMER_PRIVATE("customer_private"),
    // Approve
    WORKING_HOURS_SETTING("working_hours_setting"),
    OVERTIME_SETTING("overtime_setting"),
    RPA_PROCESS_AUTOMATION("rpa_process_automation"),
    HOLIDAY_SETTINGS("holiday_settings"),
    ONLINE_MEETING("online_meeting"),
    KNOWLEDGE_BASE("knowledge_base"),
    NOTIFICATION_MANAGEMENT("notification_management"),
    APPROVE_NODE("approve_node"),
    APPROVE_PROCESS("approve_process"),
    APPROVE_PROCESS_CONFIG_NODE("approve_process_config_node"),
    APPROVE_LOG("approve_log"),
    ANNUAL_LEAVE_SETTING("annual_leave_setting"),
    FILE_SHARING("file_sharing"),
    // After Sales Service
    AFTER_SALES_SERVICE("after_sales_service"),
    AFTER_SALES_SERVICE_FILE("after_sales_service_file"),
    AFTER_SALES_NEAR_EXPIRY("after_sales_near_expiry"),
    // Account
    ACCOUNT_INCOME("account_income"),
    BORROW_INFO("borrow_info"),
    SALES_REFUND_AMOUNT_ORDER("sales_refund_amount_order"),
    SALES_RECEIPT_RETURN("sales_receipt_return"),
    ACCOUNT_EXPENSE("account_expense"),
    ACCOUNT_FILE("account_file");
    private final String type;
    RecordTypeEnum(String type) { this.type = type; }
    public String getType() { return type; }
    /**
     * æ ¹æ® type å€¼èŽ·å–å¯¹åº”çš„æžšä¸¾å®žä¾‹
     * @param type è®°å½•类型字符串
     * @return å¯¹åº”çš„ RecordTypeEnum æžšä¸¾å®žä¾‹
     * @throws RuntimeException å¦‚æžœ type æ— æ•ˆ
     */
    public static RecordTypeEnum getByType(String type) {
        for (RecordTypeEnum enumValue : RecordTypeEnum.values()) {
            if (enumValue.getType().equals(type)) {
                return enumValue;
            }
        }
        throw new RuntimeException("无效的记录类型: " + type);
    }
}
src/main/java/com/ruoyi/basic/excel/SupplierManageExcelDto.java
@@ -3,7 +3,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java
@@ -1,7 +1,13 @@
package com.ruoyi.basic.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.basic.dto.CustomerDto;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.vo.CustomerVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -11,6 +17,7 @@
 * @author ruoyi
 * @date 2025-05-07
 */
@Mapper
public interface CustomerMapper extends BaseMapper<Customer>
{
    /**
@@ -60,4 +67,8 @@
     * @return ç»“æžœ
     */
    int deleteCustomerByIds(Long[] ids);
}
    IPage<CustomerVo> listPage(Page<CustomerDto> page, @Param("c") CustomerDto customer, @Param("loginUserId") Long loginUserId);
    List<CustomerVo> list(@Param("c") CustomerDto customer, @Param("loginUserId") Long loginUserId);
}
src/main/java/com/ruoyi/basic/mapper/CustomerUserMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.CustomerUser;
import org.apache.ibatis.annotations.Mapper;
/**
 * å®¢æˆ·å…±äº«Mapper接口
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @date 2026-04-29
 */
@Mapper
public interface CustomerUserMapper extends BaseMapper<CustomerUser> {
}
src/main/java/com/ruoyi/basic/mapper/ProductModelMapper.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.vo.ProductModelVo;
import com.ruoyi.procurementrecord.dto.ProcurementPageDto;
import org.apache.ibatis.annotations.Param;
@@ -19,7 +20,7 @@
 */
public interface ProductModelMapper extends BaseMapper<ProductModel> {
    IPage<ProductModel> listPageProductModel(Page<ProductModel> page, @Param("c") ProductModel productModel);
    IPage<ProductModelVo> listPageProductModel(Page<ProductModelVo> page, @Param("c") ProductModel productModel);
    IPage<ProductModel> listPageProductionStock(Page<ProductModel> page, @Param("req") ProcurementPageDto req);
src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.pojo.StorageBlob;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
@@ -15,4 +16,9 @@
@Mapper
public interface StorageBlobMapper extends BaseMapper<StorageBlob> {
    java.util.List<StorageBlob> selectOrphanBlobsByIdRange(@Param("lastId") long lastId, @Param("limit") int limit);
    int deleteByIdList(@Param("ids") java.util.List<Long> ids);
    java.util.List<String> selectExistingUidFilenames(@Param("fileNames") java.util.List<String> fileNames);
}
src/main/java/com/ruoyi/basic/pojo/Customer.java
@@ -7,9 +7,10 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import org.checkerframework.checker.units.qual.A;
/**
 * å®¢æˆ·æ¡£æ¡ˆå¯¹è±¡ customer
@@ -105,15 +106,32 @@
    @TableField(exist = false)
    private String addressPhone;
    @ApiModelProperty(value = "银行基本户")
    @Schema(description = "银行基本户")
    @Excel(name = "银行基本户")
    private String basicBankAccount;
    @ApiModelProperty(value = "银行账号")
    @Schema(description = "银行账号")
    @Excel(name = "银行账号")
    private String bankAccount;
    @ApiModelProperty(value = "开户行号")
    @Schema(description = "开户行号")
    @Excel(name = "开户行号")
    private String bankCode;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @Schema(description = "使用用户")
    private Long usageUser;
    @Schema(description = "使用状态")
    private Long usageStatus;
    @Schema(description = "类型 0 ç§æµ·å®¢æˆ· 1 å…¬æµ·å®¢æˆ·")
    private Integer type;
    @Schema(description = "是否被分配:0-未分配,1-已分配")
    private Integer isAssigned;
}
src/main/java/com/ruoyi/basic/pojo/CustomerFollowUp.java
@@ -1,10 +1,14 @@
package com.ruoyi.basic.pojo;
import io.swagger.v3.oas.annotations.media.Schema;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
@@ -31,9 +35,9 @@
    private Integer id;
    /**
     * å…³è”的客户ID
     * å…³è”的私海id
     */
    private Integer customerId;
    private Long customerId;
    /**
     * è·Ÿè¿›æ–¹å¼
@@ -49,6 +53,7 @@
     * è·Ÿè¿›æ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime followUpTime;
    /**
@@ -82,4 +87,11 @@
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/basic/pojo/CustomerFollowUpFile.java
@@ -1,5 +1,7 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
@@ -78,4 +80,7 @@
     * ç§Ÿæˆ·ID
     */
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/basic/pojo/CustomerReturnVisit.java
@@ -1,5 +1,7 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
@@ -34,7 +36,7 @@
    /**
     * å…³è”客户ID
     */
    private Integer customerId;
    private Long customerId;
    /**
     * æé†’开关 (0:关闭, 1:开启)
@@ -89,4 +91,7 @@
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/basic/pojo/CustomerUser.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@TableName(value = "customer_user")
@Data
public class CustomerUser implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * åºå·
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * å®¢æˆ·id
     */
    private Long customerId;
    /**
     * ç”¨æˆ·id
     */
    private Long userId;
    /**
     * ç§Ÿæˆ·id
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    /**
     * å½•入时间
     */
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}
src/main/java/com/ruoyi/basic/pojo/Product.java
@@ -1,7 +1,7 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@@ -26,7 +26,17 @@
     */
    private String productName;
    @ApiModelProperty(value = "租户ID")
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(exist = false)
    private Long[] deptIds;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/basic/pojo/ProductModel.java
@@ -2,15 +2,16 @@
import com.baomidou.mybatisplus.annotation.*;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@TableName("product_model")
public class ProductModel {
public class ProductModel implements Serializable {
    private static final long serialVersionUID = 1L;
@@ -36,6 +37,10 @@
    @Excel(name = "规格型号")
    private String model;
    @Excel(name = "产品编码")
    @TableField("product_code")
    private String productCode;
    /**
     * å•位
     */
@@ -48,7 +53,7 @@
    @Excel(name = "生产炒机")
    private String speculativeTradingName;
    @ApiModelProperty(value = "租户ID")
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(exist = false)
@@ -63,4 +68,15 @@
    @TableField(exist = false)
    private LocalDateTime createTime;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @Schema(description = "顶部父产品id")
    @TableField(exist = false)
    private Long topProductParentId;
}
src/main/java/com/ruoyi/basic/pojo/StorageAttachment.java
@@ -2,17 +2,15 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
import java.time.LocalDateTime;
/**
 * é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息 å®žä½“ç±»
 *
 * @author ruoyi
 * @date 2025-05-29
 */
@Data
@TableName("storage_attachment")
@@ -20,24 +18,18 @@
    private static final long serialVersionUID = 1L;
    /**
     *
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /** åˆ›å»ºæ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    private LocalDateTime createTime;
    /** æ›´æ–°æ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    private LocalDateTime updateTime;
    /**
     * é€»è¾‘删除
@@ -48,29 +40,20 @@
     * å…³è”的记录类型
     */
    @TableField(value = "record_type")
    private Long recordType;
    private String  recordType;
    /**
     * å…³è”的记录id
     */
    @TableField(value = "record_id")
    private Long recordId;
    /**
     * ç±»åž‹åç§°, å¦‚: file, avatar (区分同一条记录不同类型的附件)
     * æ–‡ä»¶ç”¨é€”, å¦‚: file, avatar (区分同一条记录不同类型的附件)
     */
    @TableField(value = "name")
    private String name;
    @TableField(value = "application")
    private String application;
    /**
     * å…³è”storage_blob记录id
     */
    @TableField(value = "storage_blob_id")
    private Long storageBlobId;
    @TableField(exist = false)
    private StorageBlobDTO storageBlobDTO;
    public StorageAttachment(String fileType, Long recordType, Long recordId) {
        this.name = fileType;
        this.recordType = recordType;
        this.recordId = recordId;
    }
}
}
src/main/java/com/ruoyi/basic/pojo/StorageBlob.java
@@ -1,20 +1,13 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
/**
 * é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息 å®žä½“ç±»
 *
 * @author ruoyi
 * @date 2025-05-29
 */
@Data
@TableName("storage_blob")
@@ -22,9 +15,6 @@
    private static final long serialVersionUID = 1L;
    /**
     *
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
@@ -45,15 +35,11 @@
    private String originalFilename;
    /**
     * å­˜å‚¨æ¡¶ä¸­
     * å”¯ä¸€æ–‡ä»¶åç§°
     */
    @TableField(value = "bucket_filename")
    private String bucketFilename;
    /**
     * å­˜å‚¨æ¡¶å
     */
    @TableField(value = "bucket_name")
    private String bucketName;
    @TableField(value = "uid_filename")
    private String uidFilename;
    /**
     * èµ„源尺寸(字节)
     */
@@ -61,32 +47,8 @@
    private Long byteSize;
    /**
     * 0生产前 1生产后 2生产问题
     * æ–‡ä»¶è·¯å¾„
     */
    @TableField(value = "type")
    private Long type;
    /**
     * ç§Ÿæˆ·ID
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @ApiModelProperty(value = "创建该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "记录创建时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "最后修改该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "记录最后更新时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @TableField(value = "path")
    private String path;
}
src/main/java/com/ruoyi/basic/pojo/SupplierManage.java
@@ -3,7 +3,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
@@ -16,71 +16,74 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "供应商名称")
    @Schema(description = "供应商名称")
    @Excel(name = "供应商名称")
    private String supplierName;
    @ApiModelProperty(value = "纳税人识别号")
    @Schema(description = "纳税人识别号")
    @Excel(name = "纳税人识别号")
    private String taxpayerIdentificationNum;
    @ApiModelProperty(value = "公司地址")
    @Schema(description = "公司地址")
    @Excel(name = "公司地址")
    private String companyAddress;
    @ApiModelProperty(value = "公司电话")
    @Schema(description = "公司电话")
    @Excel(name = "公司电话")
    private String companyPhone;
    @ApiModelProperty(value = "开户行")
    @Schema(description = "开户行")
    @Excel(name = "开户行")
    private String bankAccountName;
    @ApiModelProperty(value = "账号")
    @Schema(description = "账号")
    @Excel(name = "账号")
    private String bankAccountNum;
    @ApiModelProperty(value = "联系人")
    @Schema(description = "联系人")
    @Excel(name = "联系人")
    private String contactUserName;
    @ApiModelProperty(value = "联系电话")
    @Schema(description = "联系电话")
    @Excel(name = "联系电话")
    private String contactUserPhone;
    @ApiModelProperty(value = "维护人ID")
    @Schema(description = "维护人ID")
    @Excel(name = "维护人")
    private Integer maintainUserId;
    @ApiModelProperty(value = "维护时间")
    @Schema(description = "维护时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
//    @Excel(name = "维护时间", width = 30, dateFormat = "yyyy-MM-dd")
    private LocalDate maintainTime;
    @Excel(name = "是否白名单")
    @ApiModelProperty(value = "是否白名单(0是 1否)")
    @Schema(description = "是否白名单(0是 1否)")
    private Integer isWhite;
    @ApiModelProperty(value = "创建时间")
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty(value = "创建用户")
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "修改时间")
    @Schema(description = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "修改用户")
    @Schema(description = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "租户ID")
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @ApiModelProperty(value = "供应商类型")
    @Schema(description = "供应商类型")
    @TableField(value = "supplier_type")
    private String supplierType;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/basic/pojo/SupplierManageFile.java
@@ -1,10 +1,10 @@
package com.ruoyi.basic.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotBlank;
import java.io.Serializable;
import java.time.LocalDateTime;
@@ -23,38 +23,41 @@
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "文件名称")
    @Schema(description = "文件名称")
    private String name;
    @ApiModelProperty(value = "文件路径")
    @Schema(description = "文件路径")
    private String url;
    @ApiModelProperty(value = "文件大小")
    @Schema(description = "文件大小")
    private int fileSize;
    @ApiModelProperty(value = "供应商ID")
    @Schema(description = "供应商ID")
    @NotBlank(message = "供应商id不能为空!")
    private Long supplierId;
    @ApiModelProperty(value = "创建时间")
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty(value = "修改时间")
    @Schema(description = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "创建用户")
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "修改用户")
    @Schema(description = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "租户ID")
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/basic/service/CustomerFollowUpFileService.java
@@ -4,9 +4,9 @@
import com.ruoyi.basic.pojo.CustomerFollowUpFile;
import com.ruoyi.common.vo.SimpleFileVo;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Null;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
src/main/java/com/ruoyi/basic/service/CustomerUserService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.pojo.CustomerUser;
/**
 * å®¢æˆ·å…±äº«Service接口
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @date 2026-04-29
 */
public interface CustomerUserService extends IService<CustomerUser> {
}
src/main/java/com/ruoyi/basic/service/ICustomerService.java
@@ -5,7 +5,8 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.dto.CustomerDto;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.basic.vo.CustomerVo;
import com.ruoyi.framework.web.domain.R;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -32,7 +33,7 @@
     * @param id å®¢æˆ·æ¡£æ¡ˆä¸»é”®
     * @return å®¢æˆ·è¯¦æƒ…DTO
     */
    CustomerDto selectCustomerDetailById(Long id);
    CustomerVo selectCustomerDetailById(Long id);
    /**
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆåˆ—表
@@ -40,7 +41,6 @@
     * @param customer å®¢æˆ·æ¡£æ¡ˆ
     * @return å®¢æˆ·æ¡£æ¡ˆé›†åˆ
     */
    IPage<Customer> selectCustomerList(Page<Customer> page, Customer customer);
    /**
     * æ–°å¢žå®¢æˆ·æ¡£æ¡ˆ
@@ -75,7 +75,22 @@
     */
    List<Map<String, Object>> customerList(Customer customer);
    List<Customer> selectCustomerLists(Customer customer);
    List<CustomerVo> selectCustomerLists(CustomerDto customer);
    AjaxResult importData(MultipartFile file);
    R importData(MultipartFile file, Integer type);
    IPage<CustomerVo> selectCustomerList(Page<CustomerDto> page, CustomerDto customer);
    void assignCustomer(CustomerDto customer);
    void recycleCustomer(CustomerDto customer);
    /**
     * å…±äº«å®¢æˆ·ç»™å…¶ä»–用户
     *
     * @param customerDto å®¢æˆ·DTO(包含客户ID和共享用户ID列表)
     */
    void together(CustomerDto customerDto);
    Boolean back(Long id);
}
src/main/java/com/ruoyi/basic/service/IProductService.java
@@ -7,6 +7,7 @@
import com.ruoyi.basic.dto.ProductTreeDto;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.vo.ProductModelVo;
import java.util.List;
@@ -18,5 +19,5 @@
    List<ProductTreeDto> selectProductList(ProductDto productDto);
    IPage<ProductModel> listPageProductModel(Page<ProductModel> page, ProductModel productModel);
    IPage<ProductModelVo> listPageProductModel(Page<ProductModelVo> page, ProductModel productModel);
}
src/main/java/com/ruoyi/basic/service/ISupplierService.java
@@ -8,7 +8,7 @@
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
public interface ISupplierService extends IService<SupplierManage> {
src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
@@ -1,6 +1,14 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.dto.StorageAttachmentDTO;
import com.ruoyi.basic.dto.StorageAttachmentVO;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.common.constant.StorageAttachmentConstants;
import com.ruoyi.common.enums.StorageAttachmentRecordType;
@@ -16,26 +24,18 @@
 * @since 2025-05-29
 */
public interface StorageAttachmentService extends IService<StorageAttachment> {
    /**
     * æŸ¥è¯¢é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     * @param recordId å…³è”记录id
     * @param recordType å…³è”记录类型
     * @param fileType æ–‡ä»¶ç±»åž‹
     * @return æ–‡ä»¶ä¿¡æ¯åˆ—表
     */
    List<StorageAttachment> selectStorageAttachments(Long recordId, StorageAttachmentRecordType recordType, String fileType);
    /**
     * ä¿å­˜é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     * @param attachments æ–‡ä»¶ä¿¡æ¯åˆ—表
     * @param recordId ç®¡ç†è®°å½•id
     * @param recordType å…³è”记录类型
     * @param fileType æ–‡ä»¶ç±»åž‹
     */
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType);
    public void saveStorageAttachment(StorageAttachmentDTO storageAttachmentDTO);
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, String fileType);
    /**
     * åˆ†é¡µæŸ¥è¯¢é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     * @param storageAttachmentDTO å…³è”记录信息
     * @return åˆ†é¡µç»“æžœ
     */
    public List<StorageBlobVO> list(StorageAttachmentDTO storageAttachmentDTO);
    /**
     * åˆ é™¤é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
@@ -43,4 +43,11 @@
     * @return åˆ é™¤ç»“æžœ
     */
    public int deleteStorageAttachment(StorageAttachment storageAttachment);
    /**
     * æ‰¹é‡åˆ é™¤é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     * @param ids æ–‡ä»¶id列表
     * @return åˆ é™¤ç»“æžœ
     */
    public int batchDeleteStorageAttachment(List<Long> ids);
}
src/main/java/com/ruoyi/basic/service/StorageBlobService.java
@@ -1,11 +1,11 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.pojo.StorageBlob;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.List;
/**
@@ -19,20 +19,15 @@
public interface StorageBlobService extends IService<StorageBlob> {
    /**
     * æ–‡ä»¶ä¸Šä¼ æŽ¥å£
     * @param files æ–‡ä»¶ä¿¡æ¯
     * @param bucketName å­˜å‚¨æ¡¶åç§°
     * ä¸Šä¼ æ–‡ä»¶
     * @param files æ–‡ä»¶åˆ—表
     * @return ä¸Šä¼ ç»“æžœ
     */
    List<StorageBlobDTO> updateStorageBlobs(List<MultipartFile> files, String bucketName);
    List<StorageBlobVO> upload(List<MultipartFile> files, Boolean isPublic);
    List<StorageBlobDTO> updateStorageBlobs(List<MultipartFile> files, String bucketName,Long type);
    File getFileByToken(String fileName, String token);
    File getPublicFile(String fileName, String publicKey);
    /**
     * æ‰¹é‡åˆ é™¤æ–‡ä»¶
     * @param attachment
     * @return
     */
    public int deleteStorageBlobs(StorageAttachment attachment);
    String getDownloadFileName(String fileName);
}
src/main/java/com/ruoyi/basic/service/impl/CustomerFollowUpFileServiceImpl.java
@@ -1,18 +1,15 @@
package com.ruoyi.basic.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.stream.CollectorUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.ruoyi.basic.mapper.CustomerFollowUpFileMapper;
import com.ruoyi.basic.pojo.CustomerFollowUpFile;
import com.ruoyi.basic.service.CustomerFollowUpFileService;
import com.ruoyi.basic.service.CustomerFollowUpService;
import com.ruoyi.common.vo.SimpleFileVo;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import java.util.*;
@@ -30,12 +27,10 @@
 * @since 2026/03/04 14:53
 */
@Service
@RequiredArgsConstructor
public class CustomerFollowUpFileServiceImpl extends ServiceImpl<CustomerFollowUpFileMapper, CustomerFollowUpFile> implements CustomerFollowUpFileService {
    @Autowired
    @Lazy
    private CustomerFollowUpService customerFollowUpService;
    private final CustomerFollowUpFileMapper customerFollowUpFileMapper;
    @Override
    public <T> void fillAttachment(List<T> list, Function<T, String> getAttachmentIds, BiConsumer<T, List<SimpleFileVo>> setAttachmentList) {
@@ -52,7 +47,7 @@
        List<CustomerFollowUpFile> followUpFilesByIds = new ArrayList<>();
        Lists.partition(Lists.newArrayList(ids), 999).forEach(it -> {
            followUpFilesByIds.addAll(
                    customerFollowUpService.getFollowUpFilesByIds(it)
                    getFollowUpFilesByIds(it)
            );
        });
        if (CollUtil.isEmpty(followUpFilesByIds)) {
@@ -74,4 +69,15 @@
            }
        });
    }
    private List<CustomerFollowUpFile> getFollowUpFilesByIds(Collection<Long> fileIds) {
        if (fileIds == null || fileIds.isEmpty()) {
            return new ArrayList<>(0);
        }
        LambdaQueryWrapper<CustomerFollowUpFile> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(CustomerFollowUpFile::getId, fileIds)
                .select(CustomerFollowUpFile::getId, CustomerFollowUpFile::getFileUrl, CustomerFollowUpFile::getFileName);
        return customerFollowUpFileMapper.selectList(queryWrapper);
    }
}
src/main/java/com/ruoyi/basic/service/impl/CustomerFollowUpServiceImpl.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.CustomerFollowUpDto;
import com.ruoyi.basic.dto.CustomerFollowUpFileDto;
import com.ruoyi.basic.mapper.CustomerFollowUpMapper;
import com.ruoyi.basic.pojo.CustomerFollowUp;
import com.ruoyi.basic.pojo.CustomerFollowUpFile;
@@ -11,13 +12,11 @@
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.basic.dto.CustomerFollowUpFileDto;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -44,16 +43,15 @@
 * @since 2026/03/04 14:48
 */
@Service
@RequiredArgsConstructor
public class CustomerFollowUpServiceImpl extends ServiceImpl<CustomerFollowUpMapper, CustomerFollowUp> implements CustomerFollowUpService {
    @Autowired
    private CustomerFollowUpFileService customerFollowUpFileService;
    private final CustomerFollowUpFileService customerFollowUpFileService;
    @Value("${file.upload-dir}")
    private String uploadDir;
    @Autowired
    private ISysUserService sysUserService;
    private final ISysUserService sysUserService;
    @Override
    @Transactional(rollbackFor = Exception.class)
src/main/java/com/ruoyi/basic/service/impl/CustomerReturnVisitServiceImpl.java
@@ -8,8 +8,8 @@
import com.ruoyi.basic.service.CustomerReturnVisitService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -26,10 +26,10 @@
 * @since 2026/03/04 17:58
 */
@Service
@RequiredArgsConstructor
public class CustomerReturnVisitServiceImpl extends ServiceImpl<CustomerReturnVisitMapper, CustomerReturnVisit> implements CustomerReturnVisitService {
    @Autowired
    private ReturnVisitReminderService returnVisitReminderService;
    private final ReturnVisitReminderService returnVisitReminderService;
    @Override
    @Transactional(rollbackFor = Exception.class)
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -1,8 +1,6 @@
package com.ruoyi.basic.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -15,27 +13,25 @@
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.CustomerFollowUp;
import com.ruoyi.basic.pojo.CustomerFollowUpFile;
import com.ruoyi.basic.service.CustomerFollowUpFileService;
import com.ruoyi.basic.service.CustomerFollowUpService;
import com.ruoyi.basic.service.CustomerReturnVisitService;
import com.ruoyi.basic.service.ICustomerService;
import com.ruoyi.basic.pojo.CustomerUser;
import com.ruoyi.basic.service.*;
import com.ruoyi.basic.vo.CustomerVo;
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.project.system.domain.SysUser;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
@@ -51,14 +47,19 @@
@AllArgsConstructor
@Slf4j
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements ICustomerService {
    private final SalesLedgerMapper salesLedgerMapper;
    @Autowired
    private  SalesLedgerMapper salesLedgerMapper;
    @Autowired
    private CustomerMapper customerMapper;
    @Autowired
    private CustomerFollowUpService customerFollowUpService;
    @Autowired
    private CustomerFollowUpFileService customerFollowUpFileService;
    @Autowired
    private CustomerReturnVisitService customerReturnVisitService;
    @Autowired
    private CustomerUserService customerUserService;
    /**
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆ
@@ -78,14 +79,9 @@
     * @return å®¢æˆ·è¯¦æƒ…DTO
     */
    @Override
    public CustomerDto selectCustomerDetailById(Long id) {
        Customer customer = customerMapper.selectById(id);
        if (customer == null) {
            return null;
        }
        CustomerDto dto = new CustomerDto();
        BeanUtils.copyProperties(customer, dto);
    public CustomerVo selectCustomerDetailById(Long id) {
        CustomerVo customerVo = new CustomerVo();
        BeanUtils.copyProperties(this.getById(id), customerVo);
        // æŸ¥è¯¢è·Ÿè¿›è®°å½•
        List<CustomerFollowUp> followUpList = customerFollowUpService.list(
@@ -93,8 +89,7 @@
                        .eq(CustomerFollowUp::getCustomerId, id)
                        .orderByDesc(CustomerFollowUp::getFollowUpTime)
        );
        if (!CollectionUtils.isEmpty(followUpList)) {
        if (!com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(followUpList)) {
            List<CustomerFollowUpDto> followUpDtoList = followUpList.stream().map(followUp -> {
                CustomerFollowUpDto followUpDto = new CustomerFollowUpDto();
                BeanUtils.copyProperties(followUp, followUpDto);
@@ -109,10 +104,10 @@
                return followUpDto;
            }).collect(Collectors.toList());
            dto.setFollowUpList(followUpDtoList);
            customerVo.setFollowUpList(followUpDtoList);
        }
        return dto;
        return customerVo;
    }
    /**
@@ -122,62 +117,65 @@
     * @return å®¢æˆ·æ¡£æ¡ˆ
     */
    @Override
    public IPage<Customer> selectCustomerList(Page<Customer> page, Customer customer) {
        // 1. å¤„理空值场景(参数校验)
        if (page == null) {
            page = Page.of(1, 10); // é»˜è®¤ç¬¬1页,每页10条数据
        }
        if (customer == null) {
            customer = new Customer(); // é¿å…ç©ºå¯¹è±¡å¯¼è‡´çš„NPE
    public IPage<CustomerVo> selectCustomerList(Page<CustomerDto> page, CustomerDto customer) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long loginUserId = loginUser.getUserId();
        IPage<CustomerVo> customerPage = customerMapper.listPage(page, customer, loginUserId);
        List<CustomerVo> records = customerPage.getRecords();
        if (CollectionUtils.isEmpty(records)) {
            return customerPage;
        }
        // 2. æž„建查询条件(增强空值安全)
        LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<>();
        String customerName = customer.getCustomerName();
        String customerType = customer.getCustomerType();
        if (StringUtils.isNotBlank(customerName)) {
            queryWrapper.like(Customer::getCustomerName, customerName);
        }
        if (StringUtils.isNotBlank(customerType)) {
            queryWrapper.like(Customer::getCustomerType, customerType);
        }
        // 3. æ‰§è¡Œåˆ†é¡µæŸ¥è¯¢ï¼ˆä¿ç•™åˆ†é¡µå…ƒæ•°æ®ï¼‰
        IPage<Customer> customerPage = customerMapper.selectPage(page, queryWrapper);
        // 4. æ•°æ®å¤„理(增强空值安全 & ä»£ç å¯è¯»æ€§ï¼‰
        List<Customer> processedList = customerPage.getRecords().stream()
                .filter(Objects::nonNull) // è¿‡æ»¤ç©ºå¯¹è±¡ï¼ˆé¿å…åŽç»­æ“ä½œNPE)
                .peek(c -> {
                    // å®‰å…¨èŽ·å–å­—æ®µï¼Œé¿å…null值拼接
                    String address = StringUtils.defaultString(c.getCompanyAddress(), "");
                    String phone = StringUtils.defaultString(c.getCompanyPhone(), "");
                    c.setAddressPhone(address + "(" + phone + ")");
                    // æŸ¥è¯¢æœ€æ–°çš„跟进记录
                    CustomerFollowUp followUp = customerFollowUpService.getOne(
                            new LambdaQueryWrapper<CustomerFollowUp>()
                                    .eq(CustomerFollowUp::getCustomerId, c.getId())
                                    .orderByDesc(CustomerFollowUp::getFollowUpTime)
                                    .last("LIMIT 1")
                    );
                    if (followUp != null) {
                        c.setFollowUpLevel(followUp.getFollowUpLevel());
                        c.setFollowUpTime(
                                Date.from(
                                        followUp.getFollowUpTime().atZone(ZoneId.systemDefault()).toInstant()
                                )
                        );
                    }
                })
        List<Long> customerIds = records.stream()
                .map(CustomerVo::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        // 5. æ›´æ–°åˆ†é¡µç»“果中的数据(保持分页信息完整)
        IPage<Customer> resultPage = new Page<>(customerPage.getCurrent(), customerPage.getSize(), customerPage.getTotal());
        resultPage.setRecords(processedList);
        if (!CollectionUtils.isEmpty(customerIds)) {
            Map<Long, CustomerFollowUp> latestFollowUpMap = getLatestFollowUpMap(customerIds);
        return customerPage; // è¿”回包含分页信息的IPage对象
            records.forEach(c -> {
                String address = StringUtils.defaultString(c.getCompanyAddress(), "");
                String phone = StringUtils.defaultString(c.getCompanyPhone(), "");
                c.setAddressPhone(address + "(" + phone + ")");
                CustomerFollowUp followUp = latestFollowUpMap.get(c.getId());
                if (followUp != null) {
                    c.setFollowUpLevel(followUp.getFollowUpLevel());
                    c.setFollowUpTime(Date.from(
                            followUp.getFollowUpTime().atZone(ZoneId.systemDefault()).toInstant()
                    ));
                }
                // è½¬æ¢å…±äº«ç”¨æˆ·ID字符串为List<Long>
                String userIdsStr = c.getUserIdsStr();
                if (StringUtils.isNotEmpty(userIdsStr)) {
                    List<Long> userIds = Arrays.stream(userIdsStr.split(","))
                            .map(String::trim)
                            .map(Long::parseLong)
                            .collect(Collectors.toList());
                    c.setUserIds(userIds);
                }
            });
        }
        return customerPage;
    }
    private Map<Long, CustomerFollowUp> getLatestFollowUpMap(List<Long> customerIds) {
        List<CustomerFollowUp> followUps = customerFollowUpService.list(
                new LambdaQueryWrapper<CustomerFollowUp>()
                        .in(CustomerFollowUp::getCustomerId, customerIds)
                        .orderByDesc(CustomerFollowUp::getFollowUpTime)
        );
        return followUps.stream()
                .collect(Collectors.toMap(
                        CustomerFollowUp::getCustomerId,
                        followUp -> followUp,
                        (existing, replacement) -> existing
                ));
    }
    /**
@@ -222,12 +220,28 @@
        if (!salesLedgers.isEmpty()) {
            throw new RuntimeException("客户档案下有销售合同,请先删除销售合同");
        }
        //  åˆ é™¤å®¢æˆ·çš„同时也需要删除对应的客户跟随、附件和回访提醒
        // æŸ¥è¯¢æ˜¯å¦æœ‰å·²åˆ†é…çš„公海客户
        List<Customer> assignedPools = customerMapper.selectList(
                new QueryWrapper<Customer>().lambda()
                        .in(Customer::getId, idList)
                        .eq(Customer::getType, 1).
                        eq(Customer::getIsAssigned, 1)  // å…¬æµ·å®¢æˆ·
        );
        if (!assignedPools.isEmpty()) {
            throw new RuntimeException("客户档案下有已分配的公海客户,请先收回");
        }
        // åˆ é™¤å®¢æˆ·çš„同时也需要删除对应的客户跟随、附件和回访提醒
        for (Long id : ids) {
            customerFollowUpService.deleteByCustomerId(id);
            customerReturnVisitService.deleteByCustomerId(id);
            // åˆ é™¤å®¢æˆ·çš„共享关系
            customerUserService.remove(
                new QueryWrapper<CustomerUser>().lambda()
                    .eq(CustomerUser::getCustomerId, id)
            );
        }
        // åˆ é™¤å®¢æˆ·ä¸»è¡¨æ•°æ®
        return customerMapper.deleteBatchIds(idList);
    }
@@ -239,23 +253,32 @@
    }
    @Override
    public List<Customer> selectCustomerLists(Customer customer) {
        return customerMapper.selectList(null);
    public List<CustomerVo> selectCustomerLists(CustomerDto customer) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long loginUserId = loginUser.getUserId();
        return customerMapper.list(customer, loginUserId);
    }
    @Override
    public AjaxResult importData(MultipartFile file) {
    public R importData(MultipartFile file, Integer type) {
        try {
            ExcelUtil<Customer> util = new ExcelUtil<Customer>(Customer.class);
            List<Customer> userList = util.importExcel(file.getInputStream());
            if (CollectionUtils.isEmpty(userList)) {
                return AjaxResult.warn("模板错误或导入数据为空");
                return R.fail("模板错误或导入数据为空");
            }
            // æ ¹æ® type å‚数设置客户类型(私海/公海)
            if (type != null) {
                userList.forEach(customer -> {
                    customer.setType(type);
                });
            }
            this.saveOrUpdateBatch(userList);
            return AjaxResult.success(true);
            return R.ok(true);
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.error("导入失败");
            return R.fail("导入失败");
        }
    }
@@ -276,6 +299,86 @@
        ).collect(Collectors.toList());
    }
    // åˆ†é…å…¬æµ·å®¢æˆ·ç»™ç§æµ·
    @Override
    public void assignCustomer(CustomerDto customerDto) {
        Customer customer = customerMapper.selectById(customerDto.getId());
        if (customer.getType() == 1 && customer.getIsAssigned() == 0) {  // å…¬æµ·ä¸”可分配
            customer.setIsAssigned(1);
            customer.setUsageStatus(1L);
            customer.setUsageUser(customerDto.getUsageUser());
            customerMapper.updateById(customer);
        }
    }
    // å›žæ”¶ç§æµ·å®¢æˆ·åˆ°å…¬æµ·
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void recycleCustomer(CustomerDto customerDto) {
        Customer customer = customerMapper.selectById(customerDto.getId());
        if (customer.getType() == 1 && customer.getIsAssigned() == 1) {  // å…¬æµ·ä¸”已分配
            customer.setIsAssigned(0);
            customer.setUsageStatus(0L);
            customer.setUsageUser(0L);
            customerMapper.updateById(customer);
            // åˆ é™¤è¯¥å®¢æˆ·çš„æ‰€æœ‰å…±äº«å…³ç³»
            customerUserService.remove(
                new QueryWrapper<CustomerUser>().lambda()
                    .eq(CustomerUser::getCustomerId, customerDto.getId())
            );
        }
    }
    // å®¢æˆ·å…±äº«
    @Override
    public void together(CustomerDto customerDto) {
        // æŸ¥è¯¢çŽ°æœ‰çš„å…±äº«è®°å½•
        List<CustomerUser> existingUsers = customerUserService.list(
                new QueryWrapper<CustomerUser>().lambda().eq(CustomerUser::getCustomerId, customerDto.getId())
        );
        // èŽ·å–å·²å­˜åœ¨çš„ç”¨æˆ·ID列表
        List<Long> existingUserIds = existingUsers.stream()
                .map(CustomerUser::getUserId)
                .collect(Collectors.toList());
        // è¿‡æ»¤æŽ‰å·²å­˜åœ¨çš„用户,只保留新用户
        List<Long> newUserIds = customerDto.getUserIds().stream()
                .filter(userId -> !existingUserIds.contains(userId))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(newUserIds)) {
            return;
        }
        // èŽ·å–å½“å‰ç§Ÿæˆ·ID
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Long tenantId = loginUser.getTenantId();
        // æ‰¹é‡ä¿å­˜æ–°çš„共享记录
        List<CustomerUser> customerUsers = newUserIds.stream()
                .map(userId -> {
                    CustomerUser customerUser = new CustomerUser();
                    customerUser.setCustomerId(customerDto.getId());
                    customerUser.setUserId(userId);
                    customerUser.setTenantId(tenantId);
                    return customerUser;
                })
                .collect(Collectors.toList());
        customerUserService.saveBatch(customerUsers);
    }
    @Override
    public Boolean back(Long id) {
        //将客户的type改为1 ä¸”直接分配给当前用户
        Customer customer = customerMapper.selectById(id);
        customer.setType(1);
        customer.setIsAssigned(1);
        return this.updateById(customer);
    }
    /**
     * ä¸‹åˆ’线命名转驼峰命名
     */
src/main/java/com/ruoyi/basic/service/impl/CustomerUserServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.basic.service.impl;
import com.ruoyi.basic.mapper.CustomerUserMapper;
import com.ruoyi.basic.pojo.CustomerUser;
import com.ruoyi.basic.service.CustomerUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
 * å®¢æˆ·å…±äº«Service实现类
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @date 2026-04-29
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class CustomerUserServiceImpl extends ServiceImpl<CustomerUserMapper, CustomerUser> implements CustomerUserService {
}
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -25,7 +25,10 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
src/main/java/com/ruoyi/basic/service/impl/ProductServiceImpl.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
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.basic.dto.ProductDto;
@@ -11,6 +12,7 @@
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductService;
import com.ruoyi.basic.vo.ProductModelVo;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
@@ -55,7 +57,7 @@
    }
    @Override
    public IPage<ProductModel> listPageProductModel(Page<ProductModel> page, ProductModel productModel) {
    public IPage<ProductModelVo> listPageProductModel(Page<ProductModelVo> page, ProductModel productModel) {
        return productModelMapper.listPageProductModel(page, productModel);
    }
@@ -89,6 +91,9 @@
    @Override
    public int addOrEditProduct(ProductDto productDto) {
        if (ObjectUtils.isEmpty(productDto.getParentId())) {
            throw new IllegalArgumentException("请选择父节点");
        }
        if (productDto.getId() == null) {
            // æ–°å¢žäº§å“é€»è¾‘
            if (productDto.getParentId() == null) {
src/main/java/com/ruoyi/basic/service/impl/ReturnVisitReminderService.java
@@ -1,10 +1,10 @@
package com.ruoyi.basic.service.impl;
import com.ruoyi.basic.mapper.CustomerReturnVisitMapper;
import com.ruoyi.basic.pojo.CustomerReturnVisit;
import com.ruoyi.basic.service.CustomerReturnVisitService;
import com.ruoyi.framework.redis.RedisCache;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.ZoneId;
@@ -20,19 +20,18 @@
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ReturnVisitReminderService {
    private static final String REMINDER_QUEUE_KEY = "return_visit:reminder:queue";
    @Autowired
    private RedisCache redisCache;
    private final RedisCache redisCache;
    @Autowired
    private CustomerReturnVisitService customerReturnVisitService;
    private final CustomerReturnVisitMapper customerReturnVisitMapper;
    @SuppressWarnings("unchecked")
    public void scheduleReminder(Long returnVisitId) {
        CustomerReturnVisit returnVisit = customerReturnVisitService.getById(returnVisitId);
        CustomerReturnVisit returnVisit = customerReturnVisitMapper.selectById(returnVisitId);
        if (returnVisit == null || returnVisit.getIsEnabled() == 0) {
            return;
        }
src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
@@ -1,21 +1,18 @@
package com.ruoyi.basic.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.basic.dto.StorageAttachmentDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
import com.ruoyi.basic.mapper.StorageBlobMapper;
import com.ruoyi.basic.pojo.StorageBlob;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.basic.service.StorageAttachmentService;
import com.ruoyi.basic.service.StorageBlobService;
import com.ruoyi.common.constant.StorageAttachmentConstants;
import com.ruoyi.common.enums.StorageAttachmentRecordType;
import com.ruoyi.common.utils.MinioUtils;
import com.ruoyi.basic.utils.FileUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@@ -30,78 +27,42 @@
@Service
@RequiredArgsConstructor
public class StorageAttachmentServiceImpl extends ServiceImpl<StorageAttachmentMapper, StorageAttachment> implements StorageAttachmentService {
    @Autowired
    private StorageBlobMapper storageBlobMapper;
    private final StorageBlobMapper storageBlobMapper;
    @Autowired
    private StorageAttachmentMapper storageAttachmentMapper;
    private final StorageAttachmentMapper storageAttachmentMapper;
    @Autowired
    private StorageBlobService storageBlobService;
    @Autowired
    private MinioUtils minioUtils;
    private final StorageBlobService storageBlobService;
    private final FileUtil fileUtil;
    @Override
    public List<StorageAttachment> selectStorageAttachments(Long recordId, StorageAttachmentRecordType recordType, String fileType) {
        List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordId, recordId)
                .eq(StorageAttachment::getRecordType, recordType.ordinal())
                .eq(StorageAttachment::getName, fileType));
        if (storageAttachments != null) {
            for (StorageAttachment storageAttachment : storageAttachments) {
                StorageBlob storageBlob = storageBlobMapper.selectById(storageAttachment.getStorageBlobId());
                StorageBlobDTO storageBlobDTO = new StorageBlobDTO();
                BeanUtils.copyProperties(storageBlob, storageBlobDTO);
                storageBlobDTO.setUrl(minioUtils.getPreviewUrl(storageBlob.getBucketName(), storageBlob.getBucketName(), true));
                storageAttachment.setStorageBlobDTO(storageBlobDTO);
            }
        }
        return storageAttachments;
    @Transactional(rollbackFor = Exception.class)
    public void saveStorageAttachment(StorageAttachmentDTO storageAttachmentDTO) {
        fileUtil.saveStorageAttachmentByRecordTypeAndRecordId(storageAttachmentDTO.getApplication(), RecordTypeEnum.getByType(storageAttachmentDTO.getRecordType()), storageAttachmentDTO.getRecordId(), storageAttachmentDTO.getStorageBlobDTOs());
    }
    @Override
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType) {
        // åˆ é™¤æ—§å›¾
        deleteStorageAttachment(new StorageAttachment(fileType.toString(), (long) recordType.ordinal(), recordId));
        for (StorageAttachment attachment : attachments) {
            // èŽ·å–å…³è”è®°å½•
            StorageBlob storageBlob = attachment.getStorageBlobDTO();
            attachment.setName(fileType.toString());
            attachment.setRecordType((long) recordType.ordinal());
            attachment.setRecordId(recordId);
            attachment.setStorageBlobId(storageBlob.getId());
            storageAttachmentMapper.insert(attachment);
        }
    }
    @Override
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, String fileType) {
        // åˆ é™¤æ—§å›¾
//        deleteStorageAttachment(new StorageAttachment(fileType, (long) recordType.ordinal(), recordId));
        for (StorageAttachment attachment : attachments) {
            // èŽ·å–å…³è”è®°å½•
            StorageBlob storageBlob = attachment.getStorageBlobDTO();
            attachment.setName(fileType);
            attachment.setRecordType((long) recordType.ordinal());
            attachment.setRecordId(recordId);
            attachment.setStorageBlobId(storageBlob.getId());
            storageAttachmentMapper.insert(attachment);
        }
    public List<StorageBlobVO> list(StorageAttachmentDTO storageAttachmentDTO) {
        return fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(storageAttachmentDTO);
    }
    @Override
    public int deleteStorageAttachment(StorageAttachment storageAttachment) {
        // å…ˆåˆ é™¤æ˜Žç»†è¡¨
        storageBlobService.deleteStorageBlobs(storageAttachment);
        // todo fileChange
//        storageBlobService.deleteStorageBlobs(storageAttachment);
//
//
//        return storageAttachmentMapper.delete(new LambdaQueryWrapper<StorageAttachment>()
//                .eq(StorageAttachment::getRecordId, storageAttachment.getRecordId())
//                .eq(StorageAttachment::getRecordType, storageAttachment.getRecordType())
//                .eq(StorageAttachment::getName, storageAttachment.getName()));
//    }
        return 0;
    }
        return storageAttachmentMapper.delete(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordId, storageAttachment.getRecordId())
                .eq(StorageAttachment::getRecordType, storageAttachment.getRecordType())
                .eq(StorageAttachment::getName, storageAttachment.getName()));
    @Override
    public int batchDeleteStorageAttachment(List<Long> ids) {
        fileUtil.deleteStorageAttachmentsByStorageAttachmentIds(ids);
        return 1;
    }
}
src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
@@ -1,190 +1,171 @@
package com.ruoyi.basic.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.mapper.StorageBlobMapper;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.basic.pojo.StorageBlob;
import com.ruoyi.basic.service.StorageBlobService;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.exception.file.InvalidExtensionException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MinioUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.framework.web.domain.MinioResult;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.config.FileProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDateTime;
import javax.crypto.SecretKey;
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.UUID;
/**
 * <p>
 * é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author ruoyi
 * @since 2025-05-29
 */
@Service
@RequiredArgsConstructor
public class StorageBlobServiceImpl extends ServiceImpl<StorageBlobMapper, StorageBlob> implements StorageBlobService {
    @Autowired
    private StorageAttachmentMapper storageAttachmentMapper;
    @Autowired
    private StorageBlobMapper storageBlobMapper;
    @Autowired
    private MinioUtils minioUtils;
    private final FileProperties properties;
    private final StorageBlobMapper storageBlobMapper;
    private final FileUtil fileUtil;
    @Override
    public List<StorageBlobDTO> updateStorageBlobs(List<MultipartFile> files, String bucketName) {
        // è‹¥æ²¡ä¼ å…¥bucketName,则使用默认bucketName
        if (StringUtils.isEmpty(bucketName)) {
            bucketName  = minioUtils.getDefaultBucket();
    public List<StorageBlobVO> upload(List<MultipartFile> files, Boolean isPublic) {
        if (CollectionUtils.isEmpty(files)) {
            throw new IllegalArgumentException("文件不能为空");
        }
        List<StorageBlobDTO> storageBlobDTOs = new ArrayList<>();
        List<StorageBlobVO> storageBlobVOS = new ArrayList<>();
        for (MultipartFile file : files) {
            try {
                MinioResult res = minioUtils.upload(bucketName, file, false);
                StorageBlobDTO dto = new StorageBlobDTO();
                dto.setContentType(file.getContentType());
                dto.setBucketFilename(res.getBucketFileName());
                dto.setOriginalFilename(res.getOriginalName());
                dto.setByteSize(file.getSize());
                dto.setResourceKey(IdUtils.simpleUUID());
                dto.setBucketName(bucketName);
                dto.setUrl(minioUtils.getPreviewUrl(res.getBucketFileName(), bucketName, false));
                // æ’入数据库
                storageBlobMapper.insert(dto);
                storageBlobDTOs.add(dto);
            } catch (InvalidExtensionException e) {
                throw new RuntimeException("minio文件上传异常:" +  e);
            if (file == null || file.isEmpty()) {
                throw new IllegalArgumentException("文件不能为空");
            }
            String originalFileName = StringUtils.hasText(file.getOriginalFilename())
                    ? StringUtils.cleanPath(file.getOriginalFilename())
                    : UUID.randomUUID().toString();
            String fileName = UUID.randomUUID() + "_" + originalFileName;
            String relativePath = fileUtil.buildRelativePath();
            File targetDirectory = new File(properties.getPath(), relativePath);
            if (!targetDirectory.exists() && !targetDirectory.mkdirs()) {
                throw new RuntimeException("创建上传目录失败");
            }
            File dest = new File(targetDirectory, fileName);
            StorageBlobVO storageBlob;
            try {
                file.transferTo(dest);
                storageBlob = getStorageBlob(file, originalFileName, fileName, relativePath, isPublic);
                if (storageBlob == null || storageBlob.getId() == null) {
                    throw new RuntimeException("文件元数据保存失败");
                }
            } catch (RuntimeException e) {
                if (dest.exists()) {
                    dest.delete();
                }
                throw e;
            } catch (IOException e) {
                throw new RuntimeException("文件保存失败", e);
            }
            storageBlobVOS.add(storageBlob);
        }
        return storageBlobDTOs;
        return storageBlobVOS;
    }
    @Override
    public List<StorageBlobDTO> updateStorageBlobs(List<MultipartFile> files, String bucketName, Long type) {
        // è‹¥æ²¡ä¼ å…¥bucketName,则使用默认bucketName
        if (StringUtils.isEmpty(bucketName)) {
            bucketName = minioUtils.getDefaultBucket();
        }
        List<StorageBlobDTO> storageBlobDTOs = new ArrayList<>();
        for (MultipartFile file : files) {
            try {
                validateFileExtension(file);
                MinioResult res = minioUtils.upload(bucketName, file, false);
                StorageBlobDTO dto = buildStorageBlobDTO(file, res, bucketName, type);
                storageBlobMapper.insert(dto);
                storageBlobDTOs.add(dto);
            } catch (InvalidExtensionException e) {
                throw new RuntimeException("不支持的文件类型:" + file.getOriginalFilename(), e);
            } catch (Exception e) {
                throw new RuntimeException("上传文件失败:" + file.getOriginalFilename(), e);
            }
        }
        return storageBlobDTOs;
    }
    private StorageBlobDTO buildStorageBlobDTO(MultipartFile file, MinioResult res, String bucketName, Long type) {
        StorageBlobDTO dto = new StorageBlobDTO();
        dto.setContentType(file.getContentType());
        dto.setBucketFilename(res.getBucketFileName());
        dto.setOriginalFilename(res.getOriginalName());
        dto.setByteSize(file.getSize());
        dto.setResourceKey(IdUtils.simpleUUID());
        dto.setBucketName(bucketName);
        dto.setUrl(minioUtils.getPreviewUrl(res.getBucketFileName(), bucketName, false));
        dto.setDownloadUrl(minioUtils.getDownloadUrl(res.getBucketFileName(), bucketName));
        if (type != null) {
            dto.setType(type);
    public File getFileByToken(String fileName, String token) {
        if (!StringUtils.hasText(token)) {
            throw new IllegalArgumentException("token不能为空");
        }
        return dto;
    }
        String secretStr = properties.getJwtSecret();
    private void validateFileExtension(MultipartFile file) throws InvalidExtensionException {
        String filename = file.getOriginalFilename();
        String extension = FilenameUtils.getExtension(filename).toLowerCase();
        List<String> allowedExtensions = Arrays.asList(
                // å›¾ç‰‡
                "jpg", "jpeg", "png", "gif", "bmp", "webp", "tiff", "ico", "svg",
                // æ–‡æ¡£
                "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "rtf", "md", "csv", "odt",
                // è§†é¢‘
                "mp4", "mov", "avi", "wmv", "flv", "mkv", "webm", "mpeg", "3gp", "MOV",
                // éŸ³é¢‘
                "mp3", "wav", "ogg", "aac", "flac", "m4a", "wma", "amr",
                // åŽ‹ç¼©åŒ…
                "zip", "rar", "7z", "tar", "gz", "bz2", "xz",
                // ç¼–程代码文件
                "java", "py", "js", "ts", "html", "css", "cpp", "c", "cs", "json", "xml", "sql", "yaml", "yml", "sh", "bat",
                // å®‰è£…程序 & äºŒè¿›åˆ¶
                "exe", "apk", "dmg", "msi", "bin", "iso",
                // è®¾è®¡ç±»
                "psd", "ai", "xd", "sketch", "fig"
        );
        if (!allowedExtensions.contains(extension)) {
            throw new BaseException("文件类型不被允许:" + extension);
        SecretKey key = Keys.hmacShaKeyFor(secretStr.getBytes(StandardCharsets.UTF_8));
        Claims claims = Jwts.parser()
                .verifyWith(key)          // ä»£æ›¿æ—§ç‰ˆçš„ setSigningKey
                .build()                  // å¿…须先构建解析器
                .parseSignedClaims(token) // ä»£æ›¿æ—§ç‰ˆçš„ parseClaimsJws
                .getPayload();            // ä»£æ›¿æ—§ç‰ˆçš„ getBody()
        if (!fileName.equals(claims.getSubject())) {
            throw new IllegalArgumentException("token与文件不匹配");
        }
        fileUtil.validateTokenUsage(token);
        StorageBlob storageBlob = findStorageBlob(fileName);
        String path = storageBlob == null ? claims.get("path", String.class) : storageBlob.getPath();
        if (!StringUtils.hasText(path)) {
            return new File(properties.getPath(), fileName);
        }
        return new File(new File(properties.getPath(), path), fileName);
    }
    @Override
    public int deleteStorageBlobs(StorageAttachment attachment) {
        List<StorageAttachment> attachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordId, attachment.getRecordId())
                .eq(StorageAttachment::getRecordType, attachment.getRecordType())
                .eq(StorageAttachment::getName, attachment.getName()));
        List<Long> ids = attachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
        if(CollectionUtils.isEmpty(ids)){
            return 0;
    public File getPublicFile(String fileName, String publicKey) {
        if (!StringUtils.hasText(fileName)) {
            throw new IllegalArgumentException("文件名不能为空");
        }
        List<StorageBlob> storageBlobs = storageBlobMapper.selectList(new LambdaQueryWrapper<StorageBlob>()
                .in(StorageBlob::getId, ids));
        if (!storageBlobs.isEmpty()) {
            for (StorageBlob storageBlob : storageBlobs) {
                // ç§»é™¤æ¡¶å†…文件
                minioUtils.removeObjectsResult(storageBlob.getBucketName(), storageBlob.getBucketName());
            }
        if (!StringUtils.hasText(publicKey)) {
            throw new IllegalArgumentException("publicKey不能为空");
        }
        StorageBlob storageBlob = storageBlobMapper.selectOne(new LambdaQueryWrapper<StorageBlob>()
                .eq(StorageBlob::getUidFilename, fileName)
                .eq(StorageBlob::getResourceKey, publicKey)
                .last("limit 1"));
        if (storageBlob == null) {
            throw new IllegalArgumentException("公开文件不存在或publicKey不匹配");
        }
        String path = storageBlob.getPath();
        if (!StringUtils.hasText(path)) {
            return new File(properties.getPath(), fileName);
        }
        return new File(new File(properties.getPath(), path), fileName);
    }
        if (!ids.isEmpty()) {
            return storageBlobMapper.delete(new QueryWrapper<StorageBlob>().lambda().in(StorageBlob::getId, ids));
        }
    private StorageBlob findStorageBlob(String fileName) {
        return storageBlobMapper.selectOne(new LambdaQueryWrapper<StorageBlob>()
                .eq(StorageBlob::getUidFilename, fileName)
                .last("limit 1"));
    }
        return 0;
    private StorageBlobVO getStorageBlob(MultipartFile file, String originalFileName, String fileName, String relativePath, Boolean isPublic) {
        StorageBlobVO storageBlob = new StorageBlobVO();
        storageBlob.setResourceKey(UUID.randomUUID().toString().replace("-", ""));
        storageBlob.setContentType(file.getContentType());
        storageBlob.setOriginalFilename(originalFileName);
        storageBlob.setUidFilename(fileName);
        storageBlob.setByteSize(file.getSize());
        storageBlob.setPath(relativePath);
        if (isPublic) {
            storageBlob.setPreviewURL(fileUtil.buildSignedUrl(storageBlob, "/preview/", BigDecimal.valueOf(-1)));
            storageBlob.setDownloadURL(fileUtil.buildSignedUrl(storageBlob, "/download/", BigDecimal.valueOf(-1)));
        } else {
            storageBlob.setPreviewURL(fileUtil.buildSignedPreviewUrl(storageBlob));
            storageBlob.setDownloadURL(fileUtil.buildSignedDownloadUrl(storageBlob));
        }
        int affectedRows = storageBlobMapper.insert(storageBlob);
        if (affectedRows <= 0) {
            throw new RuntimeException("文件元数据保存失败");
        }
        return storageBlob;
    }
    @Override
    public String getDownloadFileName(String fileName) {
        StorageBlob storageBlob = findStorageBlob(fileName);
        if (storageBlob == null || !StringUtils.hasText(storageBlob.getOriginalFilename())) {
            return fileName;
        }
        return storageBlob.getOriginalFilename();
    }
}
src/main/java/com/ruoyi/basic/service/impl/SupplierServiceImpl.java
@@ -8,31 +8,28 @@
import com.ruoyi.basic.dto.SupplierManageDto;
import com.ruoyi.basic.excel.SupplierManageExcelDto;
import com.ruoyi.basic.mapper.SupplierManageMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.SupplierManage;
import com.ruoyi.basic.service.ISupplierService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class SupplierServiceImpl extends ServiceImpl<SupplierManageMapper,SupplierManage> implements ISupplierService {
    @Autowired
    private SupplierManageMapper supplierMapper;
    @Autowired
    private PurchaseLedgerMapper purchaseLedgerMapper;
    private final SupplierManageMapper supplierMapper;
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    /**
     * ä¾›åº”商新增
src/main/java/com/ruoyi/basic/task/ReturnVisitReminderTask.java
@@ -6,12 +6,11 @@
import com.ruoyi.project.system.domain.SysUserClient;
import com.ruoyi.project.system.service.SysUserClientService;
import com.ruoyi.project.system.service.impl.UnipushService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Set;
/**
@@ -25,21 +24,18 @@
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ReturnVisitReminderTask {
    private static final String REMINDER_QUEUE_KEY = "return_visit:reminder:queue";
    @Autowired
    private RedisCache redisCache;
    private final RedisCache redisCache;
    @Autowired
    private CustomerReturnVisitService customerReturnVisitService;
    private final CustomerReturnVisitService customerReturnVisitService;
    @Autowired
    private UnipushService unipushService;
    private final UnipushService unipushService;
    @Autowired
    private SysUserClientService userClientService;
    private final SysUserClientService userClientService;
    @SuppressWarnings("unchecked")
    @Scheduled(fixedDelay = 60000)
src/main/java/com/ruoyi/basic/task/StorageBlobCleanupTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,184 @@
package com.ruoyi.basic.task;
import com.ruoyi.basic.mapper.StorageBlobMapper;
import com.ruoyi.basic.pojo.StorageBlob;
import com.ruoyi.common.config.FileProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.File;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
 * æ¸…理无效文件定时任务。
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class StorageBlobCleanupTask {
    private static final int DB_BATCH_SIZE = 500;
    private static final int FILE_NAME_BATCH_SIZE = 1000;
    private final StorageBlobMapper storageBlobMapper;
    private final FileProperties fileProperties;
    private final AtomicBoolean running = new AtomicBoolean(false);
    /**
     * æ¯æœˆ 1 å·å‡Œæ™¨ 2 ç‚¹æ‰§è¡Œä¸€æ¬¡ï¼š
     * 1. åˆ é™¤ storage_blob ä¸­æœªè¢« storage_attachment å…³è”的记录及其文件
     * 2. åˆ é™¤ç£ç›˜ä¸Šä¸å­˜åœ¨äºŽ storage_blob.uid_filename çš„æ–‡ä»¶
     */
    @Scheduled(cron = "0 0 2 1 * ?")
    public void cleanupUnusedStorageFiles() {
        if (!running.compareAndSet(false, true)) {
            log.warn("文件清理任务正在执行,本次跳过");
            return;
        }
        long start = System.currentTimeMillis();
        log.info("文件清理任务开始执行,根目录:{}", fileProperties.getPath());
        try {
            int removedBlobCount = cleanupOrphanStorageBlobs();
            int removedDiskFileCount = cleanupOrphanDiskFiles();
            long cost = System.currentTimeMillis() - start;
            log.info("文件清理任务执行完成,删除孤儿 blob è®°å½•:{},删除磁盘无效文件:{},耗时:{} ms",
                    removedBlobCount, removedDiskFileCount, cost);
        } catch (Exception e) {
            log.error("文件清理任务执行失败", e);
        } finally {
            running.set(false);
        }
    }
    private int cleanupOrphanStorageBlobs() {
        long lastId = 0L;
        int removedCount = 0;
        while (true) {
            List<StorageBlob> orphanBlobs = storageBlobMapper.selectOrphanBlobsByIdRange(lastId, DB_BATCH_SIZE);
            if (CollectionUtils.isEmpty(orphanBlobs)) {
                break;
            }
            List<Long> ids = new ArrayList<>(orphanBlobs.size());
            for (StorageBlob storageBlob : orphanBlobs) {
                ids.add(storageBlob.getId());
                deleteBlobFiles(storageBlob);
            }
            storageBlobMapper.deleteByIdList(ids);
            removedCount += ids.size();
            lastId = orphanBlobs.get(orphanBlobs.size() - 1).getId();
            log.info("已删除一批孤儿 blob,batchSize={},lastId={}", ids.size(), lastId);
        }
        return removedCount;
    }
    private int cleanupOrphanDiskFiles() {
        File rootDirectory = new File(fileProperties.getPath());
        if (!rootDirectory.exists() || !rootDirectory.isDirectory()) {
            log.warn("文件根目录不存在或不是目录,跳过磁盘清理:{}", fileProperties.getPath());
            return 0;
        }
        int deletedCount = 0;
        Deque<File> directories = new ArrayDeque<>();
        directories.push(rootDirectory);
        while (!directories.isEmpty()) {
            File currentDirectory = directories.pop();
            File[] children = currentDirectory.listFiles();
            if (children == null || children.length == 0) {
                continue;
            }
            List<File> filesInDirectory = new ArrayList<>();
            for (File child : children) {
                if (child.isDirectory()) {
                    directories.push(child);
                } else if (child.isFile()) {
                    filesInDirectory.add(child);
                }
            }
            deletedCount += cleanupFilesInDirectory(filesInDirectory);
        }
        return deletedCount;
    }
    private int cleanupFilesInDirectory(List<File> filesInDirectory) {
        if (CollectionUtils.isEmpty(filesInDirectory)) {
            return 0;
        }
        int deletedCount = 0;
        for (int start = 0; start < filesInDirectory.size(); start += FILE_NAME_BATCH_SIZE) {
            int end = Math.min(start + FILE_NAME_BATCH_SIZE, filesInDirectory.size());
            List<File> batchFiles = filesInDirectory.subList(start, end);
            List<String> fileNames = new ArrayList<>(batchFiles.size());
            for (File file : batchFiles) {
                fileNames.add(file.getName());
            }
            Set<String> existingFileNames = new HashSet<>(storageBlobMapper.selectExistingUidFilenames(fileNames));
            for (File file : batchFiles) {
                if (!existingFileNames.contains(file.getName()) && safeDelete(file)) {
                    deletedCount++;
                }
            }
        }
        return deletedCount;
    }
    private void deleteBlobFiles(StorageBlob storageBlob) {
        File originalFile = resolveBlobFile(storageBlob);
        safeDelete(originalFile);
        File compressedFile = resolveCompressedFile(originalFile);
        safeDelete(compressedFile);
    }
    private File resolveBlobFile(StorageBlob storageBlob) {
        String basePath = fileProperties.getPath();
        if (!StringUtils.hasText(storageBlob.getPath())) {
            return new File(basePath, storageBlob.getUidFilename());
        }
        return new File(new File(basePath, storageBlob.getPath()), storageBlob.getUidFilename());
    }
    private File resolveCompressedFile(File originalFile) {
        if (originalFile == null) {
            return null;
        }
        File parent = originalFile.getParentFile();
        if (parent == null) {
            return null;
        }
        return new File(parent, "thumb_" + originalFile.getName());
    }
    private boolean safeDelete(File file) {
        if (file == null || !file.exists() || !file.isFile()) {
            return false;
        }
        if (file.delete()) {
            return true;
        }
        log.warn("删除文件失败:{}", file.getAbsolutePath());
        return false;
    }
}
src/main/java/com/ruoyi/basic/utils/FileUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,847 @@
package com.ruoyi.basic.utils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.StorageAttachmentDTO;
import com.ruoyi.basic.dto.StorageAttachmentVO;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
import com.ruoyi.basic.mapper.StorageBlobMapper;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.basic.pojo.StorageBlob;
import com.ruoyi.common.config.FileProperties;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import net.coobird.thumbnailator.Thumbnails;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.crypto.SecretKey;
import java.io.File;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
public class FileUtil {
    private final FileProperties properties;
    private final StorageAttachmentMapper storageAttachmentMapper;
    private final StringRedisTemplate stringRedisTemplate;
    private final StorageBlobMapper storageBlobMapper;
    private static final String TOKEN_USAGE_KEY_PREFIX = "file:token:usage:";
    private static final DateTimeFormatter YEAR_PATH_FORMATTER = DateTimeFormatter.ofPattern("yyyy");
    private static final DateTimeFormatter MONTH_DAY_PATH_FORMATTER = DateTimeFormatter.ofPattern("MMdd");
    /**
     * ä¿å­˜é™„件信息
     *
     * @param application     æ–‡ä»¶ç”¨é€”
     * @param recordType      å…³è”记录类型
     * @param recordId        å…³è”记录id
     * @param storageBlobDTOS æ–‡ä»¶ä¿¡æ¯
     */
    public void saveStorageAttachment(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, List<StorageBlobDTO> storageBlobDTOS) {
        if (application == null) {
            throw new RuntimeException("文件用途不能为空");
        }
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        if (recordId == null || recordId <= 0) {
            throw new RuntimeException("关联记录id不能为空");
        }
        // åˆ é™¤æ—§é™„件信息
        deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
        if (CollectionUtils.isEmpty(storageBlobDTOS)) {
            return;
        }
        List<StorageAttachment> storageAttachments = new ArrayList<>();
        for (StorageBlobDTO storageBlobDTO : storageBlobDTOS) {
            StorageAttachment storageAttachment = new StorageAttachment();
            storageAttachment.setApplication(application.getType());
            storageAttachment.setRecordType(recordType.getType());
            storageAttachment.setRecordId(recordId);
            storageAttachment.setStorageBlobId(storageBlobDTO.getId());
            storageAttachment.setDeleted(0L);
            storageAttachments.add(storageAttachment);
        }
        storageAttachmentMapper.insert(storageAttachments);
    }
    /**
     * ä¿å­˜é™„件信息
     *
     * @param recordType      å…³è”记录类型
     * @param recordId        å…³è”记录id
     * @param storageBlobDTOS æ–‡ä»¶ä¿¡æ¯
     */
    public void saveStorageAttachmentByRecordTypeAndRecordId(String application,RecordTypeEnum recordType, Long recordId, List<StorageBlobDTO> storageBlobDTOS) {
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        if (recordId == null) {
            throw new RuntimeException("关联记录id不能为空");
        }
        // åˆ é™¤æ—§é™„件信息
        if (application == null) {
            for (StorageBlobDTO storageBlobDTO : storageBlobDTOS) {
                deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.getByType(storageBlobDTO.getApplication()), recordType, recordId);
            }
        } else {
            deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.getByType(application), recordType, recordId);
        }
        if (CollectionUtils.isEmpty(storageBlobDTOS)) {
            deleteStorageAttachmentsByRecordTypeAndRecordId(recordType, recordId);
        }
        List<StorageAttachment> storageAttachments = new ArrayList<>();
        for (StorageBlobDTO storageBlobDTO : storageBlobDTOS) {
            StorageAttachment storageAttachment = new StorageAttachment();
            storageAttachment.setApplication(Objects.requireNonNullElseGet(application, () -> ApplicationTypeEnum.getByType(storageBlobDTO.getApplication()).getType()));
            storageAttachment.setRecordType(recordType.getType());
            storageAttachment.setRecordId(recordId);
            storageAttachment.setStorageBlobId(storageBlobDTO.getId());
            storageAttachment.setDeleted(0L);
            storageAttachments.add(storageAttachment);
        }
        storageAttachmentMapper.insert(storageAttachments);
    }
    /**
     * åˆ é™¤æ–‡ä»¶ä¿¡æ¯
     *
     * @param storageBlobIds æ–‡ä»¶id
     */
    public void deleteStorageBlobs(List<Long> storageBlobIds) {
        storageBlobMapper.deleteByIds(storageBlobIds);
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id删除文件信息
     *
     * @param storageAttachmentIds æ–‡ä»¶id
     */
    public void deleteStorageBlobsByStorageAttachmentIds(List<Long> storageAttachmentIds) {
        List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectByIds(storageAttachmentIds);
        List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
        deleteStorageBlobs(storageBlobIds);
    }
    /**
     * é€šè¿‡æ–‡ä»¶ç”¨é€”、关联记录类型、关联记录id删除文件信息
     *
     * @param application æ–‡ä»¶ç”¨é€”
     * @param recordType  å…³è”记录类型
     * @param recordIds    å…³è”记录id
     */
    public void deleteStorageBlobsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum application, RecordTypeEnum recordType, List<Long> recordIds) {
        if (recordIds == null || recordIds.isEmpty()) {
            throw new RuntimeException("关联记录id不能为空");
        }
        if (application == null) {
            throw new RuntimeException("文件用途不能为空");
        }
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, recordType.getType())
                .in(StorageAttachment::getRecordId, recordIds)
                .eq(StorageAttachment::getApplication, application.getType()));
        if (CollectionUtils.isNotEmpty(storageAttachments)) {
            List<Long> storageAttachmentIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId)
                    .collect(Collectors.toList());
            deleteStorageBlobsByStorageAttachmentIds(storageAttachmentIds);
        }
    }
    /**
     * é€šè¿‡å…³è”记录类型、关联记录id删除文件信息
     *
     * @param recordType  å…³è”记录类型
     * @param recordId    å…³è”记录id
     */
    public void deleteStorageBlobsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) {
        if (recordId == null || recordId <= 0) {
            throw new RuntimeException("关联记录id不能为空");
        }
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, recordType.getType())
                .eq(StorageAttachment::getRecordId, recordId));
        if (CollectionUtils.isNotEmpty(storageAttachments)) {
            List<Long> storageAttachmentIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId)
                    .collect(Collectors.toList());
            deleteStorageBlobsByStorageAttachmentIds(storageAttachmentIds);
        }
    }
    /**
     * åˆ é™¤æ–‡ä»¶å…³è”信息
     *
     * @param storageAttachmentIds æ–‡ä»¶å…³è”id
     */
    public void deleteStorageAttachmentsByStorageAttachmentIds(List<Long> storageAttachmentIds) {
        deleteStorageBlobsByStorageAttachmentIds(storageAttachmentIds);
        storageAttachmentMapper.deleteByIds(storageAttachmentIds);
    }
    /**
     * åˆ é™¤æ–‡ä»¶å…³è”信息
     *
     * @param application æ–‡ä»¶ç”¨é€”
     * @param recordType  å…³è”记录类型
     * @param recordId    å…³è”记录id
     */
    public void deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) {
        if (recordId == null || recordId <= 0) {
            throw new RuntimeException("关联记录id不能为空");
        }
        if (application == null) {
            throw new RuntimeException("文件用途不能为空");
        }
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        deleteStorageBlobsByApplicationAndRecordTypeAndRecordIds(application, recordType, Arrays.asList(recordId));
        storageAttachmentMapper.delete(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, recordType.getType())
                .eq(StorageAttachment::getRecordId, recordId)
                .eq(StorageAttachment::getApplication, application.getType()));
    }
    /**
     * åˆ é™¤æ–‡ä»¶å…³è”信息
     *
     * @param recordType  å…³è”记录类型
     * @param recordId    å…³è”记录id
     */
    public void deleteStorageAttachmentsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) {
        if (recordId == null || recordId <= 0) {
            throw new RuntimeException("关联记录id不能为空");
        }
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        deleteStorageBlobsByRecordTypeAndRecordId(recordType, recordId);
        storageAttachmentMapper.delete(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, recordType.getType())
                .eq(StorageAttachment::getRecordId, recordId));
    }
    /**
     * æ‰¹é‡åˆ é™¤æ–‡ä»¶å…³è”信息 attachment
     *
     * @param application æ–‡ä»¶ç”¨é€”
     * @param recordType  å…³è”记录类型
     * @param recordIds   å…³è”记录id
     */
    public void deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum application, RecordTypeEnum recordType, List<Long> recordIds) {
        if (recordIds == null || recordIds.isEmpty()) {
            throw new RuntimeException("关联记录id不能为空");
        }
        if (application == null) {
            throw new RuntimeException("文件用途不能为空");
        }
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        deleteStorageBlobsByApplicationAndRecordTypeAndRecordIds(application, recordType, recordIds);
        storageAttachmentMapper.delete(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, recordType.getType())
                .in(StorageAttachment::getRecordId, recordIds)
                .eq(StorageAttachment::getApplication, application.getType()));
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件信息 attachment
     *
     * @param storageAttachmentIds æ–‡ä»¶id
     */
    public List<StorageAttachment> getStorageAttachmentsByStorageAttachmentIds(List<Long> storageAttachmentIds) {
        if (CollectionUtils.isEmpty(storageAttachmentIds)) {
            throw new RuntimeException("文件id不能为空");
        }
        return storageAttachmentMapper.selectByIds(storageAttachmentIds);
    }
    /**
     * é€šè¿‡è®°å½•类型获取文件信息 attachment(分页)
     *
     * @param storageAttachmentDTO å…³è”记录信息
     */
    public List<StorageBlobVO> getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(StorageAttachmentDTO storageAttachmentDTO) {
        LambdaQueryWrapper<StorageAttachment> queryWrapper = new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, storageAttachmentDTO.getRecordType())
                .eq(StorageAttachment::getRecordId, storageAttachmentDTO.getRecordId());
        if (storageAttachmentDTO.getApplication() != null) {
            queryWrapper.eq(StorageAttachment::getApplication, storageAttachmentDTO.getApplication());
        }
        List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectList(queryWrapper);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return null;
        }
        return getStorageBlobVOsByStorageAttachmentIds(storageAttachments.stream().map(StorageAttachment::getId).collect(Collectors.toList()));
    }
    /**
     * é€šè¿‡æ–‡ä»¶ç”¨é€”、关联记录类型、关联记录id获取文件关联信息 attachment
     *
     * @param application æ–‡ä»¶ç”¨é€”
     * @param recordType  å…³è”记录类型
     * @param recordId    å…³è”记录id
     */
    public List<StorageAttachment> getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) {
        if (recordId == null || recordId <= 0) {
            throw new RuntimeException("关联记录id不能为空");
        }        if (application == null) {
            throw new RuntimeException("文件用途不能为空");
        }
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        return storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, recordType.getType())
                .eq(StorageAttachment::getRecordId, recordId)
                .eq(StorageAttachment::getApplication, application.getType()));
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件信息 blob
     *
     * @param storageAttachmentIds æ–‡ä»¶id
     */
    public List<StorageBlobVO> getStorageBlobVOsByStorageAttachmentIds(List<Long> storageAttachmentIds) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return null;
        }
        Map<Long, Long> blobIdToAttachmentIdMap = storageAttachments.stream()
                .collect(Collectors.toMap(StorageAttachment::getStorageBlobId, StorageAttachment::getId));
        List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
        List<StorageBlob> storageBlobs = storageBlobMapper.selectByIds(storageBlobIds);
        List<StorageBlobVO> storageBlobDTOS = new ArrayList<>();
        for (StorageBlob storageBlob : storageBlobs) {
            StorageBlobVO storageBlobVO = new StorageBlobVO();
            BeanUtils.copyProperties(storageBlob, storageBlobVO);
            storageBlobVO.setPreviewURL(buildSignedPreviewUrl(storageBlobVO));
            storageBlobVO.setDownloadURL(buildSignedDownloadUrl(storageBlobVO));
            storageBlobVO.setStorageAttachmentId(blobIdToAttachmentIdMap.get(storageBlob.getId()));
            storageBlobDTOS.add(storageBlobVO);
        }
        return storageBlobDTOS;
    }
    /**
     * é€šè¿‡æ–‡ä»¶ç”¨é€”、关联记录类型、关联记录id获取文件关联信息 attachment
     *
     * @param recordType  å…³è”记录类型
     * @param recordId    å…³è”记录id
     */
    public List<StorageAttachment> getStorageAttachmentsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) {
        if (recordId == null || recordId <= 0) {
            throw new RuntimeException("关联记录id不能为空");
        }
        if (recordType == null) {
            throw new RuntimeException("关联记录类型不能为空");
        }
        return storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
                .eq(StorageAttachment::getRecordType, recordType.getType())
                .eq(StorageAttachment::getRecordId, recordId));
    }
    /**
     * é€šè¿‡æ–‡ä»¶ç”¨é€”、关联记录类型、关联记录id获取文件信息 blob
     *
     * @param application æ–‡ä»¶ç”¨é€”
     * @param recordType  å…³è”记录类型
     * @param recordId    å…³è”记录id
     */
    public List<StorageBlobVO> getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return null;
        }
        // æž„建 storageBlobId -> storageAttachmentId çš„æ˜ å°„
        Map<Long, Long> blobIdToAttachmentIdMap = storageAttachments.stream()
                .collect(Collectors.toMap(StorageAttachment::getStorageBlobId, StorageAttachment::getId));
        List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
        List<StorageBlob> storageBlobs = storageBlobMapper.selectByIds(storageBlobIds);
        List<StorageBlobVO> storageBlobDTOS = new ArrayList<>();
        for (StorageBlob storageBlob : storageBlobs) {
            StorageBlobVO storageBlobVO = new StorageBlobVO();
            BeanUtils.copyProperties(storageBlob, storageBlobVO);
            storageBlobVO.setPreviewURL(buildSignedPreviewUrl(storageBlobVO));
            storageBlobVO.setDownloadURL(buildSignedDownloadUrl(storageBlobVO));
            storageBlobVO.setStorageAttachmentId(blobIdToAttachmentIdMap.get(storageBlob.getId()));
            storageBlobDTOS.add(storageBlobVO);
        }
        return storageBlobDTOS;
    }
    /**
     * é€šè¿‡æ–‡ä»¶ç”¨é€”、关联记录类型、关联记录id获取文件信息 blob
     *
     * @param recordType  å…³è”记录类型
     * @param recordId    å…³è”记录id
     */
    public List<StorageBlobVO> getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum recordType, Long recordId) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByRecordTypeAndRecordId(recordType, recordId);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return null;
        }
        List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
        List<StorageBlob> storageBlobs = storageBlobMapper.selectByIds(storageBlobIds);
        List<StorageBlobVO> storageBlobDTOS = new ArrayList<>();
        for (StorageBlob storageBlob : storageBlobs) {
            StorageBlobVO storageBlobVO = new StorageBlobVO();
            BeanUtils.copyProperties(storageBlob, storageBlobVO);
            storageBlobVO.setPreviewURL(buildSignedPreviewUrl(storageBlobVO));
            storageBlobVO.setDownloadURL(buildSignedDownloadUrl(storageBlobVO));
            storageBlobDTOS.add(storageBlobVO);
        }
        return storageBlobDTOS;
    }
    /**
     * é€šè¿‡æ–‡ä»¶ç”¨é€”、关联记录类型、关联记录id获取文件信息 è‡ªå®šä¹‰è¿‡æœŸæ—¶é—´ï¼ˆåˆ†é’Ÿï¼‰ blob
     *
     * @param application æ–‡ä»¶ç”¨é€”
     * @param recordType  å…³è”记录类型
     * @param recordId    å…³è”记录id
     * @param expired     è¿‡æœŸæ—¶é—´
     */
    public List<StorageBlobVO> getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return null;
        }
        // æž„建 storageBlobId -> storageAttachmentId çš„æ˜ å°„
        Map<Long, Long> blobIdToAttachmentIdMap = storageAttachments.stream()
                .collect(Collectors.toMap(StorageAttachment::getStorageBlobId, StorageAttachment::getId));
        List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
        List<StorageBlob> storageBlobs = storageBlobMapper.selectByIds(storageBlobIds);
        List<StorageBlobVO> storageBlobDTOS = new ArrayList<>();
        for (StorageBlob storageBlob : storageBlobs) {
            StorageBlobVO storageBlobVO = new StorageBlobVO();
            BeanUtils.copyProperties(storageBlob, storageBlobVO);
            storageBlobVO.setPreviewURL(buildSignedUrl(storageBlobVO, "/preview/", expired));
            storageBlobVO.setDownloadURL(buildSignedUrl(storageBlobVO, "/download/", expired));
            storageBlobVO.setStorageAttachmentId(blobIdToAttachmentIdMap.get(storageBlob.getId()));
            storageBlobDTOS.add(storageBlobVO);
        }
        return storageBlobDTOS;
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件信息存在过期时间 è‡ªå®šä¹‰è¿‡æœŸæ—¶é—´ï¼ˆåˆ†é’Ÿï¼‰ blob
     *
     * @param storageAttachmentIds æ–‡ä»¶id
     * @param expired              è¿‡æœŸæ—¶é—´
     */
    public List<StorageBlobVO> getStorageBlobVOsByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return null;
        }
        List<Long> storageBlobIds = storageAttachments.stream().map(StorageAttachment::getStorageBlobId).collect(Collectors.toList());
        List<StorageBlob> storageBlobs = storageBlobMapper.selectByIds(storageBlobIds);
        List<StorageBlobVO> storageBlobDTOS = new ArrayList<>();
        for (StorageBlob storageBlob : storageBlobs) {
            StorageBlobVO storageBlobVO = new StorageBlobVO();
            BeanUtils.copyProperties(storageBlob, storageBlobVO);
            storageBlobVO.setPreviewURL(buildSignedUrl(storageBlobVO, "/preview/", expired));
            storageBlobVO.setDownloadURL(buildSignedUrl(storageBlobVO, "/download/", expired));
            storageBlobDTOS.add(storageBlobVO);
        }
        return storageBlobDTOS;
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件信息 attachment
     *
     * @param storageAttachmentIds æ–‡ä»¶id
     */
    public List<StorageAttachmentVO> getStorageAttachmentVOSByStorageAttachmentIds(List<Long> storageAttachmentIds) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return new ArrayList<>();
        }
        List<StorageAttachmentVO> storageAttachmentVOS = new ArrayList<>();
        for (StorageAttachment storageAttachment : storageAttachments) {
            StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO();
            BeanUtils.copyProperties(storageAttachment, storageAttachmentVO);
            List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(Collections.singletonList(storageAttachment.getId()));
            if (CollectionUtils.isEmpty(storageBlobVOS)) {
                storageAttachmentVO.setStorageBlobVOS(new ArrayList<>());
            } else {
                storageAttachmentVO.setStorageBlobVOS(storageBlobVOS);
            }
            storageAttachmentVOS.add(storageAttachmentVO);
        }
        return storageAttachmentVOS;
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件信息存在过期时间 è‡ªå®šä¹‰è¿‡æœŸæ—¶é—´ï¼ˆåˆ†é’Ÿï¼‰ attachment
     *
     * @param storageAttachmentIds æ–‡ä»¶id
     * @param expired              è¿‡æœŸæ—¶é—´
     */
    public List<StorageAttachmentVO> getStorageAttachmentVOSByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByStorageAttachmentIds(storageAttachmentIds);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return new ArrayList<>();
        }
        List<StorageAttachmentVO> storageAttachmentVOS = new ArrayList<>();
        for (StorageAttachment storageAttachment : storageAttachments) {
            StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO();
            BeanUtils.copyProperties(storageAttachment, storageAttachmentVO);
            List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(Collections.singletonList(storageAttachment.getId()), expired);
            if (CollectionUtils.isEmpty(storageBlobVOS)) {
                storageAttachmentVO.setStorageBlobVOS(new ArrayList<>());
            } else {
                storageAttachmentVO.setStorageBlobVOS(storageBlobVOS);
            }
            storageAttachmentVOS.add(storageAttachmentVO);
        }
        return storageAttachmentVOS;
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件信息 attachment
     *
     * @param application åº”用
     * @param recordType  è®°å½•类型
     * @param recordId    è®°å½•id
     */
    public List<StorageAttachmentVO> getStorageAttachmentVOSByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return new ArrayList<>();
        }
        List<StorageAttachmentVO> storageAttachmentVOS = new ArrayList<>();
        for (StorageAttachment storageAttachment : storageAttachments) {
            StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO();
            BeanUtils.copyProperties(storageAttachment, storageAttachmentVO);
            List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(Collections.singletonList(storageAttachment.getId()));
            if (CollectionUtils.isEmpty(storageBlobVOS)) {
                storageAttachmentVO.setStorageBlobVOS(new ArrayList<>());
            } else {
                storageAttachmentVO.setStorageBlobVOS(storageBlobVOS);
            }
            storageAttachmentVOS.add(storageAttachmentVO);
        }
        return storageAttachmentVOS;
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件信息存在过期时间 è‡ªå®šä¹‰è¿‡æœŸæ—¶é—´ï¼ˆåˆ†é’Ÿï¼‰ attachment
     *
     * @param application åº”用
     * @param recordType  è®°å½•类型
     * @param recordId    è®°å½•id
     * @param expired     è¿‡æœŸæ—¶é—´
     */
    public List<StorageAttachmentVO> getStorageAttachmentVOSByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return new ArrayList<>();
        }
        List<StorageAttachmentVO> storageAttachmentVOS = new ArrayList<>();
        for (StorageAttachment storageAttachment : storageAttachments) {
            StorageAttachmentVO storageAttachmentVO = new StorageAttachmentVO();
            BeanUtils.copyProperties(storageAttachment, storageAttachmentVO);
            List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(Collections.singletonList(storageAttachment.getId()), expired);
            if (CollectionUtils.isEmpty(storageBlobVOS)) {
                storageAttachmentVO.setStorageBlobVOS(new ArrayList<>());
            } else {
                storageAttachmentVO.setStorageBlobVOS(storageBlobVOS);
            }
            storageAttachmentVOS.add(storageAttachmentVO);
        }
        return storageAttachmentVOS;
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件预览地址
     *
     * @param storageAttachmentIds æ–‡ä»¶å…³è”id
     */
    public List<String> getFilePreviewURLByStorageAttachmentIds(List<Long> storageAttachmentIds) {
        List<String> res = new ArrayList<>();
        List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds);
        for (StorageBlobVO storageBlobVO : storageBlobVOS) {
            res.add(buildSignedPreviewUrl(storageBlobVO));
        }
        return res;
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件预览地址存在过期时间 è‡ªå®šä¹‰è¿‡æœŸæ—¶é—´ï¼ˆåˆ†é’Ÿï¼‰
     *
     * @param storageAttachmentIds æ–‡ä»¶å…³è”id
     * @param expired              è¿‡æœŸæ—¶é—´
     */
    public List<String> getFilePreviewURLByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired) {
        List<String> res = new ArrayList<>();
        List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds);
        for (StorageBlobVO storageBlobVO : storageBlobVOS) {
            res.add(buildSignedUrl(storageBlobVO, "/preview/", expired));
        }
        return res;
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件下载地址
     *
     * @param storageAttachmentIds æ–‡ä»¶å…³è”id
     */
    public List<String> getFileDownloadURLByStorageAttachmentIds(List<Long> storageAttachmentIds) {
        List<String> res = new ArrayList<>();
        List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds);
        for (StorageBlobVO storageBlobVO : storageBlobVOS) {
            res.add(buildSignedDownloadUrl(storageBlobVO));
        }
        return res;
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件下载地址存在过期时间 è‡ªå®šä¹‰è¿‡æœŸæ—¶é—´ï¼ˆåˆ†é’Ÿï¼‰
     *
     * @param storageAttachmentIds æ–‡ä»¶å…³è”id
     * @param expired              è¿‡æœŸæ—¶é—´
     */
    public List<String> getFileDownloadURLByStorageAttachmentIds(List<Long> storageAttachmentIds, BigDecimal expired) {
        List<String> res = new ArrayList<>();
        List<StorageBlobVO> storageBlobVOS = getStorageBlobVOsByStorageAttachmentIds(storageAttachmentIds);
        for (StorageBlobVO storageBlobVO : storageBlobVOS) {
            res.add(buildSignedUrl(storageBlobVO, "/download/", expired));
        }
        return res;
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件预览地址
     *
     * @param application åº”用
     * @param recordType  è®°å½•类型
     * @param recordId    è®°å½•id
     */
    public List<String> getFilePreviewURLByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return new ArrayList<>();
        }
        return getFilePreviewURLByStorageAttachmentIds(storageAttachments.stream().map(StorageAttachment::getId).collect(Collectors.toList()));
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件预览地址存在过期时间
     *
     * @param application åº”用
     * @param recordType  è®°å½•类型
     * @param recordId    è®°å½•id
     * @param expired     è¿‡æœŸæ—¶é—´ï¼ˆåˆ†é’Ÿï¼‰
     */
    public List<String> getFilePreviewURLByApplicationAndRecordTypeAndRecordIdAndExpired(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return new ArrayList<>();
        }
        return getFilePreviewURLByStorageAttachmentIds(storageAttachments.stream().map(StorageAttachment::getId).collect(Collectors.toList()), expired);
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件下载地址
     *
     * @param application åº”用
     * @param recordType  è®°å½•类型
     * @param recordId    è®°å½•id
     */
    public List<String> getFileDownloadURLByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return new ArrayList<>();
        }
        return getFileDownloadURLByStorageAttachmentIds(storageAttachments.stream().map(StorageAttachment::getId).collect(Collectors.toList()));
    }
    /**
     * é€šè¿‡æ–‡ä»¶å…³è”id获取文件下载地址存在过期时间
     *
     * @param application åº”用
     * @param recordType  è®°å½•类型
     * @param recordId    è®°å½•id
     * @param expired     è¿‡æœŸæ—¶é—´ï¼ˆåˆ†é’Ÿï¼‰
     */
    public List<String> getFileDownloadURLByApplicationAndRecordTypeAndRecordIdAndExpired(ApplicationTypeEnum application, RecordTypeEnum recordType, Long recordId, BigDecimal expired) {
        List<StorageAttachment> storageAttachments = getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(application, recordType, recordId);
        if (CollectionUtils.isEmpty(storageAttachments)) {
            return new ArrayList<>();
        }
        return getFileDownloadURLByStorageAttachmentIds(storageAttachments.stream().map(StorageAttachment::getId).collect(Collectors.toList()), expired);
    }
    public String buildSignedPreviewUrl(StorageBlobVO storageBlob) {
        return buildSignedUrl(storageBlob, "/preview/", properties.getExpired());
    }
    public String buildSignedDownloadUrl(StorageBlobVO storageBlob) {
        return buildSignedUrl(storageBlob, "/download/", properties.getExpired());
    }
    /**
     * æž„建带签名的URL
     *
     * @param storageBlob æ–‡ä»¶å…ƒæ•°æ®
     * @param actionPath  æ“ä½œè·¯å¾„ "/preview/" or "/download/"
     * @param expired     è¿‡æœŸæ—¶é—´ å¦‚果不配置,不传参,将使用默认值120分钟
     * @return å¸¦ç­¾åçš„URL
     */
    public String buildSignedUrl(StorageBlobVO storageBlob, String actionPath, BigDecimal expired) {
        if (!Arrays.asList("/preview/", "/download/").contains(actionPath)) {
            throw new IllegalArgumentException("操作路径参数错误");
        }
        if (storageBlob == null || !StringUtils.hasText(storageBlob.getUidFilename())) {
            throw new IllegalArgumentException("文件信息不完整");
        }
        String domain = StringUtils.trimTrailingCharacter(properties.getDomain(), '/');
        String prefix = properties.getUrlPrefix().startsWith("/") ? properties.getUrlPrefix() : "/" + properties.getUrlPrefix();
        String normalizedActionPath = StringUtils.hasText(actionPath) ? actionPath : "/preview/";
        if (!normalizedActionPath.startsWith("/")) {
            normalizedActionPath = "/" + normalizedActionPath;
        }
        if (!normalizedActionPath.endsWith("/")) {
            normalizedActionPath = normalizedActionPath + "/";
        }
        String baseUrl = domain + prefix + normalizedActionPath + storageBlob.getUidFilename();
        // -1 è¡¨ç¤ºæ°¸ä¹…有效,不生成 token,改为 publicKey ç»„合校验
        if (expired != null && BigDecimal.valueOf(-1L).compareTo(expired) == 0) {
            if (!StringUtils.hasText(storageBlob.getResourceKey())) {
                throw new IllegalArgumentException("公开链接缺少publicKey");
            }
            return baseUrl + "?publicKey=" + storageBlob.getResourceKey();
        }
        long now = System.currentTimeMillis();
        BigDecimal expiredValue = expired == null ? new BigDecimal("120") : expired;
        long expiredMillis = expiredValue.multiply(new BigDecimal("60000")).longValue();
        if (expiredMillis <= 0L) {
            expiredMillis = 2L * 60L * 60L * 1000L;
        }
        Date issuedAt = new Date(now);
        Date expiration = new Date(now + expiredMillis);
        SecretKey key = Keys.hmacShaKeyFor(properties.getJwtSecret().getBytes(StandardCharsets.UTF_8));
        String token = Jwts.builder()
                .subject(storageBlob.getUidFilename())
                .issuedAt(issuedAt)       // æ–°ç‰ˆå»ºè®®ç›´æŽ¥è°ƒç”¨ .issuedAt()
                .expiration(expiration)   // æ–°ç‰ˆå»ºè®®ç›´æŽ¥è°ƒç”¨ .expiration()
                .claim("path", storageBlob.getPath())
                .claim("resourceKey", storageBlob.getResourceKey())
                .signWith(key)            // é‡ç‚¹ï¼šä¼ å…¥ä¸Šé¢ç”Ÿæˆçš„ key å¯¹è±¡ï¼Œè€Œä¸æ˜¯ String
                .compact();
        cacheTokenUsage(token, expiredMillis);
        return baseUrl + "?token=" + token;
    }
    private void cacheTokenUsage(String token, long expiredMillis) {
        if (!StringUtils.hasText(token)) {
            return;
        }
        long ttl = expiredMillis > 0L ? expiredMillis : 2L * 60L * 60L * 1000L;
        stringRedisTemplate.opsForValue().set(buildTokenUsageKey(token), "0", ttl, TimeUnit.MILLISECONDS);
    }
    private String buildTokenUsageKey(String token) {
        return TOKEN_USAGE_KEY_PREFIX + token;
    }
    public String buildRelativePath() {
        LocalDate now = LocalDate.now();
        return now.format(YEAR_PATH_FORMATTER) + "/" + now.format(MONTH_DAY_PATH_FORMATTER);
    }
    public void validateTokenUsage(String token) {
        String redisKey = buildTokenUsageKey(token);
        String currentCountValue = stringRedisTemplate.opsForValue().get(redisKey);
        if (!StringUtils.hasText(currentCountValue)) {
            throw new IllegalArgumentException("链接已过期或达到使用次数失效");
        }
        long currentCount = Long.parseLong(currentCountValue);
        int limit = resolveLimit();
        if (currentCount >= limit) {
            stringRedisTemplate.delete(redisKey);
            throw new IllegalArgumentException("链接达到使用次数失效");
        }
        Long updatedCount = stringRedisTemplate.opsForValue().increment(redisKey);
        if (updatedCount != null && updatedCount >= limit) {
            stringRedisTemplate.delete(redisKey);
        }
    }
    private int resolveLimit() {
        return properties.getUseLimit() == null || properties.getUseLimit() <= 0 ? 10 : properties.getUseLimit();
    }
    /**
     * åŽ‹ç¼©æ–‡ä»¶ å›¾ç‰‡
     *
     * @param file æ–‡ä»¶
     * @return åŽ‹ç¼©åŽçš„æ–‡ä»¶
     */
    public File compressFile(File file) {
        if (properties.getCompress() && isImage(file.getName()) && (file.length() > properties.getNeedCompressSize().toBytes())) {
            try {
                // åˆ›å»ºä¸€ä¸ªä¸´æ—¶æ–‡ä»¶å­˜æ”¾åŽ‹ç¼©åŽçš„å›¾ç‰‡ï¼Œé¿å…ç ´ååŽŸå›¾
                File compressedFile = new File(file.getParent(), "thumb_" + file.getName());
                // 1. å¦‚果已经存在压缩过的文件,直接返回,不再消耗 CPU åŽ‹ç¼©
                if (compressedFile.exists()) {
                    return compressedFile;
                }
                // ä½¿ç”¨ Thumbnailator è¿›è¡ŒåŽ‹ç¼©
                Thumbnails.of(file)
                        .scale(1.0f)                // ä¿æŒåŽŸå°ºå¯¸
                        .outputQuality(properties.getCompressQuality())        // æ ¸å¿ƒï¼šè®¾ç½®ç”»è´¨ (0.0~1.0)
                        .toFile(compressedFile);
                return compressedFile;
            } catch (Exception e) {
                // å¦‚果压缩失败,降级处理:返回原图
                return file;
            }
        }
        return file;
    }
    // ç®€å•的后缀判断
    private boolean isImage(String fileName) {
        String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        return "jpg".equals(ext) || "jpeg".equals(ext) || "png".equals(ext);
    }
}
src/main/java/com/ruoyi/basic/vo/CustomerVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.basic.vo;
import com.ruoyi.basic.dto.CustomerFollowUpDto;
import com.ruoyi.basic.pojo.Customer;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
public class CustomerVo extends Customer {
    @ApiModelProperty(value = "跟进记录")
    private List<CustomerFollowUpDto> followUpList;
    private String usageUserName;
    private String togetherUserNames;
    /**
     * å…±äº«ç”¨æˆ·ID列表
     */
    private List<Long> userIds;
    /**
     * å…±äº«ç”¨æˆ·ID字符串(SQL查询返回,用于转换为List)
     */
    private String userIdsStr;
}
src/main/java/com/ruoyi/basic/vo/ProductModelVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.ruoyi.basic.vo;
import com.ruoyi.basic.pojo.ProductModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
public class ProductModelVo extends ProductModel {
    private List<String> batchNoList;
}
src/main/java/com/ruoyi/collaborativeApproval/controller/DutyPlanController.java
@@ -8,47 +8,46 @@
import com.ruoyi.collaborativeApproval.service.DutyPlanService;
import com.ruoyi.common.utils.excel.ExcelUtils;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@RequestMapping("/dutyPlan")
@AllArgsConstructor
public class DutyPlanController {
    @Autowired
    private DutyPlanService dutyPlanService;
    @GetMapping("/getList")
    @ApiOperation("分页查询")
    @Operation(summary = "分页查询")
    public AjaxResult listPage(Page page, DutyPlanDTO dutyPlanDTO){
        return AjaxResult.success(dutyPlanService.listPage(page, dutyPlanDTO));
    }
    @GetMapping("/getNum")
    @ApiOperation("获取等级数据")
    @Operation(summary = "获取等级数据")
    public AjaxResult getNum(){
        return AjaxResult.success(dutyPlanService.getNum());
    }
    @PostMapping("/add")
    @ApiOperation("新增")
    @Operation(summary = "新增")
    public AjaxResult add(@RequestBody DutyPlan dutyPlan){
        return AjaxResult.success(dutyPlanService.save(dutyPlan));
    }
    @PostMapping("/update")
    @ApiOperation("修改")
    @Operation(summary = "修改")
    public AjaxResult update(@RequestBody DutyPlan dutyPlan){
        return AjaxResult.success(dutyPlanService.updateById(dutyPlan));
    }
    @DeleteMapping("/delete")
    @ApiOperation("删除")
    @Operation(summary = "删除")
    public AjaxResult delete(@RequestBody List<Long> ids){
        if (CollectionUtils.isEmpty(ids)) {
            throw new RuntimeException("请传入要删除的ID");
@@ -56,7 +55,7 @@
        return AjaxResult.success(dutyPlanService.removeBatchByIds(ids));
    }
    @PostMapping("/export")
    @ApiOperation("导出")
    @Operation(summary = "导出")
    public void exportData(HttpServletResponse response, DutyPlanDTO dutyPlanDTO){
        dutyPlanService.exportData(response, dutyPlanDTO);
    }
src/main/java/com/ruoyi/collaborativeApproval/controller/MeetingController.java
@@ -14,12 +14,12 @@
import com.ruoyi.collaborativeApproval.vo.SearchMeetingUseVo;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
@@ -32,7 +32,7 @@
@RestController
@RequestMapping("/meeting")
@RequiredArgsConstructor
@Api(tags = "会议")
@Tag(name = "会议")
public class MeetingController {
    private final MeetingService meetingService;
@@ -126,7 +126,7 @@
        return R.ok(meetingService.getMeetSummaryItems());
    }
    @ApiOperation(value = "会议室设置导出")
    @Operation(summary = "会议室设置导出")
    @PostMapping("/export")
    public void export(HttpServletResponse response) {
        List<MeetingRoom> accountExpenses = meetingService.list();
@@ -136,7 +136,7 @@
    private final MeetDraftMapper meetDraftMapper;
    @ApiOperation(value = "会议草稿导出")
    @Operation(summary = "会议草稿导出")
    @PostMapping("/exportOne")
    public void exportOne(HttpServletResponse response) {
        List<MeetDraft> accountExpenses = meetDraftMapper.selectList(new LambdaQueryWrapper<MeetDraft>());
src/main/java/com/ruoyi/collaborativeApproval/controller/NoticeController.java
@@ -11,8 +11,8 @@
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.service.ISysNoticeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
@@ -22,7 +22,7 @@
@RestController
@AllArgsConstructor
@Api(tags = "通知公告")
@Tag(name = "通知公告")
@RequestMapping("/collaborativeApproval/notice")
public class NoticeController extends BaseController {
@@ -31,14 +31,14 @@
    @GetMapping("/page")
    @Log(title = "分页查询", businessType = BusinessType.OTHER)
    @ApiOperation("分页查询")
    @Operation(summary = "分页查询")
    public AjaxResult listPage(Page page, NoticeDTO noticeDTO){
        return AjaxResult.success(noticeService.listPage(page, noticeDTO));
    }
    @PostMapping("/add")
    @Log(title = "新增", businessType = BusinessType.INSERT)
    @ApiOperation("新增")
    @Operation(summary = "新增")
    public AjaxResult add(@RequestBody NoticeDTO noticeDTO){
        if (noticeDTO.getStatus()==1){
            //正式发布通知所有人的消息通知
@@ -51,7 +51,7 @@
    @PutMapping("/update")
    @Log(title = "修改", businessType = BusinessType.UPDATE)
    @ApiOperation("修改")
    @Operation(summary = "修改")
    public AjaxResult update(@RequestBody NoticeDTO noticeDTO){
        if (ObjectUtils.isNotNull(noticeDTO.getStatus()) && noticeDTO.getStatus()==1){
            Notice notice = noticeService.getById(noticeDTO.getId());
@@ -65,7 +65,7 @@
    @DeleteMapping("/{ids}")
    @Log(title = "删除", businessType = BusinessType.DELETE)
    @ApiOperation("删除")
    @Operation(summary = "删除")
    public AjaxResult delete(@PathVariable("ids") List<Long> ids){
        if (CollectionUtils.isEmpty(ids)) {
            throw new RuntimeException("请传入要删除的ID");
@@ -75,7 +75,7 @@
    @GetMapping("/count")
    @Log(title = "获取公告数量", businessType = BusinessType.OTHER)
    @ApiOperation("获取公告数量")
    @Operation(summary = "获取公告数量")
    public AjaxResult count(){
        return AjaxResult.success(noticeService.selectCount());
    }
src/main/java/com/ruoyi/collaborativeApproval/controller/NoticeTypeController.java
@@ -6,6 +6,7 @@
import com.ruoyi.collaborativeApproval.pojo.NoticeType;
import com.ruoyi.collaborativeApproval.service.NoticeTypeService;
import com.ruoyi.framework.web.domain.AjaxResult;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
@@ -22,9 +23,9 @@
 */
@RestController
@RequestMapping("/noticeType")
@AllArgsConstructor
public class NoticeTypeController {
    @Autowired
    private NoticeTypeService noticeTypeService;
    /**
src/main/java/com/ruoyi/collaborativeApproval/controller/RulesRegulationsManagementController.java
@@ -9,47 +9,45 @@
import com.ruoyi.collaborativeApproval.service.RulesRegulationsManagementService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@RequestMapping("/rulesRegulationsManagement")
@AllArgsConstructor
@Api(tags = "制度管理")
@Tag(name = "制度管理")
public class RulesRegulationsManagementController {
    @Autowired
    private RulesRegulationsManagementService rulesRegulationsManagementService;
    @Autowired
    private ReadingStatusMapper readingStatusMapper;
    @GetMapping("/getList")
    @ApiOperation("分页查询")
    @Operation(summary = "分页查询")
    public AjaxResult listPage(Page page, RulesRegulationsManagement rulesRegulationsManagement){
        return AjaxResult.success(rulesRegulationsManagementService.listPage(page, rulesRegulationsManagement));
    }
    @PostMapping("/add")
    @ApiOperation("新增")
    @Operation(summary = "新增")
    public AjaxResult add(@RequestBody RulesRegulationsManagement rulesRegulationsManagement){
        rulesRegulationsManagementService.save(rulesRegulationsManagement);
        return AjaxResult.success(rulesRegulationsManagement.getId());
    }
    @PostMapping("/update")
    @ApiOperation("修改")
    @Operation(summary = "修改")
    public AjaxResult update(@RequestBody RulesRegulationsManagement rulesRegulationsManagement){
        return AjaxResult.success(rulesRegulationsManagementService.updateById(rulesRegulationsManagement));
    }
    @DeleteMapping("/delete")
    @ApiOperation("删除")
    @Operation(summary = "删除")
    public AjaxResult delete(@PathVariable("ids") List<Long> ids){
        if (CollectionUtils.isEmpty(ids)) {
            throw new RuntimeException("请传入要删除的ID");
@@ -58,27 +56,27 @@
    }
    //规则查看时新增阅读状态
    @PostMapping("/addReadingStatus")
    @ApiOperation("新增阅读状态")
    @Operation(summary = "新增阅读状态")
    public AjaxResult addReadingStatus(@RequestBody ReadingStatus readingStatus){
        return AjaxResult.success(readingStatusMapper.insert(readingStatus));
    }
    @PostMapping("/updateReadingStatus")
    @ApiOperation("修改阅读状态")
    @Operation(summary = "修改阅读状态")
    public AjaxResult updateReadingStatus(@RequestBody ReadingStatus readingStatus){
        return AjaxResult.success(readingStatusMapper.updateById(readingStatus));
    }
    @GetMapping("/getReadingStatusList")
    @ApiOperation("分页查询阅读状态")
    @Operation(summary = "分页查询阅读状态")
    public AjaxResult listPage(Page page, ReadingStatus readingStatus){
        return AjaxResult.success(readingStatusMapper.selectPage(page,new QueryWrapper<ReadingStatus>(readingStatus)));
    }
    @GetMapping("/getReadingStatusByRuleId/{ruleId}")
    @ApiOperation("根据制度id查询阅读状态")
    @Operation(summary = "根据制度id查询阅读状态")
    public AjaxResult getReadingStatusByRuleId(@PathVariable Long ruleId){
        return AjaxResult.success(readingStatusMapper.selectList(new QueryWrapper<ReadingStatus>().eq("rule_id", ruleId)));
    }
    @ApiOperation(value = "规章制度管理导出")
    @Operation(summary = "规章制度管理导出")
    @PostMapping("/export")
    public void export(HttpServletResponse response) {
        List<RulesRegulationsManagement> accountExpenses = rulesRegulationsManagementService.list();
src/main/java/com/ruoyi/collaborativeApproval/controller/RulesRegulationsManagementFileController.java
@@ -9,7 +9,7 @@
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import java.util.List;
/**
src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java
@@ -9,36 +9,33 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.service.ISysNoticeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
@AllArgsConstructor
@RestController
@RequestMapping("/sealApplicationManagement")
@Api(tags = "用印申请管理")
@Tag(name = "用印申请管理")
public class SealApplicationManagementController {
    @Autowired
    private SealApplicationManagementService sealApplicationManagementService;
    @Autowired
    private ISysNoticeService sysNoticeService;
    @GetMapping("/getList")
    @ApiOperation("分页查询")
    @Operation(summary = "分页查询")
    public AjaxResult listPage(Page page, SealApplicationManagement sealApplicationManagement){
        return AjaxResult.success(sealApplicationManagementService.listPage(page,sealApplicationManagement));
    }
    @PostMapping("/add")
    @ApiOperation("新增")
    @Operation(summary = "新增")
    public AjaxResult add(@RequestBody SealApplicationManagement sealApplicationManagement){
        sealApplicationManagementService.save(sealApplicationManagement);
        //消息通知
@@ -51,13 +48,13 @@
    }
    @PostMapping("/update")
    @ApiOperation("修改")
    @Operation(summary = "修改")
    public AjaxResult update(@RequestBody SealApplicationManagement sealApplicationManagement){
        return AjaxResult.success(sealApplicationManagementService.updateById(sealApplicationManagement));
    }
    @DeleteMapping("/delete")
    @ApiOperation("删除")
    @Operation(summary = "删除")
    public AjaxResult delete(@PathVariable("ids") List<Long> ids){
        if (CollectionUtils.isEmpty(ids)) {
            throw new RuntimeException("请传入要删除的ID");
@@ -65,7 +62,7 @@
        return AjaxResult.success(sealApplicationManagementService.removeBatchByIds(ids));
    }
    @ApiOperation(value = "用印申请管理导出")
    @Operation(summary = "用印申请管理导出")
    @PostMapping("/export")
    public void export(HttpServletResponse response) {
        List<SealApplicationManagement> accountExpenses = sealApplicationManagementService.list();
src/main/java/com/ruoyi/collaborativeApproval/controller/StaffContactsPersonalController.java
@@ -5,35 +5,31 @@
import com.ruoyi.collaborativeApproval.pojo.StaffContactsPersonal;
import com.ruoyi.collaborativeApproval.service.StaffContactsPersonalService;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/staffContactsPersonal")
@AllArgsConstructor
public class StaffContactsPersonalController {
    @Autowired
    private StaffContactsPersonalService staffContactsPersonalService;
    @GetMapping("/getList")
    @ApiOperation("分页查询")
    public AjaxResult listPage(Page page, StaffContactsPersonalDTO staffContactsPersonalDTO ){
    @Operation(summary = "分页查询")
    public AjaxResult listPage(Page page, StaffContactsPersonalDTO staffContactsPersonalDTO) {
        return AjaxResult.success(staffContactsPersonalService.listPage(page, staffContactsPersonalDTO));
    }
    @PostMapping("/add")
    @ApiOperation("新增")
    public AjaxResult add(@RequestBody StaffContactsPersonal staffContactsPersonal){
    @Operation(summary = "新增")
    public AjaxResult add(@RequestBody StaffContactsPersonal staffContactsPersonal) {
        return AjaxResult.success(staffContactsPersonalService.save(staffContactsPersonal));
    }
    @DeleteMapping("/delete/{id}")
    @ApiOperation("删除")
    public AjaxResult delete(@PathVariable("id") Long id){
    @Operation(summary = "删除")
    public AjaxResult delete(@PathVariable("id") Long id) {
//        if (CollectionUtils.isEmpty(id)) {
//            throw new RuntimeException("请传入要删除的ID");
//        }
src/main/java/com/ruoyi/collaborativeApproval/dto/MeetSummaryDto.java
@@ -1,6 +1,5 @@
package com.ruoyi.collaborativeApproval.dto;
import io.swagger.models.auth.In;
import lombok.Data;
import java.io.Serializable;
src/main/java/com/ruoyi/collaborativeApproval/pojo/DutyPlan.java
@@ -4,7 +4,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.approve.utils.ListToStringTypeHandler;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.apache.ibatis.type.JdbcType;
import org.springframework.format.annotation.DateTimeFormat;
@@ -22,13 +22,13 @@
     * è®¡åˆ’标题
     */
    @Excel(name = "计划标题")
    @ApiModelProperty("计划标题")
    @Schema(description = "计划标题")
    private String title;
    /**
     * è®¡åˆ’描述
     */
    @Excel(name = "计划描述")
    @ApiModelProperty("计划描述")
    @Schema(description = "计划描述")
    private String description;
@@ -36,13 +36,13 @@
     * è®¡åˆ’级别
     */
    @Excel(name = "计划级别")
    @ApiModelProperty("计划级别")
    @Schema(description = "计划级别")
    private String level;
    /**
     * æ—¶é—´å‘¨æœŸ
     */
    @Excel(name = "时间周期")
    @ApiModelProperty("时间周期")
    @Schema(description = "时间周期")
    private String period;
    /**
     * å¼€å§‹æ—¶é—´
@@ -63,31 +63,31 @@
     * è´Ÿè´£äºº
     */
    @Excel(name = "负责人")
    @ApiModelProperty("负责人")
    @Schema(description = "负责人")
    private String assignee;
    /**
     * çŠ¶æ€
     */
    @Excel(name = "状态")
    @ApiModelProperty("状态")
    @Schema(description = "状态")
    private String status;
    /**
     * ä¼˜å…ˆçº§
     */
    @Excel(name = "优先级")
    @ApiModelProperty("优先级")
    @Schema(description = "优先级")
    private String priority;
    /**
     * å®Œæˆåº¦
     */
    @Excel(name = "完成度")
    @ApiModelProperty("完成度")
    @Schema(description = "完成度")
    private Integer progress;
    /**
     * æ ‡ç­¾
     */
    @Excel(name = "标签")
    @ApiModelProperty("标签")
    @Schema(description = "标签")
    @TableField(value = "tags",typeHandler = ListToStringTypeHandler.class,jdbcType = JdbcType.VARCHAR)
    private List<String> tags;
    /**
@@ -122,4 +122,7 @@
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/MeetApplication.java
@@ -7,7 +7,6 @@
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.models.auth.In;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -137,4 +136,7 @@
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/MeetDraft.java
@@ -129,4 +129,7 @@
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/MeetingMinutes.java
@@ -76,4 +76,7 @@
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/MeetingRoom.java
@@ -95,4 +95,7 @@
    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/Notice.java
@@ -2,8 +2,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@@ -15,7 +14,7 @@
 */
@Data
@TableName("notice")
@ApiModel
@Schema
public class Notice {
    private static final long serialVersionUID = 1L;
@@ -26,31 +25,31 @@
    /**
     * å…¬å‘Šæ ‡é¢˜
     */
    @ApiModelProperty("公告标题")
    @Schema(description = "公告标题")
    private String title;
    /**
     * å…¬å‘Šç±»åž‹
     */
    @ApiModelProperty("公告类型")
    @Schema(description = "公告类型")
    private String type;
    /**
     * çŠ¶æ€ï¼ˆ0草稿 1发布 2已下线)
     */
    @ApiModelProperty("状态(0草稿 1发布 2已下线)")
    @Schema(description = "状态(0草稿 1发布 2已下线)")
    private Integer status;
    /**
     * å…¬å‘Šå†…容
     */
    @ApiModelProperty("公告内容")
    @Schema(description = "公告内容")
    private String content;
    /**
     * ä¼˜å…ˆçº§(1普通 2重要 3紧急)
     */
    @ApiModelProperty("优先级(1普通 2重要 3紧急)")
    @Schema(description = "优先级(1普通 2重要 3紧急)")
    private Integer priority;
@@ -83,7 +82,10 @@
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    @Schema(description = "备注")
    private String remark;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/NoticeType.java
@@ -3,8 +3,7 @@
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@@ -19,7 +18,7 @@
@Getter
@Setter
@TableName("notice_type")
@ApiModel(value = "NoticeType对象", description = "通知公告的公告类型维护")
@Schema(name = "NoticeType对象", description = "通知公告的公告类型维护")
public class NoticeType implements Serializable {
    private static final long serialVersionUID = 1L;
@@ -27,9 +26,16 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty("通知公告的公告类型")
    @Schema(description = "通知公告的公告类型")
    private String noticeType;
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/ReadingStatus.java
@@ -67,4 +67,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/RulesRegulationsManagement.java
@@ -4,7 +4,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.approve.utils.ListToStringTypeHandler;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.apache.ibatis.type.JdbcType;
import org.springframework.format.annotation.DateTimeFormat;
@@ -20,26 +20,26 @@
    /**
     * åˆ¶åº¦ç¼–号
     */
    @ApiModelProperty("制度编号")
    @Schema(description = "制度编号")
    @Excel(name = "制度编号")
    private String regulationNum;
    /**
     * æ ‡é¢˜
     */
    @ApiModelProperty("标题")
    @Schema(description = "标题")
    @Excel(name = "标题")
    private String title;
    /**
     * åˆ¶åº¦åˆ†ç±»
     */
    @ApiModelProperty("制度分类")
    @Schema(description = "制度分类")
    @Excel(name = "制度分类", readConverterExp = "finance=财务制度,hr=人事制度,safety=安全制度,tech=技术制度")
    private String category;
    /**
     * åˆ¶åº¦å†…容
     */
    @ApiModelProperty("制度内容")
    @Schema(description = "制度内容")
    private String content;
    /**
     * ç”Ÿæ•ˆæ—¶é—´
@@ -51,30 +51,30 @@
    /**
     * é€‚用范围
     */
    @ApiModelProperty("适用范围")
    @Schema(description = "适用范围")
    @TableField(value = "scope",typeHandler = ListToStringTypeHandler.class,jdbcType = JdbcType.VARCHAR)
    private List<String> scope;
    /**
     * æ˜¯å¦éœ€è¦ç¡®è®¤
     */
    @ApiModelProperty("是否需要确认")
    @Schema(description = "是否需要确认")
    private Boolean requireConfirm;
    /**
     * ç‰ˆæœ¬
     */
    @ApiModelProperty("版本")
    @Schema(description = "版本")
    @Excel(name = "版本")
    private String version;
    /**
     * çŠ¶æ€
     */
    @ApiModelProperty("状态")
    @Schema(description = "状态")
    @Excel(name = "状态", readConverterExp = "repealed=已废止,active=生效中")
    private String status;
    /**
     * å·²è¯»äººæ•°
     */
    @ApiModelProperty("已读人数")
    @Schema(description = "已读人数")
    @Excel(name = "已读人数")
    private Integer readCount;
    /**
@@ -108,4 +108,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/RulesRegulationsManagementFile.java
@@ -7,8 +7,7 @@
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@@ -23,7 +22,7 @@
@Getter
@Setter
@TableName("rules_regulations_management_file")
@ApiModel(value = "RulesRegulationsManagementFile对象", description = "规章制度管理--附件")
@Schema(name = "RulesRegulationsManagementFile对象", description = "规章制度管理--附件")
public class RulesRegulationsManagementFile implements Serializable {
    private static final long serialVersionUID = 1L;
@@ -31,35 +30,38 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty("文件名称")
    @Schema(description = "文件名称")
    private String name;
    @ApiModelProperty("文件路径")
    @Schema(description = "文件路径")
    private String url;
    @ApiModelProperty("文件大小")
    @Schema(description = "文件大小")
    private Integer fileSize;
    @ApiModelProperty("规章制度ID")
    @Schema(description = "规章制度ID")
    private Integer rulesRegulationsManagementId;
    @ApiModelProperty("创建时间")
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("创建用户")
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @ApiModelProperty("修改时间")
    @Schema(description = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("修改用户")
    @Schema(description = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    @ApiModelProperty("租户ID")
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/SealApplicationManagement.java
@@ -3,7 +3,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -18,37 +18,37 @@
    /**
     * ç”³è¯·ç¼–号
     */
    @ApiModelProperty("申请编号")
    @Schema(description = "申请编号")
    @Excel(name = "申请编号")
    private String applicationNum;
    /**
     * å…¬å‘Šæ ‡é¢˜
     */
    @ApiModelProperty("公告标题")
    @Schema(description = "公告标题")
    @Excel(name = "申请标题")
    private String title;
    /**
     * ç”¨å°ç±»åž‹
     */
    @ApiModelProperty("用印类型")
    @Schema(description = "用印类型")
    @Excel(name = "用印类型", readConverterExp = "official=公章,contract=合同专用章,finance=财务专用章,legal=法人章")
    private String sealType;
    /**
     * ç”³è¯·ç”¨å°åŽŸå› 
     */
    @ApiModelProperty("申请用印原因")
    @Schema(description = "申请用印原因")
    private String reason;
    /**
     * ç´§æ€¥ç¨‹åº¦
     */
    @ApiModelProperty("紧急程度")
    @Schema(description = "紧急程度")
    private String urgency;
    /**
     * çŠ¶æ€
     */
    @ApiModelProperty("状态")
    @Schema(description = "状态")
    @Excel(name = "状态", readConverterExp = "pending=待审批,approved=已通过,rejected=已拒绝")
    private String status;
@@ -74,4 +74,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/StaffContactsPersonal.java
@@ -1,7 +1,7 @@
package com.ruoyi.collaborativeApproval.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@@ -15,13 +15,13 @@
//    /**
//     * ç”¨æˆ·ID(所属者)
//     */
//    @ApiModelProperty("用户ID(所属者)")
//    @Schema(description = "用户ID(所属者)")
//    private Integer userId;
    /**
     * å‘˜å·¥ID
     */
    @ApiModelProperty("员工ID")
    @Schema(description = "员工ID")
    private Integer contactId;
    /**
     * åˆ›å»ºè€…
@@ -40,4 +40,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/service/DutyPlanService.java
@@ -6,7 +6,7 @@
import com.ruoyi.collaborativeApproval.dto.DutyPlanDTO;
import com.ruoyi.collaborativeApproval.pojo.DutyPlan;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
src/main/java/com/ruoyi/collaborativeApproval/service/impl/DutyPlanServiceImpl.java
@@ -8,17 +8,17 @@
import com.ruoyi.collaborativeApproval.pojo.DutyPlan;
import com.ruoyi.collaborativeApproval.service.DutyPlanService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import org.springframework.beans.factory.annotation.Autowired;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
public class DutyPlanServiceImpl extends ServiceImpl<DutyPlanMapper, DutyPlan> implements DutyPlanService {
    @Autowired
    private DutyPlanMapper dutyPlanMapper;
    private final DutyPlanMapper dutyPlanMapper;
    @Override
    public IPage listPage(Page page, DutyPlanDTO dutyPlanDTO) {
src/main/java/com/ruoyi/collaborativeApproval/service/impl/MeetingServiceImpl.java
@@ -3,7 +3,6 @@
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.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
src/main/java/com/ruoyi/collaborativeApproval/service/impl/RulesRegulationsManagementFileServiceImpl.java
@@ -1,16 +1,14 @@
package com.ruoyi.collaborativeApproval.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.collaborativeApproval.pojo.RulesRegulationsManagementFile;
import com.ruoyi.collaborativeApproval.mapper.RulesRegulationsManagementFileMapper;
import com.ruoyi.collaborativeApproval.service.RulesRegulationsManagementFileService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import com.ruoyi.collaborativeApproval.mapper.RulesRegulationsManagementFileMapper;
import com.ruoyi.collaborativeApproval.pojo.RulesRegulationsManagementFile;
import com.ruoyi.collaborativeApproval.service.RulesRegulationsManagementFileService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import javax.management.Query;
/**
 * <p>
@@ -21,10 +19,10 @@
 * @since 2026-01-13 01:06:41
 */
@Service
@RequiredArgsConstructor
public class RulesRegulationsManagementFileServiceImpl extends ServiceImpl<RulesRegulationsManagementFileMapper, RulesRegulationsManagementFile> implements RulesRegulationsManagementFileService {
    @Autowired
    private RulesRegulationsManagementFileMapper rulesRegulationsManagementFileMapper;
    private final RulesRegulationsManagementFileMapper rulesRegulationsManagementFileMapper;
    @Override
    public IPage<RulesRegulationsManagementFile> listPage(Page page, RulesRegulationsManagementFile rulesRegulationsManagementFile) {
src/main/java/com/ruoyi/collaborativeApproval/service/impl/RulesRegulationsManagementServiceImpl.java
@@ -7,13 +7,14 @@
import com.ruoyi.collaborativeApproval.mapper.RulesRegulationsManagementMapper;
import com.ruoyi.collaborativeApproval.pojo.RulesRegulationsManagement;
import com.ruoyi.collaborativeApproval.service.RulesRegulationsManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class RulesRegulationsManagementServiceImpl extends ServiceImpl<RulesRegulationsManagementMapper, RulesRegulationsManagement> implements RulesRegulationsManagementService {
    @Autowired
    private RulesRegulationsManagementMapper rulesRegulationsManagementMapper;
    private final RulesRegulationsManagementMapper rulesRegulationsManagementMapper;
    @Override
    public IPage<RulesRegulationsManagementDTO> listPage(Page page, RulesRegulationsManagement rulesRegulationsManagement) {
        return rulesRegulationsManagementMapper.listPage(page, rulesRegulationsManagement);
src/main/java/com/ruoyi/collaborativeApproval/service/impl/SealApplicationManagementServiceImpl.java
@@ -7,13 +7,13 @@
import com.ruoyi.collaborativeApproval.mapper.SealApplicationManagementMapper;
import com.ruoyi.collaborativeApproval.pojo.SealApplicationManagement;
import com.ruoyi.collaborativeApproval.service.SealApplicationManagementService;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class SealApplicationManagementServiceImpl extends ServiceImpl<SealApplicationManagementMapper, SealApplicationManagement> implements SealApplicationManagementService {
    @Autowired
    private SealApplicationManagementMapper sealApplicationManagementMapper;
    private final SealApplicationManagementMapper sealApplicationManagementMapper;
    @Override
    public IPage<SealApplicationManagementDTO> listPage(Page page, SealApplicationManagement sealApplicationManagement) {
src/main/java/com/ruoyi/collaborativeApproval/service/impl/StaffContactsPersonalServiceImpl.java
@@ -1,6 +1,5 @@
package com.ruoyi.collaborativeApproval.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -8,13 +7,14 @@
import com.ruoyi.collaborativeApproval.mapper.StaffContactsPersonalMapper;
import com.ruoyi.collaborativeApproval.pojo.StaffContactsPersonal;
import com.ruoyi.collaborativeApproval.service.StaffContactsPersonalService;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class StaffContactsPersonalServiceImpl extends ServiceImpl<StaffContactsPersonalMapper, StaffContactsPersonal> implements StaffContactsPersonalService {
    @Autowired
    private StaffContactsPersonalMapper staffContactsPersonalMapper;
    private final StaffContactsPersonalMapper staffContactsPersonalMapper;
    @Override
    public IPage listPage(Page page, StaffContactsPersonalDTO staffContactsPersonalDTO) {
        return staffContactsPersonalMapper.listPage(page, staffContactsPersonalDTO);
src/main/java/com/ruoyi/common/aop/DataScopeAop.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,158 @@
package com.ruoyi.common.aop;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginUser;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Map;
@Aspect
@Component
public class DataScopeAop {
    private static final String DATA_SCOPE_ALL = "1";
    private static final String DATA_SCOPE_CUSTOM = "2";
    private static final String DATA_SCOPE_DEPT = "3";
    private static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
    private static final String DATA_SCOPE_SELF = "5";
    @Before("@within(restController)")
    public void fillDataScopeCondition(JoinPoint joinPoint, RestController restController) {
        System.out.println("[DataScopeAop] enter: " + joinPoint.getSignature().toShortString());
        fillDataScopeCondition(joinPoint);
    }
    public void fillDataScopeCondition(JoinPoint joinPoint) {
        LoginUser loginUser;
        try {
            loginUser = SecurityUtils.getLoginUser();
        } catch (Exception ignored) {
            System.out.println("[DataScopeAop] skip: loginUser unavailable");
            return;
        }
        if (loginUser == null || loginUser.getUser() == null || loginUser.getUser().isAdmin()) {
            System.out.println("[DataScopeAop] skip: loginUser null or admin");
            return;
        }
        String dataScope = loginUser.getDataScope();
        if (dataScope == null || DATA_SCOPE_ALL.equals(dataScope)) {
            System.out.println("[DataScopeAop] skip: dataScope=" + dataScope);
            return;
        }
        for (Object arg : joinPoint.getArgs()) {
            bindScope(arg, loginUser, dataScope);
        }
    }
    private void bindScope(Object arg, LoginUser loginUser, String dataScope) {
        if (arg == null || isIgnoredType(arg.getClass())) {
            return;
        }
        if (arg instanceof Collection<?>) {
            for (Object item : (Collection<?>) arg) {
                bindScope(item, loginUser, dataScope);
            }
            return;
        }
        if (arg instanceof Map<?, ?>) {
            for (Object value : ((Map<?, ?>) arg).values()) {
                bindScope(value, loginUser, dataScope);
            }
            return;
        }
        if (arg.getClass().isArray()) {
            int length = Array.getLength(arg);
            for (int i = 0; i < length; i++) {
                bindScope(Array.get(arg, i), loginUser, dataScope);
            }
            return;
        }
        if (DATA_SCOPE_SELF.equals(dataScope)) {
            setFieldValue(arg, "createUser", Integer.class, loginUser.getUserId() == null ? null : loginUser.getUserId().intValue());
            return;
        }
        if (DATA_SCOPE_DEPT.equals(dataScope)) {
            setFieldValue(arg, "deptId", Long.class, resolveDeptId(loginUser));
            return;
        }
        if (DATA_SCOPE_CUSTOM.equals(dataScope) || DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
            Long[] deptIds = loginUser.getDeptIds();
            setFieldValue(arg, "deptIds", Long[].class, deptIds);
            if (deptIds != null && deptIds.length == 1) {
                setFieldValue(arg, "deptId", Long.class, deptIds[0]);
            }
        }
    }
    private Long resolveDeptId(LoginUser loginUser) {
        if (loginUser.getCurrentDeptId() != null) {
            return loginUser.getCurrentDeptId();
        }
        Long[] deptIds = loginUser.getDeptIds();
        return deptIds != null && deptIds.length > 0 ? deptIds[0] : null;
    }
    private void setFieldValue(Object target, String fieldName, Class<?> fieldType, Object value) {
        if (value == null) {
            return;
        }
        Field field = findField(target.getClass(), fieldName);
        if (field == null || !fieldType.isAssignableFrom(field.getType())) {
            return;
        }
        try {
            field.setAccessible(true);
            field.set(target, value);
            System.out.println("[DataScopeAop] inject: class=" + target.getClass().getSimpleName() + ", field=" + fieldName + ", value=" + value);
        } catch (IllegalAccessException ignored) {
        }
    }
    private Field findField(Class<?> type, String fieldName) {
        Class<?> current = type;
        while (current != null && current != Object.class) {
            try {
                return current.getDeclaredField(fieldName);
            } catch (NoSuchFieldException ignored) {
                current = current.getSuperclass();
            }
        }
        return null;
    }
    private boolean isIgnoredType(Class<?> type) {
        Package targetPackage = type.getPackage();
        String packageName = targetPackage == null ? "" : targetPackage.getName();
        return type.isPrimitive()
                || Number.class.isAssignableFrom(type)
                || CharSequence.class.isAssignableFrom(type)
                || Boolean.class == type
                || Character.class == type
                || type.isEnum()
                || Page.class.isAssignableFrom(type)
                || MultipartFile.class.isAssignableFrom(type)
                || ServletRequest.class.isAssignableFrom(type)
                || ServletResponse.class.isAssignableFrom(type)
                || packageName.startsWith("java.")
                || packageName.startsWith("jakarta.")
                || packageName.startsWith("jakarta.")
                || packageName.startsWith("org.springframework.")
                || packageName.startsWith("com.baomidou.");
    }
}
src/main/java/com/ruoyi/common/config/FileProperties.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.common.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.util.unit.DataSize;
import java.math.BigDecimal;
@Configuration
@Component
@ConfigurationProperties(prefix = "file", ignoreUnknownFields = true)
@Data
public class FileProperties {
    private String path = "D:/upload";
    private String urlPrefix = "/file";
    private String domain = "http://localhost:8080";
    private BigDecimal expired = new BigDecimal("120");
    private Integer useLimit = 10;
    // ä»¤ç‰Œç§˜é’¥
    @Value("${token.secret}")
    private String jwtSecret;
    private Boolean compress;
    private DataSize needCompressSize;
    private float compressQuality;
}
src/main/java/com/ruoyi/common/config/IgnoreTableConfig.java
@@ -34,6 +34,11 @@
        IGNORE_TABLES.add("sys_user_dept");
        IGNORE_TABLES.add("sys_job_log");
        IGNORE_TABLES.add("gen_table");
        IGNORE_TABLES.add("gen_table_column");
        IGNORE_TABLES.add("sys_notice");
        IGNORE_TABLES.add("sys_user_client");
        IGNORE_TABLES.add("product_model");
        IGNORE_TABLES.add("ai_chat_session");
        IGNORE_TABLES.add("product");
    }
}
src/main/java/com/ruoyi/common/config/MinioConfig.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/common/config/MybatisHandler.java
@@ -15,10 +15,12 @@
        Integer userId = null;
        Long tenantId = null;
        String userName = null;
        Long deptId = null;
        try {
            userId = SecurityUtils.getUserId().intValue();
            tenantId = SecurityUtils.getLoginUser().getTenantId();
            userName = SecurityUtils.getUsername();
            deptId = SecurityUtils.getLoginUser().getCurrentDeptId();
        } catch (Exception ignored) {
        }
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
@@ -29,7 +31,7 @@
        this.strictInsertFill(metaObject, "updateUser", Long.class, userId == null ? 0 : userId.longValue());
        this.strictInsertFill(metaObject, "createUserName", String.class, userName);
        this.strictInsertFill(metaObject, "updateUserName", String.class, userName);
        this.strictInsertFill(metaObject, "deptId", Long.class, deptId);
        this.strictInsertFill(metaObject, "tenantId", Long.class, tenantId);
    }
src/main/java/com/ruoyi/common/constant/Constants.java
@@ -168,6 +168,6 @@
    /**
     * å®šæ—¶ä»»åŠ¡è¿è§„çš„å­—ç¬¦
     */
    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
    public static final String[] JOB_ERROR_STR = { "java.net.URL", "jakarta.naming.InitialContext", "org.yaml.snakeyaml",
            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.framework.config", "com.ruoyi.project.tool" };
}
src/main/java/com/ruoyi/common/enums/FileNameType.java
@@ -1,6 +1,8 @@
package com.ruoyi.common.enums;
import lombok.Getter;
@Getter
public enum FileNameType {
    SALE(1),      // é”€å”®
@@ -14,17 +16,13 @@
    SHIP(9),//发货台账
    INSPECTION_PRODUCTION_BEFORE(10),
    INSPECTION_PRODUCTION_AFTER(11),
    INSPECTION(12);//巡检 ç”Ÿäº§å‰
    INSPECTION(12),//巡检 ç”Ÿäº§å‰
    APP(13);
    private final int value;
    FileNameType(int value) {
        this.value = value;
    }
    public int getValue() {
        return value;
    }
    // æ ¹æ®æ•´æ•°å€¼èŽ·å–å¯¹åº”çš„æžšä¸¾å€¼
src/main/java/com/ruoyi/common/enums/SparePartsRequisitionRecordSourceTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package com.ruoyi.common.enums;
import lombok.Getter;
@Getter
public enum SparePartsRequisitionRecordSourceTypeEnum implements BaseEnum<Integer> {
    SparePartsRequisitionRecordSourceTypeRepair(0, "ç»´ä¿®"),
    SparePartsRequisitionRecordSourceTypeMaintenance(1, "保养");
    private final Integer code;
    private final String value;
    SparePartsRequisitionRecordSourceTypeEnum(Integer code, String value) {
        this.code = code;
        this.value = value;
    }
    public static SparePartsRequisitionRecordSourceTypeEnum getByCode(Integer code) {
        for (SparePartsRequisitionRecordSourceTypeEnum type : SparePartsRequisitionRecordSourceTypeEnum.values()) {
            if (type.getCode().equals(code)) {
                return type;
            }
        }
        return null;
    }
}
src/main/java/com/ruoyi/common/enums/StockInQualifiedRecordTypeEnum.java
@@ -8,11 +8,25 @@
    CUSTOMIZATION_STOCK_IN("0", "合格自定义入库"),
    CUSTOMIZATION_STOCK_OUT("1", "合格自定义出库"),
    PRODUCTION_REPORT_STOCK_IN("2", "生产报工-入库"),
    PURCHASE_STOCK_IN("7", "采购-入库"),
    PRODUCTION_REPORT_STOCK_OUT("3", "生产报工-出库"),
    DEFECTIVE_SCRAP("4", "不合格处理-报废"),
    PRODUCTION_SCRAP("5", "生产报工-报废"),
    QUALITYINSPECT_STOCK_IN("6", "质检-合格入库"),
    PURCHASE_STOCK_IN("7", "采购-入库"),
    SALE_STOCK_OUT("8", "销售-出库"),
    CUSTOMIZATION_UNSTOCK_IN("9", "不合格自定义入库"),
    CUSTOMIZATION_UNSTOCK_OUT("10", "不合格自定义出库"),
    DEFECTIVE_PASS("11", "不合格-让步放行"),
    RETURN_HE_IN("14", "销售退货-合格入库");
    QUALITYINSPECT_UNSTOCK_IN("12", "质检-不合格入库"),
    SALE_SHIP_STOCK_OUT("13", "销售-发货出库"),
    RETURN_HE_IN("14", "销售退货-合格入库"),
    RETURN_UNSTOCK_IN("15", "销售退货-不合格入库"),
    PICK_RETURN_IN("20", "销售退货-合格入库"),
    PURCHASE_RETURN_STOCK_OUT("21", "采购退货");
    private final String code;
src/main/java/com/ruoyi/common/enums/StockOutQualifiedRecordTypeEnum.java
@@ -8,6 +8,7 @@
    CUSTOMIZATION_STOCK_OUT("1", "合格自定义出库"),
    PRODUCTION_REPORT_STOCK_OUT("3", "生产报工-出库"),
    SALE_STOCK_OUT("8", "销售-出库"),
    PURCHASE_RETURN_STOCK_OUT("9", "采购退货"),
    SALE_SHIP_STOCK_OUT("13", "销售-发货出库");
    private final String code;
src/main/java/com/ruoyi/common/filter/RepeatableFilter.java
@@ -1,13 +1,13 @@
package com.ruoyi.common.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import com.ruoyi.common.utils.StringUtils;
src/main/java/com/ruoyi/common/filter/RepeatedlyRequestWrapper.java
@@ -4,11 +4,11 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import com.ruoyi.common.utils.http.HttpHelper;
import com.ruoyi.common.constant.Constants;
src/main/java/com/ruoyi/common/filter/XssFilter.java
@@ -3,14 +3,14 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.enums.HttpMethod;
src/main/java/com/ruoyi/common/filter/XssHttpServletRequestWrapper.java
@@ -2,10 +2,10 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.io.IOUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
src/main/java/com/ruoyi/common/interceptor/DataScopeSqlInterceptor.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,257 @@
package com.ruoyi.common.interceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.ruoyi.common.config.IgnoreTableConfig;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginUser;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
import java.util.Locale;
import java.util.Set;
@Component
public class DataScopeSqlInterceptor implements InnerInterceptor {
    private static final String DATA_SCOPE_ALL = "1";
    private static final String DATA_SCOPE_CUSTOM = "2";
    private static final String DATA_SCOPE_DEPT = "3";
    private static final String DATA_SCOPE_DEPT_AND_CHILD = "4";
    private static final String DATA_SCOPE_SELF = "5";
    private static final String DATA_SCOPE_MARKER = "/*data_scope*/";
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
                            ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        LoginUser loginUser;
        try {
            loginUser = SecurityUtils.getLoginUser();
        } catch (Exception ignored) {
            return;
        }
        if (shouldSkip(loginUser, boundSql.getSql())) {
            return;
        }
        // èŽ·å–ä¸»è¡¨
        TableSegment tableSegment = resolveMainTable(boundSql.getSql());
        // ====================== ã€è¡¨ç™½åå•】直接放行 ======================
        if (tableSegment == null || ignoreTable(tableSegment.tableName)) {
            return;
        }
        String condition = buildCondition(tableSegment.qualifier, loginUser);
        if (condition == null) {
            return;
        }
        String newSql = appendCondition(boundSql.getSql(), condition);
        if (newSql.equals(boundSql.getSql())) {
            return;
        }
        MetaObject metaObject = SystemMetaObject.forObject(boundSql);
        metaObject.setValue("sql", newSql);
        System.out.println("[DataScopeSqlInterceptor] rewrite: " + ms.getId());
        System.out.println("[DataScopeSqlInterceptor] sql: " + newSql);
    }
    private boolean shouldSkip(LoginUser loginUser, String sql) {
        if (loginUser == null || loginUser.getUser() == null || loginUser.getUser().isAdmin()) {
            return true;
        }
        if (sql == null || sql.trim().isEmpty()) {
            return true;
        }
        String normalizedSql = sql.toLowerCase(Locale.ROOT);
        if (!normalizedSql.startsWith("select")) {
            return true;
        }
        if (normalizedSql.contains(DATA_SCOPE_MARKER)) {
            return true;
        }
        return DATA_SCOPE_ALL.equals(loginUser.getDataScope());
    }
    private boolean ignoreTable(String tableName) {
        Set<String> ignoreTables = IgnoreTableConfig.IGNORE_TABLES;
        return ignoreTables.contains(tableName);
    }
    private String buildCondition(String qualifier, LoginUser loginUser) {
        String prefix = qualifier + ".";
        String dataScope = loginUser.getDataScope();
        if (DATA_SCOPE_SELF.equals(dataScope)) {
            return prefix + "create_user = " + loginUser.getUserId();
        }
        if (DATA_SCOPE_DEPT.equals(dataScope)) {
            Long deptId = resolveDeptId(loginUser);
            return deptId == null ? null : prefix + "dept_id = " + deptId;
        }
        if (DATA_SCOPE_CUSTOM.equals(dataScope) || DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) {
            Long[] deptIds = loginUser.getDeptIds();
            if (deptIds == null || deptIds.length == 0) {
                return null;
            }
            StringBuilder builder = new StringBuilder(prefix).append("dept_id in (");
            for (int i = 0; i < deptIds.length; i++) {
                if (i > 0) {
                    builder.append(", ");
                }
                builder.append(deptIds[i]);
            }
            return builder.append(')').toString();
        }
        return null;
    }
    private Long resolveDeptId(LoginUser loginUser) {
        if (loginUser.getCurrentDeptId() != null) {
            return loginUser.getCurrentDeptId();
        }
        Long[] deptIds = loginUser.getDeptIds();
        return deptIds != null && deptIds.length > 0 ? deptIds[0] : null;
    }
    private String appendCondition(String sql, String condition) {
        int insertPos = findInsertPosition(sql);
        String prefixSql = sql.substring(0, insertPos);
        String suffixSql = sql.substring(insertPos);
        if (hasTopLevelKeyword(prefixSql, "where")) {
            return prefixSql + " AND " + DATA_SCOPE_MARKER + " " + condition + " " + suffixSql;
        }
        return prefixSql + " WHERE " + DATA_SCOPE_MARKER + " " + condition + " " + suffixSql;
    }
    private int findInsertPosition(String sql) {
        int orderBy = findTopLevelKeyword(sql, "order by");
        int groupBy = findTopLevelKeyword(sql, "group by");
        int having = findTopLevelKeyword(sql, "having");
        int limit = findTopLevelKeyword(sql, "limit");
        int union = findTopLevelKeyword(sql, "union");
        int insertPos = sql.length();
        insertPos = minPositive(insertPos, orderBy);
        insertPos = minPositive(insertPos, groupBy);
        insertPos = minPositive(insertPos, having);
        insertPos = minPositive(insertPos, limit);
        insertPos = minPositive(insertPos, union);
        return insertPos;
    }
    private int minPositive(int current, int candidate) {
        return candidate >= 0 && candidate < current ? candidate : current;
    }
    private boolean hasTopLevelKeyword(String sql, String keyword) {
        return findTopLevelKeyword(sql, keyword) >= 0;
    }
    private int findTopLevelKeyword(String sql, String keyword) {
        String normalizedSql = sql.toLowerCase(Locale.ROOT);
        String normalizedKeyword = keyword.toLowerCase(Locale.ROOT);
        int depth = 0;
        for (int i = 0; i <= normalizedSql.length() - normalizedKeyword.length(); i++) {
            char current = normalizedSql.charAt(i);
            if (current == '(') {
                depth++;
                continue;
            }
            if (current == ')') {
                depth = Math.max(0, depth - 1);
                continue;
            }
            if (depth > 0) {
                continue;
            }
            if (matchesKeyword(normalizedSql, i, normalizedKeyword)) {
                return i;
            }
        }
        return -1;
    }
    private boolean matchesKeyword(String sql, int index, String keyword) {
        if (!sql.regionMatches(index, keyword, 0, keyword.length())) {
            return false;
        }
        boolean startOk = index == 0 || !Character.isLetterOrDigit(sql.charAt(index - 1));
        int endIndex = index + keyword.length();
        boolean endOk = endIndex >= sql.length() || !Character.isLetterOrDigit(sql.charAt(endIndex));
        return startOk && endOk;
    }
    private TableSegment resolveMainTable(String sql) {
        int fromIndex = findTopLevelKeyword(sql, "from");
        if (fromIndex < 0) {
            return null;
        }
        String fromPart = sql.substring(fromIndex + 4).trim();
        if (fromPart.isEmpty() || fromPart.charAt(0) == '(') {
            return null;
        }
        String[] tokens = fromPart.split("\\s+");
        if (tokens.length == 0) {
            return null;
        }
        String rawTableName = trimToken(tokens[0]);
        if (rawTableName.isEmpty()) {
            return null;
        }
        String alias = null;
        if (tokens.length > 1) {
            String second = trimToken(tokens[1]);
            if ("as".equalsIgnoreCase(second) && tokens.length > 2) {
                alias = trimToken(tokens[2]);
            } else if (!isClauseKeyword(second)) {
                alias = second;
            }
        }
        String tableName = normalizeTableName(rawTableName);
        String qualifier = alias != null && !alias.isEmpty() ? alias : rawTableName;
        return new TableSegment(tableName, qualifier.replace("`", ""));
    }
    private String trimToken(String token) {
        if (token == null) {
            return "";
        }
        return token.replace(",", "").trim();
    }
    private String normalizeTableName(String tableName) {
        String normalized = tableName.replace("`", "");
        int dotIndex = normalized.lastIndexOf('.');
        if (dotIndex >= 0) {
            normalized = normalized.substring(dotIndex + 1);
        }
        return normalized;
    }
    private boolean isClauseKeyword(String token) {
        String normalized = token.toLowerCase(Locale.ROOT);
        return "left".equals(normalized)
                || "right".equals(normalized)
                || "inner".equals(normalized)
                || "outer".equals(normalized)
                || "join".equals(normalized)
                || "where".equals(normalized)
                || "order".equals(normalized)
                || "group".equals(normalized)
                || "limit".equals(normalized)
                || "union".equals(normalized)
                || "having".equals(normalized);
    }
    private static class TableSegment {
        private final String tableName;
        private final String qualifier;
        private TableSegment(String tableName, String qualifier) {
            this.tableName = tableName;
            this.qualifier = qualifier;
        }
    }
}
src/main/java/com/ruoyi/common/utils/MinioUtils.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/common/utils/OrderUtils.java
@@ -2,9 +2,6 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.common.utils.uuid.UUID;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -12,7 +9,6 @@
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -24,82 +20,88 @@
    /**
     * List<Integer> è½¬æ¢ä¸º Long[] æ•°ç»„
     * @param ids
     * @return
     * @param ids ids
     * @return Long[]
     */
    public static Long[] listIntegerToLongArray(List<Integer> ids) {
        return ids.stream()
                // å¤„理null值:如果元素为null,转换为0L(可根据业务调整,比如抛异常)
                .map(id -> id != null ? id.longValue() : -1L)
                // å°†Stream<Long>转换为Long[]数组
                .toArray(Long[]::new);
    }
    /**
     * åˆ¤æ–­ç›®æ ‡id是否在逗号分隔的字符串中
     * @param targetId
     * @param str
     * @return
     * @param targetId targetId
     * @param str source
     * @return boolean
     */
    public boolean isStaffIdExist(Object targetId,String str) {
        // ç©ºå€¼æ ¡éªŒï¼Œé¿å…ç©ºæŒ‡é’ˆ
        if (str == null || str.trim().isEmpty() || targetId == null) {
            return false;
        }
        // æŒ‰é€—号分割成数组
        String[] idArray = str.split(",");
        // éåŽ†æ•°ç»„åˆ¤æ–­æ˜¯å¦åŒ…å«ç›®æ ‡id
        for (String id : idArray) {
            // åŽ»é™¤ç©ºæ ¼ï¼ˆé˜²æ­¢å­—ç¬¦ä¸²ä¸­æœ‰å¤šä½™ç©ºæ ¼ï¼Œå¦‚"1, 121")
            String cleanId = id.trim();
            // è½¬æ¢ä¸ºæ•°å­—并比较
            try {
                if (cleanId.equals(String.valueOf(targetId))) {
                    return true;
                }
            } catch (NumberFormatException e) {
                // è‹¥å­˜åœ¨éžæ•°å­—ID,直接返回false
                return false;
            }
        }
        return false;
    }
    /**
     * æŸ¥è¯¢å½“天(基于createTime字段)的记录数量
     * @param mapper å®žä½“类对应的BaseMapper
     * @param <T> å®žä½“类泛型
     * @return å½“天记录数量
     * æŸ¥è¯¢å½“天基于 create_time çš„æœ€æ–°ç¼–号,并生成下一个编号
     * @param mapper mapper
     * @param preFix ç¼–号前缀
     * @param code ç¼–号字段
     * @param <T> å®žä½“类型
     * @return è®¢å•编号
     */
    public static <T> String countTodayByCreateTime(BaseMapper<T> mapper,String preFix) {
        // èŽ·å–å½“å¤©å¼€å§‹æ—¶é—´ï¼ˆ00:00:00)
        LocalDateTime todayStart = LocalDateTime.of(
                LocalDateTime.now().toLocalDate(),
                LocalTime.MIN
        );
        // èŽ·å–å½“å¤©ç»“æŸæ—¶é—´ï¼ˆ23:59:59.999)
        LocalDateTime todayEnd = LocalDateTime.of(
                LocalDateTime.now().toLocalDate(),
                LocalTime.MAX
        );
    public static <T> String countTodayByCreateTime(BaseMapper<T> mapper,String preFix,String code) {
        LocalDate today = LocalDate.now();
        LocalDateTime todayStart = today.atStartOfDay();
        LocalDateTime tomorrowStart = today.plusDays(1).atStartOfDay();
        String dateStr = today.format(DateTimeFormatter.BASIC_ISO_DATE);
        String codePrefix = preFix + dateStr;
        // è½¬æ¢ä¸ºDate类型(如果实体类中createTime是LocalDateTime可直接使用)
        Date startDate = Date.from(todayStart.atZone(ZoneId.systemDefault()).toInstant());
        Date endDate = Date.from(todayEnd.atZone(ZoneId.systemDefault()).toInstant());
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        wrapper.select(code)
                .ge("create_time", todayStart)
                .lt("create_time", tomorrowStart)
                .likeRight(code, codePrefix)
                .orderByDesc(code)
                .last("LIMIT 1");
        // æž„建查询条件
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        queryWrapper.ge("create_time", startDate)  // å¤§äºŽç­‰äºŽå½“天开始
                .lt("create_time", endDate);   // å°äºŽå½“天结束(避免毫秒精度问题)
        long nextSeq = 1;
        List<Map<String, Object>> records = mapper.selectMaps(wrapper);
        if (!records.isEmpty()) {
            Object lastCode = records.get(0).get(code);
            if (lastCode != null) {
                nextSeq = extractSequence(lastCode.toString(), codePrefix) + 1;
            }
        }
        // æ‰§è¡ŒæŸ¥è¯¢
        Long aLong = mapper.selectCount(queryWrapper);
        // æ‹¼æŽ¥è®¢å•编号 preFix + æ—¶é—´ï¼ˆyyyyMMdd) + è®¢å•数量(001)
        return preFix + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE).replaceAll("-", "") + String.format("%03d", (aLong + 1)) + "-" + new Date().getTime();
        return preFix + dateStr + String.format("%03d", nextSeq);
    }
    private static long extractSequence(String fullCode, String codePrefix) {
        if (!fullCode.startsWith(codePrefix)) {
            return 0;
        }
        String seqStr = fullCode.substring(codePrefix.length()).trim();
        if (seqStr.isEmpty()) {
            return 0;
        }
        try {
            return Long.parseLong(seqStr);
        } catch (NumberFormatException e) {
            return 0;
        }
    }
    /**
     * æŸ¥è¯¢å½“天(基于createTime字段)的记录数量
@@ -108,29 +110,23 @@
     * @return å½“天记录数量
     */
    public static <T> String countAfterServiceTodayByCreateTime(BaseMapper<T> mapper,String preFix) {
        // èŽ·å–å½“å¤©å¼€å§‹æ—¶é—´ï¼ˆ00:00:00)
        LocalDateTime todayStart = LocalDateTime.of(
                LocalDateTime.now().toLocalDate(),
                LocalTime.MIN
        );
        // èŽ·å–å½“å¤©ç»“æŸæ—¶é—´ï¼ˆ23:59:59.999)
        LocalDateTime todayEnd = LocalDateTime.of(
                LocalDateTime.now().toLocalDate(),
                LocalTime.MAX
        );
        // è½¬æ¢ä¸ºDate类型(如果实体类中createTime是LocalDateTime可直接使用)
        Date startDate = Date.from(todayStart.atZone(ZoneId.systemDefault()).toInstant());
        Date endDate = Date.from(todayEnd.atZone(ZoneId.systemDefault()).toInstant());
        // æž„建查询条件
        QueryWrapper<T> queryWrapper = new QueryWrapper<>();
        queryWrapper.ge("create_time", startDate)  // å¤§äºŽç­‰äºŽå½“天开始
                .lt("create_time", endDate);   // å°äºŽå½“天结束(避免毫秒精度问题)
        queryWrapper.ge("create_time", startDate)
                .lt("create_time", endDate);
        // æ‰§è¡ŒæŸ¥è¯¢
        Long aLong = mapper.selectCount(queryWrapper);
        // æ‹¼æŽ¥è®¢å•编号 preFix + æ—¶é—´ï¼ˆyyyyMMdd) + è®¢å•数量(001)
        return preFix + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE).replaceAll("-", "") + String.format("%03d", (aLong + 1));
    }
}
src/main/java/com/ruoyi/common/utils/ServletUtils.java
@@ -7,10 +7,10 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
src/main/java/com/ruoyi/common/utils/bean/BeanValidators.java
@@ -1,9 +1,9 @@
package com.ruoyi.common.utils.bean;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validator;
/**
 * bean对象属性验证
src/main/java/com/ruoyi/common/utils/excel/ExcelUtils.java
@@ -15,8 +15,8 @@
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;
 
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.math.BigDecimal;
src/main/java/com/ruoyi/common/utils/file/FileUtils.java
@@ -9,8 +9,8 @@
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
src/main/java/com/ruoyi/common/utils/http/HttpHelper.java
@@ -5,7 +5,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import javax.servlet.ServletRequest;
import jakarta.servlet.ServletRequest;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
src/main/java/com/ruoyi/common/utils/http/HttpUtils.java
@@ -1,27 +1,19 @@
package com.ruoyi.common.utils.http;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import javax.net.ssl.*;
import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.http.MediaType;
/**
 * é€šç”¨http发送方法
src/main/java/com/ruoyi/common/utils/ip/IpUtils.java
@@ -2,7 +2,7 @@
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
@@ -23,7 +23,7 @@
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
src/main/java/com/ruoyi/common/vo/FileVo.java
@@ -1,6 +1,6 @@
package com.ruoyi.common.vo;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@@ -8,25 +8,25 @@
@Data
public class FileVo {
    @ApiModelProperty(value = "文件名称")
    @Schema(description = "文件名称")
    private String name;
    @ApiModelProperty(value = "文件路径")
    @Schema(description = "文件路径")
    private String url;
    @ApiModelProperty(value = "文件大小")
    @Schema(description = "文件大小")
    private int fileSize;
    @ApiModelProperty(value = "创建时间")
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "修改时间")
    @Schema(description = "修改时间")
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "创建用户")
    @Schema(description = "创建用户")
    private Integer createUser;
    @ApiModelProperty(value = "修改用户")
    @Schema(description = "修改用户")
    private Integer updateUser;
    private Long id;
src/main/java/com/ruoyi/common/vo/SimpleFileVo.java
@@ -5,7 +5,7 @@
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.NotNull;
import java.io.Serializable;
/**
src/main/java/com/ruoyi/common/xss/Xss.java
@@ -1,7 +1,7 @@
package com.ruoyi.common.xss;
import javax.validation.Constraint;
import javax.validation.Payload;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
src/main/java/com/ruoyi/common/xss/XssValidator.java
@@ -1,8 +1,8 @@
package com.ruoyi.common.xss;
import com.ruoyi.common.utils.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
src/main/java/com/ruoyi/compensationperformance/controller/CompensationPerformanceController.java
@@ -1,6 +1,5 @@
package com.ruoyi.compensationperformance.controller;
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.ruoyi.common.utils.poi.ExcelUtil;
@@ -10,42 +9,37 @@
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.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.purchase.dto.PaymentRegistrationDto;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.StaffOnJob;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
 * @author :yys
 * @date : 2025/8/8 9:56
 */
@RestController
@Api(tags = "薪酬绩效")
@Tag(name = "薪酬绩效")
@RequestMapping("/compensationPerformance")
@AllArgsConstructor
public class CompensationPerformanceController extends BaseController {
    @Autowired
    private CompensationPerformanceService compensationPerformanceService;
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    @GetMapping("/listPage")
    @Log(title = "薪酬绩效-分页查询", businessType = BusinessType.OTHER)
    @ApiOperation("薪酬绩效-分页查询")
    @Operation(summary = "薪酬绩效-分页查询")
    public AjaxResult listPage(Page page, String staffName, String payDateStr) {
        IPage<CompensationPerformance> listPage = compensationPerformanceService.listPage(page, staffName, payDateStr);
        return AjaxResult.success(listPage);
@@ -53,7 +47,7 @@
    @PostMapping("/add")
    @Log(title = "薪酬绩效-添加", businessType = BusinessType.INSERT)
    @ApiOperation("薪酬绩效-添加")
    @Operation(summary = "薪酬绩效-添加")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult add(@RequestBody CompensationPerformance compensationPerformance) {
        boolean save = compensationPerformanceService.save(compensationPerformance);
@@ -62,7 +56,7 @@
    @PostMapping("/update")
    @Log(title = "薪酬绩效-修改", businessType = BusinessType.UPDATE)
    @ApiOperation("薪酬绩效-修改")
    @Operation(summary = "薪酬绩效-修改")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult update(@RequestBody CompensationPerformance compensationPerformance) {
        boolean update = compensationPerformanceService.updateById(compensationPerformance);
@@ -71,7 +65,7 @@
    @DeleteMapping("/delete")
    @Log(title = "薪酬绩效-删除", businessType = BusinessType.DELETE)
    @ApiOperation("薪酬绩效-删除")
    @Operation(summary = "薪酬绩效-删除")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
src/main/java/com/ruoyi/compensationperformance/pojo/CompensationPerformance.java
@@ -3,8 +3,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -21,25 +20,25 @@
 */
@Data
@TableName("compensation_performance")
@ApiModel("薪酬绩效明细")
@Schema(name = "薪酬绩效明细")
public class CompensationPerformance implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    @ApiModelProperty("主键")
    @Schema(description = "主键")
    private Long id;
    /**
     * å‘˜å·¥id
     */
    @ApiModelProperty("员工id")
    @Schema(description = "员工id")
    private Long staffId;
    /**
     * å‘˜å·¥å§“名
     */
    @ApiModelProperty("员工姓名")
    @Schema(description = "员工姓名")
    @Excel(name = "员工姓名")
    @TableField(exist = false)
    private String staffName;
@@ -47,7 +46,7 @@
    /**
     * å²—位名称
     */
    @ApiModelProperty("岗位名称")
    @Schema(description = "岗位名称")
    @Excel(name = "岗位名称")
    @TableField(exist = false)
    private String postName;
@@ -55,7 +54,7 @@
    /**
     * éƒ¨é—¨åç§°
     */
    @ApiModelProperty("部门名称")
    @Schema(description = "部门名称")
    @Excel(name = "部门名称")
    @TableField(exist = false)
    private String deptName;
@@ -63,7 +62,7 @@
    /**
     * è–ªèµ„月份
     */
    @ApiModelProperty("薪资月份")
    @Schema(description = "薪资月份")
    @Excel(name = "月份", dateFormat = "yyyy-MM", width = 20)
    @JsonFormat(pattern = "yyyy-MM", timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM")
@@ -72,84 +71,84 @@
    /**
     * åŸºæœ¬å·¥èµ„
     */
    @ApiModelProperty("基本工资")
    @Schema(description = "基本工资")
    @Excel(name = "基本工资")
    private BigDecimal basicSalary;
    /**
     * è®¡ä»¶å·¥èµ„
     */
    @ApiModelProperty("计件工资")
    @Schema(description = "计件工资")
    @Excel(name = "计件工资")
    private BigDecimal pieceworkSalary;
    /**
     * è®¡æ—¶å·¥èµ„
     */
    @ApiModelProperty("计时工资")
    @Schema(description = "计时工资")
    @Excel(name = "计时工资")
    private BigDecimal hourlySalary;
    /**
     * å…¶ä»–æ”¶å…¥
     */
    @ApiModelProperty("其他收入")
    @Schema(description = "其他收入")
    @Excel(name = "其他收入")
    private BigDecimal otherIncome;
    /**
     * ç¤¾ä¿ä¸ªäºº
     */
    @ApiModelProperty("社保个人")
    @Schema(description = "社保个人")
    @Excel(name = "社保个人")
    private BigDecimal socialSecurityIndividuals;
    /**
     * å…¬ç§¯é‡‘个人
     */
    @ApiModelProperty("公积金个人")
    @Schema(description = "公积金个人")
    @Excel(name = "公积金个人")
    private BigDecimal providentFundIndividuals;
    /**
     * å·¥èµ„个税
     */
    @ApiModelProperty("工资个税")
    @Schema(description = "工资个税")
    @Excel(name = "工资个税")
    private BigDecimal personalIncomeTax;
    /**
     * å…¶ä»–支出
     */
    @ApiModelProperty("其他支出")
    @Schema(description = "其他支出")
    @Excel(name = "其他支出")
    private BigDecimal otherDeductions;
    /**
     * åº”发工资
     */
    @ApiModelProperty("应发工资")
    @Schema(description = "应发工资")
    @Excel(name = "应发工资")
    private BigDecimal payableWages;
    /**
     * åº”扣工资
     */
    @ApiModelProperty("应扣工资")
    @Schema(description = "应扣工资")
    @Excel(name = "应扣工资")
    private BigDecimal deductibleWages;
    /**
     * å®žå‘工资
     */
    @ApiModelProperty("实发工资")
    @Schema(description = "实发工资")
    @Excel(name = "实发工资")
    private BigDecimal actualWages;
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    @Schema(description = "备注")
    @Excel(name = "备注")
    private String remark;
@@ -185,4 +184,7 @@
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/compensationperformance/service/impl/CompensationPerformanceServiceImpl.java
@@ -6,8 +6,8 @@
import com.ruoyi.compensationperformance.mapper.CompensationPerformanceMapper;
import com.ruoyi.compensationperformance.pojo.CompensationPerformance;
import com.ruoyi.compensationperformance.service.CompensationPerformanceService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -18,10 +18,10 @@
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class CompensationPerformanceServiceImpl extends ServiceImpl<CompensationPerformanceMapper, CompensationPerformance> implements CompensationPerformanceService {
    @Autowired
    private CompensationPerformanceMapper compensationPerformanceMapper;
    private final CompensationPerformanceMapper compensationPerformanceMapper;
    @Override
src/main/java/com/ruoyi/customervisits/controller/CustomerVisitsController.java
@@ -9,8 +9,9 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
@@ -20,23 +21,23 @@
 * @date : 2025/8/29 10:28
 */
@RestController
@Api(tags = "客户拜访")
@Tag(name = "客户拜访")
@RequestMapping("/customerVisits")
@AllArgsConstructor
public class CustomerVisitsController extends BaseController {
    @Autowired
    private CustomerVisitsServiceImpl customerVisitsService;
    @GetMapping("/listPage")
    @Log(title = "客户拜访-分页查询", businessType = BusinessType.OTHER)
    @ApiOperation("客户拜访-分页查询")
    @Operation(summary = "客户拜访-分页查询")
    public AjaxResult listPage(Page page, CustomerVisits customerVisits) {
        IPage<CustomerVisits> listPage = customerVisitsService.listPage(page, customerVisits);
        return AjaxResult.success(listPage);
    }
    @Log(title = "客户拜访-添加", businessType = BusinessType.INSERT)
    @ApiOperation("客户拜访-添加")
    @Operation(summary = "客户拜访-添加")
    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult add(@RequestBody CustomerVisits customerVisits) {
@@ -48,7 +49,7 @@
    }
    @Log(title = "客户拜访-编辑", businessType = BusinessType.UPDATE)
    @ApiOperation("客户拜访-编辑")
    @Operation(summary = "客户拜访-编辑")
    @PostMapping("update")
    public AjaxResult updateCustomerVisit(@RequestBody CustomerVisits customerVisits) {
        boolean updateResult = customerVisitsService.updateCustomerVisit(customerVisits);
@@ -59,7 +60,7 @@
    }
    @Log(title = "客户拜访-删除", businessType = BusinessType.DELETE)
    @ApiOperation("客户拜访-删除")
    @Operation(summary = "客户拜访-删除")
    @DeleteMapping("{customerId}")
    public AjaxResult deleteCustomerVisit(@PathVariable Integer customerId) {
        if (customerId == null) {
src/main/java/com/ruoyi/customervisits/pojo/CustomerVisits.java
@@ -4,8 +4,7 @@
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -21,7 +20,7 @@
@TableName("customer_visits")
@Data
@Builder
@ApiModel
@Schema
public class CustomerVisits {
    private static final long serialVersionUID = 1L;
@@ -32,55 +31,55 @@
    /**
     * å®¢æˆ·åç§°
     */
    @ApiModelProperty("客户名称")
    @Schema(description = "客户名称")
    private String customerName;
    /**
     * è”系人
     */
    @ApiModelProperty("联系人")
    @Schema(description = "联系人")
    private String contact;
    /**
     * è”系电话
     */
    @ApiModelProperty("联系电话")
    @Schema(description = "联系电话")
    private String contactPhone;
    /**
     * ä½ç½®
     */
    @ApiModelProperty("位置")
    @Schema(description = "位置")
    private String location;
    /**
     * æ‹œè®¿äºº
     */
    @ApiModelProperty("拜访人")
    @Schema(description = "拜访人")
    private String visitingPeople;
    /**
     * æ‹œè®¿ç›®çš„
     */
    @ApiModelProperty("拜访目的")
    @Schema(description = "拜访目的")
    private String purposeVisit;
    /**
     * æ‹œè®¿æ—¶é—´
     */
    @ApiModelProperty("拜访时间")
    @Schema(description = "拜访时间")
    private String purposeDate;
    /**
     * æ‹œè®¿åœ°å€
     */
    @ApiModelProperty("拜访地址")
    @Schema(description = "拜访地址")
    private String visitAddress;
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    @Schema(description = "备注")
    private String remark;
    /**
@@ -112,4 +111,7 @@
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
@@ -8,8 +8,8 @@
import com.ruoyi.customervisits.mapper.CustomerVisitsMapper;
import com.ruoyi.customervisits.pojo.CustomerVisits;
import com.ruoyi.customervisits.service.CustomerVisitsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -19,10 +19,10 @@
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class CustomerVisitsServiceImpl extends ServiceImpl<CustomerVisitsMapper, CustomerVisits> implements CustomerVisitsService {
    @Autowired
    private CustomerVisitsMapper customerVisitsMapper;
    private final CustomerVisitsMapper customerVisitsMapper;
    @Override
    public IPage<CustomerVisits> listPage(Page page, CustomerVisits customerVisits) {
src/main/java/com/ruoyi/device/controller/DeviceDefectRecordController.java
@@ -5,23 +5,23 @@
import com.ruoyi.device.pojo.DeviceDefectRecord;
import com.ruoyi.device.service.DeviceDefectRecordService;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Api(tags = "设备缺陷记录管理")
@Tag(name = "设备缺陷记录管理")
@RequestMapping("/defect")
@AllArgsConstructor
@RestController
public class DeviceDefectRecordController {
    @Autowired
    private DeviceDefectRecordService deviceDefectRecordService;
    @ApiOperation("设备缺陷记录列表")
    @Operation(summary = "设备缺陷记录列表")
    @GetMapping("/page")
    public AjaxResult page(Page page , DeviceDefectRecordDto deviceDefectRecordDto) {
        return AjaxResult.success(deviceDefectRecordService.listPage(page,deviceDefectRecordDto));
    }
    @ApiOperation("设备id查询设备缺陷记录列表")
    @Operation(summary = "设备id查询设备缺陷记录列表")
    @GetMapping("/find/{deviceLedgerId}")
    public AjaxResult find(@PathVariable Long deviceLedgerId) {
        DeviceDefectRecordDto deviceDefectRecordDto = new DeviceDefectRecordDto();
@@ -30,17 +30,17 @@
    }
    @PostMapping("/add")
    @ApiOperation("添加设备缺陷记录")
    @Operation(summary = "添加设备缺陷记录")
    public AjaxResult add(@RequestBody DeviceDefectRecord deviceDefectRecord) {
        return AjaxResult.success(deviceDefectRecordService.add(deviceDefectRecord));
    }
    @PostMapping("/update")
    @ApiOperation("修改设备缺陷记录")
    @Operation(summary = "修改设备缺陷记录")
    public AjaxResult update(@RequestBody DeviceDefectRecord deviceDefectRecord) {
        return AjaxResult.success(deviceDefectRecordService.updateByDDR(deviceDefectRecord));
    }
    @DeleteMapping("/delete")
    @ApiOperation("删除设备缺陷记录")
    @Operation(summary = "删除设备缺陷记录")
    public AjaxResult delete(@PathVariable Long id) {
        return AjaxResult.success(deviceDefectRecordService.removeById(id));
    }
src/main/java/com/ruoyi/device/controller/DeviceLedgerController.java
@@ -3,7 +3,9 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.device.dto.DeviceLedgerDto;
import com.ruoyi.device.execl.DeviceLedgerExeclDto;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.mapper.DeviceMaintenanceMapper;
import com.ruoyi.device.pojo.DeviceLedger;
@@ -11,60 +13,56 @@
import com.ruoyi.device.service.IDeviceLedgerService;
import com.ruoyi.framework.aspectj.lang.annotation.Anonymous;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@Api(tags = "设备台账管理")
@Tag(name = "设备台账管理")
@RequestMapping("/device/ledger")
@RestController
@AllArgsConstructor
public class DeviceLedgerController {
    @Autowired
    private IDeviceLedgerService deviceLedgerService;
    @Autowired
    private DeviceLedgerMapper deviceLedgerMapper;
    @Autowired
    private DeviceMaintenanceMapper deviceMaintenanceMapper;
    @ApiOperation("设备台账列表")
    @Operation(summary = "设备台账列表")
    @GetMapping("/page")
    public AjaxResult page(Page page , DeviceLedgerDto deviceLedger) {
        return AjaxResult.success(deviceLedgerService.queryPage(page,deviceLedger));
    }
    @PostMapping()
    @ApiOperation("添加设备台账")
    @Operation(summary = "添加设备台账")
    public AjaxResult add(@RequestBody DeviceLedger deviceLedger) {
        return deviceLedgerService.saveDeviceLedger(deviceLedger);
    }
    @ApiOperation("根据id查询设备台账")
    @Operation(summary = "根据id查询设备台账")
    @GetMapping("/{id}")
    public AjaxResult detail(@PathVariable Long id) {
        return AjaxResult.success(deviceLedgerService.getById(id));
    }
    @PutMapping ()
    @ApiOperation("修改设备台账")
    @Operation(summary = "修改设备台账")
    public AjaxResult update(@RequestBody DeviceLedger deviceLedger) {
        return deviceLedgerService.updateDeviceLedger(deviceLedger);
    }
    @DeleteMapping("/{ids}")
    @ApiOperation("删除设备台账")
    @Operation(summary = "删除设备台账")
    public AjaxResult delete(@PathVariable("ids") ArrayList<Long> ids) {
        boolean b = deviceLedgerService.removeBatchByIds(ids);
        if (!b) {
@@ -74,13 +72,20 @@
    }
    @PostMapping("export")
    @ApiOperation("导出设备台账")
    @Operation(summary = "导出设备台账")
    public void export(HttpServletResponse response, Long[] ids) {
         deviceLedgerService.export(response, ids);
    }
    @PostMapping("import")
    @ApiOperation("导入设备台账")
    @Operation(summary = "下载模板")
    @PostMapping("/downloadTemplate")
    public void downloadTemplate(HttpServletResponse response) {
        ExcelUtil<DeviceLedgerExeclDto> util = new ExcelUtil<>(DeviceLedgerExeclDto.class);
        util.importTemplateExcel(response, "设备导入模板");
    }
    @PostMapping("/import")
    @Operation(summary = "导入设备台账")
    public AjaxResult importData(MultipartFile file) throws IOException {
        Boolean b = deviceLedgerService.importData(file);
        if (b) {
@@ -91,14 +96,14 @@
    @GetMapping("getDeviceLedger")
    @ApiOperation("获取设备台账")
    @Operation(summary = "获取设备台账")
    public AjaxResult getDeviceLedger( ) {
        return AjaxResult.success(deviceLedgerService.list(new QueryWrapper<DeviceLedger>().lambda()
                .select(DeviceLedger::getId, DeviceLedger::getDeviceName,DeviceLedger::getDeviceModel)));
    }
    @GetMapping("scanDevice")
    @ApiOperation("获取设备台账")
    @Operation(summary = "获取设备台账")
    @Anonymous
    public AjaxResult scanDevice(Long id) {
        List<DeviceMaintenance> list = deviceMaintenanceMapper.list1(id);
src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java
@@ -8,50 +8,48 @@
import com.ruoyi.device.service.IDeviceLedgerService;
import com.ruoyi.device.service.IDeviceMaintenanceService;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
@Api(tags = "设备保养")
@Tag(name = "设备保养")
@RestController
@RequestMapping("/device/maintenance")
@AllArgsConstructor
public class DeviceMaintenanceController {
    @Autowired
    private IDeviceMaintenanceService deviceMaintenanceService;
    @Autowired
    private IDeviceLedgerService deviceLedgerService;
    @ApiOperation("设备保养列表")
    @Operation(summary = "设备保养列表")
    @GetMapping("/page")
    public AjaxResult page(Page page , DeviceMaintenanceDto deviceMaintenanceDto) {
        return AjaxResult.success(deviceMaintenanceService.queryPage(page,deviceMaintenanceDto));
    }
    @PostMapping()
    @ApiOperation("添加设备保养")
    public AjaxResult add(@RequestBody DeviceMaintenance deviceMaintenance) {
    @Operation(summary = "添加设备保养")
    public AjaxResult add(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
        DeviceLedger byId = deviceLedgerService.getById(deviceMaintenance.getDeviceLedgerId());
        deviceMaintenance.setDeviceName(byId.getDeviceName());
        deviceMaintenance.setDeviceModel(byId.getDeviceModel());
        return deviceMaintenanceService.saveDeviceRepair(deviceMaintenance);
    }
    @ApiOperation("根据id查询设备保养")
    @Operation(summary = "根据id查询设备保养")
    @GetMapping("/{id}")
    public AjaxResult detail(@PathVariable Long id) {
        return AjaxResult.success(deviceMaintenanceService.detailById(id));
    }
    @PutMapping ()
    @ApiOperation("修改设备保养")
    public AjaxResult update(@RequestBody DeviceMaintenance deviceMaintenance) {
    @Operation(summary = "修改设备保养")
    public AjaxResult update(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
        DeviceLedger byId = deviceLedgerService.getById(deviceMaintenance.getDeviceLedgerId());
        deviceMaintenance.setDeviceName(byId.getDeviceName());
        deviceMaintenance.setDeviceModel(byId.getDeviceModel());
@@ -59,14 +57,14 @@
    }
    @PostMapping ("maintenance")
    @ApiOperation("修改设备保养")
    public AjaxResult maintenance(@RequestBody DeviceMaintenance deviceMaintenance) {
    @Operation(summary = "修改设备保养")
    public AjaxResult maintenance(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
        return deviceMaintenanceService.updateDeviceDeviceMaintenance(deviceMaintenance);
    }
    @DeleteMapping("/{ids}")
    @ApiOperation("删除设备保养")
    @Operation(summary = "删除设备保养")
    public AjaxResult delete(@PathVariable("ids") Long[] ids) {
        boolean b = deviceMaintenanceService.removeBatchByIds(Arrays.asList(ids));
        if (!b) {
@@ -76,7 +74,7 @@
    }
    @PostMapping("export")
    @ApiOperation("导出设备保养")
    @Operation(summary = "导出设备保养")
    public void export(HttpServletResponse response, Long[] ids) {
        deviceMaintenanceService.export(response, ids);
    }
src/main/java/com/ruoyi/device/controller/DeviceMaintenanceFileController.java
@@ -5,11 +5,11 @@
import com.ruoyi.device.pojo.DeviceMaintenanceFile;
import com.ruoyi.device.service.DeviceMaintenanceFileService;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import java.util.List;
/**
@@ -22,7 +22,7 @@
 */
@RestController
@RequestMapping("/maintenanceTaskFile")
@Api(tags = "设备保养附件")
@Tag(name = "设备保养附件")
public class DeviceMaintenanceFileController {
    @Resource
src/main/java/com/ruoyi/device/controller/DeviceRepairController.java
@@ -4,61 +4,56 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.device.dto.DeviceRepairDto;
import com.ruoyi.device.pojo.DeviceRepair;
import com.ruoyi.device.service.IDeviceLedgerService;
import com.ruoyi.device.service.IDeviceRepairService;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
@Api(tags = "设备报修管理")
@Tag(name = "设备报修管理")
@RequestMapping("/device/repair")
@RestController
@AllArgsConstructor
public class DeviceRepairController {
    @Autowired
    private IDeviceRepairService deviceRepairService;
    @Autowired
    private IDeviceLedgerService deviceLedgerService;
    @ApiOperation("设备报修列表")
    @Operation(summary = "设备报修列表")
    @GetMapping("/page")
    public AjaxResult page(Page page , DeviceRepairDto deviceRepairDto) {
        return AjaxResult.success(deviceRepairService.queryPage(page,deviceRepairDto));
    }
    @PostMapping()
    @ApiOperation("添加设备报修")
    public AjaxResult add( @RequestBody DeviceRepair deviceRepair) {
        return deviceRepairService.saveDeviceRepair(deviceRepair);
    @Operation(summary = "添加设备报修")
    public AjaxResult add( @RequestBody DeviceRepairDto deviceRepairDto) {
        return deviceRepairService.saveDeviceRepair(deviceRepairDto);
    }
    @ApiOperation("根据id查询设备报修")
    @Operation(summary = "根据id查询设备报修")
    @GetMapping("/{id}")
    public AjaxResult detail(@PathVariable Long id) {
        DeviceRepairDto byId = deviceRepairService.detailById(id);
        return AjaxResult.success(byId);
        return AjaxResult.success(deviceRepairService.detailById(id));
    }
    @PutMapping ()
    @ApiOperation("修改设备报修")
    public AjaxResult update( @RequestBody DeviceRepair deviceRepair) {
        return deviceRepairService.updateDeviceRepair(deviceRepair);
    @Operation(summary = "修改设备报修")
    public AjaxResult update( @RequestBody DeviceRepairDto deviceRepairDto) {
        return deviceRepairService.updateDeviceRepair(deviceRepairDto);
    }
    @PostMapping ("repair")
    @ApiOperation("设备维修")
    public AjaxResult repair( @RequestBody DeviceRepair deviceRepair) {
        return deviceRepairService.updateDeviceRepair(deviceRepair);
    @Operation(summary = "设备维修")
    public AjaxResult repair( @RequestBody DeviceRepairDto deviceRepairDto) {
        return deviceRepairService.updateDeviceRepair(deviceRepairDto);
    }
    @DeleteMapping("/{ids}")
    @ApiOperation("删除设备报修")
    @Operation(summary = "删除设备报修")
    public AjaxResult delete(@PathVariable("ids") Long[] ids) {
        boolean b = deviceRepairService.removeBatchByIds(Arrays.asList(ids));
        if (!b) {
@@ -68,7 +63,7 @@
    }
    @PostMapping("export")
    @ApiOperation("导出设备报修")
    @Operation(summary = "导出设备报修")
    public void export(HttpServletResponse response, Long[] ids) {
        deviceRepairService.export(response, ids);
    }
src/main/java/com/ruoyi/device/controller/MaintenanceTaskController.java
@@ -7,8 +7,9 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -18,39 +19,39 @@
 * @author :yys
 * @date : 2025/12/22 14:58
 */
@Api(tags = "设备保养定时任务管理")
@Tag(name = "设备保养定时任务管理")
@RestController
@RequestMapping("/deviceMaintenanceTask")
@AllArgsConstructor
public class MaintenanceTaskController extends BaseController {
    @Autowired
    private MaintenanceTaskService maintenanceTaskService;
    @GetMapping("/listPage")
    @ApiOperation(value = "设备保养定时任务列表")
    @Operation(summary = "设备保养定时任务列表")
    public AjaxResult listPage(Page page, MaintenanceTask maintenanceTask) {
        return maintenanceTaskService.listPage(page,maintenanceTask);
    }
    @PostMapping("/add")
    @ApiOperation(value = "添加设备保养定时任务")
    @Operation(summary = "添加设备保养定时任务")
    @Log(title = "设备保养定时任务", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody MaintenanceTask maintenanceTask) {
        return maintenanceTaskService.add(maintenanceTask);
    }
    @PostMapping("/update")
    @ApiOperation(value = "修改设备保养定时任务")
    @Operation(summary = "修改设备保养定时任务")
    @Log(title = "设备保养定时任务", businessType = BusinessType.UPDATE)
    public AjaxResult update(@RequestBody MaintenanceTask maintenanceTask) {
        return maintenanceTaskService.updateByMaintenanceTaskId(maintenanceTask);
    }
    @DeleteMapping("/delete")
    @ApiOperation(value = "删除设备保养定时任务")
    @Operation(summary = "删除设备保养定时任务")
    @Log(title = "设备保养定时任务", businessType = BusinessType.DELETE)
    public AjaxResult delete(@RequestBody List<Long> ids) {
        return maintenanceTaskService.delete(ids);
src/main/java/com/ruoyi/device/dto/DeviceDefectRecordDto.java
@@ -1,14 +1,14 @@
package com.ruoyi.device.dto;
import com.ruoyi.device.pojo.DeviceDefectRecord;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class DeviceDefectRecordDto extends DeviceDefectRecord {
    @ApiModelProperty("设备名称")
    @Schema(description = "设备名称")
    private String deviceName;
    @ApiModelProperty("设备型号")
    @Schema(description = "设备型号")
    private String deviceModel;
}
src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java
@@ -5,7 +5,7 @@
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.dto.DateQueryDto;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -39,13 +39,13 @@
    /**
     * è®¾å¤‡å“ç‰Œ
     */
    @ApiModelProperty("设备品牌")
    @Schema(description = "设备品牌")
    private String deviceBrand;
    /**
     * å­˜æ”¾ä½ç½®
     */
    @ApiModelProperty("存放位置")
    @Schema(description = "存放位置")
    private String storageLocation;
@@ -115,34 +115,34 @@
     */
    private Long tenantId;
    @ApiModelProperty("状态")
    @Schema(description = "状态")
    private String status;
    @ApiModelProperty("计划运行时间")
    @Schema(description = "计划运行时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate planRuntimeTime;
    @ApiModelProperty("开始运行时间")
    @Schema(description = "开始运行时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime startRuntimeTime;
    @ApiModelProperty("结束运行时间")
    @Schema(description = "结束运行时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime endRuntimeTime;
    @ApiModelProperty("运行时长")
    @Schema(description = "运行时长")
    private String runtimeDuration;
    @ApiModelProperty("是否折旧 1-是 2-否")
    @Schema(description = "是否折旧 1-是 2-否")
    private Integer isDepr;
    @ApiModelProperty("每年折旧金额")
    @Schema(description = "每年折旧金额")
    private BigDecimal annualDepreciationAmount;
    @ApiModelProperty("设备类型")
    @Schema(description = "设备类型")
    private String type;
}
src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java
@@ -2,68 +2,56 @@
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModelProperty;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.device.pojo.DeviceMaintenance;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class DeviceMaintenanceDto {
public class DeviceMaintenanceDto extends DeviceMaintenance {
    @ApiModelProperty("设备保养id")
    @Schema(description = "设备保养id")
    private Long id;
    @ApiModelProperty("设备台账id")
    @Schema(description = "设备台账id")
    private Long deviceLedgerId;
    @ApiModelProperty("设备名称")
    @Schema(description = "设备名称")
    private String deviceName;
    @ApiModelProperty("规格型号")
    @Schema(description = "规格型号")
    private String deviceModel;
    @ApiModelProperty("计划保养日期")
    private String maintenancePlanTimeReq;
    @ApiModelProperty("计划保养日期")
    private String maintenancePlanTime;
    @ApiModelProperty("实际保养人")
    @Schema(description = "实际保养人")
    private String maintenanceActuallyName;
    @ApiModelProperty("实际保养日期")
    private String maintenanceActuallyTime;
    @ApiModelProperty("实际保养日期")
    private String maintenanceActuallyTimeReq;
    @ApiModelProperty("保养结果 0 ç»´ä¿® 1 å®Œå¥½")
    @Schema(description = "保养结果 0 ç»´ä¿® 1 å®Œå¥½")
    private String maintenanceResult;
    @ApiModelProperty("状态 0 å¾…保养 1 å®Œç»“ 2 å¤±è´¥")
    @Schema(description = "状态 0 å¾…保养 1 å®Œç»“ 2 å¤±è´¥")
    private Integer status;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.UPDATE)
    @Schema(description = "更新时间")
    private LocalDateTime updateTime;
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private String createUser;
    @Schema(description = "更新人")
    private String updateUserName;
    @ApiModelProperty("更新人")
    @TableField(fill = FieldFill.UPDATE)
    private String updateUser;
    @ApiModelProperty("租户id")
    @TableField(fill = FieldFill.INSERT)
    @Schema(description = "租户id")
    private Long tenantId;
    @ApiModelProperty("创建人名称")
    @Schema(description = "创建人名称")
    private String createUserName;
    @Schema(description = "保养图片")
    private List<StorageBlobDTO> storageBlobDTOs;
}
src/main/java/com/ruoyi/device/dto/DeviceRepairDto.java
@@ -1,74 +1,20 @@
package com.ruoyi.device.dto;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModelProperty;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.device.pojo.DeviceRepair;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@Data
public class DeviceRepairDto {
public class DeviceRepairDto extends DeviceRepair {
    @ApiModelProperty("设备报修id")
    private Long id;
    @ApiModelProperty("设备台账id")
    private Long deviceLedgerId;
    @ApiModelProperty("设备名称")
    private String deviceName;
    @ApiModelProperty("设备型号")
    private String deviceModel;
    @ApiModelProperty("报修时间")
    private Date repairTime;
    @Schema(description = "报修时间字符串")
    private String repairTimeStr;
    @ApiModelProperty("报修人")
    private String repairName;
    @ApiModelProperty("报修内容")
    private String remark;
    @ApiModelProperty("维修人")
    private String maintenanceName;
    @ApiModelProperty("维修时间")
    private Date maintenanceTime;
    @Schema(description = "维修时间字符串")
    private String maintenanceTimeStr;
    @ApiModelProperty("维修结果")
    private String maintenanceResult;
    @ApiModelProperty("状态")
    private Integer status;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private String createUser;
    @ApiModelProperty("更新人")
    @TableField(fill = FieldFill.UPDATE)
    private String updateUser;
    @ApiModelProperty("租户id")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    private List<StorageBlobDTO> storageBlobDTOs;
}
src/main/java/com/ruoyi/device/execl/DeviceLedgerExeclDto.java
@@ -1,9 +1,14 @@
package com.ruoyi.device.execl;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
@Data
public class DeviceLedgerExeclDto {
@@ -14,6 +19,18 @@
     */
    @Excel(name = "设备名称" ,sort = 1)
    private String deviceName;
    /**
     * è®¾å¤‡ç±»åž‹
     */
    @Excel(name = "设备类型",sort = 0,combo = {"生产设备","办公设备","检查设备","运输设备","其他设备"})
    private String type;
    @Schema(description = "计划运行时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "计划运行时间",sort = 10,dateFormat = "yyyy-MM-dd")
    private Date planRuntimeTime;
    /**
     * è§„格型号
@@ -36,8 +53,8 @@
    /**
     * æ•°é‡
     */
    @Excel(name = "数量",sort = 5)
    private BigDecimal number;
    @Excel(name = "数量",sort = 5, type = Excel.Type.EXPORT)
    private BigDecimal number = BigDecimal.ONE;
    /**
     * å«ç¨Žå•ä»·
@@ -48,7 +65,7 @@
    /**
     * å«ç¨Žæ€»ä»·
     */
    @Excel(name = "含税总价",sort = 7)
    @Excel(name = "含税总价",sort = 7, type = Excel.Type.EXPORT)
    private BigDecimal taxIncludingPriceTotal;
    /**
@@ -60,17 +77,8 @@
    /**
     * ä¸å«ç¨Žæ€»ä»·
     */
    @Excel(name = "不含税总价",sort = 9)
    @Excel(name = "不含税总价",sort = 9, type = Excel.Type.EXPORT)
    private BigDecimal unTaxIncludingPriceTotal;
//
//    /**
//     * å½•入时间
//     *
//     */
//    @Excel(name = "录入时间",sort = 10)
//    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
//    private LocalDateTime createTime;
    /**
src/main/java/com/ruoyi/device/execl/DeviceMaintenanceExeclDto.java
@@ -1,7 +1,7 @@
package com.ruoyi.device.execl;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@@ -13,39 +13,43 @@
    @ApiModelProperty("设备名称")
    @Schema(description = "设备名称")
    @Excel(name = "设备名称")
    private String deviceName;
    @Excel(name = "规格型号")
    @ApiModelProperty("规格型号")
    @Schema(description = "规格型号")
    private String deviceModel;
    @Schema(description = "项目")
    @Excel(name = "项目")
    private String machineryCategory;
    @Excel(name = "计划保养日期", dateFormat = "yyyy-MM-dd HH:mm:ss", width = 30)
    @ApiModelProperty("计划保养日期")
    @Schema(description = "计划保养日期")
    private Date maintenancePlanTime;
    @ApiModelProperty("实际保养人")
    @Schema(description = "实际保养人")
    @Excel(name = "实际保养人")
    private String maintenanceActuallyName;
    @ApiModelProperty("实际保养日期")
    @Schema(description = "实际保养日期")
    @Excel(name = "实际保养日期", dateFormat = "yyyy-MM-dd HH:mm:ss", width = 30)
    private LocalDateTime maintenanceActuallyTime;
    @ApiModelProperty("保养结果 0 ç»´ä¿® 1 å®Œå¥½")
    @Schema(description = "保养结果 0 ç»´ä¿® 1 å®Œå¥½")
    @Excel(name = "保养结果")
    private String maintenanceResult;
    @ApiModelProperty("状态 0 å¾…保养 1 å®Œç»“ 2 å¤±è´¥")
    @Schema(description = "状态 0 å¾…保养 1 å®Œç»“ 2 å¤±è´¥")
    @Excel(name = "状态")
    private String status;
    @ApiModelProperty("创建时间")
    @Schema(description = "创建时间")
    @Excel(name = "录入时间", dateFormat = "yyyy-MM-dd HH:mm:ss", width = 30)
    private Date createTime;
    @ApiModelProperty("创建人")
    @Schema(description = "创建人")
//    @Excel(name = "录入人")
    private String createUser;
src/main/java/com/ruoyi/device/execl/DeviceRepairExeclDto.java
@@ -2,7 +2,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@@ -11,48 +11,52 @@
@Data
public class DeviceRepairExeclDto {
    @ApiModelProperty("设备名称")
    @Schema(description = "设备名称")
    @Excel(name = "设备名称")
    private String deviceName;
    @ApiModelProperty("设备型号")
    @Schema(description = "设备型号")
    @Excel(name = "设备型号")
    private String deviceModel;
    @ApiModelProperty("报修时间")
    @Schema(description = "项目")
    @Excel(name = "项目")
    private String machineryCategory;
    @Schema(description = "报修时间")
    @Excel(name = "报修时间", width = 30, dateFormat = "yyyy-MM-dd")
    private Date repairTime;
    @ApiModelProperty("报修人")
    @Schema(description = "报修人")
    @Excel(name = "报修人")
    private String repairName;
    @ApiModelProperty("报修内容")
    @Schema(description = "报修内容")
    @Excel(name = "报修内容")
    private String remark;
    @ApiModelProperty("维修人")
    @Schema(description = "维修人")
    @Excel(name = "维修人")
    private String maintenanceName;
    @ApiModelProperty("维修时间")
    @Schema(description = "维修时间")
    @Excel(name = "维修时间", width = 30, dateFormat = "yyyy-MM-dd")
    private LocalDateTime maintenanceTime;
    @ApiModelProperty("维修结果")
    @Schema(description = "维修结果")
    @Excel(name = "维修结果")
    private String maintenanceResult;
    @ApiModelProperty("状态")
    @Schema(description = "状态")
    @Excel(name = "状态")
    private String statusStr;
    @ApiModelProperty("创建时间")
    @Schema(description = "创建时间")
    @Excel(name = "录入时间", width = 30, dateFormat = "yyyy-MM-dd")
    private LocalDateTime createTime;
//    @Excel(name = "录入人")
    @ApiModelProperty("创建人")
    @Schema(description = "创建人")
    private String createUser;
src/main/java/com/ruoyi/device/mapper/DeviceMaintenanceMapper.java
@@ -7,6 +7,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.device.dto.DeviceMaintenanceDto;
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.vo.DeviceMaintenanceVo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@@ -16,7 +17,7 @@
    IPage<DeviceMaintenanceDto> queryPage(Page page, DeviceMaintenanceDto deviceMaintenanceDto);
    DeviceMaintenanceDto detailById(Long id);
    DeviceMaintenanceVo detailById(Long id);
    @InterceptorIgnore(tenantLine = "true")
    List<DeviceMaintenance> list1(Long id);
src/main/java/com/ruoyi/device/mapper/DeviceRepairMapper.java
@@ -5,12 +5,13 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.device.dto.DeviceRepairDto;
import com.ruoyi.device.pojo.DeviceRepair;
import com.ruoyi.device.vo.DeviceRepairVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface DeviceRepairMapper extends BaseMapper<DeviceRepair> {
    IPage<DeviceRepairDto> queryPage(Page page, @Param("deviceRepairDto") DeviceRepairDto deviceRepairDto);
    IPage<DeviceRepairVo> queryPage(Page page, @Param("deviceRepairDto") DeviceRepairDto deviceRepairDto);
    DeviceRepairDto detailById(Long id);
    DeviceRepairVo detailById(Long id);
}
src/main/java/com/ruoyi/device/pojo/DeviceDefectRecord.java
@@ -4,7 +4,7 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -13,39 +13,42 @@
@Data
@TableName("device_defect_record")
public class DeviceDefectRecord {
    @ApiModelProperty("设备缺陷记录id")
    @Schema(description = "设备缺陷记录id")
    private Long id;
    @ApiModelProperty("设备台账id")
    @Schema(description = "设备台账id")
    private Long deviceLedgerId;
    @ApiModelProperty("缺陷描述")
    @Schema(description = "缺陷描述")
    private String defectDescription;
    @ApiModelProperty("状态")
    @Schema(description = "状态")
    private String status;
    @ApiModelProperty("消除时间")
    @Schema(description = "消除时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime eliminateTime;
    @ApiModelProperty("创建时间")
    @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;
    @ApiModelProperty("更新时间")
    @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;
    @ApiModelProperty("创建人")
    @Schema(description = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty("更新人")
    @Schema(description = "更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty("租户id")
    @Schema(description = "租户id")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/device/pojo/DeviceLedger.java
@@ -4,8 +4,7 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -18,7 +17,7 @@
 */
@Data
@TableName("device_ledger")
@ApiModel
@Schema
public class DeviceLedger {
    /**
@@ -30,25 +29,25 @@
    /**
     * è®¾å¤‡åç§°
     */
    @ApiModelProperty("设备名称")
    @Schema(description = "设备名称")
    private String deviceName;
    /**
     * è§„格型号
     */
    @ApiModelProperty("规格型号")
    @Schema(description = "规格型号")
    private String deviceModel;
    /**
     * è®¾å¤‡å“ç‰Œ
     */
    @ApiModelProperty("设备品牌")
    @Schema(description = "设备品牌")
    private String deviceBrand;
    /**
     * å­˜æ”¾ä½ç½®
     */
    @ApiModelProperty("存放位置")
    @Schema(description = "存放位置")
    private String storageLocation;
    /**
@@ -122,41 +121,44 @@
    /* ***************************     è¿è¡Œç®¡ç†        ***************************   */
    @ApiModelProperty("状态")
    @Schema(description = "状态")
    private String status;
    @ApiModelProperty("计划运行时间")
    @Schema(description = "计划运行时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate planRuntimeTime;
    @ApiModelProperty("开始运行时间")
    @Schema(description = "开始运行时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime startRuntimeTime;
    @ApiModelProperty("结束运行时间")
    @Schema(description = "结束运行时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime endRuntimeTime;
    @ApiModelProperty("运行时长")
    @Schema(description = "运行时长")
    private String runtimeDuration;
    @ApiModelProperty("是否折旧 1-是 2-否")
    @Schema(description = "是否折旧 1-是 2-否")
    private Integer isDepr;
    @ApiModelProperty("每年折旧金额")
    @Schema(description = "每年折旧金额")
    private BigDecimal annualDepreciationAmount;
    @TableField(exist = false)
    @ApiModelProperty("累计折旧")
    @Schema(description = "累计折旧")
    private BigDecimal deprAmount;
    @TableField(exist = false)
    @ApiModelProperty("净值")
    @Schema(description = "净值")
    private BigDecimal netValue;
    @ApiModelProperty("设备类型")
    @Schema(description = "设备类型")
    private String type;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java
@@ -4,84 +4,103 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
@Data
@TableName("device_maintenance")
@ApiModel("设备保养记录")
@Schema(name = "设备保养记录")
public class DeviceMaintenance {
    @ApiModelProperty("设备保养id")
    @Schema(description = "设备保养id")
    private Long id;
    @ApiModelProperty("设备台账id")
    @Schema(description = "设备台账id")
    private Long deviceLedgerId;
    @ApiModelProperty("保养任务id")
    @Schema(description = "保养任务id")
    private Long maintenanceTaskId;
    @ApiModelProperty(value = "频次")
    @Schema(description = "频次")
    private String frequencyType;
    @ApiModelProperty(value = "频次详情")
    @Schema(description = "频次详情")
    private String frequencyDetail;
    @ApiModelProperty(value = "下次执行时间")
    @Schema(description = "下次执行时间")
    private LocalDateTime nextExecutionTime;
    @ApiModelProperty(value = "最后执行时间")
    @Schema(description = "最后执行时间")
    private LocalDateTime lastExecutionTime;
    @Schema(description = "设备项目")
    private String machineryCategory;
    private String deviceName;
    private String deviceModel;
    @ApiModelProperty("计划保养日期")
    @Schema(description = "计划保养日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime maintenancePlanTime;
    @ApiModelProperty("实际保养人")
    @Schema(description = "实际保养人")
    private String maintenanceActuallyName;
    @ApiModelProperty("实际保养日期")
    @Schema(description = "实际保养日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime maintenanceActuallyTime;
    @ApiModelProperty("保养结果 0 ç»´ä¿® 1 å®Œå¥½")
    @Schema(description = "保养结果 0 ç»´ä¿® 1 å®Œå¥½")
    private String maintenanceResult;
    @ApiModelProperty("状态 0 å¾…保养 1 å®Œç»“ 2 å¤±è´¥")
    @Schema(description = "状态 0 å¾…保养 1 å®Œç»“ 2 å¤±è´¥")
    private Integer status;
    @ApiModelProperty("创建时间")
    @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;
    @ApiModelProperty("更新时间")
    @Schema(description = "更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("创建人")
    @Schema(description = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty("更新人")
    @Schema(description = "更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty("租户id")
    @Schema(description = "租户id")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @Schema(description = "领用备件ids")
    private String sparePartsIds;
    @Schema(description = "使用备件列表")
    @TableField(exist = false)
    private List<SparePartUse> sparePartsUseList;
    @Data
    public static class SparePartUse {
        private Long id;
        private Integer quantity;
    }
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/device/pojo/DeviceMaintenanceFile.java
@@ -7,8 +7,7 @@
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
@@ -23,7 +22,7 @@
@Getter
@Setter
@TableName("device_maintenance_file")
@ApiModel(value = "DeviceMaintenanceFile对象", description = "设备保养记录附件")
@Schema(name = "DeviceMaintenanceFile对象", description = "设备保养记录附件")
public class DeviceMaintenanceFile implements Serializable {
    private static final long serialVersionUID = 1L;
@@ -31,35 +30,38 @@
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty("文件名称")
    @Schema(description = "文件名称")
    private String name;
    @ApiModelProperty("文件路径")
    @Schema(description = "文件路径")
    private String url;
    @ApiModelProperty("文件大小")
    @Schema(description = "文件大小")
    private Integer fileSize;
    @ApiModelProperty("设备保养记录ID")
    @Schema(description = "设备保养记录ID")
    private Integer deviceMaintenanceId;
    @ApiModelProperty("创建时间")
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("创建用户")
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @ApiModelProperty("修改时间")
    @Schema(description = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("修改用户")
    @Schema(description = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    @ApiModelProperty("租户ID")
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/device/pojo/DeviceRepair.java
@@ -4,73 +4,92 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@Data
@TableName("device_repair")
public class DeviceRepair {
    @ApiModelProperty("设备报修id")
    @Schema(description = "设备报修id")
    private Long id;
    @ApiModelProperty("设备台账id")
    @Schema(description = "设备台账id")
    private Long deviceLedgerId;
    private String deviceName;
    private String deviceModel;
    @ApiModelProperty("报修时间")
    @Schema(description = "报修时间")
    private Date repairTime;
    @ApiModelProperty("报修人")
    @Schema(description = "报修人")
    private String repairName;
    @ApiModelProperty("报修内容")
    @Schema(description = "报修内容")
    private String remark;
    @ApiModelProperty("维修人")
    @Schema(description = "设备项目")
    private String machineryCategory;
    @Schema(description = "维修人")
    private String maintenanceName;
    @ApiModelProperty("维修时间")
    @Schema(description = "维修时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime maintenanceTime;
    @ApiModelProperty("维修结果")
    @Schema(description = "维修结果")
    private String maintenanceResult;
    @ApiModelProperty("状态 0 å¾…ç»´ä¿® 1完结 2 å¤±è´¥")
    @Schema(description = "状态 0 å¾…ç»´ä¿® 1完结 2 å¤±è´¥")
    private Integer status;
    @ApiModelProperty("创建时间")
    @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;
    @ApiModelProperty("更新时间")
    @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;
    @ApiModelProperty("创建人")
    @Schema(description = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty("更新人")
    @Schema(description = "更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty("租户id")
    @Schema(description = "租户id")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @Schema(description = "领用备件ids")
    private String sparePartsIds;
    @Schema(description = "使用备件列表")
    @TableField(exist = false)
    private List<SparePartUse> sparePartsUseList;
    @Data
    public static class SparePartUse {
        private Long id;
        private Integer quantity;
    }
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
@@ -1,13 +1,13 @@
package com.ruoyi.device.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 com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
@@ -19,13 +19,13 @@
 * @date : 2025/9/19 10:27
 */
@Data
@ApiModel
@Schema
@TableName("maintenance_task")
public class MaintenanceTask {
    private static final long serialVersionUID = 1L;
    @ApiModelProperty(value = "规格型号")
    @Schema(description = "规格型号")
    private String deviceModel;
    /**
@@ -34,76 +34,79 @@
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "设备名称")
    @Schema(description = "设备名称")
    @Excel(name = "保养任务名称")
    private String taskName;
    @ApiModelProperty(value = "设备id")
    @Schema(description = "设备id")
    private Long taskId;
    @ApiModelProperty(value = "频次")
    @Schema(description = "频次")
    @Excel(name = "频次")
    private String frequencyType;
    @ApiModelProperty(value = "频次详情")
    @Schema(description = "频次详情")
    @Excel(name = "开始日期与时间")
    private String frequencyDetail;
    @ApiModelProperty(value = "下次执行时间")
    @Schema(description = "下次执行时间")
    private LocalDateTime nextExecutionTime;
    @ApiModelProperty(value = "最后执行时间")
    @Schema(description = "最后执行时间")
    private LocalDateTime lastExecutionTime;
    @ApiModelProperty(value = "是否激活")
    @Schema(description = "是否激活")
    private boolean isActive;
    @ApiModelProperty(value = "备注")
    @Schema(description = "备注")
    @Excel(name = "备注")
    private String remarks;
    @ApiModelProperty(value = "录入人id")
    @Schema(description = "录入人id")
    private Long registrantId;
    @ApiModelProperty(value = "录入人")
    @Schema(description = "录入人")
    @Excel(name = "录入人")
    private String registrant;
    @ApiModelProperty(value = "录入日期")
    @Schema(description = "录入日期")
    @Excel(name = "录入日期", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate registrationDate;
    @ApiModelProperty(value = "状态")
    @Schema(description = "状态")
    private String status;
    @ApiModelProperty(value = "软删除标志,0=未删除,1=已删除")
    @Schema(description = "软删除标志,0=未删除,1=已删除")
    private Integer deleted;
    @TableField(exist = false)
    private String dateStr;
    @ApiModelProperty(value = "创建该记录的用户")
    @Schema(description = "创建该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "记录创建时间")
    @Schema(description = "记录创建时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
//    @JsonFormat(pattern = "yyyy-MM-dd")
//    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "最后修改该记录的用户")
    @Schema(description = "最后修改该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "记录最后更新时间")
    @Schema(description = "记录最后更新时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "租户ID")
    @Schema(description = "租户ID")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private Long tenantId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/device/service/IDeviceLedgerService.java
@@ -8,7 +8,7 @@
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public interface IDeviceLedgerService  extends IService<DeviceLedger> {
src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java
@@ -5,19 +5,20 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.device.dto.DeviceMaintenanceDto;
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.vo.DeviceMaintenanceVo;
import com.ruoyi.framework.web.domain.AjaxResult;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
public interface IDeviceMaintenanceService extends IService<DeviceMaintenance> {
    IPage<DeviceMaintenanceDto> queryPage(Page page, DeviceMaintenanceDto deviceMaintenanceDto);
    AjaxResult saveDeviceRepair(DeviceMaintenance deviceMaintenance);
    AjaxResult saveDeviceRepair(DeviceMaintenanceDto deviceMaintenance);
    AjaxResult updateDeviceDeviceMaintenance(DeviceMaintenance deviceMaintenance);
    AjaxResult updateDeviceDeviceMaintenance(DeviceMaintenanceDto deviceMaintenance);
    void export(HttpServletResponse response, Long[] ids);
    DeviceMaintenanceDto detailById(Long id);
    DeviceMaintenanceVo detailById(Long id);
}
src/main/java/com/ruoyi/device/service/IDeviceRepairService.java
@@ -5,20 +5,21 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.device.dto.DeviceRepairDto;
import com.ruoyi.device.pojo.DeviceRepair;
import com.ruoyi.device.vo.DeviceRepairVo;
import com.ruoyi.framework.web.domain.AjaxResult;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
public interface IDeviceRepairService extends IService<DeviceRepair> {
    IPage<DeviceRepairDto> queryPage(Page page, DeviceRepairDto deviceRepairDto);
    IPage<DeviceRepairVo> queryPage(Page page, DeviceRepairDto deviceRepairDto);
    AjaxResult saveDeviceRepair(DeviceRepair deviceRepair);
    AjaxResult saveDeviceRepair(DeviceRepairDto deviceRepairDto);
    AjaxResult updateDeviceRepair(DeviceRepair deviceRepair);
    AjaxResult updateDeviceRepair(DeviceRepairDto deviceRepairDto);
    void export(HttpServletResponse response, Long[] ids);
    DeviceRepairDto detailById(Long id);
    DeviceRepairVo detailById(Long id);
}
src/main/java/com/ruoyi/device/service/impl/DeviceDefectRecordServiceImpl.java
@@ -10,7 +10,7 @@
import com.ruoyi.device.pojo.DeviceDefectRecord;
import com.ruoyi.device.pojo.DeviceRepair;
import com.ruoyi.device.service.DeviceDefectRecordService;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -19,12 +19,10 @@
@Service
@Transactional(rollbackFor = Exception.class)
@RequiredArgsConstructor
public class DeviceDefectRecordServiceImpl extends ServiceImpl<DeviceDefectRecordMapper, DeviceDefectRecord> implements DeviceDefectRecordService {
    @Autowired
    private DeviceDefectRecordMapper deviceDefectRecordMapper;
    @Autowired
    private DeviceRepairMapper deviceRepairMapper;
    private final DeviceDefectRecordMapper deviceDefectRecordMapper;
    private final DeviceRepairMapper deviceRepairMapper;
    @Override
    public IPage<DeviceDefectRecordDto> listPage(Page page, DeviceDefectRecordDto deviceDefectRecordDto) {
src/main/java/com/ruoyi/device/service/impl/DeviceLedgerServiceImpl.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
@@ -15,27 +16,26 @@
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import lombok.AllArgsConstructor;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
@AllArgsConstructor
@RequiredArgsConstructor
@Slf4j
public class DeviceLedgerServiceImpl  extends ServiceImpl<DeviceLedgerMapper, DeviceLedger> implements IDeviceLedgerService {
    @Autowired
    private DeviceLedgerMapper deviceLedgerMapper;
    @Autowired
    private SysUserMapper sysUserMapper;
    private final DeviceLedgerMapper deviceLedgerMapper;
    private final SysUserMapper sysUserMapper;
    @Override
    public IPage<DeviceLedgerDto> queryPage(Page page, DeviceLedgerDto deviceLedger) {
@@ -112,6 +112,11 @@
                deviceLedger.setCreateUser(SecurityUtils.getUserId().intValue());
            }
            BeanUtils.copyProperties(c,deviceLedger);
            // é€šè¿‡å«ç¨Žå•价、数量、税率计算含税总价,不含税总价
            deviceLedger.setTaxIncludingPriceTotal(c.getTaxIncludingPriceUnit());
            deviceLedger.setNumber(BigDecimal.ONE);
            deviceLedger.setPlanRuntimeTime(DateUtils.toLocalDate(c.getPlanRuntimeTime()));
            deviceLedger.setUnTaxIncludingPriceTotal(deviceLedger.getTaxIncludingPriceTotal().divide(BigDecimal.ONE.add(c.getTaxRate()),2, RoundingMode.HALF_UP));
            deviceLedgerMapper.insert(deviceLedger);
        });
src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
@@ -4,6 +4,8 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.device.dto.DeviceMaintenanceDto;
@@ -11,22 +13,32 @@
import com.ruoyi.device.mapper.DeviceMaintenanceMapper;
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.service.IDeviceMaintenanceService;
import com.ruoyi.device.vo.DeviceMaintenanceVo;
import com.ruoyi.device.vo.DeviceRepairVo;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import com.ruoyi.measuringinstrumentledger.mapper.SparePartsMapper;
import com.ruoyi.measuringinstrumentledger.pojo.SpareParts;
import com.ruoyi.measuringinstrumentledger.pojo.SparePartsRequisitionRecord;
import com.ruoyi.measuringinstrumentledger.service.SparePartsRequisitionRecordService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
@Service
@RequiredArgsConstructor
public class DeviceMaintenanceServiceImpl extends ServiceImpl<DeviceMaintenanceMapper, DeviceMaintenance> implements IDeviceMaintenanceService {
    @Autowired
    private DeviceMaintenanceMapper deviceMaintenanceMapper;
    private final DeviceMaintenanceMapper deviceMaintenanceMapper;
    private final SparePartsMapper sparePartsMapper;
    private final SparePartsRequisitionRecordService sparePartsRequisitionRecordService;
    private final FileUtil fileUtil;
    @Override
    public IPage<DeviceMaintenanceDto> queryPage(Page page, DeviceMaintenanceDto deviceMaintenanceDto) {
@@ -35,17 +47,57 @@
    }
    @Override
    public AjaxResult saveDeviceRepair(DeviceMaintenance deviceMaintenance) {
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult saveDeviceRepair(DeviceMaintenanceDto deviceMaintenance) {
        boolean save = this.save(deviceMaintenance);
        if (save){
            // å¤„理图片上传
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_MAINTENANCE, deviceMaintenance.getId(), deviceMaintenance.getStorageBlobDTOs());
            return AjaxResult.success();
        }
        return AjaxResult.error();
    }
    @Override
    public AjaxResult updateDeviceDeviceMaintenance(DeviceMaintenance deviceMaintenance) {
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult updateDeviceDeviceMaintenance(DeviceMaintenanceDto deviceMaintenance) {
        DeviceMaintenance oldDeviceMaintenance = this.getById(deviceMaintenance.getId());
        // å¤„理备件使用情况
        if (com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(deviceMaintenance.getSparePartsUseList())) {
            List<Long> sparePartIds = new ArrayList<>();
            for (DeviceMaintenance.SparePartUse sparePartUse : deviceMaintenance.getSparePartsUseList()) {
                // èŽ·å–å¤‡ä»¶ä¿¡æ¯
                SpareParts spareParts = sparePartsMapper.selectById(sparePartUse.getId());
                if (spareParts != null) {
                    // æ£€æŸ¥æ•°é‡æ˜¯å¦è¶³å¤Ÿ
                    if (spareParts.getQuantity().compareTo(new BigDecimal(sparePartUse.getQuantity())) >= 0) {
                        // æ›´æ–°æ•°é‡
                        spareParts.setQuantity(spareParts.getQuantity().subtract(new BigDecimal(sparePartUse.getQuantity())));
                        sparePartsMapper.updateById(spareParts);
                        sparePartIds.add(sparePartUse.getId());
                        // åˆ›å»ºå¤‡ä»¶é¢†ç”¨è®°å½•
                        SparePartsRequisitionRecord record = new SparePartsRequisitionRecord();
                        record.setSourceType(1); // 1 ä¿å…»
                        record.setSourceId(deviceMaintenance.getId());
                        record.setDeviceLedgerId(oldDeviceMaintenance.getDeviceLedgerId());
                        record.setSparePartsId(sparePartUse.getId());
                        record.setQuantity(sparePartUse.getQuantity());
                        sparePartsRequisitionRecordService.save(record);
                    } else {
                        return AjaxResult.error("备件 " + spareParts.getName() + " æ•°é‡ä¸è¶³");
                    }
                }
            }
            // æ›´æ–°å¤‡ä»¶IDs字段
            if (!sparePartIds.isEmpty()) {
                deviceMaintenance.setSparePartsIds(StringUtils.join(sparePartIds, ","));
            }
        }
        if (this.updateById(deviceMaintenance)) {
            // å¤„理图片上传
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_MAINTENANCE, deviceMaintenance.getId(), deviceMaintenance.getStorageBlobDTOs());
            return AjaxResult.success();
        }
        return AjaxResult.error();
@@ -67,8 +119,9 @@
    }
    @Override
    public DeviceMaintenanceDto detailById(Long id) {
        return deviceMaintenanceMapper.detailById(id);
    public DeviceMaintenanceVo detailById(Long id) {
        DeviceMaintenanceVo vo = deviceMaintenanceMapper.detailById(id);
        vo.setStorageBlobVOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.DEVICE_MAINTENANCE, id));
        return vo;
    }
}
src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
@@ -3,6 +3,10 @@
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.github.xiaoymin.knife4j.core.util.CollectionUtils;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.device.dto.DeviceDefectRecordDto;
@@ -14,50 +18,99 @@
import com.ruoyi.device.service.DeviceDefectRecordService;
import com.ruoyi.device.service.IDeviceLedgerService;
import com.ruoyi.device.service.IDeviceRepairService;
import com.ruoyi.device.vo.DeviceRepairVo;
import com.ruoyi.framework.web.domain.AjaxResult;
import lombok.AllArgsConstructor;
import com.ruoyi.measuringinstrumentledger.mapper.SparePartsMapper;
import com.ruoyi.measuringinstrumentledger.pojo.SpareParts;
import com.ruoyi.measuringinstrumentledger.pojo.SparePartsRequisitionRecord;
import com.ruoyi.measuringinstrumentledger.service.SparePartsRequisitionRecordService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
@AllArgsConstructor
@RequiredArgsConstructor
@Slf4j
public class DeviceRepairServiceImpl extends ServiceImpl<DeviceRepairMapper, DeviceRepair> implements IDeviceRepairService {
    @Autowired
    private DeviceDefectRecordService deviceDefectRecordService;
    @Autowired
    private DeviceRepairMapper deviceRepairMapper;
    @Autowired
    private IDeviceLedgerService deviceLedgerService;
    @Override
    public IPage<DeviceRepairDto> queryPage(Page page, DeviceRepairDto deviceRepairDto) {
    private final DeviceDefectRecordService deviceDefectRecordService;
    private final DeviceRepairMapper deviceRepairMapper;
    private final IDeviceLedgerService deviceLedgerService;
    private final SparePartsMapper sparePartsMapper;
    private final SparePartsRequisitionRecordService sparePartsRequisitionRecordService;
    private final FileUtil fileUtil;
        return deviceRepairMapper.queryPage(page, deviceRepairDto);
    @Override
    public IPage<DeviceRepairVo> queryPage(Page page, DeviceRepairDto deviceRepairDto) {
        IPage<DeviceRepairVo> pageDto = deviceRepairMapper.queryPage(page, deviceRepairDto);
        for (DeviceRepairVo vo : pageDto.getRecords()) {
           vo.setStorageBlobVOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.DEVICE_REPAIR, vo.getId()));
        }
        return pageDto;
    }
    @Override
    public AjaxResult saveDeviceRepair(DeviceRepair deviceRepair) {
        DeviceLedger byId = deviceLedgerService.getById(deviceRepair.getDeviceLedgerId());
        deviceRepair.setDeviceName(byId.getDeviceName());
        deviceRepair.setDeviceModel(byId.getDeviceModel());
        boolean save = this.save(deviceRepair);
        if (save){
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult saveDeviceRepair(DeviceRepairDto deviceRepairDto) {
        DeviceLedger byId = deviceLedgerService.getById(deviceRepairDto.getDeviceLedgerId());
        deviceRepairDto.setDeviceName(byId.getDeviceName());
        deviceRepairDto.setDeviceModel(byId.getDeviceModel());
        boolean save = this.save(deviceRepairDto);
        if (save) {
            // å¤„理图片上传
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_REPAIR, deviceRepairDto.getId(), deviceRepairDto.getStorageBlobDTOs());
            return AjaxResult.success();
        }
        return AjaxResult.error();
        return AjaxResult.error("保存失败");
    }
    @Override
    public AjaxResult updateDeviceRepair(DeviceRepair deviceRepair) {
        if (this.updateById(deviceRepair)) {
            Long id = deviceRepair.getId();
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult updateDeviceRepair(DeviceRepairDto deviceRepairDto) {
        DeviceRepair oldDeviceRepair = this.getById(deviceRepairDto.getId());
        // å¤„理备件使用情况
        if (CollectionUtils.isNotEmpty(deviceRepairDto.getSparePartsUseList())) {
            List<Long> sparePartIds = new ArrayList<>();
            for (DeviceRepairDto.SparePartUse sparePartUse : deviceRepairDto.getSparePartsUseList()) {
                // èŽ·å–å¤‡ä»¶ä¿¡æ¯
                SpareParts spareParts = sparePartsMapper.selectById(sparePartUse.getId());
                if (spareParts != null) {
                    // æ£€æŸ¥æ•°é‡æ˜¯å¦è¶³å¤Ÿ
                    if (spareParts.getQuantity().compareTo(new BigDecimal(sparePartUse.getQuantity())) >= 0) {
                        // æ›´æ–°æ•°é‡
                        spareParts.setQuantity(spareParts.getQuantity().subtract(new BigDecimal(sparePartUse.getQuantity())));
                        sparePartsMapper.updateById(spareParts);
                        sparePartIds.add(sparePartUse.getId());
                        // åˆ›å»ºå¤‡ä»¶é¢†ç”¨è®°å½•
                        SparePartsRequisitionRecord record = new SparePartsRequisitionRecord();
                        record.setSourceType(0); // 0 ç»´ä¿®
                        record.setSourceId(deviceRepairDto.getId());
                        record.setDeviceLedgerId(oldDeviceRepair.getDeviceLedgerId());
                        record.setSparePartsId(sparePartUse.getId());
                        record.setQuantity(sparePartUse.getQuantity());
                        sparePartsRequisitionRecordService.save(record);
                    } else {
                        return AjaxResult.error("备件 " + spareParts.getName() + " æ•°é‡ä¸è¶³");
                    }
                }
            }
            // æ›´æ–°å¤‡ä»¶IDs字段
            if (!sparePartIds.isEmpty()) {
                deviceRepairDto.setSparePartsIds(StringUtils.join(sparePartIds, ","));
            }
        }
        if (this.updateById(deviceRepairDto)) {
            Long id = deviceRepairDto.getId();
            //
            DeviceDefectRecordDto deviceDefectRecordDto = new DeviceDefectRecordDto();
            deviceDefectRecordDto.setDeviceLedgerId(id);
@@ -69,6 +122,8 @@
                    deviceDefectRecordService.updateByDDR(deviceDefectRecord);
                });
            }
            // å¤„理图片上传
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_REPAIR, id, deviceRepairDto.getStorageBlobDTOs());
            return AjaxResult.success();
        }
        return AjaxResult.error();
@@ -109,9 +164,10 @@
    }
    @Override
    public DeviceRepairDto detailById(Long id) {
        return deviceRepairMapper.detailById(id);
    public DeviceRepairVo detailById(Long id) {
        DeviceRepairVo vo = deviceRepairMapper.detailById(id);
        vo.setStorageBlobVOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.DEVICE_REPAIR, id));
        return vo;
    }
}
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java
@@ -2,8 +2,8 @@
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.pojo.MaintenanceTask;
import lombok.RequiredArgsConstructor;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
@@ -19,14 +19,13 @@
@Component
@DisallowConcurrentExecution // ç¦æ­¢å¹¶å‘执行同一个Job
@RequiredArgsConstructor
public class MaintenanceTaskJob implements Job, Serializable {
    private static final long serialVersionUID = 1L; // å¿…须定义序列化ID
    @Autowired
    private DeviceMaintenanceServiceImpl deviceMaintenanceService;
    private final DeviceMaintenanceServiceImpl deviceMaintenanceService;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    private final JdbcTemplate jdbcTemplate;
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskScheduler.java
@@ -1,9 +1,9 @@
package com.ruoyi.device.service.impl;
import com.ruoyi.device.pojo.MaintenanceTask;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalTime;
@@ -19,10 +19,10 @@
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class MaintenanceTaskScheduler {
    @Autowired
    private Scheduler scheduler;
    private final Scheduler scheduler;
    /**
     * æ·»åŠ æ–°ä»»åŠ¡åˆ°è°ƒåº¦å™¨
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
@@ -11,8 +11,8 @@
import com.ruoyi.inspectiontask.service.impl.TimingTaskServiceImpl;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@@ -24,19 +24,13 @@
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class MaintenanceTaskServiceImpl extends ServiceImpl<MaintenanceTaskMapper, MaintenanceTask> implements MaintenanceTaskService {
    @Autowired
    private MaintenanceTaskMapper maintenanceTaskMapper;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private TimingTaskServiceImpl timingTaskService;
    @Autowired
    private MaintenanceTaskScheduler maintenanceTaskScheduler;
    private final MaintenanceTaskMapper maintenanceTaskMapper;
    private final SysUserMapper sysUserMapper;
    private final TimingTaskServiceImpl timingTaskService;
    private final MaintenanceTaskScheduler maintenanceTaskScheduler;
    @Override
    public AjaxResult listPage(Page page, MaintenanceTask maintenanceTask) {
src/main/java/com/ruoyi/device/vo/DeviceMaintenanceVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package com.ruoyi.device.vo;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.device.pojo.DeviceMaintenance;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class DeviceMaintenanceVo extends DeviceMaintenance {
    @Schema(description = "设备保养id")
    private Long id;
    @Schema(description = "设备台账id")
    private Long deviceLedgerId;
    @Schema(description = "设备名称")
    private String deviceName;
    @Schema(description = "规格型号")
    private String deviceModel;
    @Schema(description = "实际保养人")
    private String maintenanceActuallyName;
    @Schema(description = "保养结果 0 ç»´ä¿® 1 å®Œå¥½")
    private String maintenanceResult;
    @Schema(description = "状态 0 å¾…保养 1 å®Œç»“ 2 å¤±è´¥")
    private Integer status;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "更新时间")
    private LocalDateTime updateTime;
    @Schema(description = "更新人")
    private String updateUserName;
    @Schema(description = "租户id")
    private Long tenantId;
    @Schema(description = "创建人名称")
    private String createUserName;
    @Schema(description = "保养图片列表")
    private List<StorageBlobVO> storageBlobVOs;
}
src/main/java/com/ruoyi/device/vo/DeviceRepairVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.device.vo;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.device.pojo.DeviceRepair;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
public class DeviceRepairVo extends DeviceRepair {
    @Schema(description = "报修时间字符串")
    private String repairTimeStr;
    @Schema(description = "维修时间字符串")
    private String maintenanceTimeStr;
    private List<StorageBlobVO> storageBlobVOs;
}
src/main/java/com/ruoyi/dto/DateQueryDto.java
@@ -1,8 +1,7 @@
package com.ruoyi.dto;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
@@ -10,14 +9,14 @@
 * @date : 2025/7/23 11:31
 */
@Data
@ApiModel
@Schema
public class DateQueryDto {
    @ApiModelProperty(value = "开始时间")
    @Schema(description = "开始时间")
    @TableField(exist = false)
    private String entryDateStart;
    @ApiModelProperty(value = "结束时间")
    @Schema(description = "结束时间")
    @TableField(exist = false)
    private String entryDateEnd;
src/main/java/com/ruoyi/dto/MapDto.java
@@ -1,7 +1,6 @@
package com.ruoyi.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@@ -11,16 +10,16 @@
 * @date : 2025/7/25 10:37
 */
@Data
@ApiModel
@Schema
public class MapDto {
    @ApiModelProperty(value = "名称")
    @Schema(description = "名称")
    private String name;
    @ApiModelProperty(value = "数量")
    @Schema(description = "数量")
    private String value;
    @ApiModelProperty(value = "占比")
    @Schema(description = "占比")
    private String rate;
}
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/ElectricityConsumptionAreaController.java
@@ -2,10 +2,6 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductTreeDto;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.equipmentenergyconsumption.dto.ElectricityConsumptionAreaTreeDto;
import com.ruoyi.equipmentenergyconsumption.pojo.ElectricityConsumptionArea;
import com.ruoyi.equipmentenergyconsumption.service.ElectricityConsumptionAreaService;
@@ -13,22 +9,21 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@Api(tags = "用电区域")
@Tag(name = "用电区域")
@RequestMapping("/electricityConsumptionArea")
@AllArgsConstructor
public class ElectricityConsumptionAreaController extends BaseController {
    @Autowired
    private ElectricityConsumptionAreaService electricityConsumptionAreaService;
    private final ElectricityConsumptionAreaService electricityConsumptionAreaService;
    /**
     * æŸ¥è¯¢æ ‘结构
@@ -39,7 +34,7 @@
    }
    @GetMapping("/listPage")
    @ApiOperation("用电区域-分页查询")
    @Operation(summary = "用电区域-分页查询")
    @Log(title = "用电区域-分页查询", businessType = BusinessType.OTHER)
    public AjaxResult listPage(Page page, ElectricityConsumptionArea electricityConsumptionArea) {
        IPage<ElectricityConsumptionArea> listPage = electricityConsumptionAreaService.listPage(page, electricityConsumptionArea);
@@ -47,7 +42,7 @@
    }
    @PostMapping("/add")
    @ApiOperation("用电区域-新增")
    @Operation(summary = "用电区域-新增")
    @Log(title = "用电区域-新增", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody ElectricityConsumptionArea electricityConsumptionArea) {
        boolean save = electricityConsumptionAreaService.saveOrUpdate(electricityConsumptionArea);
@@ -55,7 +50,7 @@
    }
    @DeleteMapping("/delete")
    @ApiOperation("用电区域-删除")
    @Operation(summary = "用电区域-删除")
    @Log(title = "用电区域-删除", businessType = BusinessType.DELETE)
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请选择至少一条数据");
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/EnergyPeriodController.java
@@ -3,30 +3,30 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.equipmentenergyconsumption.pojo.ElectricityConsumptionArea;
import com.ruoyi.equipmentenergyconsumption.pojo.EnergyPeriod;
import com.ruoyi.equipmentenergyconsumption.service.EnergyPeriodService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@RequestMapping("/energyPeriod")
@Api(tags = "用电时段")
@Tag(name = "用电时段")
@AllArgsConstructor
public class EnergyPeriodController extends BaseController {
    @Autowired
    private EnergyPeriodService energyPeriodService;
    private final EnergyPeriodService energyPeriodService;
    @GetMapping("/listPage")
    @ApiOperation("用电时段-分页查询")
    @Operation(summary = "用电时段-分页查询")
    @Log(title = "用电时段-分页查询", businessType = BusinessType.OTHER)
    public AjaxResult listPage(Page page, EnergyPeriod energyPeriod) {
        IPage<EnergyPeriod> listPage = energyPeriodService.listPage(page, energyPeriod);
@@ -34,7 +34,7 @@
    }
    @PostMapping("/add")
    @ApiOperation("用电时段-新增")
    @Operation(summary = "用电时段-新增")
    @Log(title = "用电时段-新增", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody EnergyPeriod energyPeriod) {
        boolean save = energyPeriodService.save(energyPeriod);
@@ -42,7 +42,7 @@
    }
    @PostMapping("/addBatch")
    @ApiOperation("用电时段-批量新增")
    @Operation(summary = "用电时段-批量新增")
    @Log(title = "用电时段-批量新增", businessType = BusinessType.INSERT)
    public AjaxResult addBatch(@RequestBody List<EnergyPeriod> energyPeriods) {
        boolean save = energyPeriodService.saveBatch(energyPeriods);
@@ -50,7 +50,7 @@
    }
    @PostMapping("/update")
    @ApiOperation("用电时段-修改")
    @Operation(summary = "用电时段-修改")
    @Log(title = "用电时段-修改", businessType = BusinessType.UPDATE)
    public AjaxResult update(@RequestBody EnergyPeriod energyPeriod) {
        boolean update = energyPeriodService.updateById(energyPeriod);
@@ -58,10 +58,10 @@
    }
    @DeleteMapping("/delete")
    @ApiOperation("用电时段-删除")
    @Operation(summary = "用电时段-删除")
    @Log(title = "用电时段-删除", businessType = BusinessType.DELETE)
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请选择至少一条数据");
        if (CollectionUtils.isEmpty(ids)) return AjaxResult.error("请选择至少一条数据");
        boolean remove = energyPeriodService.removeBatchByIds(ids);
        return remove ? AjaxResult.success() : AjaxResult.error("删除失败");
    }
@@ -72,13 +72,13 @@
     */
    @Log(title = "导出用电时段", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    @ApiOperation("导出用电时段")
    @Operation(summary = "导出用电时段")
    public void export(HttpServletResponse response) {
        Page page = new Page(-1,-1);
        Page page = new Page(-1, -1);
        EnergyPeriod energyPeriod = new EnergyPeriod();
        IPage<EnergyPeriod> listPage = energyPeriodService.listPage(page, energyPeriod);
        ExcelUtil<EnergyPeriod> util = new ExcelUtil<EnergyPeriod>(EnergyPeriod.class);
        util.exportExcel(response, listPage.getRecords() , "用电时段数据");
        util.exportExcel(response, listPage.getRecords(), "用电时段数据");
    }
}
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/EquipmentEnergyConsumptionController.java
@@ -13,15 +13,16 @@
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;
@@ -30,16 +31,15 @@
 * @date : 2025/7/29 13:19
 */
@RestController
@Api(tags = "设备能耗")
@Tag(name = "设备能耗")
@RequestMapping("/equipmentEnergyConsumption")
@AllArgsConstructor
public class EquipmentEnergyConsumptionController extends BaseController {
    @Autowired
    private EquipmentEnergyConsumptionService equipmentEnergyConsumptionService;
    @GetMapping("/listPage")
    @ApiOperation("设备能耗-分页查询")
    @Operation(summary = "设备能耗-分页查询")
    @Log(title = "设备能耗-分页查询", businessType = BusinessType.OTHER)
    public AjaxResult listPage(Page page, EquipmentEnergyConsumption equipmentEnergyConsumption) {
        IPage<EquipmentEnergyConsumption> listPage = equipmentEnergyConsumptionService.listPage(page, equipmentEnergyConsumption);
@@ -47,7 +47,7 @@
    }
    @GetMapping("/deviceList")
    @ApiOperation("设备台账-查询")
    @Operation(summary = "设备台账-查询")
    @Log(title = "设备台账-查询", businessType = BusinessType.OTHER)
    public AjaxResult deviceList(DeviceLedger deviceLedger) {
        List<DeviceLedger> listPage = equipmentEnergyConsumptionService.deviceList(deviceLedger);
@@ -55,7 +55,7 @@
    }
    @PostMapping("/add")
    @ApiOperation("设备能耗-新增")
    @Operation(summary = "设备能耗-新增")
    @Log(title = "设备能耗-新增", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody EquipmentEnergyConsumption equipmentEnergyConsumption) {
        boolean save = equipmentEnergyConsumptionService.save(equipmentEnergyConsumption);
@@ -63,7 +63,7 @@
    }
    @PostMapping("/addBatch")
    @ApiOperation("设备能耗-批量新增")
    @Operation(summary = "设备能耗-批量新增")
    @Log(title = "设备能耗-批量新增", businessType = BusinessType.INSERT)
    public AjaxResult addBatch(@RequestBody List<EquipmentEnergyConsumption> list) {
        boolean save = equipmentEnergyConsumptionService.saveBatch(list);
@@ -71,7 +71,7 @@
    }
    @PostMapping("/update")
    @ApiOperation("设备能耗-修改")
    @Operation(summary = "设备能耗-修改")
    @Log(title = "设备能耗-修改", businessType = BusinessType.UPDATE)
    public AjaxResult update(@RequestBody EquipmentEnergyConsumption equipmentEnergyConsumption) {
        boolean update = equipmentEnergyConsumptionService.updateById(equipmentEnergyConsumption);
@@ -79,7 +79,7 @@
    }
    @DeleteMapping("/delete")
    @ApiOperation("设备能耗-删除")
    @Operation(summary = "设备能耗-删除")
    @Log(title = "设备能耗-删除", businessType = BusinessType.DELETE)
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请选择至少一条数据");
@@ -92,7 +92,7 @@
     */
    @Log(title = "导入设备能耗", businessType = BusinessType.IMPORT)
    @PostMapping("/importData")
    @ApiOperation("导入设备能耗")
    @Operation(summary = "导入设备能耗")
    public AjaxResult importData(MultipartFile file) throws Exception {
        return equipmentEnergyConsumptionService.importData(file);
    }
@@ -102,7 +102,7 @@
     */
    @Log(title = "导出设备能耗", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    @ApiOperation("导出设备能耗")
    @Operation(summary = "导出设备能耗")
    public void export(HttpServletResponse response) {
        Page page = new Page<>(-1,-1);
        EquipmentEnergyConsumption equipmentEnergyConsumption = new EquipmentEnergyConsumption();
@@ -112,7 +112,7 @@
    }
    @GetMapping("/listPageByTrend")
    @ApiOperation("设备能耗-能源趋势-分页查询")
    @Operation(summary = "设备能耗-能源趋势-分页查询")
    @Log(title = "设备能耗-能源趋势-分页查询", businessType = BusinessType.OTHER)
    public AjaxResult listPageByTrend(Page page, EquipmentEnergyConsumption equipmentEnergyConsumption) {
        IPage<EquipmentEnergyConsumption> listPage = equipmentEnergyConsumptionService.listPageByTrend(page, equipmentEnergyConsumption);
@@ -124,7 +124,7 @@
     */
    @Log(title = "导出能源趋势", businessType = BusinessType.EXPORT)
    @PostMapping("/exportTwo")
    @ApiOperation("导出能源趋势")
    @Operation(summary = "导出能源趋势")
    public void exportTwo(HttpServletResponse response) {
        Page page = new Page<>(-1,-1);
        EquipmentEnergyConsumption equipmentEnergyConsumption = new EquipmentEnergyConsumption();
src/main/java/com/ruoyi/equipmentenergyconsumption/dto/ElectricityConsumptionAreaTreeDto.java
@@ -3,8 +3,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.ruoyi.basic.dto.ProductTreeDto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
src/main/java/com/ruoyi/equipmentenergyconsumption/dto/EquipmentEnergyConsumptionDto.java
@@ -6,7 +6,7 @@
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@@ -28,28 +28,28 @@
    /**
     * è®¾å¤‡ç¼–号
     */
    @ApiModelProperty("设备编号")
    @Schema(description = "设备编号")
    @Excel(name = "规格型号")
    private String code;
    /**
     * è®¾å¤‡åç§°
     */
    @ApiModelProperty("设备名称")
    @Schema(description = "设备名称")
    @Excel(name = "设备名称")
    private String name;
    /**
     * é¢å®šåŠŸçŽ‡
     */
    @ApiModelProperty("额定功率")
    @Schema(description = "额定功率")
//    @Excel(name = "额定功率")
    private String powerRating;
    /**
     * å®žé™…功率
     */
    @ApiModelProperty("实际功率")
    @Schema(description = "实际功率")
//    @Excel(name = "实际功率")
    private String powerActual;
@@ -57,14 +57,14 @@
    /**
     * å½“日用电量
     */
    @ApiModelProperty("当日用电量")
    @Schema(description = "当日用电量")
//    @Excel(name = "当日用电量")
    private BigDecimal dayNum;
    /**
     * æ˜¨ç”¨ç”µé‡
     */
    @ApiModelProperty("昨用电量")
    @Schema(description = "昨用电量")
    @TableField(exist = false)
    @Excel(name = "昨用电量")
    private BigDecimal toDayNum;
@@ -72,7 +72,7 @@
    /**
     * æœ¬æœˆå¹³å‡ç”µé‡ï¼ˆ30天计算)
     */
    @ApiModelProperty("本月平均电量(30天计算)")
    @Schema(description = "本月平均电量(30天计算)")
    @TableField(exist = false)
    @Excel(name = "本月平均电量")
    private BigDecimal avgNum;
@@ -81,7 +81,7 @@
    /**
     * è¶‹åŠ¿
     */
    @ApiModelProperty("趋势")
    @Schema(description = "趋势")
    @TableField(exist = false)
    @Excel(name = "趋势")
    private String trend;
@@ -89,13 +89,13 @@
    /**
     * ç´¯è®¡ç”¨ç”µé‡
     */
    @ApiModelProperty("累计用电量")
    @Schema(description = "累计用电量")
//    @Excel(name = "累计用电量")
    private BigDecimal sumNum;
    /**
     * è¿è¡Œæ—¶é—´
     */
    @ApiModelProperty("运行时间")
    @Schema(description = "运行时间")
    @Excel(name = "运行时间" , width = 30, dateFormat = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date runDate;
@@ -103,7 +103,7 @@
    /**
     * æ¯æ—¥é™åˆ¶ç”µé‡
     */
    @ApiModelProperty("每日限制电量")
    @Schema(description = "每日限制电量")
//    @Excel(name = "每日限制电量")
    private BigDecimal everyNum;
src/main/java/com/ruoyi/equipmentenergyconsumption/pojo/ElectricityConsumptionArea.java
@@ -2,14 +2,13 @@
import com.baomidou.mybatisplus.annotation.*;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@TableName("electricity_consumption_area")
@ApiModel
@Schema
public class ElectricityConsumptionArea {
        @TableId(value = "id", type = IdType.AUTO)
@@ -18,13 +17,13 @@
        /**
         * åŒºåŸŸåç§°
         */
        @ApiModelProperty("区域名称")
        @Schema(description = "区域名称")
        private String areaName;
        /**
         * åŒºåŸŸç±»åž‹
         */
        @ApiModelProperty("区域类型")
        @Schema(description = "区域类型")
        private String areaType;
        /**
         * æŽ’序
@@ -40,4 +39,11 @@
         */
        @TableField(fill = FieldFill.INSERT)
        private Long tenantId;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/equipmentenergyconsumption/pojo/EnergyPeriod.java
@@ -2,29 +2,28 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Date;
@Data
@TableName("energy_period")
@ApiModel
@Schema
public class EnergyPeriod {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * æ—¥æœŸ
     */
    @ApiModelProperty("日期")
    @Schema(description = "日期")
    @Excel(name = "日期", width = 30, dateFormat = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date date;
    /**
     * å•ä»·
     */
    @ApiModelProperty("单价")
    @Schema(description = "单价")
    @Excel(name = "单价")
    private Double price;
    /**
@@ -35,25 +34,32 @@
    /**
     * å³°æ®µ
     */
    @ApiModelProperty("峰段")
    @Schema(description = "峰段")
    @Excel(name = "峰段")
    private Double peak;
    /**
     * è°·æ®µ
     */
    @ApiModelProperty("谷段")
    @Schema(description = "谷段")
    @Excel(name = "谷段")
    private Double valley;
    /**
     * å¹³æ®µ
     */
    @ApiModelProperty("平段")
    @Schema(description = "平段")
    @Excel(name = "平段")
    private Double flat;
    /**
     * å°–段
     */
    @ApiModelProperty("尖段")
    @Schema(description = "尖段")
    @Excel(name = "尖段")
    private Double sharp;
    @Schema(description = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/equipmentenergyconsumption/pojo/EquipmentEnergyConsumption.java
@@ -3,8 +3,7 @@
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
@@ -19,7 +18,7 @@
 */
@Data
@TableName("equipment_energy_consumption")
@ApiModel
@Schema
public class EquipmentEnergyConsumption {
    private static final long serialVersionUID = 1L;
@@ -30,28 +29,28 @@
    /**
     * è®¾å¤‡ç¼–号
     */
    @ApiModelProperty("设备编号")
    @Schema(description = "设备编号")
    @Excel(name = "设备编号")
    private String code;
    /**
     * è®¾å¤‡åç§°
     */
    @ApiModelProperty("设备名称")
    @Schema(description = "设备名称")
    @Excel(name = "设备名称")
    private String name;
    /**
     * é¢å®šåŠŸçŽ‡
     */
    @ApiModelProperty("额定功率")
    @Schema(description = "额定功率")
    @Excel(name = "额定功率")
    private String powerRating;
    /**
     * å®žé™…功率
     */
    @ApiModelProperty("实际功率")
    @Schema(description = "实际功率")
    @Excel(name = "实际功率")
    private String powerActual;
@@ -59,21 +58,21 @@
    /**
     * å½“日用电量
     */
    @ApiModelProperty("当日用电量")
    @Schema(description = "当日用电量")
    @Excel(name = "当日用电量")
    private BigDecimal dayNum;
    /**
     * æ˜¨ç”¨ç”µé‡
     */
    @ApiModelProperty("昨用电量")
    @Schema(description = "昨用电量")
    @TableField(exist = false)
    private BigDecimal toDayNum;
    /**
     * æœ¬æœˆå¹³å‡ç”µé‡ï¼ˆ30天计算)
     */
    @ApiModelProperty("本月平均电量(30天计算)")
    @Schema(description = "本月平均电量(30天计算)")
    @TableField(exist = false)
    private BigDecimal avgNum;
@@ -81,20 +80,20 @@
    /**
     * è¶‹åŠ¿
     */
    @ApiModelProperty("趋势")
    @Schema(description = "趋势")
    @TableField(exist = false)
    private String trend;
    /**
     * ç´¯è®¡ç”¨ç”µé‡
     */
    @ApiModelProperty("累计用电量")
    @Schema(description = "累计用电量")
    @Excel(name = "累计用电量")
    private BigDecimal sumNum;
    /**
     * è¿è¡Œæ—¶é—´
     */
    @ApiModelProperty("运行时间")
    @Schema(description = "运行时间")
    @Excel(name = "运行时间" , width = 30, dateFormat = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date runDate;
@@ -102,7 +101,7 @@
    /**
     * æ¯æ—¥é™åˆ¶ç”µé‡
     */
    @ApiModelProperty("每日限制电量")
    @Schema(description = "每日限制电量")
    @Excel(name = "每日限制电量")
    private BigDecimal everyNum;
@@ -139,4 +138,7 @@
     *所属用电区域id
     */
    private Long electricityConsumptionAreaId;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/equipmentenergyconsumption/service/impl/ElectricityConsumptionAreaServiceImpl.java
@@ -4,16 +4,13 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.ProductTreeDto;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.equipmentenergyconsumption.dto.ElectricityConsumptionAreaTreeDto;
import com.ruoyi.equipmentenergyconsumption.mapper.ElectricityConsumptionAreaMapper;
import com.ruoyi.equipmentenergyconsumption.pojo.ElectricityConsumptionArea;
import com.ruoyi.equipmentenergyconsumption.service.ElectricityConsumptionAreaService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@@ -22,10 +19,10 @@
@Service
@Slf4j
@RequiredArgsConstructor
public class ElectricityConsumptionAreaServiceImpl extends ServiceImpl<ElectricityConsumptionAreaMapper, ElectricityConsumptionArea> implements ElectricityConsumptionAreaService {
    @Autowired
    private ElectricityConsumptionAreaMapper electricityConsumptionAreaMapper;
    private final ElectricityConsumptionAreaMapper electricityConsumptionAreaMapper;
    @Override
    public IPage<ElectricityConsumptionArea> listPage(Page page, ElectricityConsumptionArea electricityConsumptionArea) {
src/main/java/com/ruoyi/equipmentenergyconsumption/service/impl/EnergyPeriodServiceImpl.java
@@ -6,15 +6,15 @@
import com.ruoyi.equipmentenergyconsumption.mapper.EnergyPeriodMapper;
import com.ruoyi.equipmentenergyconsumption.pojo.EnergyPeriod;
import com.ruoyi.equipmentenergyconsumption.service.EnergyPeriodService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
@RequiredArgsConstructor
public class EnergyPeriodServiceImpl extends ServiceImpl<EnergyPeriodMapper, EnergyPeriod> implements EnergyPeriodService {
    @Autowired
    private EnergyPeriodMapper energyPeriodMapper;
    private final EnergyPeriodMapper energyPeriodMapper;
    @Override
    public IPage<EnergyPeriod> listPage(Page page, EnergyPeriod energyPeriod) {
src/main/java/com/ruoyi/equipmentenergyconsumption/service/impl/EquipmentEnergyConsumptionServiceImpl.java
@@ -4,7 +4,6 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.pojo.DeviceLedger;
@@ -12,8 +11,8 @@
import com.ruoyi.equipmentenergyconsumption.pojo.EquipmentEnergyConsumption;
import com.ruoyi.equipmentenergyconsumption.service.EquipmentEnergyConsumptionService;
import com.ruoyi.framework.web.domain.AjaxResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
@@ -29,14 +28,12 @@
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class EquipmentEnergyConsumptionServiceImpl extends ServiceImpl<EquipmentEnergyConsumptionMapper, EquipmentEnergyConsumption> implements EquipmentEnergyConsumptionService {
    @Autowired
    private EquipmentEnergyConsumptionMapper equipmentEnergyConsumptionMapper;
    @Autowired
    private DeviceLedgerMapper deviceLedgerMapper;
    private final EquipmentEnergyConsumptionMapper equipmentEnergyConsumptionMapper;
    private final DeviceLedgerMapper deviceLedgerMapper;
    @Override
    public IPage<EquipmentEnergyConsumption> listPage(Page page, EquipmentEnergyConsumption equipmentEnergyConsumption) {
src/main/java/com/ruoyi/framework/aspectj/LogAspect.java
@@ -2,8 +2,8 @@
import java.util.Collection;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
src/main/java/com/ruoyi/framework/aspectj/RateLimiterAspect.java
@@ -1,8 +1,10 @@
package com.ruoyi.framework.aspectj;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.aspectj.lang.annotation.RateLimiter;
import com.ruoyi.framework.aspectj.lang.enums.LimitType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@@ -13,11 +15,10 @@
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.aspectj.lang.annotation.RateLimiter;
import com.ruoyi.framework.aspectj.lang.enums.LimitType;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
/**
 * é™æµå¤„理
src/main/java/com/ruoyi/framework/config/DruidConfig.java
@@ -1,28 +1,25 @@
package com.ruoyi.framework.config;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.aspectj.lang.enums.DataSourceType;
import com.ruoyi.framework.config.properties.DruidProperties;
import com.ruoyi.framework.datasource.DynamicDataSource;
import jakarta.servlet.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.framework.aspectj.lang.enums.DataSourceType;
import com.ruoyi.framework.config.properties.DruidProperties;
import com.ruoyi.framework.datasource.DynamicDataSource;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
 * druid é…ç½®å¤šæ•°æ®æº
@@ -96,7 +93,7 @@
        Filter filter = new Filter()
        {
            @Override
            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
            public void init(jakarta.servlet.FilterConfig filterConfig) throws ServletException
            {
            }
            @Override
src/main/java/com/ruoyi/framework/config/FilterConfig.java
@@ -1,16 +1,18 @@
package com.ruoyi.framework.config;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.DispatcherType;
import com.ruoyi.common.filter.RepeatableFilter;
import com.ruoyi.common.filter.XssFilter;
import com.ruoyi.common.utils.StringUtils;
import jakarta.servlet.DispatcherType;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.ruoyi.common.filter.RepeatableFilter;
import com.ruoyi.common.filter.XssFilter;
import com.ruoyi.common.utils.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
 * Filter配置
@@ -18,6 +20,7 @@
 * @author ruoyi
 */
@Configuration
@RequiredArgsConstructor
public class FilterConfig
{
    @Value("${xss.excludes}")
src/main/java/com/ruoyi/framework/config/MybatisPlusConfig.java
@@ -7,87 +7,68 @@
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import com.ruoyi.common.handler.CustomTenantLineHandler;
import com.ruoyi.common.interceptor.DataScopeSqlInterceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.sql.SQLException;
/**
 * Mybatis Plus é…ç½®
 *
 * @author ruoyi
 * MyBatis Plus config.
 */
@EnableTransactionManagement(proxyTargetClass = true)
@Configuration
public class MybatisPlusConfig
{
public class MybatisPlusConfig {
    @Autowired
    private DataScopeSqlInterceptor dataScopeSqlInterceptor;
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor()
    {
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // ç§Ÿæˆ·æ’ä»¶
//        TenantLineInnerInterceptor tenantLineInnerInterceptor = new TenantLineInnerInterceptor(new CustomTenantLineHandler());
//        interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
        // åˆ†é¡µæ’ä»¶
        // Rewrite the original SQL before pagination generates the count query.
        interceptor.addInnerInterceptor(dataScopeSqlInterceptor);
        interceptor.addInnerInterceptor(paginationInnerInterceptor());
        // ä¹è§‚锁插件
        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
        // é˜»æ–­æ’ä»¶
        interceptor.addInnerInterceptor(blockAttackInnerInterceptor());
        return interceptor;
    }
    /**
     * åˆ†é¡µæ’件,自动识别数据库类型 https://baomidou.com/guide/interceptor-pagination.html
     * Pagination interceptor.
     */
//    public PaginationInnerInterceptor paginationInnerInterceptor()
//    {
//        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
//        // è®¾ç½®æ•°æ®åº“类型为mysql
//        paginationInnerInterceptor.setDbType(DbType.MYSQL);
//        // è®¾ç½®æœ€å¤§å•页限制数量,默认 500 æ¡ï¼Œ-1 ä¸å—限制
//        paginationInnerInterceptor.setMaxLimit(-1L);
//        return paginationInnerInterceptor;
//    }
    public PaginationInnerInterceptor paginationInnerInterceptor() {
        PaginationInnerInterceptor interceptor = new PaginationInnerInterceptor(DbType.MYSQL) {
            @Override
            public void beforeQuery(Executor executor, MappedStatement ms, Object parameter,
                                    RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
                                    RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
                IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);
                if (page != null && page.getSize() <= 0) {
                    // å½“size<=0时,不进行分页
                    return;
                }
                super.beforeQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);
            }
        };
        interceptor.setMaxLimit(1000L); // å»ºè®®è®¾ç½®åˆç†çš„æœ€å¤§å€¼
        interceptor.setMaxLimit(1000L);
        return interceptor;
    }
    /**
     * ä¹è§‚锁插件 https://baomidou.com/guide/interceptor-optimistic-locker.html
     * Optimistic lock interceptor.
     */
    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor()
    {
    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
        return new OptimisticLockerInnerInterceptor();
    }
    /**
     * å¦‚果是对全表的删除或更新操作,就会终止该操作 https://baomidou.com/guide/interceptor-block-attack.html
     * Block full-table update and delete.
     */
    public BlockAttackInnerInterceptor blockAttackInnerInterceptor()
    {
    public BlockAttackInnerInterceptor blockAttackInnerInterceptor() {
        return new BlockAttackInnerInterceptor();
    }
}
}
src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
@@ -1,7 +1,8 @@
package com.ruoyi.framework.config;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
@@ -11,8 +12,8 @@
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
import java.util.concurrent.TimeUnit;
/**
 * é€šç”¨é…ç½®
@@ -20,10 +21,10 @@
 * @author ruoyi
 */
@Configuration
@RequiredArgsConstructor
public class ResourcesConfig implements WebMvcConfigurer
{
    @Autowired
    private RepeatSubmitInterceptor repeatSubmitInterceptor;
    private final RepeatSubmitInterceptor repeatSubmitInterceptor;
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry)
src/main/java/com/ruoyi/framework/config/ScheduleConfig.java
@@ -3,7 +3,7 @@
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.scheduling.quartz.SchedulerFactoryBean;
//import javax.sql.DataSource;
//import jakarta.sql.DataSource;
//import java.util.Properties;
//
///**
src/main/java/com/ruoyi/framework/config/SecurityConfig.java
@@ -1,6 +1,10 @@
package com.ruoyi.framework.config;
import org.springframework.beans.factory.annotation.Autowired;
import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
@@ -16,10 +20,6 @@
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;
import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
/**
 * spring security配置
@@ -28,43 +28,38 @@
 */
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
@RequiredArgsConstructor
public class SecurityConfig
{
    /**
     * è‡ªå®šä¹‰ç”¨æˆ·è®¤è¯é€»è¾‘
     */
    @Autowired
    private UserDetailsService userDetailsService;
    private final UserDetailsService userDetailsService;
    
    /**
     * è®¤è¯å¤±è´¥å¤„理类
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;
    private final AuthenticationEntryPointImpl unauthorizedHandler;
    /**
     * é€€å‡ºå¤„理类
     */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;
    private final LogoutSuccessHandlerImpl logoutSuccessHandler;
    /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;
    private final JwtAuthenticationTokenFilter authenticationTokenFilter;
    
    /**
     * è·¨åŸŸè¿‡æ»¤å™¨
     */
    @Autowired
    private CorsFilter corsFilter;
    private final CorsFilter corsFilter;
    /**
     * å…è®¸åŒ¿åè®¿é—®çš„地址
     */
    @Autowired
    private PermitAllUrlProperties permitAllUrl;
    private final PermitAllUrlProperties permitAllUrl;
    /**
     * èº«ä»½éªŒè¯å®žçް
@@ -108,16 +103,51 @@
            // åŸºäºŽtoken,所以不需要session
            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            // æ³¨è§£æ ‡è®°å…è®¸åŒ¿åè®¿é—®çš„url
            .authorizeHttpRequests((requests) -> {
                permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
                // å¯¹äºŽç™»å½•login æ³¨å†Œregister éªŒè¯ç captchaImage å…è®¸åŒ¿åè®¿é—®
                requests.antMatchers("/login", "/register", "/captchaImage","/loginCheck","/userLoginFacotryList/**","/loginCheckFactory").permitAll()
                    // é™æ€èµ„源,可匿名访问
                    .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**","/javaWork/**","/**/*.pdf").permitAll()
                    .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
                    // é™¤ä¸Šé¢å¤–的所有请求全部需要鉴权认证
                    .anyRequest().authenticated();
            })
                // todo ai生成, åŽæœŸè‹¥æœ‰é—®é¢˜è¯·è‡ªè¡Œä¼˜åŒ–
                .authorizeHttpRequests(requests -> {
                    // 1. æ”¾è¡ŒåŠ¨æ€é…ç½®çš„ URL
                    permitAllUrl.getUrls().forEach(url ->
                            requests.requestMatchers(url).permitAll()
                    );
                    // 2. ç™»å½• / æ³¨å†Œ / éªŒè¯ç  / ç­‰æ”¾è¡Œ
                    requests.requestMatchers(
                            "/login",
                            "/register",
                            "/captchaImage",
                            "/loginCheck",
                            "/userLoginFacotryList/**",
                            "/loginCheckFactory"
                    ).permitAll();
                    // 3. é™æ€èµ„源放行
                    requests.requestMatchers(HttpMethod.GET,
                            "/",
                            "/*.html",
                            "/**/*.html",
                            "/**/*.css",
                            "/**/*.js",
                            "/profile/**",
                            "/javaWork/**",
                            "/**/*.pdf"
                    ).permitAll();
                    // 4. swagger / druid æ”¾è¡Œ
                    requests.requestMatchers(
                            "/swagger-ui.html",
                            "/doc.html",
                            "/swagger-ui/**",
                            "/swagger-resources/**",
                            "/v3/api-docs/**",
                            "/webjars/**",
                            "/*/api-docs",
                            "/druid/**"
                    ).permitAll();
                    // 5. å…¶ä»–全部拦截
                    requests.anyRequest().authenticated();
                })
            // æ·»åŠ Logout filter
            .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
            // æ·»åŠ JWT filter
src/main/java/com/ruoyi/framework/config/ServerConfig.java
@@ -1,6 +1,6 @@
package com.ruoyi.framework.config;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.ruoyi.common.utils.ServletUtils;
src/main/java/com/ruoyi/framework/config/SwaggerConfig.java
@@ -1,124 +1,53 @@
package com.ruoyi.framework.config;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.annotations.ApiOperation;
import io.swagger.models.auth.In;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.service.SecurityScheme;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
/**
 * Swagger2的接口配置
 *
 * @author ruoyi
 */
@Configuration
public class SwaggerConfig
{
    /** ç³»ç»ŸåŸºç¡€é…ç½® */
    @Autowired
    private RuoYiConfig ruoyiConfig;
    /** æ˜¯å¦å¼€å¯swagger */
    @Value("${swagger.enabled}")
    private boolean enabled;
    /** è®¾ç½®è¯·æ±‚的统一前缀 */
    @Value("${swagger.pathMapping}")
    private String pathMapping;
    /**
     * åˆ›å»ºAPI
     */
    @Bean
    public Docket createRestApi()
    {
        return new Docket(DocumentationType.OAS_30)
                // æ˜¯å¦å¯ç”¨Swagger
                .enable(enabled)
                // ç”¨æ¥åˆ›å»ºè¯¥API的基本信息,展示在文档的页面中(自定义展示的信息)
                .apiInfo(apiInfo())
                // è®¾ç½®å“ªäº›æŽ¥å£æš´éœ²ç»™Swagger展示
                .select()
                // æ‰«ææ‰€æœ‰æœ‰æ³¨è§£çš„api,用这种方式更灵活
                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                // æ‰«ææŒ‡å®šåŒ…中的swagger注解
                // .apis(RequestHandlerSelectors.basePackage("com.ruoyi.project.tool.swagger"))
                // æ‰«ææ‰€æœ‰ .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                /* è®¾ç½®å®‰å…¨æ¨¡å¼ï¼Œswagger可以设置访问token */
                .securitySchemes(securitySchemes())
                .securityContexts(securityContexts());
//                .pathMapping(pathMapping);
    }
    /**
     * å®‰å…¨æ¨¡å¼ï¼Œè¿™é‡ŒæŒ‡å®štoken通过Authorization头请求头传递
     */
    private List<SecurityScheme> securitySchemes()
    {
        List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
        apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
        return apiKeyList;
    }
    /**
     * å®‰å…¨ä¸Šä¸‹æ–‡
     */
    private List<SecurityContext> securityContexts()
    {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(
                SecurityContext.builder()
                        .securityReferences(defaultAuth())
                        .operationSelector(o -> o.requestMappingPattern().matches("/.*"))
                        .build());
        return securityContexts;
    }
    /**
     * é»˜è®¤çš„安全上引用
     */
    private List<SecurityReference> defaultAuth()
    {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }
    /**
     * æ·»åŠ æ‘˜è¦ä¿¡æ¯
     */
    private ApiInfo apiInfo()
    {
        // ç”¨ApiInfoBuilder进行定制
        return new ApiInfoBuilder()
                // è®¾ç½®æ ‡é¢˜
                .title("标题:若依管理系统_接口文档")
                // æè¿°
                .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...")
                // ä½œè€…信息
                .contact(new Contact(ruoyiConfig.getName(), null, null))
                // ç‰ˆæœ¬
                .version("版本号:" + ruoyiConfig.getVersion())
                .build();
    }
}
package com.ruoyi.framework.config;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * OpenAPI æ–‡æ¡£é…ç½®ã€‚
 */
@Configuration
@ConditionalOnProperty(prefix = "swagger", name = "enabled", havingValue = "true", matchIfMissing = true)
public class SwaggerConfig
{
    @Autowired
    private RuoYiConfig ruoyiConfig;
    @Bean
    public OpenAPI openAPI()
    {
        String schemeName = "Authorization";
        return new OpenAPI()
                .info(new Info()
                        .title("标题:若依管理系统接口文档")
                        .description("描述:用于管理集团旗下公司的人员信息,具体包括 XXX、XXX æ¨¡å—。")
                        .version("版本号:" + ruoyiConfig.getVersion())
                        .contact(new Contact().name(ruoyiConfig.getName())))
                .components(new Components().addSecuritySchemes(schemeName,
                        new SecurityScheme()
                                .name(schemeName)
                                .type(SecurityScheme.Type.APIKEY)
                                .in(SecurityScheme.In.HEADER)))
                .addSecurityItem(new SecurityRequirement().addList(schemeName));
    }
    @Bean
    public GroupedOpenApi defaultOpenApi()
    {
        return GroupedOpenApi.builder()
                .group("default")
                // æ‰«æé¡¹ç›®çœŸå®žæš´éœ²çš„æŽ¥å£è·¯å¾„,不用 swagger.pathMapping åšäºŒæ¬¡è¿‡æ»¤ï¼Œ
                // å¦åˆ™åƒ /dev-api è¿™ç±»ç½‘关前缀会把本地 Controller å…¨éƒ¨è¿‡æ»¤æŽ‰ã€‚
                .pathsToMatch("/**")
                .build();
    }
}
src/main/java/com/ruoyi/framework/config/properties/PermitAllUrlProperties.java
@@ -1,11 +1,6 @@
package com.ruoyi.framework.config.properties;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import com.ruoyi.framework.aspectj.lang.annotation.Anonymous;
import org.apache.commons.lang3.RegExUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
@@ -16,7 +11,9 @@
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import com.ruoyi.framework.aspectj.lang.annotation.Anonymous;
import java.util.*;
import java.util.regex.Pattern;
/**
 * è®¾ç½®Anonymous注解允许匿名访问的url
@@ -40,18 +37,36 @@
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
//        map.keySet().forEach(info -> {
//            HandlerMethod handlerMethod = map.get(info);
//
//            // èŽ·å–æ–¹æ³•ä¸Šè¾¹çš„æ³¨è§£ æ›¿ä»£path variable ä¸º *
//            Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
//            Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
//                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
//
//            // èŽ·å–ç±»ä¸Šè¾¹çš„æ³¨è§£, æ›¿ä»£path variable ä¸º *
//            Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
//            Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
//                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
//        });
        map.keySet().forEach(info -> {
            HandlerMethod handlerMethod = map.get(info);
            // èŽ·å–æ–¹æ³•ä¸Šçš„åŒ¿åè®¿é—®æ³¨è§£
            Anonymous anonymous = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
            if (Objects.nonNull(anonymous)) {
                // é‡ç‚¹ï¼šSpring Boot 3 å…¼å®¹æ€§å†™æ³•
                Set<String> patterns = new HashSet<>();
                if (info.getPatternsCondition() != null) {
                    patterns.addAll(info.getPatternsCondition().getPatterns());
                }
                if (info.getPathPatternsCondition() != null) {
                    patterns.addAll(info.getPathPatternsCondition().getPatternValues());
                }
            // èŽ·å–æ–¹æ³•ä¸Šè¾¹çš„æ³¨è§£ æ›¿ä»£path variable ä¸º *
            Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
            Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
            // èŽ·å–ç±»ä¸Šè¾¹çš„æ³¨è§£, æ›¿ä»£path variable ä¸º *
            Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
            Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
                patterns.forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, "*")));
            }
        });
    }
src/main/java/com/ruoyi/framework/datasource/DynamicDataSource.java
@@ -1,9 +1,10 @@
package com.ruoyi.framework.datasource;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
 * åŠ¨æ€æ•°æ®æº
 * 
src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java
@@ -1,8 +1,8 @@
package com.ruoyi.framework.interceptor;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java
@@ -3,7 +3,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
src/main/java/com/ruoyi/framework/manager/ShutdownManager.java
@@ -1,9 +1,9 @@
package com.ruoyi.framework.manager;
import jakarta.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
/**
 * ç¡®ä¿åº”用退出时能关闭后台线程
src/main/java/com/ruoyi/framework/security/LoginUser.java
@@ -76,47 +76,58 @@
     */
    private Long tenantId;
    /**
     * å½“前部门id
     */
    private Long currentDeptId;
    /**
     * å½“前部门id
     */
    private Long currentDeptId;
    /**
     * æ˜¯å¦å¼€é€šAI功能(0否 1是)
     */
    private Integer aiEnabled;
    private String dataScope;
    public LoginUser()
    {
    }
    public LoginUser(SysUser user, Set<String> permissions)
    {
        this.user = user;
        this.permissions = permissions;
    }
    public LoginUser(SysUser user, Set<String> permissions)
    {
        this.user = user;
        this.permissions = permissions;
        this.aiEnabled = user == null ? null : user.getAiEnabled();
    }
    public LoginUser(Long userId, Long [] deptId, SysUser user, Set<String> permissions)
    {
        this.userId = userId;
        this.deptIds = deptId;
        this.user = user;
        this.permissions = permissions;
    }
    public LoginUser(Long userId, Long [] deptId, SysUser user, Set<String> permissions)
    {
        this.userId = userId;
        this.deptIds = deptId;
        this.user = user;
        this.permissions = permissions;
        this.aiEnabled = user == null ? null : user.getAiEnabled();
    }
    public LoginUser(Long userId, Long [] deptIds, SysUser user,Long tenantId, Set<String> permissions)
    {
        this.userId = userId;
        this.deptIds = deptIds;
        this.user = user;
        this.permissions = permissions;
        this.tenantId = tenantId;
    }
    public LoginUser(Long userId, Long [] deptIds, SysUser user,Long tenantId, Set<String> permissions)
    {
        this.userId = userId;
        this.deptIds = deptIds;
        this.user = user;
        this.permissions = permissions;
        this.tenantId = tenantId;
        this.aiEnabled = user == null ? null : user.getAiEnabled();
    }
    public LoginUser(Long userId, Long [] deptIds, SysUser user,Long tenantId,Long currentDeptId, Set<String> permissions)
    {
        this.userId = userId;
        this.deptIds = deptIds;
        this.user = user;
        this.permissions = permissions;
        this.tenantId = tenantId;
        this.currentDeptId = currentDeptId;
    }
    public LoginUser(Long userId, Long [] deptIds, SysUser user,Long tenantId,Long currentDeptId, Set<String> permissions)
    {
        this.userId = userId;
        this.deptIds = deptIds;
        this.user = user;
        this.permissions = permissions;
        this.tenantId = tenantId;
        this.currentDeptId = currentDeptId;
        this.aiEnabled = user == null ? null : user.getAiEnabled();
    }
    public Long getUserId()
    {
@@ -287,10 +298,11 @@
        return user;
    }
    public void setUser(SysUser user)
    {
        this.user = user;
    }
    public void setUser(SysUser user)
    {
        this.user = user;
        this.aiEnabled = user == null ? null : user.getAiEnabled();
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
@@ -314,7 +326,29 @@
        return currentDeptId;
    }
    public void setCurrentDeptId(Long currentDeptId) {
        this.currentDeptId = currentDeptId;
    }
}
    public void setCurrentDeptId(Long currentDeptId) {
        this.currentDeptId = currentDeptId;
    }
    public Integer getAiEnabled() {
        if (aiEnabled != null) {
            return aiEnabled;
        }
        if (user != null && user.getAiEnabled() != null) {
            return user.getAiEnabled();
        }
        return 0;
    }
    public void setAiEnabled(Integer aiEnabled) {
        this.aiEnabled = aiEnabled;
    }
    public String getDataScope() {
        return dataScope;
    }
    public void setDataScope(String dataScope) {
        this.dataScope = dataScope;
    }
}
src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java
@@ -1,10 +1,10 @@
package com.ruoyi.framework.security.filter;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -22,23 +22,35 @@
 * @author ruoyi
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
    @Autowired
    private TokenService tokenService;
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
        {
            tokenService.verifyToken(loginUser);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}
        }
        chain.doFilter(request, response);
    }
    @Override
    protected boolean shouldNotFilterAsyncDispatch()
    {
        return false;
    }
    @Override
    protected boolean shouldNotFilterErrorDispatch()
    {
        return false;
    }
}
src/main/java/com/ruoyi/framework/security/handle/AuthenticationEntryPointImpl.java
@@ -2,8 +2,8 @@
import java.io.IOException;
import java.io.Serializable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
src/main/java/com/ruoyi/framework/security/handle/LogoutSuccessHandlerImpl.java
@@ -1,13 +1,5 @@
package com.ruoyi.framework.security.handle;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.ServletUtils;
@@ -17,21 +9,30 @@
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.security.service.TokenService;
import com.ruoyi.framework.web.domain.AjaxResult;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import java.io.IOException;
/**
 * è‡ªå®šä¹‰é€€å‡ºå¤„理类 è¿”回成功
 *
 *
 * @author ruoyi
 */
@Configuration
@RequiredArgsConstructor
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler
{
    @Autowired
    private TokenService tokenService;
    private final TokenService tokenService;
    /**
     * é€€å‡ºå¤„理
     *
     *
     * @return
     */
    @Override
src/main/java/com/ruoyi/framework/security/service/SysLoginService.java
@@ -1,25 +1,10 @@
package com.ruoyi.framework.security.service;
import javax.annotation.Resource;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.mapper.SysUserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.BlackListException;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.CaptchaExpireException;
import com.ruoyi.common.exception.user.UserNotExistsException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.exception.user.*;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
@@ -30,10 +15,18 @@
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.project.system.service.ISysConfigService;
import com.ruoyi.project.system.service.ISysUserService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -43,22 +36,19 @@
 * @author ruoyi
 */
@Component
@RequiredArgsConstructor
public class SysLoginService
{
    @Autowired
    private TokenService tokenService;
    private final TokenService tokenService;
    @Resource
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisCache redisCache;
    private final RedisCache redisCache;
    private final ISysUserService userService;
    private final ISysConfigService configService;
    private final SysUserMapper sysUserMapper;
    @Autowired
    private ISysUserService userService;
    @Autowired
    private ISysConfigService configService;
    /**
     * ç™»å½•验证
@@ -219,8 +209,6 @@
        return loginUser.getUserId();
    }
    @Autowired
    private SysUserMapper sysUserMapper;
    /**
     * ç™»å½•验证
src/main/java/com/ruoyi/framework/security/service/SysPasswordService.java
@@ -1,10 +1,5 @@
package com.ruoyi.framework.security.service;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException;
@@ -12,6 +7,12 @@
import com.ruoyi.framework.redis.RedisCache;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.project.system.domain.SysUser;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
 * ç™»å½•密码方法
@@ -19,10 +20,10 @@
 * @author ruoyi
 */
@Component
@RequiredArgsConstructor
public class SysPasswordService
{
    @Autowired
    private RedisCache redisCache;
    private final RedisCache redisCache;
    @Value(value = "${user.password.maxRetryCount}")
    private int maxRetryCount;
src/main/java/com/ruoyi/framework/security/service/SysPermissionService.java
@@ -1,17 +1,18 @@
package com.ruoyi.framework.security.service;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.project.system.domain.SysRole;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.service.ISysMenuService;
import com.ruoyi.project.system.service.ISysRoleService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
 * ç”¨æˆ·æƒé™å¤„理
@@ -19,13 +20,11 @@
 * @author ruoyi
 */
@Component
@RequiredArgsConstructor
public class SysPermissionService
{
    @Autowired
    private ISysRoleService roleService;
    @Autowired
    private ISysMenuService menuService;
    private final ISysRoleService roleService;
    private final ISysMenuService menuService;
    /**
     * èŽ·å–è§’è‰²æ•°æ®æƒé™
src/main/java/com/ruoyi/framework/security/service/SysRegisterService.java
@@ -1,7 +1,5 @@
package com.ruoyi.framework.security.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
@@ -17,6 +15,8 @@
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.service.ISysConfigService;
import com.ruoyi.project.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
 * æ³¨å†Œæ ¡éªŒæ–¹æ³•
@@ -24,16 +24,12 @@
 * @author ruoyi
 */
@Component
@RequiredArgsConstructor
public class SysRegisterService
{
    @Autowired
    private ISysUserService userService;
    @Autowired
    private ISysConfigService configService;
    @Autowired
    private RedisCache redisCache;
    private final ISysUserService userService;
    private final ISysConfigService configService;
    private final RedisCache redisCache;
    /**
     * æ³¨å†Œ
src/main/java/com/ruoyi/framework/security/service/TokenService.java
@@ -1,20 +1,6 @@
package com.ruoyi.framework.security.service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.project.system.domain.SysUserDept;
import com.ruoyi.project.system.mapper.SysUserDeptMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.ServletUtils;
@@ -24,11 +10,27 @@
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.framework.redis.RedisCache;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.project.system.domain.SysRole;
import com.ruoyi.project.system.domain.SysUserDept;
import com.ruoyi.project.system.mapper.SysUserDeptMapper;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
 * token验证处理
@@ -36,6 +38,7 @@
 * @author ruoyi
 */
@Component
@RequiredArgsConstructor
public class TokenService
{
    private static final Logger log = LoggerFactory.getLogger(TokenService.class);
@@ -58,8 +61,7 @@
    private static final Long MILLIS_MINUTE_TWENTY = 20 * 60 * 1000L;
    @Autowired
    private RedisCache redisCache;
    private final RedisCache redisCache;
    /**
     * èŽ·å–ç”¨æˆ·èº«ä»½ä¿¡æ¯
@@ -147,8 +149,7 @@
        }
    }
    @Autowired
    private SysUserDeptMapper sysUserDeptMapper;
    private final SysUserDeptMapper sysUserDeptMapper;
    /**
     * åˆ·æ–°ä»¤ç‰Œæœ‰æ•ˆæœŸ
@@ -160,10 +161,70 @@
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        loginUser.setDeptIds(getDeptIdsByUserId(loginUser.getUserId()));
        loginUser.setCurrentDeptId(loginUser.getDeptIds()[0]);
        if (loginUser.getDeptIds() != null && loginUser.getDeptIds().length > 0)
        {
            loginUser.setCurrentDeptId(loginUser.getDeptIds()[0]);
        }
        loginUser.setDataScope(resolveDataScope(loginUser));
        // æ ¹æ®uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }
    public String resolveDataScope(LoginUser loginUser)
    {
        if (loginUser == null || loginUser.getUser() == null || CollectionUtils.isEmpty(loginUser.getUser().getRoles()))
        {
            return null;
        }
        boolean hasCustom = false;
        boolean hasDeptAndChild = false;
        boolean hasDept = false;
        boolean hasSelf = false;
        for (SysRole role : loginUser.getUser().getRoles())
        {
            if (role == null || !"0".equals(role.getStatus()))
            {
                continue;
            }
            if ("1".equals(role.getDataScope()))
            {
                return "1";
            }
            if ("2".equals(role.getDataScope()))
            {
                hasCustom = true;
            }
            else if ("4".equals(role.getDataScope()))
            {
                hasDeptAndChild = true;
            }
            else if ("3".equals(role.getDataScope()))
            {
                hasDept = true;
            }
            else if ("5".equals(role.getDataScope()))
            {
                hasSelf = true;
            }
        }
        if (hasCustom)
        {
            return "2";
        }
        if (hasDeptAndChild)
        {
            return "4";
        }
        if (hasDept)
        {
            return "3";
        }
        if (hasSelf)
        {
            return "5";
        }
        return null;
    }
    public Long[] getDeptIdsByUserId(Long userId){
@@ -191,6 +252,11 @@
        loginUser.setOs(userAgent.getOperatingSystem().getName());
    }
    private SecretKey getSigningKey() {
        byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
        return Keys.hmacShaKeyFor(keyBytes);
    }
    /**
     * ä»Žæ•°æ®å£°æ˜Žç”Ÿæˆä»¤ç‰Œ
     *
@@ -199,10 +265,10 @@
     */
    private String createToken(Map<String, Object> claims)
    {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
        return Jwts.builder()
                .claims(claims) // æ³¨æ„ï¼šæ–°ç‰ˆæ–¹æ³•名变了,不再是 setClaims
                .signWith(getSigningKey(), Jwts.SIG.HS512) // ä½¿ç”¨æ–°çš„签名常量
                .compact();
    }
    /**
@@ -214,9 +280,10 @@
    private Claims parseToken(String token)
    {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
                .verifyWith(getSigningKey()) // æ–°ç‰ˆä½¿ç”¨ verifyWith
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }
    /**
在上述文件截断后对比
src/main/java/com/ruoyi/framework/security/service/UserDetailsServiceImpl.java src/main/java/com/ruoyi/framework/web/exception/GlobalExceptionHandler.java src/main/java/com/ruoyi/home/controller/HomeController.java src/main/java/com/ruoyi/home/dto/AnalysisCustomerContractAmountsDto.java src/main/java/com/ruoyi/home/dto/CustomerContributionRankingDto.java src/main/java/com/ruoyi/home/dto/CustomerRevenueAnalysisDto.java src/main/java/com/ruoyi/home/dto/DeptStaffDistributionDto.java src/main/java/com/ruoyi/home/dto/HomeBusinessDto.java src/main/java/com/ruoyi/home/dto/HomeSummaryDto.java src/main/java/com/ruoyi/home/dto/ProductCategoryDistributionDto.java src/main/java/com/ruoyi/home/dto/ProductionProgressDto.java src/main/java/com/ruoyi/home/dto/ProductionProgressOrderDto.java src/main/java/com/ruoyi/home/dto/ProductionTaskStatisticsDto.java src/main/java/com/ruoyi/home/dto/ProductionTurnoverDto.java src/main/java/com/ruoyi/home/dto/QualityStatisticsDto.java src/main/java/com/ruoyi/home/dto/QualityStatisticsItem.java src/main/java/com/ruoyi/home/dto/StatisticsReceivablePayableDto.java src/main/java/com/ruoyi/home/dto/SupplierPurchaseRankingDto.java src/main/java/com/ruoyi/home/dto/WorkOrderEfficiencyDto.java src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java src/main/java/com/ruoyi/inspectiontask/controller/QrCodeController.java src/main/java/com/ruoyi/inspectiontask/controller/QrCodeScanRecordController.java src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java src/main/java/com/ruoyi/inspectiontask/dto/QrCodeScanRecordDto.java src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java src/main/java/com/ruoyi/inspectiontask/pojo/QrCode.java src/main/java/com/ruoyi/inspectiontask/pojo/QrCodeScanRecord.java src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeServiceImpl.java src/main/java/com/ruoyi/inspectiontask/service/impl/QuartzConfig.java src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduler.java src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java src/main/java/com/ruoyi/lavorissue/controller/LavorIssueController.java src/main/java/com/ruoyi/lavorissue/dto/StatisticsLaborIssue.java src/main/java/com/ruoyi/lavorissue/pojo/LaborIssue.java src/main/java/com/ruoyi/lavorissue/service/LavorIssueService.java src/main/java/com/ruoyi/lavorissue/service/impl/LavorIssueServiceImpl.java src/main/java/com/ruoyi/measuringinstrumentledger/controller/MeasuringInstrumentLedgerController.java src/main/java/com/ruoyi/measuringinstrumentledger/controller/MeasuringInstrumentLedgerRecordController.java src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsController.java src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsRequisitionRecordController.java src/main/java/com/ruoyi/measuringinstrumentledger/dto/MeasuringInstrumentLedgerDto.java src/main/java/com/ruoyi/measuringinstrumentledger/dto/SparePartsRequisitionRecordDto.java src/main/java/com/ruoyi/measuringinstrumentledger/mapper/SparePartsRequisitionRecordMapper.java src/main/java/com/ruoyi/measuringinstrumentledger/pojo/MeasuringInstrumentLedger.java src/main/java/com/ruoyi/measuringinstrumentledger/pojo/MeasuringInstrumentLedgerRecord.java src/main/java/com/ruoyi/measuringinstrumentledger/pojo/SpareParts.java src/main/java/com/ruoyi/measuringinstrumentledger/pojo/SparePartsRequisitionRecord.java src/main/java/com/ruoyi/measuringinstrumentledger/service/MeasuringInstrumentLedgerRecordService.java src/main/java/com/ruoyi/measuringinstrumentledger/service/MeasuringInstrumentLedgerService.java src/main/java/com/ruoyi/measuringinstrumentledger/service/SparePartsRequisitionRecordService.java src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/MeasuringInstrumentLedgerRecordServiceImpl.java src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/MeasuringInstrumentLedgerServiceImpl.java src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/SparePartsRequisitionRecordServiceImpl.java src/main/java/com/ruoyi/measuringinstrumentledger/service/impl/SparePartsServiceImpl.java src/main/java/com/ruoyi/oA/controller/OaProjectController.java src/main/java/com/ruoyi/oA/controller/OaProjectPhaseController.java src/main/java/com/ruoyi/oA/controller/OaProjectPhaseTaskController.java src/main/java/com/ruoyi/oA/pojo/OaProject.java src/main/java/com/ruoyi/oA/pojo/OaProjectPhase.java src/main/java/com/ruoyi/oA/pojo/OaProjectPhaseTask.java src/main/java/com/ruoyi/oA/service/OaProjectService.java src/main/java/com/ruoyi/oA/service/impl/OaProjectPhaseServiceImpl.java src/main/java/com/ruoyi/oA/service/impl/OaProjectServiceImpl.java src/main/java/com/ruoyi/officesupplies/controller/OfficeSuppliesController.java src/main/java/com/ruoyi/officesupplies/pojo/OfficeSupplies.java src/main/java/com/ruoyi/officesupplies/service/impl/OfficeSuppliesServiceImpl.java src/main/java/com/ruoyi/other/controller/PdaVersionController.java src/main/java/com/ruoyi/other/controller/TempFileController.java (已删除) src/main/java/com/ruoyi/other/dto/PdaVersionDTO.java src/main/java/com/ruoyi/other/mapper/PdaVersionMapper.java src/main/java/com/ruoyi/other/pojo/PdaVersion.java src/main/java/com/ruoyi/other/pojo/TempFile.java src/main/java/com/ruoyi/other/service/PdaVersionService.java src/main/java/com/ruoyi/other/service/TempFileService.java (已删除) src/main/java/com/ruoyi/other/service/impl/PdaVersionServiceImpl.java src/main/java/com/ruoyi/other/service/impl/TempFileServiceImpl.java (已删除) src/main/java/com/ruoyi/procurementrecord/controller/GasTankWarningController.java src/main/java/com/ruoyi/procurementrecord/controller/InboundManagementController.java src/main/java/com/ruoyi/procurementrecord/controller/ProcurementExceptionRecordController.java src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPlanController.java src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPriceManagementController.java src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordController.java src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordOutController.java src/main/java/com/ruoyi/procurementrecord/controller/ReturnManagementController.java src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDto.java src/main/java/com/ruoyi/procurementrecord/dto/ProcurementPageDtoCopy.java src/main/java/com/ruoyi/procurementrecord/dto/ReturnManagementDto.java src/main/java/com/ruoyi/procurementrecord/pojo/CustomStorage.java src/main/java/com/ruoyi/procurementrecord/pojo/GasTankWarning.java src/main/java/com/ruoyi/procurementrecord/pojo/InboundManagement.java src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementExceptionRecord.java src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementPlan.java src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementPriceManagement.java src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecordOut.java src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementRecordStorage.java src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java src/main/java/com/ruoyi/procurementrecord/pojo/ReturnSaleProduct.java src/main/java/com/ruoyi/procurementrecord/service/GasTankWarningService.java src/main/java/com/ruoyi/procurementrecord/service/ProcurementPlanService.java src/main/java/com/ruoyi/procurementrecord/service/ProcurementPriceManagementService.java src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordOutService.java src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.java src/main/java/com/ruoyi/procurementrecord/service/impl/GasTankWarningServiceImpl.java src/main/java/com/ruoyi/procurementrecord/service/impl/InboundManagementServiceImpl.java src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementPlanServiceImpl.java src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementPriceManagementServiceImpl.java src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordOutServiceImpl.java src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnSaleProductServiceImpl.java src/main/java/com/ruoyi/procurementrecord/utils/StockUtils.java src/main/java/com/ruoyi/production/bean/dto/BomImportDto.java src/main/java/com/ruoyi/production/bean/dto/ProductStructureDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionAccountDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionBomStructureDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionOperationTaskDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionOrderDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionOrderPickDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionOrderPickRecordDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionOrderRoutingOperationParamDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionOrderRoutingOperationParamSyncDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionPlanDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionPlanImportDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionPlanSummaryDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionProductInputDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionProductMainDto.java src/main/java/com/ruoyi/production/bean/dto/ProductionProductOutputDto.java src/main/java/com/ruoyi/production/bean/dto/SalesLedgerProductionAccountingDto.java src/main/java/com/ruoyi/production/bean/dto/UserAccountDto.java src/main/java/com/ruoyi/production/bean/dto/UserProductionAccountingDto.java src/main/java/com/ruoyi/production/bean/vo/ProductionAccountVo.java src/main/java/com/ruoyi/production/bean/vo/ProductionBomStructureVo.java src/main/java/com/ruoyi/production/bean/vo/ProductionOperationTaskVo.java src/main/java/com/ruoyi/production/bean/vo/ProductionOrderPickRecordVo.java src/main/java/com/ruoyi/production/bean/vo/ProductionOrderPickVo.java src/main/java/com/ruoyi/production/bean/vo/ProductionOrderRoutingOperationParamVo.java src/main/java/com/ruoyi/production/bean/vo/ProductionOrderRoutingOperationVo.java src/main/java/com/ruoyi/production/bean/vo/ProductionOrderVo.java src/main/java/com/ruoyi/production/bean/vo/ProductionOrderWorkOrderDetailVo.java src/main/java/com/ruoyi/production/bean/vo/ProductionPlanVo.java src/main/java/com/ruoyi/production/controller/ProcessRouteController.java (已删除) src/main/java/com/ruoyi/production/controller/ProcessRouteItemController.java (已删除) src/main/java/com/ruoyi/production/controller/ProductBomController.java (已删除) src/main/java/com/ruoyi/production/controller/ProductOrderController.java (已删除) src/main/java/com/ruoyi/production/controller/ProductProcessController.java (已删除) src/main/java/com/ruoyi/production/controller/ProductProcessRouteController.java (已删除) src/main/java/com/ruoyi/production/controller/ProductProcessRouteItemController.java (已删除) src/main/java/com/ruoyi/production/controller/ProductStructureController.java (已删除) src/main/java/com/ruoyi/production/controller/ProductWorkOrderController.java (已删除) src/main/java/com/ruoyi/production/controller/ProductWorkOrderFileController.java (已删除) src/main/java/com/ruoyi/production/controller/ProductionAccountController.java src/main/java/com/ruoyi/production/controller/ProductionBomStructureController.java src/main/java/com/ruoyi/production/controller/ProductionOperationMainParamController.java src/main/java/com/ruoyi/production/controller/ProductionOperationTaskController.java src/main/java/com/ruoyi/production/controller/ProductionOrderBomController.java src/main/java/com/ruoyi/production/controller/ProductionOrderController.java src/main/java/com/ruoyi/production/controller/ProductionOrderPickController.java src/main/java/com/ruoyi/production/controller/ProductionOrderPickRecordController.java src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingController.java src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingOperationController.java src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingOperationParamController.java src/main/java/com/ruoyi/production/controller/ProductionPlanController.java src/main/java/com/ruoyi/production/controller/ProductionProductInputController.java src/main/java/com/ruoyi/production/controller/ProductionProductMainController.java src/main/java/com/ruoyi/production/controller/ProductionProductOutputController.java src/main/java/com/ruoyi/production/controller/SalesLedgerProductionAccountingController.java (已删除) src/main/java/com/ruoyi/production/controller/SalesLedgerSchedulingController.java (已删除) src/main/java/com/ruoyi/production/controller/SalesLedgerWorkController.java (已删除) src/main/java/com/ruoyi/production/dto/BomImportDto.java (已删除) src/main/java/com/ruoyi/production/dto/DaiDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProcessRouteDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProcessRouteItemDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProcessSchedulingDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductBomDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductOrderDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductProcessDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductProcessRouteItemDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductStructureDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductWorkOrderDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductionDispatchAddDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductionProductInputDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductionProductOutputDto.java (已删除) src/main/java/com/ruoyi/production/dto/ProductionReportDto.java (已删除) src/main/java/com/ruoyi/production/dto/SalesLedgerProductDto.java (已删除) src/main/java/com/ruoyi/production/dto/SalesLedgerProductionAccountingDto.java (已删除) src/main/java/com/ruoyi/production/dto/SalesLedgerSchedulingDto.java (已删除) src/main/java/com/ruoyi/production/dto/SalesLedgerSchedulingProcessDto.java (已删除) src/main/java/com/ruoyi/production/dto/SalesLedgerWorkDto.java (已删除) src/main/java/com/ruoyi/production/dto/UserAccountDto.java (已删除) src/main/java/com/ruoyi/production/dto/UserProductionAccountingDto.java (已删除) src/main/java/com/ruoyi/production/enums/ProductOrderStatusEnum.java src/main/java/com/ruoyi/production/mapper/ProcessRouteItemMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/ProcessRouteMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/ProductBomMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/ProductOrderMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/ProductProcessMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/ProductProcessRouteItemMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/ProductProcessRouteMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/ProductStructureMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/ProductWorkOrderFileMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/ProductWorkOrderMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/ProductionAccountMapper.java src/main/java/com/ruoyi/production/mapper/ProductionBomStructureMapper.java src/main/java/com/ruoyi/production/mapper/ProductionOperationMainParamMapper.java src/main/java/com/ruoyi/production/mapper/ProductionOperationTaskMapper.java src/main/java/com/ruoyi/production/mapper/ProductionOrderBomMapper.java src/main/java/com/ruoyi/production/mapper/ProductionOrderMapper.java src/main/java/com/ruoyi/production/mapper/ProductionOrderPickMapper.java src/main/java/com/ruoyi/production/mapper/ProductionOrderPickRecordMapper.java src/main/java/com/ruoyi/production/mapper/ProductionOrderRoutingMapper.java src/main/java/com/ruoyi/production/mapper/ProductionOrderRoutingOperationMapper.java src/main/java/com/ruoyi/production/mapper/ProductionOrderRoutingOperationParamMapper.java src/main/java/com/ruoyi/production/mapper/ProductionPlanMapper.java src/main/java/com/ruoyi/production/mapper/ProductionProductInputMapper.java src/main/java/com/ruoyi/production/mapper/ProductionProductMainMapper.java src/main/java/com/ruoyi/production/mapper/ProductionProductOutputMapper.java src/main/java/com/ruoyi/production/mapper/SalesLedgerProductionAccountingMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/SalesLedgerSchedulingMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/SalesLedgerWorkMapper.java (已删除) src/main/java/com/ruoyi/production/mapper/SpeculativeTradingInfoMapper.java (已删除) src/main/java/com/ruoyi/production/pojo/ProcessRoute.java (已删除) src/main/java/com/ruoyi/production/pojo/ProcessRouteItem.java (已删除) src/main/java/com/ruoyi/production/pojo/ProductBom.java (已删除) src/main/java/com/ruoyi/production/pojo/ProductOrder.java (已删除) src/main/java/com/ruoyi/production/pojo/ProductProcess.java (已删除) src/main/java/com/ruoyi/production/pojo/ProductProcessRoute.java (已删除) src/main/java/com/ruoyi/production/pojo/ProductProcessRouteItem.java (已删除) src/main/java/com/ruoyi/production/pojo/ProductStructure.java (已删除) src/main/java/com/ruoyi/production/pojo/ProductWorkOrder.java (已删除) src/main/java/com/ruoyi/production/pojo/ProductWorkOrderFile.java (已删除) src/main/java/com/ruoyi/production/pojo/ProductionAccount.java src/main/java/com/ruoyi/production/pojo/ProductionBomStructure.java src/main/java/com/ruoyi/production/pojo/ProductionOperationMainParam.java src/main/java/com/ruoyi/production/pojo/ProductionOperationTask.java src/main/java/com/ruoyi/production/pojo/ProductionOrder.java src/main/java/com/ruoyi/production/pojo/ProductionOrderBom.java src/main/java/com/ruoyi/production/pojo/ProductionOrderPick.java src/main/java/com/ruoyi/production/pojo/ProductionOrderPickRecord.java src/main/java/com/ruoyi/production/pojo/ProductionOrderRouting.java src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperation.java src/main/java/com/ruoyi/production/pojo/ProductionOrderRoutingOperationParam.java src/main/java/com/ruoyi/production/pojo/ProductionPlan.java src/main/java/com/ruoyi/production/pojo/ProductionProductInput.java src/main/java/com/ruoyi/production/pojo/ProductionProductMain.java src/main/java/com/ruoyi/production/pojo/ProductionProductOutput.java src/main/java/com/ruoyi/production/pojo/SalesLedgerProductionAccounting.java (已删除) src/main/java/com/ruoyi/production/pojo/SalesLedgerScheduling.java (已删除) src/main/java/com/ruoyi/production/pojo/SalesLedgerWork.java (已删除) src/main/java/com/ruoyi/production/pojo/SpeculativeTradingInfo.java (已删除) src/main/java/com/ruoyi/production/service/ProcessRouteItemService.java (已删除) src/main/java/com/ruoyi/production/service/ProcessRouteService.java (已删除) src/main/java/com/ruoyi/production/service/ProductBomService.java (已删除) src/main/java/com/ruoyi/production/service/ProductOrderService.java (已删除) src/main/java/com/ruoyi/production/service/ProductProcessRouteItemService.java (已删除) src/main/java/com/ruoyi/production/service/ProductProcessRouteService.java (已删除) src/main/java/com/ruoyi/production/service/ProductProcessService.java (已删除) src/main/java/com/ruoyi/production/service/ProductStructureService.java (已删除) src/main/java/com/ruoyi/production/service/ProductWorkOrderFileService.java (已删除) src/main/java/com/ruoyi/production/service/ProductWorkOrderService.java (已删除) src/main/java/com/ruoyi/production/service/ProductionAccountService.java src/main/java/com/ruoyi/production/service/ProductionBomStructureService.java src/main/java/com/ruoyi/production/service/ProductionOperationMainParamService.java src/main/java/com/ruoyi/production/service/ProductionOperationTaskService.java src/main/java/com/ruoyi/production/service/ProductionOrderBomService.java src/main/java/com/ruoyi/production/service/ProductionOrderPickRecordService.java src/main/java/com/ruoyi/production/service/ProductionOrderPickService.java src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationParamService.java src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationService.java src/main/java/com/ruoyi/production/service/ProductionOrderRoutingService.java src/main/java/com/ruoyi/production/service/ProductionOrderService.java src/main/java/com/ruoyi/production/service/ProductionPlanService.java src/main/java/com/ruoyi/production/service/ProductionProductInputService.java src/main/java/com/ruoyi/production/service/ProductionProductMainService.java src/main/java/com/ruoyi/production/service/ProductionProductOutputService.java src/main/java/com/ruoyi/production/service/SalesLedgerProductionAccountingService.java src/main/java/com/ruoyi/production/service/SalesLedgerSchedulingService.java (已删除) src/main/java/com/ruoyi/production/service/SalesLedgerWorkService.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProcessRouteItemServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProcessRouteServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProductBomServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteItemServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProductProcessRouteServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProductProcessServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProductStructureServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderFileServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProductWorkOrderServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/ProductionAccountServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionOperationMainParamServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionOperationTaskServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionOrderBomServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickRecordServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationParamServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingOperationServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionOrderRoutingServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionProductInputServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionProductOutputServiceImpl.java src/main/java/com/ruoyi/production/service/impl/SalesLedgerProductionAccountingServiceImpl.java src/main/java/com/ruoyi/production/service/impl/SalesLedgerSchedulingServiceImpl.java (已删除) src/main/java/com/ruoyi/production/service/impl/SalesLedgerWorkServiceImpl.java (已删除) src/main/java/com/ruoyi/project/common/CaptchaController.java src/main/java/com/ruoyi/project/common/CommonController.java src/main/java/com/ruoyi/project/monitor/controller/CacheController.java src/main/java/com/ruoyi/project/monitor/controller/SysJobController.java src/main/java/com/ruoyi/project/monitor/controller/SysJobLogController.java src/main/java/com/ruoyi/project/monitor/controller/SysLogininforController.java src/main/java/com/ruoyi/project/monitor/controller/SysOperlogController.java src/main/java/com/ruoyi/project/monitor/controller/SysUserOnlineController.java src/main/java/com/ruoyi/project/monitor/domain/SysJob.java src/main/java/com/ruoyi/project/monitor/service/impl/SysJobLogServiceImpl.java src/main/java/com/ruoyi/project/monitor/service/impl/SysJobServiceImpl.java src/main/java/com/ruoyi/project/monitor/service/impl/SysLogininforServiceImpl.java src/main/java/com/ruoyi/project/monitor/service/impl/SysOperLogServiceImpl.java src/main/java/com/ruoyi/project/system/controller/SysConfigController.java src/main/java/com/ruoyi/project/system/controller/SysDeptController.java src/main/java/com/ruoyi/project/system/controller/SysDictDataController.java src/main/java/com/ruoyi/project/system/controller/SysDictTypeController.java src/main/java/com/ruoyi/project/system/controller/SysIndexController.java src/main/java/com/ruoyi/project/system/controller/SysLoginController.java src/main/java/com/ruoyi/project/system/controller/SysMenuController.java src/main/java/com/ruoyi/project/system/controller/SysNoticeController.java src/main/java/com/ruoyi/project/system/controller/SysPostController.java src/main/java/com/ruoyi/project/system/controller/SysProfileController.java src/main/java/com/ruoyi/project/system/controller/SysRegisterController.java src/main/java/com/ruoyi/project/system/controller/SysRoleController.java src/main/java/com/ruoyi/project/system/controller/SysUserClientController.java src/main/java/com/ruoyi/project/system/controller/SysUserController.java src/main/java/com/ruoyi/project/system/domain/SysConfig.java src/main/java/com/ruoyi/project/system/domain/SysDept.java src/main/java/com/ruoyi/project/system/domain/SysDictData.java src/main/java/com/ruoyi/project/system/domain/SysDictType.java src/main/java/com/ruoyi/project/system/domain/SysMenu.java src/main/java/com/ruoyi/project/system/domain/SysNotice.java src/main/java/com/ruoyi/project/system/domain/SysPost.java src/main/java/com/ruoyi/project/system/domain/SysRole.java src/main/java/com/ruoyi/project/system/domain/SysUser.java src/main/java/com/ruoyi/project/system/domain/SysUserDept.java src/main/java/com/ruoyi/project/system/domain/vo/SysUserDeptVo.java src/main/java/com/ruoyi/project/system/mapper/SysRoleMapper.java src/main/java/com/ruoyi/project/system/service/impl/SysConfigServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/SysDeptServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/SysDictDataServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/SysDictTypeServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/SysMenuServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/SysPostServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/SysRoleServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/SysUserDeptServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/SysUserServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java src/main/java/com/ruoyi/project/tool/gen/controller/GenController.java src/main/java/com/ruoyi/project/tool/gen/domain/GenTable.java src/main/java/com/ruoyi/project/tool/gen/domain/GenTableColumn.java src/main/java/com/ruoyi/project/tool/gen/service/GenTableColumnServiceImpl.java src/main/java/com/ruoyi/project/tool/gen/service/GenTableServiceImpl.java src/main/java/com/ruoyi/project/tool/swagger/TestController.java src/main/java/com/ruoyi/projectManagement/controller/InfoController.java src/main/java/com/ruoyi/projectManagement/controller/PlanController.java src/main/java/com/ruoyi/projectManagement/controller/RolesController.java src/main/java/com/ruoyi/projectManagement/dto/InfoStageDto.java src/main/java/com/ruoyi/projectManagement/dto/RoleDto.java src/main/java/com/ruoyi/projectManagement/dto/SaveInfoDto.java src/main/java/com/ruoyi/projectManagement/dto/UpdateStateInfo.java src/main/java/com/ruoyi/projectManagement/pojo/ContractInfo.java src/main/java/com/ruoyi/projectManagement/pojo/Info.java src/main/java/com/ruoyi/projectManagement/pojo/InfoStage.java src/main/java/com/ruoyi/projectManagement/pojo/Plan.java src/main/java/com/ruoyi/projectManagement/pojo/PlanNode.java src/main/java/com/ruoyi/projectManagement/pojo/Roles.java src/main/java/com/ruoyi/projectManagement/pojo/ShippingAddress.java src/main/java/com/ruoyi/projectManagement/service/InfoService.java src/main/java/com/ruoyi/projectManagement/service/PlanService.java src/main/java/com/ruoyi/projectManagement/service/impl/PlanServiceImpl.java src/main/java/com/ruoyi/projectManagement/service/impl/handle/ContractInfoHandleService.java src/main/java/com/ruoyi/projectManagement/service/impl/handle/InfoHandleService.java src/main/java/com/ruoyi/projectManagement/service/impl/handle/InfoStageHandleService.java src/main/java/com/ruoyi/projectManagement/service/impl/handle/ShippingAddressHandleService.java src/main/java/com/ruoyi/projectManagement/vo/InfoStageVo.java src/main/java/com/ruoyi/projectManagement/vo/PlanVo.java src/main/java/com/ruoyi/projectManagement/vo/SaveInfoStageVo.java src/main/java/com/ruoyi/projectManagement/vo/SaveInfoVo.java src/main/java/com/ruoyi/projectManagement/vo/SavePlanNodeVo.java src/main/java/com/ruoyi/projectManagement/vo/SavePlanVo.java src/main/java/com/ruoyi/projectManagement/vo/SearchPlanVo.java src/main/java/com/ruoyi/purchase/controller/AccountingReportController.java src/main/java/com/ruoyi/purchase/controller/InvoicePurchaseController.java src/main/java/com/ruoyi/purchase/controller/PaymentRegistrationController.java src/main/java/com/ruoyi/purchase/controller/ProcurementBusinessSummaryController.java src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerTemplateController.java src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrderProductsController.java src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java src/main/java/com/ruoyi/purchase/dto/ProcurementBusinessSummaryDto.java src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerImportDto.java src/main/java/com/ruoyi/purchase/dto/PurchaseReturnOrderDto.java src/main/java/com/ruoyi/purchase/dto/SimpleReturnOrderGroupDto.java src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrderProductsMapper.java src/main/java/com/ruoyi/purchase/mapper/PurchaseReturnOrdersMapper.java src/main/java/com/ruoyi/purchase/pojo/InvoicePurchase.java src/main/java/com/ruoyi/purchase/pojo/PaymentRegistration.java src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java src/main/java/com/ruoyi/purchase/pojo/PurchaseLedgerTemplate.java src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrderProducts.java src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java src/main/java/com/ruoyi/purchase/pojo/SalesLedgerProductTemplate.java src/main/java/com/ruoyi/purchase/pojo/TicketRegistration.java src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java src/main/java/com/ruoyi/purchase/service/PurchaseReturnOrdersService.java src/main/java/com/ruoyi/purchase/service/impl/InvoicePurchaseServiceImpl.java src/main/java/com/ruoyi/purchase/service/impl/PaymentRegistrationServiceImpl.java src/main/java/com/ruoyi/purchase/service/impl/ProcurementBusinessSummaryServiceImpl.java src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerTemplateServiceImpl.java src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java src/main/java/com/ruoyi/purchase/vo/PurchaseReturnDetailsVo.java src/main/java/com/ruoyi/purchase/vo/PurchaseReturnOrderVo.java src/main/java/com/ruoyi/quality/controller/QualityInspectController.java src/main/java/com/ruoyi/quality/controller/QualityInspectFileController.java src/main/java/com/ruoyi/quality/controller/QualityInspectParamController.java src/main/java/com/ruoyi/quality/controller/QualityReportController.java src/main/java/com/ruoyi/quality/controller/QualityTestStandardBindingController.java src/main/java/com/ruoyi/quality/controller/QualityTestStandardController.java src/main/java/com/ruoyi/quality/controller/QualityTestStandardParamController.java src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java src/main/java/com/ruoyi/quality/dto/QualityInspectDto.java src/main/java/com/ruoyi/quality/dto/QualityInspectStatDto.java src/main/java/com/ruoyi/quality/dto/QualityMonthlyDetailDto.java src/main/java/com/ruoyi/quality/dto/QualityMonthlyPassRateDto.java src/main/java/com/ruoyi/quality/dto/QualityMonthlyPassRateWrapperDto.java src/main/java/com/ruoyi/quality/dto/QualityParameterStatDto.java src/main/java/com/ruoyi/quality/dto/QualityPassRateDto.java src/main/java/com/ruoyi/quality/dto/QualityTopParameterDto.java src/main/java/com/ruoyi/quality/mapper/QualityInspectMapper.java src/main/java/com/ruoyi/quality/pojo/QualityInspect.java src/main/java/com/ruoyi/quality/pojo/QualityInspectFile.java src/main/java/com/ruoyi/quality/pojo/QualityInspectParam.java src/main/java/com/ruoyi/quality/pojo/QualityTestStandard.java src/main/java/com/ruoyi/quality/pojo/QualityTestStandardBinding.java src/main/java/com/ruoyi/quality/pojo/QualityTestStandardParam.java src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java src/main/java/com/ruoyi/quality/service/IQualityInspectService.java src/main/java/com/ruoyi/quality/service/IQualityUnqualifiedService.java src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java src/main/java/com/ruoyi/quality/service/impl/QualityReportServiceImpl.java src/main/java/com/ruoyi/quality/service/impl/QualityTestStandardBindingServiceImpl.java src/main/java/com/ruoyi/quality/service/impl/QualityTestStandardServiceImpl.java src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java src/main/java/com/ruoyi/safe/controller/SafeAccidentController.java src/main/java/com/ruoyi/safe/controller/SafeCertificationController.java src/main/java/com/ruoyi/safe/controller/SafeCertificationFileController.java src/main/java/com/ruoyi/safe/controller/SafeContingencyPlanController.java src/main/java/com/ruoyi/safe/controller/SafeHazardController.java src/main/java/com/ruoyi/safe/controller/SafeHazardRecordController.java src/main/java/com/ruoyi/safe/controller/SafeHiddenController.java src/main/java/com/ruoyi/safe/controller/SafeHiddenFileController.java src/main/java/com/ruoyi/safe/controller/SafeTrainingController.java src/main/java/com/ruoyi/safe/controller/SafeTrainingDetailsController.java src/main/java/com/ruoyi/safe/controller/SafeTrainingFileController.java src/main/java/com/ruoyi/safe/dto/SafeHazardRecordDto.java src/main/java/com/ruoyi/safe/dto/SafeHiddenDto.java src/main/java/com/ruoyi/safe/dto/SafeTrainingDetailsDto.java src/main/java/com/ruoyi/safe/dto/SafeTrainingDto.java src/main/java/com/ruoyi/safe/pojo/SafeAccident.java src/main/java/com/ruoyi/safe/pojo/SafeCertification.java src/main/java/com/ruoyi/safe/pojo/SafeCertificationFile.java src/main/java/com/ruoyi/safe/pojo/SafeContingencyPlan.java src/main/java/com/ruoyi/safe/pojo/SafeHazard.java src/main/java/com/ruoyi/safe/pojo/SafeHazardRecord.java src/main/java/com/ruoyi/safe/pojo/SafeHidden.java src/main/java/com/ruoyi/safe/pojo/SafeHiddenFile.java src/main/java/com/ruoyi/safe/pojo/SafeTraining.java src/main/java/com/ruoyi/safe/pojo/SafeTrainingDetails.java src/main/java/com/ruoyi/safe/pojo/SafeTrainingFile.java src/main/java/com/ruoyi/safe/service/SafeTrainingDetailsService.java src/main/java/com/ruoyi/safe/service/SafeTrainingService.java src/main/java/com/ruoyi/safe/service/impl/SafeAccidentServiceImpl.java src/main/java/com/ruoyi/safe/service/impl/SafeCertificationServiceImpl.java src/main/java/com/ruoyi/safe/service/impl/SafeContingencyPlanServiceImpl.java src/main/java/com/ruoyi/safe/service/impl/SafeHazardRecordServiceImpl.java src/main/java/com/ruoyi/safe/service/impl/SafeHazardServiceImpl.java src/main/java/com/ruoyi/safe/service/impl/SafeHiddenServiceImpl.java src/main/java/com/ruoyi/safe/service/impl/SafeTrainingDetailsServiceImpl.java src/main/java/com/ruoyi/safe/service/impl/SafeTrainingServiceImpl.java src/main/java/com/ruoyi/sales/controller/InvoiceLedgerController.java src/main/java/com/ruoyi/sales/controller/InvoiceRegistrationController.java src/main/java/com/ruoyi/sales/controller/MetricStatisticsController.java src/main/java/com/ruoyi/sales/controller/PaymentShippingController.java src/main/java/com/ruoyi/sales/controller/ReceiptPaymentController.java src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java src/main/java/com/ruoyi/sales/controller/SalesQuotationController.java src/main/java/com/ruoyi/sales/controller/SalespersonManagementController.java src/main/java/com/ruoyi/sales/controller/ShipmentApprovalController.java src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java src/main/java/com/ruoyi/sales/controller/ShippingProductDetailController.java src/main/java/com/ruoyi/sales/dto/InvoiceLedgerDto.java src/main/java/com/ruoyi/sales/dto/InvoiceRegistrationDto.java src/main/java/com/ruoyi/sales/dto/InvoiceRegistrationProductDto.java src/main/java/com/ruoyi/sales/dto/ReceiptPaymentDto.java src/main/java/com/ruoyi/sales/dto/ReceiptPaymentExeclDto.java src/main/java/com/ruoyi/sales/dto/ReceiptPaymentRecordDto.java src/main/java/com/ruoyi/sales/dto/SalesLedgerDto.java src/main/java/com/ruoyi/sales/dto/SalesLedgerImportDto.java src/main/java/com/ruoyi/sales/dto/SalesLedgerProductDto.java src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java src/main/java/com/ruoyi/sales/dto/ShippingProductDetailDto.java src/main/java/com/ruoyi/sales/dto/StatisticsTableDto.java src/main/java/com/ruoyi/sales/excel/InvoiceLedgerExcelDto.java src/main/java/com/ruoyi/sales/excel/InvoiceRegisAndProductExcelDto.java src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java src/main/java/com/ruoyi/sales/mapper/ShippingInfoMapper.java src/main/java/com/ruoyi/sales/mapper/ShippingProductDetailMapper.java src/main/java/com/ruoyi/sales/pojo/CommonFile.java src/main/java/com/ruoyi/sales/pojo/InvoiceLedger.java src/main/java/com/ruoyi/sales/pojo/InvoiceLedgerFile.java src/main/java/com/ruoyi/sales/pojo/InvoiceRegistration.java src/main/java/com/ruoyi/sales/pojo/InvoiceRegistrationProduct.java src/main/java/com/ruoyi/sales/pojo/Loss.java src/main/java/com/ruoyi/sales/pojo/PaymentShipping.java src/main/java/com/ruoyi/sales/pojo/PurchaseLedgerFile.java src/main/java/com/ruoyi/sales/pojo/ReceiptPayment.java src/main/java/com/ruoyi/sales/pojo/SalesLedger.java src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java src/main/java/com/ruoyi/sales/pojo/SalesQuotationProduct.java src/main/java/com/ruoyi/sales/pojo/SalespersonManagement.java src/main/java/com/ruoyi/sales/pojo/ShipmentApproval.java src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java src/main/java/com/ruoyi/sales/pojo/ShippingProductDetail.java src/main/java/com/ruoyi/sales/service/ICommonFileService.java src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java src/main/java/com/ruoyi/sales/service/InvoiceLedgerService.java src/main/java/com/ruoyi/sales/service/InvoiceRegistrationService.java src/main/java/com/ruoyi/sales/service/ReceiptPaymentService.java src/main/java/com/ruoyi/sales/service/ShippingInfoService.java src/main/java/com/ruoyi/sales/service/ShippingProductDetailService.java src/main/java/com/ruoyi/sales/service/impl/CommonFileServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/InvoiceLedgerServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/InvoiceRegistrationServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/MetricStatisticsServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/PaymentShippingServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/ReceiptPaymentServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/SalespersonManagementServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/ShipmentApprovalServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java src/main/java/com/ruoyi/sales/service/impl/ShippingProductDetailServiceImpl.java src/main/java/com/ruoyi/sales/vo/SalesLedgerVo.java src/main/java/com/ruoyi/staff/controller/AnalyticsController.java src/main/java/com/ruoyi/staff/controller/BankController.java src/main/java/com/ruoyi/staff/controller/HolidayApplicationController.java src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java src/main/java/com/ruoyi/staff/controller/PersonalShiftController.java src/main/java/com/ruoyi/staff/controller/SchemeApplicableStaffController.java src/main/java/com/ruoyi/staff/controller/StaffContractController.java src/main/java/com/ruoyi/staff/controller/StaffLeaveController.java src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java src/main/java/com/ruoyi/staff/controller/StaffSalaryMainController.java src/main/java/com/ruoyi/staff/controller/StaffSchedulingController.java src/main/java/com/ruoyi/staff/dto/PerformanceShiftAddDto.java src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java src/main/java/com/ruoyi/staff/dto/SaveStaffSchedulingDto.java src/main/java/com/ruoyi/staff/dto/StaffOnJobExcelDto.java src/main/java/com/ruoyi/staff/pojo/Bank.java src/main/java/com/ruoyi/staff/pojo/HolidayApplication.java src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceLocationConfig.java src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceRecords.java src/main/java/com/ruoyi/staff/pojo/PersonalShift.java src/main/java/com/ruoyi/staff/pojo/SchemeApplicableStaff.java src/main/java/com/ruoyi/staff/pojo/SchemeInsuranceDetail.java src/main/java/com/ruoyi/staff/pojo/StaffContract.java src/main/java/com/ruoyi/staff/pojo/StaffEducation.java src/main/java/com/ruoyi/staff/pojo/StaffEmergencyContact.java src/main/java/com/ruoyi/staff/pojo/StaffLeave.java src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java src/main/java/com/ruoyi/staff/pojo/StaffSalaryDetail.java src/main/java/com/ruoyi/staff/pojo/StaffSalaryMain.java src/main/java/com/ruoyi/staff/pojo/StaffScheduling.java src/main/java/com/ruoyi/staff/pojo/StaffWorkExperience.java src/main/java/com/ruoyi/staff/service/IStaffOnJobService.java src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java src/main/java/com/ruoyi/staff/service/StaffLeaveService.java src/main/java/com/ruoyi/staff/service/impl/AnalyticsServiceImpl.java src/main/java/com/ruoyi/staff/service/impl/HolidayApplicationServiceImpl.java src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java src/main/java/com/ruoyi/staff/service/impl/PersonalShiftServiceImpl.java src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java src/main/java/com/ruoyi/staff/service/impl/StaffSchedulingServiceImpl.java src/main/java/com/ruoyi/staff/task/PersonalAttendanceRecordsTask.java src/main/java/com/ruoyi/staff/vo/MonthlyTurnoverRateVo.java src/main/java/com/ruoyi/staff/vo/TotalTurnoverRateVo.java src/main/java/com/ruoyi/stock/controller/StockInRecordController.java src/main/java/com/ruoyi/stock/controller/StockInventoryController.java src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java src/main/java/com/ruoyi/stock/controller/StockUninventoryController.java src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java src/main/java/com/ruoyi/stock/dto/StockOutRecordDto.java src/main/java/com/ruoyi/stock/execl/StockInventoryExportData.java src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java src/main/java/com/ruoyi/stock/mapper/StockUninventoryMapper.java src/main/java/com/ruoyi/stock/pojo/StockInRecord.java src/main/java/com/ruoyi/stock/pojo/StockInventory.java src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java src/main/java/com/ruoyi/stock/pojo/StockUninventory.java src/main/java/com/ruoyi/stock/service/StockInRecordService.java src/main/java/com/ruoyi/stock/service/StockInventoryService.java src/main/java/com/ruoyi/stock/service/StockOutRecordService.java src/main/java/com/ruoyi/stock/service/StockUninventoryService.java src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java src/main/java/com/ruoyi/technology/bean/dto/BomImportDto.java src/main/java/com/ruoyi/technology/bean/dto/TechnologyBomDto.java src/main/java/com/ruoyi/technology/bean/dto/TechnologyBomStructureDto.java src/main/java/com/ruoyi/technology/bean/dto/TechnologyOperationDto.java src/main/java/com/ruoyi/technology/bean/dto/TechnologyOperationParamDto.java src/main/java/com/ruoyi/technology/bean/dto/TechnologyParamDto.java src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingDto.java src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingOperationDto.java src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingOperationParamDto.java src/main/java/com/ruoyi/technology/bean/dto/TechnologyRoutingOperationParamSyncDto.java src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomStructureVo.java src/main/java/com/ruoyi/technology/bean/vo/TechnologyBomVo.java src/main/java/com/ruoyi/technology/bean/vo/TechnologyOperationParamVo.java src/main/java/com/ruoyi/technology/bean/vo/TechnologyOperationVo.java src/main/java/com/ruoyi/technology/bean/vo/TechnologyParamVo.java src/main/java/com/ruoyi/technology/bean/vo/TechnologyRoutingOperationParamVo.java src/main/java/com/ruoyi/technology/bean/vo/TechnologyRoutingOperationVo.java src/main/java/com/ruoyi/technology/bean/vo/TechnologyRoutingVo.java src/main/java/com/ruoyi/technology/controller/TechnologyBomController.java src/main/java/com/ruoyi/technology/controller/TechnologyBomStructureController.java src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java src/main/java/com/ruoyi/technology/controller/TechnologyParamController.java src/main/java/com/ruoyi/technology/controller/TechnologyRoutingController.java src/main/java/com/ruoyi/technology/controller/TechnologyRoutingOperationController.java src/main/java/com/ruoyi/technology/controller/TechnologyRoutingOperationParamController.java src/main/java/com/ruoyi/technology/mapper/TechnologyBomMapper.java src/main/java/com/ruoyi/technology/mapper/TechnologyBomStructureMapper.java src/main/java/com/ruoyi/technology/mapper/TechnologyOperationMapper.java src/main/java/com/ruoyi/technology/mapper/TechnologyOperationParamMapper.java src/main/java/com/ruoyi/technology/mapper/TechnologyParamMapper.java src/main/java/com/ruoyi/technology/mapper/TechnologyRoutingMapper.java src/main/java/com/ruoyi/technology/mapper/TechnologyRoutingOperationMapper.java src/main/java/com/ruoyi/technology/mapper/TechnologyRoutingOperationParamMapper.java src/main/java/com/ruoyi/technology/pojo/TechnologyBom.java src/main/java/com/ruoyi/technology/pojo/TechnologyBomStructure.java src/main/java/com/ruoyi/technology/pojo/TechnologyOperation.java src/main/java/com/ruoyi/technology/pojo/TechnologyOperationParam.java src/main/java/com/ruoyi/technology/pojo/TechnologyParam.java src/main/java/com/ruoyi/technology/pojo/TechnologyRouting.java src/main/java/com/ruoyi/technology/pojo/TechnologyRoutingOperation.java src/main/java/com/ruoyi/technology/pojo/TechnologyRoutingOperationParam.java src/main/java/com/ruoyi/technology/service/TechnologyBomService.java src/main/java/com/ruoyi/technology/service/TechnologyBomStructureService.java src/main/java/com/ruoyi/technology/service/TechnologyOperationParamService.java src/main/java/com/ruoyi/technology/service/TechnologyOperationService.java src/main/java/com/ruoyi/technology/service/TechnologyParamService.java src/main/java/com/ruoyi/technology/service/TechnologyRoutingOperationParamService.java src/main/java/com/ruoyi/technology/service/TechnologyRoutingOperationService.java src/main/java/com/ruoyi/technology/service/TechnologyRoutingService.java src/main/java/com/ruoyi/technology/service/impl/TechnologyBomServiceImpl.java src/main/java/com/ruoyi/technology/service/impl/TechnologyBomStructureServiceImpl.java src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationParamServiceImpl.java src/main/java/com/ruoyi/technology/service/impl/TechnologyOperationServiceImpl.java src/main/java/com/ruoyi/technology/service/impl/TechnologyParamServiceImpl.java src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingOperationParamServiceImpl.java src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingOperationServiceImpl.java src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java src/main/java/com/ruoyi/warehouse/controller/DocumentClassificationController.java src/main/java/com/ruoyi/warehouse/controller/DocumentationBorrowManagementController.java src/main/java/com/ruoyi/warehouse/controller/DocumentationController.java src/main/java/com/ruoyi/warehouse/controller/DocumentationFileController.java src/main/java/com/ruoyi/warehouse/controller/WarehouseController.java src/main/java/com/ruoyi/warehouse/controller/WarehouseGoodsShelvesController.java src/main/java/com/ruoyi/warehouse/controller/WarehouseGoodsShelvesRowcolController.java src/main/java/com/ruoyi/warehouse/dto/DocumentationBorrowManagementDto.java src/main/java/com/ruoyi/warehouse/dto/ReturnExportDto.java src/main/java/com/ruoyi/warehouse/mapper/DocumentationBorrowManagementMapper.java src/main/java/com/ruoyi/warehouse/pojo/DocumentClassification.java src/main/java/com/ruoyi/warehouse/pojo/Documentation.java src/main/java/com/ruoyi/warehouse/pojo/DocumentationBorrowManagement.java src/main/java/com/ruoyi/warehouse/pojo/DocumentationFile.java src/main/java/com/ruoyi/warehouse/pojo/DocumentationReturnManagement.java src/main/java/com/ruoyi/warehouse/pojo/Warehouse.java src/main/java/com/ruoyi/warehouse/pojo/WarehouseGoodsShelves.java src/main/java/com/ruoyi/warehouse/pojo/WarehouseGoodsShelvesRowcol.java src/main/java/com/ruoyi/warehouse/service/DocumentationBorrowManagementService.java src/main/java/com/ruoyi/warehouse/service/DocumentationService.java src/main/java/com/ruoyi/warehouse/service/impl/DocumentClassificationServiceImpl.java src/main/java/com/ruoyi/warehouse/service/impl/DocumentationBorrowManagementServiceImpl.java src/main/java/com/ruoyi/warehouse/service/impl/DocumentationServiceImpl.java src/main/java/com/ruoyi/warehouse/service/impl/WarehouseGoodsShelvesRowcolServiceImpl.java src/main/java/com/ruoyi/warehouse/service/impl/WarehouseGoodsShelvesServiceImpl.java src/main/java/com/ruoyi/warehouse/service/impl/WarehouseServiceImpl.java src/main/java/com/ruoyi/waterrecord/controller/WaterRecordController.java src/main/java/com/ruoyi/waterrecord/pojo/WaterRecord.java src/main/java/com/ruoyi/waterrecord/service/impl/WaterRecordServiceImpl.java src/main/resources/application-demo.yml src/main/resources/application-dev-pro.yml src/main/resources/application-dev.yml src/main/resources/application-hbtmblc.yml src/main/resources/application-new-pro.yml src/main/resources/application.yml src/main/resources/approve-todo-agent-prompt.txt src/main/resources/mapper/account/AccountExpenseMapper.xml src/main/resources/mapper/account/AccountIncomeMapper.xml src/main/resources/mapper/account/SalesRefundAmountOrderMapper.xml src/main/resources/mapper/approve/ApproveProcessConfigNodeMapper.xml src/main/resources/mapper/approve/ApproveProcessMapper.xml src/main/resources/mapper/basic/CustomerMapper.xml src/main/resources/mapper/basic/ProductModelMapper.xml src/main/resources/mapper/basic/StorageAttachmentMapper.xml src/main/resources/mapper/basic/StorageBlobMapper.xml src/main/resources/mapper/device/DeviceMaintenanceMapper.xml src/main/resources/mapper/device/DeviceRepairMapper.xml src/main/resources/mapper/measuringinstrumentledger/SparePartsRequisitionRecordMapper.xml src/main/resources/mapper/procurementrecord/ReturnSaleProductMapper.xml src/main/resources/mapper/production/ProcessRouteItemMapper.xml (已删除) src/main/resources/mapper/production/ProcessRouteMapper.xml (已删除) src/main/resources/mapper/production/ProductBomMapper.xml (已删除) src/main/resources/mapper/production/ProductOrderMapper.xml (已删除) src/main/resources/mapper/production/ProductProcessMapper.xml (已删除) src/main/resources/mapper/production/ProductProcessRouteItemMapper.xml (已删除) src/main/resources/mapper/production/ProductProcessRouteMapper.xml (已删除) src/main/resources/mapper/production/ProductStructureMapper.xml (已删除) src/main/resources/mapper/production/ProductWorkOrderFileMapper.xml (已删除) src/main/resources/mapper/production/ProductWorkOrderMapper.xml (已删除) src/main/resources/mapper/production/ProductionAccountMapper.xml src/main/resources/mapper/production/ProductionBomStructureMapper.xml src/main/resources/mapper/production/ProductionOperationMainParamMapper.xml src/main/resources/mapper/production/ProductionOperationTaskMapper.xml src/main/resources/mapper/production/ProductionOrderBomMapper.xml src/main/resources/mapper/production/ProductionOrderMapper.xml src/main/resources/mapper/production/ProductionOrderPickMapper.xml src/main/resources/mapper/production/ProductionOrderPickRecordMapper.xml src/main/resources/mapper/production/ProductionOrderRoutingMapper.xml src/main/resources/mapper/production/ProductionOrderRoutingOperationMapper.xml src/main/resources/mapper/production/ProductionOrderRoutingOperationParamMapper.xml src/main/resources/mapper/production/ProductionPlanMapper.xml src/main/resources/mapper/production/ProductionProductInputMapper.xml src/main/resources/mapper/production/ProductionProductMainMapper.xml src/main/resources/mapper/production/ProductionProductOutputMapper.xml src/main/resources/mapper/production/SalesLedgerProductionAccountingMapper.xml (已删除) src/main/resources/mapper/production/SalesLedgerSchedulingMapper.xml (已删除) src/main/resources/mapper/production/SalesLedgerWorkMapper.xml (已删除) src/main/resources/mapper/purchase/PaymentRegistrationMapper.xml src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml src/main/resources/mapper/purchase/PurchaseReturnOrderProductsMapper.xml src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml src/main/resources/mapper/quality/QualityInspectMapper.xml src/main/resources/mapper/quality/QualityTestStandardMapper.xml src/main/resources/mapper/sales/InvoiceLedgerMapper.xml src/main/resources/mapper/sales/SalesLedgerMapper.xml src/main/resources/mapper/sales/SalesLedgerProductMapper.xml src/main/resources/mapper/sales/SalesQuotationMapper.xml src/main/resources/mapper/sales/ShippingInfoMapper.xml src/main/resources/mapper/sales/ShippingProductDetailMapper.xml src/main/resources/mapper/staff/PersonalShiftMapper.xml src/main/resources/mapper/staff/StaffOnJobMapper.xml src/main/resources/mapper/stock/StockInRecordMapper.xml src/main/resources/mapper/stock/StockInventoryMapper.xml src/main/resources/mapper/stock/StockOutRecordMapper.xml src/main/resources/mapper/stock/StockUninventoryMapper.xml src/main/resources/mapper/system/SysRoleMapper.xml src/main/resources/mapper/system/SysUserMapper.xml src/main/resources/mapper/technology/TechnologyBomMapper.xml src/main/resources/mapper/technology/TechnologyBomStructureMapper.xml src/main/resources/mapper/technology/TechnologyOperationMapper.xml src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml src/main/resources/mapper/technology/TechnologyParamMapper.xml src/main/resources/mapper/technology/TechnologyRoutingMapper.xml src/main/resources/mapper/technology/TechnologyRoutingOperationMapper.xml src/main/resources/mapper/technology/TechnologyRoutingOperationParamMapper.xml src/main/resources/my-prompt-template.txt src/main/resources/my-prompt-template3.txt src/main/resources/mybatis/mybatis-config.xml src/main/resources/purchase-agent-prompt.txt src/main/resources/static/work-order-template.docx src/main/resources/vm/java/controller.java.vm