4 小时以前 620bb4712a31791231c4381581f0f60088f079fe
Merge branch 'refs/heads/dev_New_pro' into dev_宁夏_英泽防锈

# Conflicts:
# src/main/resources/application-dev.yml
# src/main/resources/mapper/system/SysUserMapper.xml
已添加183个文件
已重命名1个文件
已修改284个文件
已删除84个文件
32147 ■■■■■ 文件已修改
doc/20260522_StockInRecord列表源单号前端联调文档.md 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260522_StockInRecord列表源单号前端联调文档_276补充.md 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260522_财务助手提问优化前端变更文档.md 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260522_财务升级AI模块前端变更联调文档.md 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260522_采购台账入库状态_销售产品入库审核状态前端联调文档.md 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260522_首页财务接口升级前端变更文档.md 120 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260523_ 协同审批新增出差时间和结束时间.sql 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/financial-ai-front-integration.md 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/CodeGenerator.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/AccountDto.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/AccountDto2.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/AccountDto3.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/AccountReportDto.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/StatementAccountDto.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/purchase/AccountPaymentApplicationDto.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/purchase/AccountPurchaseInvoiceDto.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/purchase/AccountPurchasePaymentDto.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/purchase/PurchaseInboundDto.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/purchase/PurchaseReturnDto.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/sales/AccountInvoiceApplicationDto.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/sales/AccountSalesCollectionDto.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/sales/AccountSalesInvoiceDto.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/sales/SalesOutboundDto.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/dto/sales/SalesReturnDto.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/AccountReportVo.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/StatementAccountVo.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/purchase/AccountPaymentApplicationVo.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/purchase/AccountPurchaseInvoiceVo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/purchase/AccountPurchasePaymentVo.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/purchase/PurchaseInboundVo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/sales/AccountInvoiceApplicationVo.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/sales/AccountSalesCollectionVo.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/sales/AccountSalesInvoiceVo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/bean/vo/sales/SalesOutboundVo.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountExpenseController.java 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountFileController.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountIncomeController.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountStatementController.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/AccountingController.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/BorrowInfoController.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/purchase/AccountPaymentApplicationController.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/purchase/AccountPurchaseInvoiceController.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/purchase/AccountPurchasePaymentController.java 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/sales/AccountInvoiceApplicationController.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/sales/AccountSalesCollectionController.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/controller/sales/AccountSalesInvoiceController.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/AccountFileMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/AccountStatementDetailsMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/AccountStatementMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/BorrowInfoMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/purchase/AccountPaymentApplicationMapper.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/purchase/AccountPurchaseInvoiceMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/purchase/AccountPurchasePaymentMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/sales/AccountInvoiceApplicationMapper.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/sales/AccountSalesCollectionMapper.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/mapper/sales/AccountSalesInvoiceMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountExpense.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountFile.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountIncome.java 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountStatement.java 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountStatementDetails.java 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/BorrowInfo.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/financial/AccountSubject.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/purchase/AccountPaymentApplication.java 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/purchase/AccountPurchaseInvoice.java 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/purchase/AccountPurchasePayment.java 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/sales/AccountInvoiceApplication.java 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/sales/AccountSalesCollection.java 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/sales/AccountSalesInvoice.java 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountExpenseService.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountFileService.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountIncomeService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountStatementDetailsService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountStatementService.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/AccountingService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/BorrowInfoService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountExpenseServiceImpl.java 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountFileServiceImpl.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountIncomeServiceImpl.java 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountStatementDetailsServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountStatementServiceImpl.java 298 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/AccountingServiceImpl.java 219 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/BorrowInfoServiceImpl.java 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/financial/AccountSubjectServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/purchase/AccountPaymentApplicationServiceImpl.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/purchase/AccountPurchaseInvoiceServiceImpl.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/purchase/AccountPurchasePaymentServiceImpl.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/sales/AccountInvoiceApplicationServiceImpl.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/sales/AccountSalesCollectionServiceImpl.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/sales/AccountSalesInvoiceServiceImpl.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/purchase/AccountPaymentApplicationService.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/purchase/AccountPurchaseInvoiceService.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/purchase/AccountPurchasePaymentService.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/sales/AccountInvoiceApplicationService.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/sales/AccountSalesCollectionService.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/sales/AccountSalesInvoiceService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | 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 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/FinancialAgent.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java 284 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/config/FinancialAgentConfig.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/controller/FinancialAiController.java 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/controller/SalesAiController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/controller/XiaozhiController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/schedule/AiSessionCleanupTask.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/service/PurchaseAiService.java 110 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java 2311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java 257 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java 426 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/dto/ApprovalInstanceDto.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeApproverDto.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/dto/FinReimbursementDto.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeApproverVo.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeVo.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateVo.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/ApproveGetAndUpdateVo.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessVO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/bean/vo/FinReimbursementVo.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApprovalInstanceController.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApprovalInstanceNodeController.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApprovalRecordController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApprovalTaskController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApprovalTemplateController.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeApproverController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApproveNodeController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/FinReimbursementController.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/FinReimbursementDetailController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/FinReimbursementTravelController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/HolidaySettingsController.java 77 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/NotificationManagementController.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/RpaProcessAutomationController.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceNodeMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApprovalRecordMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApprovalTaskMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeApproverMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/FinReimbursementDetailMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/FinReimbursementMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/FinReimbursementTravelMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApprovalInstance.java 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApprovalInstanceNode.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApprovalRecord.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApprovalTask.java 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApprovalTemplate.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNode.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNodeApprover.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/FinReimbursement.java 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/FinReimbursementDetail.java 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/FinReimbursementTravel.java 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/ApprovalInstanceNodeService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/ApprovalRecordService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/ApprovalTaskService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeApproverService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeService.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/ApprovalTemplateService.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/FinReimbursementDetailService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/FinReimbursementService.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/FinReimbursementTravelService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceNodeServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java 753 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalRecordServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalTaskServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeApproverServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeServiceImpl.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveBusinessStatusService.java 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 129 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/FinReimbursementDetailServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java 544 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/FinReimbursementTravelServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/utils/ApproveProcessConfigNodeUtils.java 363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/CustomerFollowUpController.java 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/ProductController.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/SupplierManageController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/SupplierManageFileController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/excel/SupplierManageExcelDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/mapper/SupplierManageMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/ProductModel.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/SupplierManage.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ICustomerService.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/IProductModelService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/ISupplierService.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/SupplierServiceImpl.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/DutyPlanController.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsController.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeDeptController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeUserController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/NoticeController.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/NoticeTypeController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/RulesRegulationsManagementController.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/RulesRegulationsManagementFileController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/controller/StaffContactsPersonalController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/dto/EnterpriseNewsDto.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeDeptMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeUserMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNews.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeDept.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeUser.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeDeptService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeUserService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsService.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeDeptServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeUserServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsServiceImpl.java 409 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/collaborativeApproval/vo/EnterpriseNewsVo.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/ApprovalStatusEnum.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/EnterpriseNewsStatusEnum.java 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/SalesQuotationStatusEnum.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/ShippingStatusEnum.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/TypeEnums.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/OrderUtils.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/controller/CompensationPerformanceController.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customervisits/controller/CustomerVisitsController.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customervisits/service/CustomerVisitsService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceDefectRecordController.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceLedgerController.java 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceMaintenanceFileController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceRepairController.java 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/MaintenanceTaskController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/IDeviceLedgerService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/IDeviceRepairService.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/MaintenanceTaskService.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceLedgerServiceImpl.java 67 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/ElectricityConsumptionAreaController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/EnergyPeriodController.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/EquipmentEnergyConsumptionController.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/service/EquipmentEnergyConsumptionService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/equipmentenergyconsumption/service/impl/EquipmentEnergyConsumptionServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/framework/web/controller/BaseController.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/controller/HomeController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/dto/StatisticsReceivablePayableDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java 588 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/lavorissue/controller/LavorIssueController.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/controller/MeasuringInstrumentLedgerController.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/controller/MeasuringInstrumentLedgerRecordController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsController.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/measuringinstrumentledger/controller/SparePartsRequisitionRecordController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/officesupplies/controller/OfficeSuppliesController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/officesupplies/service/OfficeSuppliesService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/officesupplies/service/impl/OfficeSuppliesServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/GasTankWarningController.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/InboundManagementController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementExceptionRecordController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPlanController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPriceManagementController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordController.java 76 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementRecordOutController.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ReturnManagementController.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/CustomStorage.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementRecordServiceImpl.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ReturnManagementServiceImpl.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionOrderRoutingController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/pojo/ProductionOrder.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java 131 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java 48 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/common/CaptchaController.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/CacheController.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/ServerController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/SysJobController.java 58 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/SysJobLogController.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/SysLogininforController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/SysOperlogController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/monitor/controller/SysUserOnlineController.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysConfigController.java 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysDeptController.java 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysDictDataController.java 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysDictTypeController.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysLoginController.java 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysMenuController.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysNoticeController.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysPostController.java 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysProfileController.java 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysRegisterController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysRoleController.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysUserClientController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysUserController.java 128 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/mapper/SysUserDeptMapper.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/ISysUserService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysUserServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/tool/gen/controller/GenController.java 40 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/controller/InfoController.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/controller/PlanController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/controller/RolesController.java 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/projectManagement/pojo/Roles.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/AccountingReportController.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/InvoicePurchaseController.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PaymentRegistrationController.java 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/ProcurementBusinessSummaryController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerController.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseLedgerTemplateController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/PurchaseReturnOrdersController.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/InvoicePurchaseDto.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/InvoicePurchaseReportDto.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PaymentRegistrationDto.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/ProductRecordDto.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/TicketRegistrationDto.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/dto/VatDto.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/InvoicePurchaseMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/PaymentRegistrationMapper.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/ProductRecordMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/mapper/TicketRegistrationMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/InvoicePurchase.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PaymentRegistration.java 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/ProductRecord.java 139 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseLedger.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/PurchaseReturnOrders.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/pojo/TicketRegistration.java 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/IInvoicePurchaseService.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/IPaymentRegistrationService.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/IProductRecordService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/ITicketRegistrationService.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/PurchaseReportService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/InvoicePurchaseServiceImpl.java 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PaymentRegistrationServiceImpl.java 564 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 113 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseReportServiceImpl.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseReturnOrdersServiceImpl.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/TicketRegistrationServiceImpl.java 464 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/PurchaseReportVo.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsDetailsVo.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsVo.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityInspectFileController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityTestStandardParamController.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityInspect.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/utils/QualityInspectHelper.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/CommonFileController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/InvoiceLedgerController.java 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/InvoiceRegistrationController.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/MetricStatisticsController.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/PaymentShippingController.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ReceiptPaymentController.java 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java 52 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesQuotationController.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalespersonManagementController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/ShippingInfoController.java 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/InvoiceLedgerDto.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/InvoiceRegistrationDto.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/InvoiceRegistrationProductDto.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ReceiptPaymentDto.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ReceiptPaymentExeclDto.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ReceiptPaymentRecordDto.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/SalesQuotationDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/excel/InvoiceLedgerExcelDto.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/excel/InvoiceRegisAndProductExcelDto.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/InvoiceLedgerFileMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/InvoiceLedgerMapper.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/InvoiceRegistrationMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/InvoiceRegistrationProductMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/ReceiptPaymentMapper.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/InvoiceLedger.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/InvoiceLedgerFile.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/InvoiceRegistration.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/InvoiceRegistrationProduct.java 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/PaymentShipping.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ReceiptPayment.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedgerProduct.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesQuotation.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/ShippingInfo.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ISalesLedgerProductService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ISalesLedgerService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/InvoiceLedgerService.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/InvoiceRegistrationService.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ReceiptPaymentService.java 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/ShippingInfoService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/InvoiceLedgerServiceImpl.java 509 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/InvoiceRegistrationServiceImpl.java 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/MetricStatisticsServiceImpl.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ReceiptPaymentServiceImpl.java 349 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerProductServiceImpl.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesQuotationServiceImpl.java 92 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/vo/CustomerTransactionsVo.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/AnalyticsController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/BankController.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/HolidayApplicationController.java 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/SchemeApplicableStaffController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffContractController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffLeaveController.java 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffSalaryMainController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffSchedulingController.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/StaffOnJobDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/IStaffOnJobService.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/SchemeApplicableStaffService.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/StaffSalaryMainService.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/SchemeApplicableStaffServiceImpl.java 29 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java 214 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffSalaryMainServiceImpl.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockInRecordController.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/controller/StockOutRecordController.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockInRecordDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/dto/StockInventoryDto.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockInRecord.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/StockInRecordService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInRecordServiceImpl.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockOutRecordServiceImpl.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/service/impl/StockUninventoryServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyOperationController.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyParam.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/pojo/TechnologyRouting.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/technology/service/impl/TechnologyRoutingServiceImpl.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/DocumentClassificationController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/DocumentationBorrowManagementController.java 46 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/DocumentationController.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/DocumentationFileController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/WarehouseController.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/WarehouseGoodsShelvesController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/controller/WarehouseGoodsShelvesRowcolController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/mapper/DocumentationFileMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/warehouse/service/DocumentationFileService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/waterrecord/controller/WaterRecordController.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/waterrecord/service/WaterRecordService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/waterrecord/service/impl/WaterRecordServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-ckgm.yml 268 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-hqjc.yml 268 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/financial-agent-prompt.txt 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/AccountExpenseMapper.xml 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/AccountFileMapper.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/AccountIncomeMapper.xml 111 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/AccountStatementMapper.xml 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/BorrowInfoMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/purchase/AccountPurchaseInvoiceMapper.xml 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/purchase/AccountPurchasePaymentMapper.xml 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/sales/AccountInvoiceApplicationMapper.xml 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/sales/AccountSalesCollectionMapper.xml 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/sales/AccountSalesInvoiceMapper.xml 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/aftersalesservice/AfterSalesNearExpiryMapper.xml 23 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/aftersalesservice/AfterSalesServiceMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApprovalInstanceMapper.xml 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApprovalInstanceNodeMapper.xml 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApprovalRecordMapper.xml 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApprovalTaskMapper.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApprovalTemplateMapper.xml 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApprovalTemplateNodeApproverMapper.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApprovalTemplateNodeMapper.xml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApproveProcessMapper.xml 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/FinReimbursementDetailMapper.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/FinReimbursementMapper.xml 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/FinReimbursementTravelMapper.xml 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/KnowledgeBaseMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/CustomerMapper.xml 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/ProductModelMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/basic/SupplierManageMapper.xml 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/collaborativeApproval/EnterpriseNewsMapper.xml 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeDeptMapper.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeUserMapper.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/collaborativeApproval/NoticeMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/collaborativeApproval/RulesRegulationsManagementMapper.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/device/DeviceMaintenanceMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/device/DeviceRepairMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerRecordMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/measuringinstrumentledger/SparePartsMapper.xml 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/measuringinstrumentledger/SparePartsRequisitionRecordMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOperationTaskMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/InvoicePurchaseMapper.xml 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PaymentRegistrationMapper.xml 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/ProductRecordMapper.xml 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseReturnOrdersMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityInspectMapper.xml 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityTestStandardMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/InvoiceLedgerMapper.xml 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/InvoiceRegistrationMapper.xml 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/InvoiceRegistrationProductMapper.xml 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/ReceiptPaymentMapper.xml 454 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerMapper.xml 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesQuotationMapper.xml 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/PersonalShiftMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffLeaveMapper.xml 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffOnJobMapper.xml 108 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInRecordMapper.xml 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInventoryMapper.xml 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockOutRecordMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysDeptMapper.xml 38 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysDictTypeMapper.xml 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysNoticeMapper.xml 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysPostMapper.xml 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysUserMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyOperationMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/warehouse/DocumentationBorrowManagementMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/warehouse/DocumentationMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/warehouse/DocumentationReturnManagementMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/warehouse/WarehouseGoodsShelvesRowcolMapper.xml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260522_StockInRecordÁбíÔ´µ¥ºÅǰ¶ËÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
# StockInRecord åˆ—表源单号前端联调文档
更新时间:2026-05-22
适用接口:`GET /stockInRecord/listPage`
## 1. å˜æ›´è¯´æ˜Ž
本次对入库管理列表接口增加返回字段 `sourceOrderNo`(源单号),用于原材料场景展示采购来源单号。
生效条件:
- å½“请求参数 `topParentProductId = 278` æ—¶ï¼ŒåŽç«¯è¿”回 `sourceOrderNo`。
- å…¶ä»– `topParentProductId` åœºæ™¯ä¸‹ï¼Œè¯¥å­—段返回 `null`(或不展示)。
## 2. å­—段定义
新增字段:
- `sourceOrderNo`:`string`,源单号(采购合同号)。
## 3. å–值规则
仅在 `topParentProductId = 278` æ—¶æŒ‰â€œæ¥æºâ€è®¡ç®—:
1. æ¥æº = `采购-入库`(`recordType = 7`)
   - å…ˆæŒ‰ `recordId` æŸ¥é‡‡è´­äº§å“è¡¨ `sales_ledger_product`(`type=2`);
   - å†é€šè¿‡ `sales_ledger_product.sales_ledger_id` æŸ¥é‡‡è´­å°è´¦è¡¨ `purchase_ledger`;
   - è¿”回 `purchase_ledger.purchase_contract_number` ä½œä¸º `sourceOrderNo`。
   - å…¼å®¹å…œåº•:若未命中采购产品链路,则按 `recordId` ç›´æŽ¥æŸ¥ `purchase_ledger.id` å–单号。
2. æ¥æº = `采购-质检-合格入库`(`recordType = 10`)
   - å…ˆæŒ‰ `recordId` æŸ¥è´¨æ£€è¡¨ `quality_inspect`;
   - å†é€šè¿‡ `quality_inspect.purchase_ledger_id` æŸ¥é‡‡è´­å°è´¦è¡¨ `purchase_ledger`;
   - è¿”回 `purchase_ledger.purchase_contract_number` ä½œä¸º `sourceOrderNo`。
## 4. è¿”回示例
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "id": 1024,
        "recordType": "7",
        "productName": "铜排",
        "model": "T2-30x3",
        "sourceOrderNo": "CG-2026-00128"
      },
      {
        "id": 1025,
        "recordType": "10",
        "productName": "铜排",
        "model": "T2-30x3",
        "sourceOrderNo": "CG-2026-00131"
      }
    ],
    "total": 2
  }
}
```
## 5. å‰ç«¯è”调建议
1. åˆ—表列新增“源单号”,读取字段 `sourceOrderNo`。
2. å»ºè®®ä»…在 `topParentProductId = 278` çš„页面/筛选条件下展示该列。
3. å½“ `sourceOrderNo` ä¸ºç©ºæ—¶å±•示 `--`,避免空白。
## 6. å›žå½’清单
1. `topParentProductId=278` + `recordType=7`:应返回采购合同号。
2. `topParentProductId=278` + `recordType=10`:应返回采购合同号(经质检链路)。
3. `topParentProductId!=278`:`sourceOrderNo` åº”为 `null` æˆ–前端不展示。
4. åŽŸæœ‰å­—æ®µï¼ˆ`productName/model/unit/createBy` ç­‰ï¼‰ä¸å—影响。
doc/20260522_StockInRecordÁбíÔ´µ¥ºÅǰ¶ËÁªµ÷Îĵµ_276²¹³ä.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
# StockInRecord åˆ—表源单号前端联调文档(`topParentProductId=276`补充)
更新时间:2026-05-22
适用接口:`GET /stockInRecord/listPage`
## 1. å˜æ›´è¯´æ˜Ž
在已有 `sourceOrderNo` åŸºç¡€ä¸Šï¼Œæ–°å¢ž `topParentProductId = 276` çš„æºå•号溯源逻辑:
- æ ¹æ®â€œæ¥æºï¼ˆrecordType)+ recordId”溯源;
- ä¼˜å…ˆè¿”回销售单号(销售合同号);
- è‹¥é”€å”®å•号为空,则回退返回生产订单号;
- ä¸è€ƒè™‘自定义入库(`recordType=0/9`)。
## 2. è¿”回字段
字段无新增,继续使用:
- `sourceOrderNo`:`string`,源单号。
## 3. 276 åœºæ™¯å–值规则
请求参数满足 `topParentProductId = 276` æ—¶ï¼š
1. `recordType = 14/15`(销售退货-合格/不合格入库)
   - `stock_in_record.record_id -> return_sale_product.id -> return_management.shipping_id -> shipping_info.sales_ledger_id -> sales_ledger.sales_contract_no`
   - è¿”回销售合同号。
2. `recordType = 2/5`(生产报工-入库/报废)
   - `stock_in_record.record_id -> production_product_main.id -> production_operation_task.production_order_id -> production_order`
   - å…ˆå–该生产订单关联销售合同号(由生产计划关联销售台账聚合);
   - é”€å”®åˆåŒå·ä¸ºç©ºæ—¶ï¼Œè¿”回 `production_order.nps_no`。
3. `recordType = 6`(质检-合格入库)
   - `stock_in_record.record_id -> quality_inspect.id -> quality_inspect.product_main_id -> production_product_main -> production_operation_task -> production_order`
   - å…ˆå–销售合同号,空则回退 `production_order.nps_no`。
4. `recordType = 4/11`(不合格处理-报废/让步放行)
   - `stock_in_record.record_id -> quality_unqualified.id -> quality_unqualified.inspect_id -> quality_inspect -> production_product_main -> production_operation_task -> production_order`
   - å…ˆå–销售合同号,空则回退 `production_order.nps_no`。
5. `recordType = 20/22`(领料退料/生产退料-合格入库)
   - `stock_in_record.record_id -> production_order_pick.id -> production_order`
   - å…ˆå–销售合同号,空则回退 `production_order.nps_no`。
6. `recordType = 0/9`(自定义入库)
   - ä¸å‚与溯源,`sourceOrderNo = null`。
## 4. å…¶ä»–场景说明
- `topParentProductId = 278` çš„采购链路源单号逻辑保持不变。
- å…¶ä»– `topParentProductId` ä¸è§¦å‘本次 276 è§„则,`sourceOrderNo` ä¸ºç©ºã€‚
## 5. å‰ç«¯è”调建议
1. åœ¨ `topParentProductId=276` çš„列表场景展示“源单号”列,读取 `sourceOrderNo`。
2. å»ºè®®ç©ºå€¼ç»Ÿä¸€å±•示 `--`。
3. ä¸éœ€è¦æ–°å¢žè¯·æ±‚参数,沿用现有 `/stockInRecord/listPage`。
## 6. å›žå½’清单
1. `topParentProductId=276` + `recordType=14/15`:应返回销售合同号。
2. `topParentProductId=276` + `recordType=2/5/6/4/11/20/22`:优先销售合同号,缺失时返回生产订单号。
3. `topParentProductId=276` + `recordType=0/9`:`sourceOrderNo` ä¸ºç©ºã€‚
4. `topParentProductId=278`:仍按采购链路返回采购合同号。
doc/20260522_²ÆÎñÖúÊÖÌáÎÊÓÅ»¯Ç°¶Ë±ä¸üÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
# è´¢åŠ¡åŠ©æ‰‹æé—®ä¼˜åŒ–å‰ç«¯å˜æ›´æ–‡æ¡£
更新时间:2026-05-22
适用模块:财务智能助手(`/financial-ai`)
## 1. èƒŒæ™¯
当前首页财务助手快捷提问为:
1. `生成本周经营周报`
2. `为什么利润下降`
3. `哪个客户最赚钱`
问题点:
- ç¬¬ 3 æ¡é—®æ³•在部分场景下意图命中不稳定,容易走普通文本回答,导致图表链接以原始 Markdown æ–‡æœ¬å±•示(如 `![...](https://local/generate_chart?options=...)`)。
- å¿«æ·æé—®ç¼ºå°‘时间范围和分析目标,结果稳定性与可解释性较弱。
## 2. å‰ç«¯å¿«æ·æé—®æ–‡æ¡ˆä¼˜åŒ–(必改)
建议将默认三条快捷提问调整为:
1. `生成本周经营周报(利润与现金流)`
2. `分析本月利润下降原因`
3. `近30天哪个客户利润贡献最高`
说明:
- ä¸‰æ¡é—®æ³•均带时间范围或分析目标,后端命中更稳定。
- ç¬¬ 3 æ¡ä¸Žâ€œæœ€èµšé’±å®¢æˆ·â€è¯­ä¹‰ä¸€è‡´ï¼Œä½†â€œåˆ©æ¶¦è´¡çŒ®æœ€é«˜â€æ›´æ˜Žç¡®ï¼Œé€‚合直接驱动利润分析结果页。
## 3. ä¸ŽåŽç«¯èƒ½åŠ›æ˜ å°„
| å¿«æ·æé—® | é¢„期命中能力 | é¢„期 `type` |
| --- | --- | --- |
| ç”Ÿæˆæœ¬å‘¨ç»è¥å‘¨æŠ¥ï¼ˆåˆ©æ¶¦ä¸ŽçŽ°é‡‘æµï¼‰ | ç»è¥æŠ¥å‘Šç”Ÿæˆ | `financial_operation_report` |
| åˆ†æžæœ¬æœˆåˆ©æ¶¦ä¸‹é™åŽŸå›  | è®¢å•利润分析 | `financial_order_profit_analysis` |
| è¿‘30天哪个客户利润贡献最高 | è®¢å•利润分析 | `financial_order_profit_analysis` |
后端已同步增强“最赚钱客户/客户利润最高/利润贡献最高”等同义问法识别,前端按以上文案改造后可直接联调。
## 4. èŠå¤©å†…容渲染兜底(建议改)
针对聊天返回文本中出现的图表 Markdown é“¾æŽ¥ï¼ˆ`https://local/generate_chart?options=...`),建议前端增加兜底处理:
1. è¯†åˆ« Markdown å›¾ç‰‡è¯­æ³•中的 `local/generate_chart` é“¾æŽ¥ã€‚
2. è§£æž `options` å‚数并转换为 ECharts `option` åŽæ¸²æŸ“图表组件。
3. è§£æžå¤±è´¥æ—¶ä¸å±•示原始长链接文本,展示统一空态提示。
## 5. è”调回归清单
1. ç‚¹å‡»å¿«æ·æé—® `生成本周经营周报(利润与现金流)`
   - æ ¡éªŒè¿”回 `type=financial_operation_report`,并正常渲染摘要/建议/图表。
2. ç‚¹å‡»å¿«æ·æé—® `分析本月利润下降原因`
   - æ ¡éªŒè¿”回 `type=financial_order_profit_analysis`,并展示亏损订单与客户利润排行。
3. ç‚¹å‡»å¿«æ·æé—® `近30天哪个客户利润贡献最高`
   - æ ¡éªŒè¿”回 `type=financial_order_profit_analysis`,`summary.topCustomerByProfit` æœ‰å€¼ã€‚
4. æ‰‹å·¥è¾“å…¥ `哪个客户最赚钱`、`哪个客户利润最高`
   - æ ¡éªŒä»å‘½ä¸­ `financial_order_profit_analysis`,不再出现原始图表 Markdown é“¾æŽ¥ç›´å‡ºã€‚
doc/20260522_²ÆÎñÉý¼¶AIÄ£¿éǰ¶Ë±ä¸üÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,150 @@
# è´¢åŠ¡æ¨¡å—å‡çº§åŽ AI æ¨¡å—前端变更联调文档(采购/销售/生产/待办)
更新日期:2026-05-22
适用范围:`/sales-ai`、`/purchase-ai`、`/manufacturing-ai`、`/xiaozhi`(审批待办)
## 1. å˜æ›´æ€»è§ˆ
| æ¨¡å— | å¯¹å¤–接口 | æ˜¯å¦éœ€è¦å‰ç«¯æ”¹é€  | ç»“论 |
| --- | --- | --- | --- |
| é”€å”® AI | `POST /sales-ai/chat` | æ˜¯ | è´¢åŠ¡å£å¾„åˆ‡æ¢åˆ°æ–°æ”¶æ¬¾æ¨¡åž‹ï¼Œéƒ¨åˆ† `type` çš„字段语义变化 |
| é‡‡è´­ AI | `POST /purchase-ai/chat` | æ˜¯ | ä»˜æ¬¾/发票/待付款计算切换到新财务链路,统计值从占位改为真实值 |
| ç”Ÿäº§ AI | `POST /manufacturing-ai/chat` | å¦ | å·²æ ¸æŸ¥ï¼Œæ— æ—§è´¢åŠ¡é€»è¾‘ä¾èµ–ï¼Œæ— å­—æ®µå˜æ›´ |
| å¾…办 AI | `POST /xiaozhi/chat` | å¦ | å·²æ ¸æŸ¥ï¼Œæ— æ—§è´¢åŠ¡é€»è¾‘ä¾èµ–ï¼Œæ— å­—æ®µå˜æ›´ |
## 2. é”€å”® AI å˜æ›´ï¼ˆ`/sales-ai/chat`)
### 2.1 `type = sales_return_list`(销售退款/回款记录)
当前返回数据来源统一为新财务表 `account_sales_collection`,不再走旧收款退货逻辑。
`data.items[]` å…³é”®å­—段:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
| --- | --- | --- |
| id | number | æ”¶æ¬¾è®°å½•ID |
| refundId | string | æ˜ å°„ `collectionNumber`,前端可继续作为“退款/回款单号”展示 |
| collectionNumber | string | æ”¶æ¬¾å•号 |
| paymentMethod | string | æ”¶æ¬¾æ–¹å¼ |
| actualAmount | number | æ”¶æ¬¾é‡‘额(与 `collectionAmount` åŒå€¼ï¼‰ |
| collectionAmount | number | æ”¶æ¬¾é‡‘额(推荐主展示字段) |
| customerId | number | å®¢æˆ·ID |
| remark | string | å¤‡æ³¨ |
| createTime | string | æ”¶æ¬¾æ—¥æœŸï¼ˆyyyy-MM-dd) |
`summary` å¢žé‡å…³æ³¨ï¼š
- `returnAmount`:时间范围内金额汇总(按 `collectionAmount` ç»Ÿè®¡ï¼‰
### 2.2 `type = sales_customer_interaction_list`(客户往来)
当前返回基于新链路:
`account_sales_collection.stock_out_record_ids -> stock_out_record(record_type=13) -> shipping_info -> sales_ledger`
返回约定:
- æ— æ•°æ®æ—¶ï¼š`description = "no_customer_interactions"`
- æœ‰æ•°æ®æ—¶ï¼š`description = "ok"`
`summary` å…³é”®å­—段:
- `totalReceiptAmount`
- `customerCount`
`data.items[]` å…³é”®å­—段:
- `salesLedgerId`
- `salesContractNo`
- `customerName`
- `projectName`
- `receiptPaymentDate`
- `receiptPaymentAmount`
- `receiptPaymentType`
- `collectionNumber`
- `registrant`
- `remark`
### 2.3 `type = sales_ledger_list`(销售台账)
字段结构不变,但金额口径已切换:
- `receivedAmount` ç”±æ–°æ”¶æ¬¾æ¨¡åž‹æ±‡æ€»å¾—到;
- `pendingAmount = max(0, invoicedAmount - receivedAmount)`;
- è‹¥æ”¶æ¬¾è®°å½•未显式关联台账,则按客户维度兜底归集。
前端改造建议:
- ä¸æ”¹å­—段名;
- é‡ç‚¹å›žå½’“已收金额/待回款金额”是否与财务台账一致。
## 3. é‡‡è´­ AI å˜æ›´ï¼ˆ`/purchase-ai/chat`)
### 3.1 `type = purchase_stats`(采购统计)
以下字段已从占位值改为真实统计值:
- `summary.paymentCount`
- `summary.invoiceCount`
- `summary.paymentAmount`
- `summary.invoiceAmount`
发票金额口径:
- ä¼˜å…ˆ `taxInclusivePrice`
- è‹¥ä¸ºç©º/0,则使用 `taxExclusivelPrice + taxPrice`
### 3.2 `type = purchase_pending_payment_list`(待付款采购单)
核心计算已切换到新财务链路:
`account_purchase_payment -> account_payment_application -> stock_in_record -> (purchase_ledger / quality_inspect) -> purchase_ledger_id`
映射规则:
1. `stock_in_record.record_type = 7`:`record_id` ç›´æŽ¥è§†ä¸º `purchase_ledger_id`
2. `stock_in_record.record_type = 10`:通过 `quality_inspect.id = record_id` å– `quality_inspect.purchase_ledger_id`
金额字段口径:
- `paidAmount`:新链路累计已付款金额
- `pendingAmount = contractAmount - paidAmount`(<=0 çš„记录不返回)
`summary` å…³é”®å­—段(均为真实值):
- `pendingOrderCount`
- `totalContractAmount`
- `totalPaidAmount`
- `totalPendingAmount`
### 3.3 æ•°æ®æ¸…洗修复
已修复 `record_type` å¸¦ç©ºæ ¼å¯¼è‡´çš„æ˜ å°„丢失问题(后端统一 `trim()` åŽå†åˆ¤æ–­ `7/10`)。
## 4. ç”Ÿäº§ AI / å¾…办 AI æ ¸æŸ¥ç»“论
已核查以下模块代码,未发现旧财务逻辑耦合点:
- `ManufacturingAgentTools`(生产)
- `ApproveTodoTools`(待办审批)
结论:
- å¯¹å¤– `type` ä¸Žå­—段结构无变更;
- å‰ç«¯æ— éœ€åšå…¼å®¹æ”¹é€ ï¼Œä»…需做一次回归验证。
## 5. å‰ç«¯è”调要点
1. `/sales-ai/chat`、`/purchase-ai/chat` ç»§ç»­æŒ‰ SSE æ–‡æœ¬æµæ‹¼æŽ¥åŽåš JSON è§£æžã€‚
2. æŒ‰ `type` è·¯ç”±æ¸²æŸ“,不要仅依赖 `description` æ–‡æ¡ˆã€‚
3. `sales_customer_interaction_list` éœ€å…¼å®¹ `description` æžšä¸¾ï¼š`ok` / `no_customer_interactions`。
4. `sales_return_list` é‡‘额展示统一用 `collectionAmount`(`actualAmount` ä¿ç•™å…¼å®¹ï¼‰ã€‚
5. `purchase_pending_payment_list` çš„æ±‡æ€»å¡ç‰‡è¯·ç›´æŽ¥è¯»å– `summary.totalPendingAmount` ç­‰å­—段,不再前端二次估算。
## 6. å›žå½’清单(建议)
### é”€å”®
1. æé—®ï¼šâ€œè¿‘30天哪个订单回款最少”
   - æ ¡éªŒ `sales_ledger_list` çš„ `receivedAmount/pendingAmount`。
2. æé—®ï¼šâ€œæŸ¥è¯¢æœ¬æœˆé”€å”®é€€æ¬¾â€
   - æ ¡éªŒ `sales_return_list` çš„ `collectionNumber/collectionAmount/returnAmount`。
3. æé—®ï¼šâ€œæŸ¥è¯¢æœ¬æœˆå®¢æˆ·å¾€æ¥â€
   - æ ¡éªŒ `sales_customer_interaction_list` çš„ `totalReceiptAmount/customerCount`。
### é‡‡è´­
1. æé—®ï¼šâ€œç»Ÿè®¡æœ¬æœˆé‡‡è´­æ•°æ®â€
   - æ ¡éªŒ `purchase_stats` çš„ `paymentCount/invoiceCount/paymentAmount/invoiceAmount` éžå›ºå®š0。
2. æé—®ï¼šâ€œåˆ—出待付款采购单”
   - æ ¡éªŒ `purchase_pending_payment_list` çš„ `paidAmount/pendingAmount` ä¸Žè´¢åŠ¡å®žé™…ä¸€è‡´ã€‚
### ç”Ÿäº§/待办
1. ç”Ÿäº§æé—®ï¼šâ€œæŸ¥è¯¢æœ¬å‘¨è®¾å¤‡ç»´ä¿®è®°å½•”
2. å¾…办提问:“查询我的待审批列表”
   - æ ¡éªŒè¿”回结构与升级前一致(无字段破坏)。
doc/20260522_²É¹ºÌ¨ÕËÈë¿â״̬_ÏúÊÛ²úÆ·Èë¿âÉóºË״̬ǰ¶ËÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
# é‡‡è´­å…¥åº“状态前端联调文档
更新时间:2026-05-22
适用版本:本次后端变更后
## 1. å˜æ›´èŒƒå›´
1. `GET /purchaseLedger/listPage`
   - æ–°å¢žæŸ¥è¯¢æ¡ä»¶ï¼š`stockInStatus`(入库状态)
   - æ–°å¢žè¿”回字段:`stockInStatus`(入库状态)
2. `GET /salesLedgerProduct/list`
   - æ–°å¢žè¿”回字段:`stockInApprovalStatus`(每个产品的入库审核状态)
---
## 2. å…¥åº“状态枚举(两接口一致)
- `待入库`
- `入库中`
- `完全入库`
说明:前端筛选值请直接使用以上中文枚举值。
---
## 3. æŽ¥å£ä¸€ï¼š`GET /purchaseLedger/listPage`
### 3.1 æ–°å¢žè¯·æ±‚参数
- `stockInStatus`:`string`,可选
  å¯ä¼ å€¼ï¼š`待入库` / `入库中` / `完全入库`
### 3.2 æ–°å¢žè¿”回字段
- `stockInStatus`:`string`,采购台账维度入库状态
### 3.3 çŠ¶æ€è®¡ç®—è§„åˆ™ï¼ˆé‡‡è´­å°è´¦ç»´åº¦ï¼‰
以该采购台账下 `sales_ledger_product.type = 2` çš„采购产品为计算范围:
1. å…¨éƒ¨äº§å“éƒ½è¾¾åˆ°â€œå®Œå…¨å…¥åº“” => å°è´¦çŠ¶æ€ `完全入库`
2. è‡³å°‘有一个产品存在“审核通过入库”,但未全部完全入库 => å°è´¦çŠ¶æ€ `入库中`
3. æ²¡æœ‰ä»»ä½•产品存在“审核通过入库” => å°è´¦çŠ¶æ€ `待入库`
“审核通过入库”统计口径:`stock_in_record.approval_status = 1`,并按以下来源溯源:
- `record_type = 7`(采购-入库):按采购台账+产品关联统计
- `record_type = 10`(采购-质检-合格入库):通过 `quality_inspect` å›žæº¯åˆ°é‡‡è´­å°è´¦+产品统计
### 3.4 è¿”回示例(节选)
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "records": [
      {
        "id": 1201,
        "purchaseContractNumber": "CG20260522001",
        "supplierName": "XX供应商",
        "stockInStatus": "入库中"
      }
    ],
    "total": 1
  }
}
```
---
## 4. æŽ¥å£äºŒï¼š`GET /salesLedgerProduct/list`
### 4.1 æ–°å¢žè¿”回字段
- `stockInApprovalStatus`:`string`,当前产品行的入库审核状态
### 4.2 çŠ¶æ€è®¡ç®—è§„åˆ™ï¼ˆäº§å“ç»´åº¦ï¼‰
仅当产品 `type = 2`(采购产品)时计算并返回:
1. å®¡æ ¸é€šè¿‡å…¥åº“数量 `<= 0` => `待入库`
2. å®¡æ ¸é€šè¿‡å…¥åº“数量 `>= äº§å“é‡‡è´­æ•°é‡` => `完全入库`
3. å…¶ä»–情况 => `入库中`
其中“审核通过入库数量”统计同样基于:
- `stock_in_record.approval_status = 1`
- æ¥æº `record_type = 7 / 10` çš„æº¯æºå…³è”逻辑
`type != 2` çš„产品,该字段返回 `null`。
### 4.3 è¿”回示例(节选)
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": [
    {
      "id": 5566,
      "type": 2,
      "productCategory": "铜材",
      "specificationModel": "T2-30x3",
      "quantity": 100,
      "stockInApprovalStatus": "待入库"
    }
  ]
}
```
---
## 5. å‰ç«¯æ”¹é€ å»ºè®®
1. é‡‡è´­å°è´¦åˆ—表新增“入库状态”筛选项,值固定:`待入库/入库中/完全入库`。
2. é‡‡è´­å°è´¦åˆ—表新增“入库状态”列,展示 `stockInStatus`。
3. é‡‡è´­äº§å“åˆ—表新增“入库审核状态”列,展示 `stockInApprovalStatus`。
4. ç©ºå€¼ï¼ˆå¦‚ `type != 2`)建议展示为 `--`。
---
## 6. è”调检查清单
1. `/purchaseLedger/listPage` ä¸ä¼  `stockInStatus`:应正常返回全部数据,并带 `stockInStatus`。
2. `/purchaseLedger/listPage?stockInStatus=待入库`:仅返回待入库台账。
3. `/purchaseLedger/listPage?stockInStatus=入库中`:仅返回入库中台账。
4. `/purchaseLedger/listPage?stockInStatus=完全入库`:仅返回完全入库台账。
5. `/salesLedgerProduct/list` åœ¨é‡‡è´­äº§å“ï¼ˆ`type=2`)下返回 `stockInApprovalStatus`。
6. `/salesLedgerProduct/list` åœ¨éžé‡‡è´­äº§å“ï¼ˆ`type!=2`)下 `stockInApprovalStatus` ä¸º `null`。
doc/20260522_Ê×Ò³²ÆÎñ½Ó¿ÚÉý¼¶Ç°¶Ë±ä¸üÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,120 @@
# é¦–页财务接口升级前端变更文档
更新时间:2026-05-22
适用模块:首页(`/home`)
## 1. å˜æ›´æ¦‚览
本次为 **兼容式升级**,接口 URL、请求参数、返回字段保持不变。
主要变更是首页财务数据从占位/半成品逻辑,切换为按财务真实数据口径计算。
涉及接口:
1. `GET /home/statisticsReceivablePayable`
2. `GET /home/monthlyIncome`
3. `GET /home/monthlyExpenditure`
## 2. å‚数说明(无新增)
### 2.1 `GET /home/statisticsReceivablePayable`
- `type`:`1` æœ¬å‘¨ï¼Œ`2` æœ¬æœˆï¼Œ`3` æœ¬å­£åº¦ï¼ˆé»˜è®¤ `1`)
### 2.2 `GET /home/monthlyIncome`
- æ— å‚æ•°
### 2.3 `GET /home/monthlyExpenditure`
- æ— å‚æ•°
## 3. è¿”回字段口径变更
### 3.1 åº”收应付统计 `statisticsReceivablePayable`
返回字段不变:
- `receivableMoney`
- `payableMoney`
- `advanceMoney`
- `prepayMoney`
新口径:
- `receivableMoney = max(销售合同金额合计 - æ”¶æ¬¾é‡‘额合计, 0)`
- `payableMoney = max(采购合同金额合计 - ä»˜æ¬¾é‡‘额合计, 0)`
- `advanceMoney = æ”¶æ¬¾é‡‘额合计`
- `prepayMoney = ä»˜æ¬¾é‡‘额合计`
以上金额均按 `type` å¯¹åº”时间范围统计,保留两位小数。
返回示例:
```json
{
  "receivableMoney": 128000.00,
  "payableMoney": 76000.00,
  "advanceMoney": 42000.00,
  "prepayMoney": 31000.00
}
```
### 3.2 æœˆåº¦æ”¶å…¥ `monthlyIncome`
返回字段不变:
- `monthlyIncome`
- `collectionRate`
- `overdueNum`
- `overdueRate`
新口径:
- `monthlyIncome`:当月收款合计
- `collectionRate`:`当月收款合计 / å½“月销售合同金额合计 * 100`
- `overdueNum`:历史应收对账单(`account_statement.account_type=1`)中,早于当月且 `closing_balance > 0` çš„æ•°é‡
- `overdueRate`:`overdueNum / åŽ†å²åº”æ”¶å¯¹è´¦å•æ€»æ•° * 100`
返回示例:
```json
{
  "monthlyIncome": 89500.00,
  "collectionRate": "62.80",
  "overdueNum": 4,
  "overdueRate": "18.18"
}
```
### 3.3 æœˆåº¦æ”¯å‡º `monthlyExpenditure`
返回字段不变:
- `monthlyExpenditure`
- `paymentRate`
- `grossProfit`
- `profitMarginRate`
新口径:
- `monthlyExpenditure`:当月付款合计
- `paymentRate`:`当月付款合计 / å½“月采购合同金额合计 * 100`
- `grossProfit`:`当月收款合计 - å½“月付款合计`
- `profitMarginRate`:`grossProfit / å½“月收款合计 * 100`
返回示例:
```json
{
  "monthlyExpenditure": 73400.00,
  "paymentRate": "57.34",
  "grossProfit": 16100.00,
  "profitMarginRate": "17.99"
}
```
## 4. å‰ç«¯è”调说明
1. å‰ç«¯å­—段映射无需调整,可直接沿用现有解析逻辑。
2. ç™¾åˆ†æ¯”字段仍为不带 `%` çš„字符串,前端如需展示 `%` è¯·ç»§ç»­å‰ç«¯æ‹¼æŽ¥ã€‚
3. æœ¬æ¬¡åŽç«¯è¿”回值由真实财务数据驱动,建议重点回归卡片汇总与趋势图的数值联动。
doc/20260523_ ЭͬÉóÅúÐÂÔö³ö²îʱ¼äºÍ½áÊøÊ±¼ä.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
ALTER TABLE approve_process
    ADD COLUMN start_date_time datetime DEFAULT NULL COMMENT '出差开始时间',
    ADD COLUMN end_date_time   datetime DEFAULT NULL COMMENT '出差结束时间';
doc/financial-ai-front-integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,192 @@
# è´¢åŠ¡æ™ºèƒ½ä½“å‰ç«¯è”è°ƒæ–‡æ¡£
## 1. æ¨¡å—说明
财务智能体后端已新增统一入口 `financial-ai`,用于业财一体化分析,覆盖:
- æ™ºèƒ½æˆæœ¬æ ¸ç®—
- è®¢å•利润分析
- åº“存资金分析
- åº”收应付与现金流预测
- ç»è¥å¼‚常预警
- AI ç»è¥é©¾é©¶èˆ±
- æ—¥æŠ¥/周报自动生成
- è´¢åŠ¡çŸ¥è¯†æ£€ç´¢ï¼ˆè½»é‡ RAG ä¸Šä¸‹æ–‡ï¼‰
接口采用 **SSE æµå¼è¾“出**,工具命中时返回结构化 JSON å­—符串。
## 2. æŽ¥å£æ¸…单
### 2.1 å¯¹è¯æŽ¥å£ï¼ˆSSE)
- `POST /financial-ai/chat`
- `Content-Type: application/json`
- `Accept: text/stream;charset=utf-8`
请求体:
```json
{
  "memoryId": "finance-uuid-001",
  "message": "查询近30天亏损订单"
}
```
字段说明:
- `memoryId`:会话唯一标识(前端生成 UUID,单会话复用)
- `message`:自然语言问题
---
### 2.2 ä¼šè¯åˆ—表
- `GET /financial-ai/history/sessions`
---
### 2.3 ä¼šè¯æ¶ˆæ¯
- `GET /financial-ai/history/messages/{memoryId}`
---
### 2.4 åˆ é™¤ä¼šè¯
- `DELETE /financial-ai/history/{memoryId}`
## 3. SSE è¿”回处理规范
### 3.1 è¿”回形态
- æ™®é€šé—®ç­”:流式文本片段
- å·¥å…·å‘½ä¸­ï¼šå®Œæ•´ JSON å­—符串(通常一次性输出,也可能分片)
前端建议处理流程:
1. å°† SSE åˆ†ç‰‡æŒ‰é¡ºåºæ‹¼æŽ¥æˆ `rawText`
2. å¯¹ `rawText` å°è¯• `JSON.parse`
3. è‹¥å¯è§£æžï¼ŒæŒ‰ `type` åˆ†å‘渲染图表/表格
4. è‹¥ä¸å¯è§£æžï¼ŒæŒ‰æ™®é€šæ–‡æœ¬å±•示
### 3.2 ç»“构化 JSON é€šç”¨æ ¼å¼
```json
{
  "success": true,
  "type": "financial_order_profit_analysis",
  "description": "已完成订单利润分析",
  "summary": {},
  "data": {},
  "charts": {}
}
```
字段说明:
- `type`:结果类型(前端渲染分发键)
- `summary`:头部指标
- `data`:表格明细/建议列表
- `charts`:ECharts `option` æ•°æ®
## 4. type ä¸Žå‰ç«¯é¡µé¢æ˜ å°„
建议按 `type` å»ºç«‹æ¸²æŸ“策略:
- `financial_cost_accounting`:成本核算页
- `financial_order_profit_analysis`:订单利润页
- `financial_inventory_capital_analysis`:库存资金页
- `financial_cashflow_forecast`:现金流页
- `financial_business_anomaly_warning`:风险预警页
- `financial_business_cockpit`:经营驾驶舱
- `financial_operation_report`:日报周报页
- `financial_rag_knowledge`:知识检索/口径说明卡片
## 5. å…³é”®æ•°æ®å­—段(联调重点)
### 5.1 æˆæœ¬/利润类
- `data.orders[]`:
  - `salesContractNo`
  - `customerName`
  - `revenue`
  - `materialCost`
  - `laborCost`
  - `depreciationCost`
  - `scrapCost`
  - `totalCost`
  - `profit`
  - `profitRate`
  - `riskLevel`
  - `reasons`
  - `suggestion`
### 5.2 åº“存资金类
- `data.items[]`:
  - `productName`
  - `model`
  - `quantity`
  - `inventoryValue`
  - `stagnantDays`
  - `overstock`
  - `riskLevel`
### 5.3 çŽ°é‡‘æµç±»
- `data.actualMonthly[]` / `data.forecastMonthly[]`:
  - `month`
  - `income`
  - `expense`
  - `netFlow`
- `data.receivableRiskTop[]` / `data.payablePressureTop[]`
### 5.4 å¼‚常预警类
- `data.items[]`:
  - `riskLevel`
  - `type`
  - `message`
  - `detail`
### 5.5 æŠ¥å‘Šç±»
- `data.headline`
- `data.conclusions[]`
- `data.riskSuggestions[]`
- `data.orderProfitTop[]`
## 6. å›¾è¡¨è”调规范
`charts` å†…字段均为 ECharts `option`,可直接喂给图表组件。
常见字段:
- æŸ±çŠ¶å›¾ï¼š`orderProfitBarOption` / `processCostBarOption` / `inventoryValueTopOption`
- é¥¼å›¾ï¼š`costCompositionPieOption` / `inventoryAgingPieOption` / `anomalyLevelPieOption`
- è¶‹åŠ¿å›¾ï¼š`cashFlowTrendOption`
- ä»ªè¡¨ç›˜ï¼š`fundGapGaugeOption` / `inventoryTurnoverGauge`
## 7. æŽ¨èå‰ç«¯é—®å¥ï¼ˆå›žå½’测试)
1. `查看本月经营驾驶舱`
2. `查询近30天亏损订单`
3. `分析近30天库存资金占用`
4. `预测未来3个月现金流`
5. `生成本周经营周报`
6. `为什么利润下降`
7. `哪个客户最赚钱`
8. `哪个工序成本最高`
## 8. å¼‚常与兜底处理
- `memoryId` ä¸ºç©ºï¼šè¿”回文本 `memoryId不能为空`
- `message` ä¸ºç©ºï¼šè¿”回文本 `message不能为空`
- æ— æ•°æ®åœºæ™¯ï¼š`success=true` ä¸” `data.items=[]`,前端按空态展示
- éž JSON æµè¿”回:按普通聊天文本展示
## 9. è”调建议
1. å…ˆåš `type` åˆ†å‘器(保证所有结构化结果可落地)
2. å†åšæ‘˜è¦å¡ç‰‡ï¼ˆ`summary`)+ è¡¨æ ¼ï¼ˆ`data`)+ å›¾è¡¨ï¼ˆ`charts`)
3. æœ€åŽè¡¥ä¼šè¯åŽ†å²ä¸Žåˆ é™¤èƒ½åŠ›ï¼Œå½¢æˆå®Œæ•´å¯¹è¯é—­çŽ¯
src/main/java/com/ruoyi/CodeGenerator.java
@@ -20,11 +20,11 @@
// æ¼”示例子,执行 main æ–¹æ³•控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
    public static String database_url = "jdbc:mysql://localhost:3300/product-inventory-management-new-pro";
    public static String database_url = "jdbc:mysql://localhost:3306/product-inventory-management-new-pro";
    public static String database_username = "root";
    public static String database_password= "root";
    public static String database_password= "123456";
    public static String author = "芯导软件(江苏)有限公司";
    public static String model = "sales"; // æ¨¡å—
    public static String model = "account"; // æ¨¡å—
    public static String setParent = "com.ruoyi."+ model; // åŒ…路径
    public static String tablePrefix = ""; // è®¾ç½®è¿‡æ»¤è¡¨å‰ç¼€
    public static void main(String[] args) {
src/main/java/com/ruoyi/account/bean/dto/AccountDto.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/bean/dto/AccountDto2.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/bean/dto/AccountDto3.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/bean/dto/AccountReportDto.java
ÎļþÃû´Ó src/main/java/com/ruoyi/account/bean/dto/ReportDateDto.java ÐÞ¸Ä
@@ -1,17 +1,16 @@
package com.ruoyi.account.bean.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
/**
 * @author :yys
 * @date : 2026/1/16 16:57
 */
@Data
public class ReportDateDto {
@Schema(name = "AccountReportDto", description = "财务报表--日期参数")
public class AccountReportDto {
    /**
     * å¼€å§‹æ—¶é—´
@@ -26,15 +25,5 @@
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate entryDateEnd;
    /**
     * å¼€å§‹æœˆä»½
     */
    private Integer startMonth;
    /**
     * ç»“束月份
     */
    private Integer endMonth;
}
src/main/java/com/ruoyi/account/bean/dto/StatementAccountDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.ruoyi.account.bean.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
@Schema(name = "StatementAccountDto", description = "财务管理--生成对账单(传参)")
public class StatementAccountDto {
    //业务类型(1应收对账;2应付对账)
    @Schema(name = "accountType", description = "业务类型(1应收对账;2应付对账)")
    private Integer accountType;
    //选择的客户(应收是客户,应付是供应商supplierId)
    @Schema(name = "customerId", description = "客户ID")
    private Long customerId;
    //对账月份yyyy-MM
    @Schema(name = "statementMonth", description = "对账月份")
    private String statementMonth;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;
}
src/main/java/com/ruoyi/account/bean/dto/purchase/AccountPaymentApplicationDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.account.bean.dto.purchase;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
@Schema(name = "AccountPaymentApplicationDto", description = "财务管理--付款申请台账(传参)")
public class AccountPaymentApplicationDto {
    @Schema(description = "供应商ID")
    private Integer supplierId;
    @Schema(description = "申请单号")
    private String invoiceApplicationNo;
    @Schema(description = "审核状态:0待审核1审核通过2审核不通过")
    private Integer status;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;
}
src/main/java/com/ruoyi/account/bean/dto/purchase/AccountPurchaseInvoiceDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.account.bean.dto.purchase;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
@Schema(name = "AccountPurchaseInvoiceDto", description = "财务管理--进项发票台账(传参)")
public class AccountPurchaseInvoiceDto {
    @Schema(description = "供应商ID")
    private Integer supplierId;
    @Schema(description = "发票号码")
    private String invoiceNumber;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;
    @Schema(description = "状态")
    private Integer status;
}
src/main/java/com/ruoyi/account/bean/dto/purchase/AccountPurchasePaymentDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.account.bean.dto.purchase;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
@Schema(name = "AccountPurchasePaymentDto", description = "财务管理--付款单台账(传参)")
public class AccountPurchasePaymentDto {
    @Schema(description = "供应商ID")
    private Integer supplierId;
    @Schema(description = "付款单号")
    private String paymentNumber;
    @Schema(description = "付款方式")
    private String paymentMethod;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;
}
src/main/java/com/ruoyi/account/bean/dto/purchase/PurchaseInboundDto.java
@@ -5,7 +5,7 @@
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDate;
@Data
@Schema(name = "PurchaseInboundDto", description = "财务管理--采购入库台账(传参)")
@@ -14,16 +14,18 @@
    @Schema(description = "入库单号")
    private String inboundBatches;
    private Long supplierId;
    @Schema(description = "供应商")
    private String supplierName;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    private LocalDate endDate;
}
src/main/java/com/ruoyi/account/bean/dto/purchase/PurchaseReturnDto.java
@@ -5,7 +5,7 @@
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDate;
@Data
@Schema(name = "PurchaseReturnDto", description = "财务管理--采购退货台账(传参)")
@@ -14,16 +14,18 @@
    @Schema(description = "退货单号")
    private String returnNo;
    private Long supplierId;
    @Schema(description = "供应商")
    private String supplierName;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    private LocalDate endDate;
}
src/main/java/com/ruoyi/account/bean/dto/sales/AccountInvoiceApplicationDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.account.bean.dto.sales;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
@Schema(name = "AccountInvoiceApplicationDto", description = "财务管理--开票申请台账(传参)")
public class AccountInvoiceApplicationDto  {
    @Schema(description = "客户ID")
    private Integer customerId;
    @Schema(description = "申请单号")
    private String invoiceApplicationNo;
    @Schema(description = "审核状态:0待审核1审核通过2审核不通过")
    private Integer status;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;
}
src/main/java/com/ruoyi/account/bean/dto/sales/AccountSalesCollectionDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.account.bean.dto.sales;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
@Schema(name = "AccountSalesCollectionDto", description = "财务管理--收款单台账(传参)")
public class AccountSalesCollectionDto {
    @Schema(description = "客户ID")
    private Integer customerId;
    @Schema(description = "收款单号")
    private String collectionNumber;
    @Schema(description = "收款方式")
    private String collectionMethod;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;
}
src/main/java/com/ruoyi/account/bean/dto/sales/AccountSalesInvoiceDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.ruoyi.account.bean.dto.sales;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@Data
@Schema(name = "AccountSalesInvoiceDto", description = "财务管理--销项发票台账(传参)")
public class AccountSalesInvoiceDto {
    @Schema(description = "客户ID")
    private Integer customerId;
    @Schema(description = "发票号码")
    private String invoiceNumber;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate endDate;
    @Schema(description = "状态")
    private Integer status;
}
src/main/java/com/ruoyi/account/bean/dto/sales/SalesOutboundDto.java
@@ -5,7 +5,7 @@
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDate;
@Data
@Schema(name = "SalesOutboundDto", description = "财务管理--销售出库台账(传参)")
@@ -14,16 +14,18 @@
    @Schema(description = "出库单号")
    private String outboundBatches;
    private Long customerId;
    @Schema(description = "客户名称")
    private String customerName;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    private LocalDate endDate;
}
src/main/java/com/ruoyi/account/bean/dto/sales/SalesReturnDto.java
@@ -5,7 +5,7 @@
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
import java.time.LocalDate;
@Data
@Schema(name = "SalesReturnDto", description = "财务管理--销售退货台账(传参)")
@@ -14,16 +14,18 @@
    @Schema(description = "退货单号")
    private String returnNo;
    private Long customerId;
    @Schema(description = "客户名称")
    private String customerName;
    @Schema(description = "开始日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date startDate;
    private LocalDate startDate;
    @Schema(description = "结束日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    private LocalDate endDate;
}
src/main/java/com/ruoyi/account/bean/vo/AccountReportVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
package com.ruoyi.account.bean.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
@Schema(name = "AccountReportVo", description = "财务报表--返回参数")
public class AccountReportVo {
    @Schema(description = "总营收")
    private BigDecimal totalIncome;
    @Schema(description = "总支出")
    private BigDecimal totalExpense;
    @Schema(description = "应收账款")
    private BigDecimal accountsReceivable;
    @Schema(description = "应付账款")
    private BigDecimal accountsPayable;
    @Schema(description = "净收入")
    private BigDecimal netRevenue;
    // --- æŠ˜çº¿å›¾ï¼šæœˆåº¦è¶‹åŠ¿æ•°æ® ---
    @Schema(description = "月度趋势数据列表")
    private List<MonthlyTrendVO> monthlyTrendList;
    // --- æŸ±çŠ¶å›¾ï¼šåº”æ”¶åº”ä»˜æœˆåº¦æ•°æ® ---
    @Schema(description = "应收应付月度数据列表")
    private List<ReceivablePayableVO> receivablePayableList;
    @Data
    @Schema(description = "月度趋势VO(折线图用)")
    public static class MonthlyTrendVO {
        @Schema(description = "月份,格式:yyyy-MM")
        private String month;
        @Schema(description = "当月营收")
        private BigDecimal income;
        @Schema(description = "当月支出")
        private BigDecimal expense;
        @Schema(description = "当月净利润")
        private BigDecimal profit;
    }
    @Data
    @Schema(description = "应收应付月度VO(柱状图用)")
    public static class ReceivablePayableVO {
        @Schema(description = "月份,格式:yyyy-MM")
        private String month;
        @Schema(description = "应收账款金额")
        private BigDecimal receivable;
        @Schema(description = "应付账款金额")
        private BigDecimal payable;
    }
}
src/main/java/com/ruoyi/account/bean/vo/StatementAccountVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.ruoyi.account.bean.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.ruoyi.account.pojo.AccountStatement;
import com.ruoyi.account.pojo.AccountStatementDetails;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
@Schema(name = "StatementAccountVo", description = "财务管理--对账单详情(返回)")
@ExcelIgnoreUnannotated
public class StatementAccountVo extends AccountStatement {
    //客户名称(应收是客户,应付是供应商)
    @Schema(description = "客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    //对账明细
    @Schema(description = "对账明细")
    private List<AccountStatementDetails> accountStatementDetails;
}
src/main/java/com/ruoyi/account/bean/vo/purchase/AccountPaymentApplicationVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package com.ruoyi.account.bean.vo.purchase;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.ruoyi.account.pojo.purchase.AccountPaymentApplication;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(name = "AccountPaymentApplicationVo", description = "财务管理--付款申请台账(返回)")
@ExcelIgnoreUnannotated
public class AccountPaymentApplicationVo extends AccountPaymentApplication {
    @Schema(description = "供应商名称")
    @Excel(name = "供应商名称")
    private String supplierName;
    @Schema(description = "入库单号")
    @Excel(name = "入库单号")
    private String inboundBatches;
}
src/main/java/com/ruoyi/account/bean/vo/purchase/AccountPurchaseInvoiceVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.account.bean.vo.purchase;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(name = "AccountPurchaseInvoiceVo", description = "财务管理--进项发票台账(返回)")
@ExcelIgnoreUnannotated
public class AccountPurchaseInvoiceVo extends AccountPurchaseInvoice {
    @Schema(description = "供应商名称")
    @Excel(name = "供应商名称")
    private String supplierName;
}
src/main/java/com/ruoyi/account/bean/vo/purchase/AccountPurchasePaymentVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.account.bean.vo.purchase;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(name = "AccountPurchasePaymentVo", description = "财务管理--付款单台账(返回)")
@ExcelIgnoreUnannotated
public class AccountPurchasePaymentVo extends AccountPurchasePayment {
    @Schema(description = "供应商名称")
    @Excel(name = "供应商名称")
    private String supplierName;
    @Schema(description = "付款申请单号")
    @Excel(name = "付款申请单号")
    private String invoiceApplicationNo;
    @Schema(description = "开户行")
    @Excel(name = "开户行")
    private String bankAccountName;
    @Schema(description = "银行账号")
    @Excel(name = "银行账号")
    private String bankAccountNum;
    @Schema(description = "是否生成了对账单")
    private boolean isAccountStatemen;
}
src/main/java/com/ruoyi/account/bean/vo/purchase/PurchaseInboundVo.java
@@ -7,7 +7,7 @@
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.time.LocalDate;
@Data
@Schema(name = "PurchaseInboundVo", description = "财务管理--采购入库台账(返回)")
@@ -28,7 +28,7 @@
    @Schema(description = "入库日期")
    @Excel(name = "入库日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date InboundDate;
    private LocalDate InboundDate;
    @Schema(description = "产品名称")
    @Excel(name = "产品名称")
src/main/java/com/ruoyi/account/bean/vo/sales/AccountInvoiceApplicationVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package com.ruoyi.account.bean.vo.sales;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.ruoyi.account.pojo.sales.AccountInvoiceApplication;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(name = "AccountInvoiceApplicationVo", description = "财务管理--开票申请台账(返回)")
@ExcelIgnoreUnannotated
public class AccountInvoiceApplicationVo extends AccountInvoiceApplication {
    @Schema(description = "客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    @Schema(description = "出库单号")
    @Excel(name = "出库单号")
    private String outboundBatches;
}
src/main/java/com/ruoyi/account/bean/vo/sales/AccountSalesCollectionVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.ruoyi.account.bean.vo.sales;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(name = "AccountSalesCollectionVo", description = "财务管理--收款单台账(返回)")
@ExcelIgnoreUnannotated
public class AccountSalesCollectionVo extends AccountSalesCollection {
    @Schema(description = "客户名称")
    @Excel(name = "客户名称")
    private String customerName;
    @Schema(description = "出库单号")
    @Excel(name = "出库单号")
    private String outboundBatches;
    @Schema(description = "是否生成了对账单")
    private boolean isAccountStatemen;
}
src/main/java/com/ruoyi/account/bean/vo/sales/AccountSalesInvoiceVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.account.bean.vo.sales;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.ruoyi.account.pojo.sales.AccountSalesInvoice;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
@Schema(name = "AccountSalesInvoiceVo", description = "财务管理--销项发票台账(返回)")
@ExcelIgnoreUnannotated
public class AccountSalesInvoiceVo extends AccountSalesInvoice {
    @Schema(description = "客户名称")
    @Excel(name = "客户名称")
    private String customerName;
}
src/main/java/com/ruoyi/account/bean/vo/sales/SalesOutboundVo.java
@@ -7,7 +7,7 @@
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.time.LocalDate;
@Data
@Schema(name = "SalesOutboundVo", description = "财务管理--销售出库台账(返回)")
@@ -28,7 +28,7 @@
    @Schema(description = "出库日期")
    @Excel(name = "出库日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date shippingDate;
    private LocalDate shippingDate;
    @Schema(description = "产品名称")
    @Excel(name = "产品名称")
@@ -42,6 +42,9 @@
    @Excel(name = "金额")
    private BigDecimal outboundAmount;
    @Schema(description = "税率")
    private BigDecimal taxRate;
    @Schema(description = "发货编号")
    @Excel(name = "发货编号")
    private String shippingNo;
src/main/java/com/ruoyi/account/controller/AccountExpenseController.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/controller/AccountFileController.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/controller/AccountIncomeController.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/controller/AccountStatementController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
package com.ruoyi.account.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.StatementAccountDto;
import com.ruoyi.account.bean.vo.StatementAccountVo;
import com.ruoyi.account.service.AccountStatementService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
/**
 * <p>
 *  å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 09:42:47
 */
@RestController
@RequestMapping("/accountStatement")
@RequiredArgsConstructor
@Tag(name = "财务管理-对账单")
public class AccountStatementController {
    private final AccountStatementService accountStatementService;
    @GetMapping("/getAccountStatementDetailsByMonth")
    @Log(title = "根据客户和月份查询对账单明细", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--根据客户和月份查询对账单明细")
    public R getAccountStatementDetailsByMonth(StatementAccountDto statementAccountDto) {
        return R.ok(accountStatementService.getAccountStatementDetailsByMonth(statementAccountDto));
    }
    @PostMapping("/addAccountStatement")
    @Log(title = "新增对账单", businessType = BusinessType.INSERT)
    @Operation(summary = "财务管理--新增对账单")
    public R addAccountStatement(@RequestBody StatementAccountVo statementAccountVo) {
        return R.ok(accountStatementService.addAccountStatement(statementAccountVo));
    }
    @DeleteMapping("/deleteAccountStatement")
    @Log(title = "删除对账单", businessType = BusinessType.DELETE)
    @Operation(summary = "财务管理--删除对账单")
    public R deleteAccountStatement(@RequestParam("ids") Long[] ids) {
        return R.ok(accountStatementService.deleteAccountStatement(Arrays.asList(ids)));
    }
    @GetMapping("/listPageAccountStatement")
    @Log(title = "对账单台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--对账单台账")
    public R<IPage<StatementAccountVo>> listPageAccountStatement(Page page, StatementAccountDto statementAccountDto) {
        IPage<StatementAccountVo> listPage = accountStatementService.listPageAccountStatement(page,statementAccountDto);
        return R.ok(listPage);
    }
    @PostMapping("/exportAccountStatement")
    @Operation(summary = "导出对账单文件")
    @Log(title = "导出对账单文件", businessType = BusinessType.EXPORT)
    public void exportAccountStatement(HttpServletResponse response, StatementAccountDto statementAccountDto) {
        accountStatementService.exportAccountStatement(response,statementAccountDto);
    }
}
src/main/java/com/ruoyi/account/controller/AccountingController.java
@@ -1,12 +1,16 @@
package com.ruoyi.account.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.service.impl.AccountingServiceImpl;
import com.ruoyi.account.bean.dto.AccountReportDto;
import com.ruoyi.account.service.AccountingService;
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 io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@@ -19,28 +23,38 @@
@Tag(name = "会计核算")
@RestController
@RequestMapping("/accounting")
@AllArgsConstructor
@RequiredArgsConstructor
public class AccountingController extends BaseController {
    private AccountingServiceImpl accountingService;
    private final AccountingService accountingService;
    @Operation(summary = "总计")
    @GetMapping("/total")
    public R<?> total(@RequestParam Integer year) {
    public AjaxResult total(@RequestParam Integer year) {
        return accountingService.total(year);
    }
    @Operation(summary = "设备类型分布")
    @GetMapping("/deviceTypeDistribution")
    public R<?> deviceTypeDistribution(@RequestParam Integer year) {
    public AjaxResult deviceTypeDistribution(@RequestParam Integer year) {
        return accountingService.deviceTypeDistribution(year);
    }
    @Operation(summary = "设备分页查询计算折旧")
    @GetMapping("/calculateDepreciation")
    public R<?> calculateDepreciation(Page page, @RequestParam Integer year) {
    public AjaxResult calculateDepreciation(Page page, @RequestParam Integer year) {
        return accountingService.calculateDepreciation(page,year);
    }
    /*****************************************财务报表******************************************************************************/
    @GetMapping("/accountStatementDetailsByMonth")
    @Log(title = "财务报表", businessType = BusinessType.OTHER)
    @Operation(summary = "财务报表")
    public R getAccountStatementDetailsByMonth(AccountReportDto accountReportDto) {
        return R.ok(accountingService.getAccountStatementDetailsByMonth(accountReportDto));
    }
}
src/main/java/com/ruoyi/account/controller/BorrowInfoController.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/controller/purchase/AccountPaymentApplicationController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
package com.ruoyi.account.controller.purchase;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.purchase.AccountPaymentApplicationDto;
import com.ruoyi.account.bean.vo.purchase.AccountPaymentApplicationVo;
import com.ruoyi.account.pojo.purchase.AccountPaymentApplication;
import com.ruoyi.account.service.purchase.AccountPaymentApplicationService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--付款申请 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 03:44:22
 */
@RestController
@RequestMapping("/accountPaymentApplication")
@Tag(name = "财务管理--付款申请")
@RequiredArgsConstructor
public class AccountPaymentApplicationController {
    private final AccountPaymentApplicationService accountPaymentApplicationService;
    @GetMapping("/listPageAccountPaymentApplication")
    @Log(title = "付款申请台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--付款申请台账")
    public R<IPage<AccountPaymentApplicationVo>> listPageAccountPaymentApplication(Page page, AccountPaymentApplicationDto accountPaymentApplicationDto) {
        IPage<AccountPaymentApplicationVo> listPage = accountPaymentApplicationService.listPageAccountPaymentApplication(page,accountPaymentApplicationDto);
        return R.ok(listPage);
    }
    @GetMapping("/getInboundBatchesBySupplier")
    @Log(title = "根据供应商查询入库单号(付款申请)", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--根据供应商查询入库单号(付款申请)")
    public R getInboundBatchesBySupplier(Integer supplierId) {
        return R.ok(accountPaymentApplicationService.getInboundBatchesBySupplier(supplierId));
    }
    @PostMapping("/addAccountPaymentApplication")
    @Log(title = "新增付款申请", businessType = BusinessType.INSERT)
    @Operation(summary = "财务管理--新增付款申请")
    public R addAccountPaymentApplication(@RequestBody AccountPaymentApplication accountPaymentApplication) {
        return R.ok(accountPaymentApplicationService.addAccountPaymentApplication(accountPaymentApplication));
    }
    @PutMapping("/updateAccountPaymentApplication")
    @Log(title = "修改付款申请", businessType = BusinessType.UPDATE)
    @Operation(summary = "财务管理--修改付款申请")
    public R updateAccountPaymentApplication(@RequestBody AccountPaymentApplication accountPaymentApplication) {
        return R.ok(accountPaymentApplicationService.updateById(accountPaymentApplication));
    }
    @PutMapping("/auditAccountPaymentApplication")
    @Log(title = "审核付款申请", businessType = BusinessType.UPDATE)
    @Operation(summary = "财务管理--审核付款申请")
    public R auditAccountPaymentApplication(@RequestBody AccountPaymentApplication accountPaymentApplication) {
        return R.ok(accountPaymentApplicationService.updateById(accountPaymentApplication));
    }
    @DeleteMapping("/deleteAccountPaymentApplication")
    @Log(title = "删除付款申请", businessType = BusinessType.DELETE)
    @Operation(summary = "财务管理--删除付款申请")
    public R deleteAccountPaymentApplication(@RequestParam("ids") Long[] ids) {
        return R.ok(accountPaymentApplicationService.deleteAccountPaymentApplication(Arrays.asList(ids)));
    }
    @PostMapping("/exportAccountPaymentApplication")
    @Operation(summary = "导出付款申请文件")
    @Log(title = "导出付款申请文件", businessType = BusinessType.EXPORT)
    public void exportAccountPaymentApplication(HttpServletResponse response, AccountPaymentApplicationDto accountPaymentApplicationDto) {
        accountPaymentApplicationService.exportAccountPaymentApplication(response,accountPaymentApplicationDto);
    }
}
src/main/java/com/ruoyi/account/controller/purchase/AccountPurchaseInvoiceController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
package com.ruoyi.account.controller.purchase;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.purchase.AccountPurchaseInvoiceDto;
import com.ruoyi.account.bean.vo.purchase.AccountPurchaseInvoiceVo;
import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice;
import com.ruoyi.account.service.purchase.AccountPurchaseInvoiceService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--进项发票 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 03:06:17
 */
@RestController
@RequestMapping("/accountPurchaseInvoice")
@Tag(name = "财务管理--进项发票")
@RequiredArgsConstructor
public class AccountPurchaseInvoiceController {
    private final AccountPurchaseInvoiceService accountPurchaseInvoiceService;
    @GetMapping("/listPageAccountPurchaseInvoice")
    @Log(title = "进项发票台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--进项发票台账")
    public R<IPage<AccountPurchaseInvoiceVo>> listPageAccountPurchaseInvoice(Page page, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto) {
        IPage<AccountPurchaseInvoiceVo> listPage = accountPurchaseInvoiceService.listPageAccountPurchaseInvoice(page,accountPurchaseInvoiceDto);
        return R.ok(listPage);
    }
    @GetMapping("/getInboundBatchesBySupplier")
    @Log(title = "根据供应商查询入库单号(进项发票)", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--根据供应商查询入库单号(进项发票)")
    public R getInboundBatchesBySupplier(Integer supplierId) {
        return R.ok(accountPurchaseInvoiceService.getInboundBatchesBySupplier(supplierId));
    }
    @PostMapping("/addAccountPurchaseInvoice")
    @Log(title = "新增进项发票", businessType = BusinessType.INSERT)
    @Operation(summary = "财务管理--新增进项发票")
    public R addAccountPurchaseInvoice(@RequestBody AccountPurchaseInvoice accountPurchaseInvoice) {
        return R.ok(accountPurchaseInvoiceService.save(accountPurchaseInvoice));
    }
    @PutMapping("/cancelAccountPurchaseInvoice")
    @Log(title = "作废进项发票", businessType = BusinessType.INSERT)
    @Operation(summary = "财务管理--作废销项发票")
    public R cancelAccountPurchaseInvoice(@RequestBody AccountPurchaseInvoice accountPurchaseInvoice) {
        return R.ok(accountPurchaseInvoiceService.updateById(accountPurchaseInvoice));
    }
    @DeleteMapping("/deleteAccountPurchaseInvoice")
    @Log(title = "删除进项发票", businessType = BusinessType.DELETE)
    @Operation(summary = "财务管理--删除进项发票")
    public R deleteAccountPurchaseInvoice(@RequestParam("ids") Long[] ids) {
        return R.ok(accountPurchaseInvoiceService.deleteAccountPurchaseInvoice(Arrays.asList(ids)));
    }
    @PostMapping("/exportAccountPurchaseInvoice")
    @Operation(summary = "导出进项发票文件")
    @Log(title = "导出进项发票文件", businessType = BusinessType.EXPORT)
    public void exportAccountPurchaseInvoice(HttpServletResponse response, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto) {
        accountPurchaseInvoiceService.exportAccountPurchaseInvoice(response,accountPurchaseInvoiceDto);
    }
}
src/main/java/com/ruoyi/account/controller/purchase/AccountPurchasePaymentController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,71 @@
package com.ruoyi.account.controller.purchase;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.purchase.AccountPurchasePaymentDto;
import com.ruoyi.account.bean.vo.purchase.AccountPurchasePaymentVo;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
import com.ruoyi.account.service.purchase.AccountPurchasePaymentService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--付款单 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 04:14:51
 */
@RestController
@RequestMapping("/accountPurchasePayment")
@Tag(name = "财务管理--付款单")
@RequiredArgsConstructor
public class AccountPurchasePaymentController {
    private final AccountPurchasePaymentService accountPurchasePaymentService;
    @GetMapping("/listPageAccountPurchasePayment")
    @Log(title = "付款单台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--付款单台账")
    public R<IPage<AccountPurchasePaymentVo>> listPageAccountPurchasePayment(Page page, AccountPurchasePaymentDto accountPurchasePaymentDto) {
        IPage<AccountPurchasePaymentVo> listPage = accountPurchasePaymentService.listPageAccountPurchasePayment(page,accountPurchasePaymentDto);
        return R.ok(listPage);
    }
    @PostMapping("/addAccountPurchasePayment")
    @Log(title = "新增付款单", businessType = BusinessType.INSERT)
    @Operation(summary = "财务管理--新增付款单")
    public R addAccountPurchasePayment(@RequestBody AccountPurchasePayment accountPurchasePayment) {
        return R.ok(accountPurchasePaymentService.addAccountPurchasePayment(accountPurchasePayment));
    }
    @PutMapping("/updateAccountPurchasePayment")
    @Log(title = "编辑付款单", businessType = BusinessType.UPDATE)
    @Operation(summary = "财务管理--编辑付款单")
    public R updateAccountPurchasePayment(@RequestBody AccountPurchasePayment accountPurchasePayment) {
        return R.ok(accountPurchasePaymentService.updateById(accountPurchasePayment));
    }
    @DeleteMapping("/deleteAccountPurchasePayment")
    @Log(title = "删除付款单", businessType = BusinessType.DELETE)
    @Operation(summary = "财务管理--删除付款单")
    public R deleteAccountPurchasePayment(@RequestParam("ids") Long[] ids) {
        return R.ok(accountPurchasePaymentService.deleteAccountPurchasePayment(Arrays.asList(ids)));
    }
    @PostMapping("/exportAccountPurchasePayment")
    @Operation(summary = "导出付款单文件")
    @Log(title = "导出付款单文件", businessType = BusinessType.EXPORT)
    public void exportAccountPurchasePayment(HttpServletResponse response, AccountPurchasePaymentDto accountPurchasePaymentDto) {
        accountPurchasePaymentService.exportAccountPurchasePayment(response,accountPurchasePaymentDto);
    }
}
src/main/java/com/ruoyi/account/controller/sales/AccountInvoiceApplicationController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
package com.ruoyi.account.controller.sales;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.sales.AccountInvoiceApplicationDto;
import com.ruoyi.account.bean.vo.sales.AccountInvoiceApplicationVo;
import com.ruoyi.account.pojo.sales.AccountInvoiceApplication;
import com.ruoyi.account.service.sales.AccountInvoiceApplicationService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--开票申请 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 01:38:32
 */
@RestController
@RequestMapping("/accountInvoiceApplication")
@Tag(name = "财务管理--开票申请")
@RequiredArgsConstructor
public class AccountInvoiceApplicationController {
    private final AccountInvoiceApplicationService accountInvoiceApplicationService;
    @GetMapping("/listPageAccountInvoiceApplication")
    @Log(title = "开票申请台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--开票申请台账")
    public R<IPage<AccountInvoiceApplicationVo>> listPageAccountInvoiceApplication(Page page, AccountInvoiceApplicationDto accountInvoiceApplicationDto) {
        IPage<AccountInvoiceApplicationVo> listPage = accountInvoiceApplicationService.listPageAccountInvoiceApplication(page,accountInvoiceApplicationDto);
        return R.ok(listPage);
    }
    @GetMapping("/getOutboundBatchesByCustomer")
    @Log(title = "根据客户查询出库单号(开票申请)", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--根据客户查询出库单号(开票申请)")
    public R getOutboundBatchesByCustomer(Integer customerId) {
        return R.ok(accountInvoiceApplicationService.getOutboundBatchesByCustomer(customerId));
    }
    @PostMapping("/addAccountInvoiceApplication")
    @Log(title = "新增开票申请", businessType = BusinessType.INSERT)
    @Operation(summary = "财务管理--新增开票申请")
    public R addAccountInvoiceApplication(@RequestBody AccountInvoiceApplication accountInvoiceApplication) {
        return R.ok(accountInvoiceApplicationService.addAccountInvoiceApplication(accountInvoiceApplication));
    }
    @PutMapping("/updateAccountInvoiceApplication")
    @Log(title = "修改开票申请", businessType = BusinessType.UPDATE)
    @Operation(summary = "财务管理--修改开票申请")
    public R updateAccountInvoiceApplication(@RequestBody AccountInvoiceApplication accountInvoiceApplication) {
        return R.ok(accountInvoiceApplicationService.updateById(accountInvoiceApplication));
    }
    @PutMapping("/auditAccountInvoiceApplication")
    @Log(title = "审核开票申请", businessType = BusinessType.UPDATE)
    @Operation(summary = "财务管理--审核开票申请")
    public R auditAccountInvoiceApplication(@RequestBody AccountInvoiceApplication accountInvoiceApplication) {
        return R.ok(accountInvoiceApplicationService.updateById(accountInvoiceApplication));
    }
    @DeleteMapping("/deleteAccountInvoiceApplication")
    @Log(title = "删除开票申请", businessType = BusinessType.DELETE)
    @Operation(summary = "财务管理--删除开票申请")
    public R deleteAccountInvoiceApplication(@RequestParam("ids") Long[] ids) {
        return R.ok(accountInvoiceApplicationService.deleteAccountInvoiceApplication(Arrays.asList(ids)));
    }
    @PostMapping("/exportAccountInvoiceApplication")
    @Operation(summary = "导出开票申请文件")
    @Log(title = "导出开票申请文件", businessType = BusinessType.EXPORT)
    public void exportAccountInvoiceApplication(HttpServletResponse response, AccountInvoiceApplicationDto accountInvoiceApplicationDto) {
        accountInvoiceApplicationService.exportAccountInvoiceApplication(response,accountInvoiceApplicationDto);
    }
}
src/main/java/com/ruoyi/account/controller/sales/AccountSalesCollectionController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
package com.ruoyi.account.controller.sales;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.sales.AccountSalesCollectionDto;
import com.ruoyi.account.bean.vo.sales.AccountSalesCollectionVo;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.ruoyi.account.service.sales.AccountSalesCollectionService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--收款单 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:49:56
 */
@RestController
@RequestMapping("/accountSalesCollection")
@Tag(name = "财务管理--收款单")
@RequiredArgsConstructor
public class AccountSalesCollectionController {
    private final AccountSalesCollectionService accountSalesCollectionService;
    @GetMapping("/listPageAccountSalesCollection")
    @Log(title = "收款单台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--收款单台账")
    public R<IPage<AccountSalesCollectionVo>> listPageAccountSalesCollection(Page page, AccountSalesCollectionDto accountSalesCollectionDto) {
        IPage<AccountSalesCollectionVo> listPage = accountSalesCollectionService.listPageAccountSalesCollection(page,accountSalesCollectionDto);
        return R.ok(listPage);
    }
    @GetMapping("/getOutboundBatchesByCustomer")
    @Log(title = "根据客户查询出库单号(收款单)", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--根据客户查询出库单号(收款单)")
    public R getOutboundBatchesByCustomer(Integer customerId) {
        return R.ok(accountSalesCollectionService.getOutboundBatchesByCustomer(customerId));
    }
    @PostMapping("/addAccountSalesCollection")
    @Log(title = "新增收款单", businessType = BusinessType.INSERT)
    @Operation(summary = "财务管理--新增收款单")
    public R addAccountSalesCollection(@RequestBody AccountSalesCollection accountSalesCollection) {
        return R.ok(accountSalesCollectionService.addAccountSalesCollection(accountSalesCollection));
    }
    @PutMapping("/updateAccountSalesCollection")
    @Log(title = "编辑收款单", businessType = BusinessType.UPDATE)
    @Operation(summary = "财务管理--编辑收款单")
    public R updateAccountSalesCollection(@RequestBody AccountSalesCollection accountSalesCollection) {
        return R.ok(accountSalesCollectionService.updateById(accountSalesCollection));
    }
    @DeleteMapping("/deleteAccountSalesCollection")
    @Log(title = "删除收款单", businessType = BusinessType.DELETE)
    @Operation(summary = "财务管理--删除收款单")
    public R deleteAccountSalesCollection(@RequestParam("ids") Long[] ids) {
        return R.ok(accountSalesCollectionService.deleteAccountSalesCollection(Arrays.asList(ids)));
    }
    @PostMapping("/exportAccountSalesCollection")
    @Operation(summary = "导出收款单文件")
    @Log(title = "导出收款单文件", businessType = BusinessType.EXPORT)
    public void exportAccountSalesCollection(HttpServletResponse response, AccountSalesCollectionDto accountSalesCollectionDto) {
        accountSalesCollectionService.exportAccountSalesCollection(response,accountSalesCollectionDto);
    }
}
src/main/java/com/ruoyi/account/controller/sales/AccountSalesInvoiceController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,72 @@
package com.ruoyi.account.controller.sales;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.sales.AccountSalesInvoiceDto;
import com.ruoyi.account.bean.vo.sales.AccountSalesInvoiceVo;
import com.ruoyi.account.pojo.sales.AccountSalesInvoice;
import com.ruoyi.account.service.sales.AccountSalesInvoiceService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--销项发票 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:10:20
 */
@RestController
@RequestMapping("/accountSalesInvoice")
@Tag(name = "财务管理--销项发票")
@RequiredArgsConstructor
public class AccountSalesInvoiceController {
    private final AccountSalesInvoiceService accountSalesInvoiceService;
    @GetMapping("/listPageAccountSalesInvoice")
    @Log(title = "销项发票台账", businessType = BusinessType.OTHER)
    @Operation(summary = "财务管理--销项发票台账")
    public R<IPage<AccountSalesInvoiceVo>> listPageAccountSalesInvoice(Page page, AccountSalesInvoiceDto accountSalesInvoiceDto) {
        IPage<AccountSalesInvoiceVo> listPage = accountSalesInvoiceService.listPageAccountSalesInvoice(page,accountSalesInvoiceDto);
        return R.ok(listPage);
    }
    @PostMapping("/addAccountSalesInvoice")
    @Log(title = "新增销项发票", businessType = BusinessType.INSERT)
    @Operation(summary = "财务管理--新增销项发票")
    public R addAccountSalesInvoice(@RequestBody AccountSalesInvoice accountSalesInvoice) {
        return R.ok(accountSalesInvoiceService.save(accountSalesInvoice));
    }
    @PutMapping("/cancelAccountSalesInvoice")
    @Log(title = "作废销项发票", businessType = BusinessType.INSERT)
    @Operation(summary = "财务管理--作废销项发票")
    public R cancelAccountSalesInvoice(@RequestBody AccountSalesInvoice accountSalesInvoice) {
        return R.ok(accountSalesInvoiceService.updateById(accountSalesInvoice));
    }
    @DeleteMapping("/deleteAccountSalesInvoice")
    @Log(title = "删除销项发票", businessType = BusinessType.DELETE)
    @Operation(summary = "财务管理--删除销项发票")
    public R deleteAccountSalesInvoice(@RequestParam("ids") Long[] ids) {
        return R.ok(accountSalesInvoiceService.deleteAccountSalesInvoice(Arrays.asList(ids)));
    }
    @PostMapping("/exportAccountSalesInvoice")
    @Operation(summary = "导出销项发票文件")
    @Log(title = "导出销项发票文件", businessType = BusinessType.EXPORT)
    public void exportAccountSalesInvoice(HttpServletResponse response, AccountSalesInvoiceDto accountSalesInvoiceDto) {
        accountSalesInvoiceService.exportAccountSalesInvoice(response,accountSalesInvoiceDto);
    }
}
src/main/java/com/ruoyi/account/mapper/AccountExpenseMapper.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/mapper/AccountFileMapper.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/mapper/AccountIncomeMapper.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/mapper/AccountStatementDetailsMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.account.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.account.pojo.AccountStatementDetails;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--对账单明细 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 10:12:42
 */
@Mapper
public interface AccountStatementDetailsMapper extends BaseMapper<AccountStatementDetails> {
}
src/main/java/com/ruoyi/account/mapper/AccountStatementMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.account.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.account.bean.dto.StatementAccountDto;
import com.ruoyi.account.bean.vo.StatementAccountVo;
import com.ruoyi.account.pojo.AccountStatement;
import com.ruoyi.purchase.dto.VatDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
 *  Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 09:42:47
 */
@Mapper
public interface AccountStatementMapper extends BaseMapper<AccountStatement> {
    IPage<StatementAccountVo> listPageAccountStatement(Page page, @Param("req") StatementAccountDto statementAccountDto);
    IPage<VatDto> selectVatDtoPage(Page page, @Param("month") String month);
}
src/main/java/com/ruoyi/account/mapper/BorrowInfoMapper.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/mapper/purchase/AccountPaymentApplicationMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.account.mapper.purchase;
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.account.bean.dto.purchase.AccountPaymentApplicationDto;
import com.ruoyi.account.bean.vo.purchase.AccountPaymentApplicationVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo;
import com.ruoyi.account.pojo.purchase.AccountPaymentApplication;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--付款申请 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 03:44:22
 */
@Mapper
public interface AccountPaymentApplicationMapper extends BaseMapper<AccountPaymentApplication> {
    IPage<AccountPaymentApplicationVo> listPageAccountPaymentApplication(Page page, @Param("req") AccountPaymentApplicationDto accountPaymentApplicationDto);
    List<PurchaseInboundVo> getInboundBatchesBySupplier(@Param("supplierId") Integer supplierId);
    //判断该出库记录是否有开票申请
    boolean existsByStockInRecordId(@Param("stockInRecordIds") List<Long> stockInRecordIds);
}
src/main/java/com/ruoyi/account/mapper/purchase/AccountPurchaseInvoiceMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.account.mapper.purchase;
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.account.bean.dto.purchase.AccountPurchaseInvoiceDto;
import com.ruoyi.account.bean.vo.purchase.AccountPurchaseInvoiceVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo;
import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--进项发票 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 03:06:17
 */
@Mapper
public interface AccountPurchaseInvoiceMapper extends BaseMapper<AccountPurchaseInvoice> {
    IPage<AccountPurchaseInvoiceVo> listPageAccountPurchaseInvoice(Page page, @Param("req") AccountPurchaseInvoiceDto accountPurchaseInvoiceDto);
    List<PurchaseInboundVo> getInboundBatchesBySupplier(@Param("supplierId") Integer supplierId);
}
src/main/java/com/ruoyi/account/mapper/purchase/AccountPurchasePaymentMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.account.mapper.purchase;
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.account.bean.dto.purchase.AccountPurchasePaymentDto;
import com.ruoyi.account.bean.vo.purchase.AccountPurchasePaymentVo;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
import com.ruoyi.home.dto.IncomeExpenseAnalysisDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--付款单 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 04:14:51
 */
@Mapper
public interface AccountPurchasePaymentMapper extends BaseMapper<AccountPurchasePayment> {
    IPage<AccountPurchasePaymentVo> listPageAccountPurchasePayment(Page page, @Param("req") AccountPurchasePaymentDto accountPurchasePaymentDto);
    List<IncomeExpenseAnalysisDto> selectPayment(@Param("startStr") String startStr, @Param("endStr") String endStr, @Param("dateFormat") String dateFormat);
}
src/main/java/com/ruoyi/account/mapper/sales/AccountInvoiceApplicationMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.account.mapper.sales;
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.account.bean.dto.sales.AccountInvoiceApplicationDto;
import com.ruoyi.account.bean.vo.sales.AccountInvoiceApplicationVo;
import com.ruoyi.account.bean.vo.sales.SalesOutboundVo;
import com.ruoyi.account.pojo.sales.AccountInvoiceApplication;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--开票申请 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 01:38:32
 */
@Mapper
public interface AccountInvoiceApplicationMapper extends BaseMapper<AccountInvoiceApplication> {
    IPage<AccountInvoiceApplicationVo> listPageAccountInvoiceApplication(Page page, @Param("req") AccountInvoiceApplicationDto accountInvoiceApplicationDto);
    List<SalesOutboundVo> getOutboundBatchesByCustomer(@Param("customerId") Integer customerId);
    //判断该出库记录是否有开票申请
    boolean existsByStockOutRecordId(@Param("stockOutRecordIds") List<Long> stockOutRecordIds);
}
src/main/java/com/ruoyi/account/mapper/sales/AccountSalesCollectionMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package com.ruoyi.account.mapper.sales;
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.account.bean.dto.sales.AccountSalesCollectionDto;
import com.ruoyi.account.bean.vo.sales.AccountSalesCollectionVo;
import com.ruoyi.account.bean.vo.sales.SalesOutboundVo;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.ruoyi.home.dto.IncomeExpenseAnalysisDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--收款单 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:49:56
 */
@Mapper
public interface AccountSalesCollectionMapper extends BaseMapper<AccountSalesCollection> {
    IPage<AccountSalesCollectionVo> listPageAccountSalesCollection(Page page, @Param("req") AccountSalesCollectionDto accountSalesCollectionDto);
    //判断该出库记录是否有收款单
    boolean existsByStockOutRecordId(@Param("stockOutRecordIds") List<Long> stockOutRecordIds);
    List<SalesOutboundVo> getOutboundBatchesByCustomer(@Param("customerId") Integer customerId);
    List<IncomeExpenseAnalysisDto> selectIncomeStats(@Param("startStr") String startStr, @Param("endStr") String endStr, @Param("dateFormat") String dateFormat);
}
src/main/java/com/ruoyi/account/mapper/sales/AccountSalesInvoiceMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.account.mapper.sales;
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.account.bean.dto.sales.AccountSalesInvoiceDto;
import com.ruoyi.account.bean.vo.sales.AccountSalesInvoiceVo;
import com.ruoyi.account.pojo.sales.AccountSalesInvoice;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--销项发票 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:10:20
 */
@Mapper
public interface AccountSalesInvoiceMapper extends BaseMapper<AccountSalesInvoice> {
    IPage<AccountSalesInvoiceVo> listPageAccountSalesInvoice(Page page, @Param("req") AccountSalesInvoiceDto accountSalesInvoiceDto);
}
src/main/java/com/ruoyi/account/pojo/AccountExpense.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/pojo/AccountFile.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/pojo/AccountIncome.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/pojo/AccountStatement.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
package com.ruoyi.account.pojo;
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 lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <p>
 *
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 09:42:47
 */
@Getter
@Setter
@ToString
@TableName("account_statement")
@ApiModel(value = "AccountStatement对象", description = "财务管理--对账单")
public class AccountStatement implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * å®¢æˆ·id(应收是客户customer,应付是供应商supplier)
     */
    @ApiModelProperty("客户id")
    private Integer customerId;
    /**
     * å¯¹è´¦æœˆä»½(yyyy-MM)
     */
    @ApiModelProperty("对账月份(yyyy-MM)")
    @Excel(name = "对账月份")
    private String statementMonth;
    /**
     * ä¸šåŠ¡ç±»åž‹(1应收对账;2应付对账)
     */
    @ApiModelProperty("业务类型(1应收对账;2应付对账)")
    @Excel(name = "业务类型",readConverterExp = "1=应收对账,2=应付对账")
    private Integer accountType;
    /**
     * å¯¹è´¦å•号
     */
    @ApiModelProperty("对账单号")
    @Excel(name = "对账单号")
    private String statementNumber;
    /**
     * æœŸåˆä½™é¢
     */
    @ApiModelProperty("期初余额")
    @Excel(name = "期初余额")
    private BigDecimal openingBalance;
    /**
     * æœ¬æœŸåº”æ”¶/应付
     */
    @ApiModelProperty("本期应收/应付")
    @Excel(name = "本期应收/应付")
    private BigDecimal currentPlan;
    /**
     * æœ¬æœŸæ”¶æ¬¾/付款
     */
    @ApiModelProperty("本期收款/付款")
    @Excel(name = "本期收款/付款")
    private BigDecimal currentActually;
    /**
     * æœŸæœ«ä½™é¢
     */
    @ApiModelProperty("期末余额")
    @Excel(name = "期末余额")
    private BigDecimal closingBalance;
    /**
     * åˆ›å»ºäºº
     */
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹äºº
     */
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    /**
     * éƒ¨é—¨ID
     */
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/pojo/AccountStatementDetails.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,115 @@
package com.ruoyi.account.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--对账单明细
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 10:12:42
 */
@Getter
@Setter
@ToString
@TableName("account_statement_details")
@ApiModel(value = "AccountStatementDetails对象", description = "财务管理--对账单明细")
public class AccountStatementDetails implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * å…³è”对账单id
     */
    @ApiModelProperty("关联对账单id")
    private Integer accountStatementId;
    /**
     * æ•°æ®æ—¥æœŸ
     */
    @ApiModelProperty("数据日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate occurrenceDate;
    /**
     * å•据编号
     */
    @ApiModelProperty("单据编号")
    private String receiptNumber;
    /**
     * æ•°æ®ç±»åž‹(1出库;2入库;3收款;4付款;5退货)
     */
    @ApiModelProperty("数据类型(1出库;2入库;3收款;4付款;5退货)")
    private Integer type;
    /**
     * é‡‘额
     */
    @ApiModelProperty("金额")
    private BigDecimal amount;
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    private String remark;
    /**
     * åˆ›å»ºäºº
     */
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹äºº
     */
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    /**
     * éƒ¨é—¨ID
     */
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/account/pojo/BorrowInfo.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/pojo/financial/AccountSubject.java
@@ -85,7 +85,7 @@
     */
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private String createUser;
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
@@ -99,7 +99,7 @@
     */
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateUser;
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
src/main/java/com/ruoyi/account/pojo/purchase/AccountPaymentApplication.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,141 @@
package com.ruoyi.account.pojo.purchase;
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 lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--付款申请
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 03:44:22
 */
@Getter
@Setter
@ToString
@TableName("account_payment_application")
@ApiModel(value = "AccountPaymentApplication对象", description = "财务管理--付款申请")
public class AccountPaymentApplication implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * åˆ›å»ºäºº
     */
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹äºº
     */
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    /**
     * éƒ¨é—¨ID
     */
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * ä¾›åº”商id
     */
    @ApiModelProperty("供应商id")
    private Integer supplierId;
    /**
     * å…³è”入库单id(多选)
     */
    @ApiModelProperty("关联入库单id(多选)")
    private String stockInRecordIds;
    /**
     * ä»˜æ¬¾ç”³è¯·å•号
     */
    @ApiModelProperty("付款申请单号")
    @Excel(name = "付款申请单号")
    private String invoiceApplicationNo;
    /**
     * ä»˜æ¬¾æ–¹å¼
     */
    @ApiModelProperty("付款方式")
    @Excel(name = "付款方式")
    private String paymentMethod;
    /**
     * ä»˜æ¬¾äº‹ç”±
     */
    @ApiModelProperty("付款事由")
    @Excel(name = "付款事由")
    private String paymentContent;
    /**
     * ç”³è¯·æ—¥æœŸ
     */
    @ApiModelProperty("申请日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "申请日期")
    private LocalDate applyDate;
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    @Excel(name = "备注")
    private String remark;
    /**
     * å®¡æ ¸çŠ¶æ€:0待审核1审核通过2审核不通过
     */
    @ApiModelProperty("审核状态:0待审核1审核通过2审核不通过")
    @Excel(name = "审核状态",readConverterExp = "0=待审核,1=审核通过,2=审核不通过")
    private Integer status;
    /**
     * ä»˜æ¬¾é‡‘额
     */
    @ApiModelProperty("付款金额")
    @Excel(name = "付款金额")
    private BigDecimal paymentAmount;
}
src/main/java/com/ruoyi/account/pojo/purchase/AccountPurchaseInvoice.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,168 @@
package com.ruoyi.account.pojo.purchase;
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 lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--进项发票
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 03:06:17
 */
@Getter
@Setter
@ToString
@TableName("account_purchase_invoice")
@ApiModel(value = "AccountPurchaseInvoice对象", description = "财务管理--进项发票")
public class AccountPurchaseInvoice implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * åˆ›å»ºäºº
     */
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹äºº
     */
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    /**
     * éƒ¨é—¨ID
     */
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * å‘票号码
     */
    @ApiModelProperty("发票号码")
    @Excel(name = "发票号码")
    private String invoiceNumber;
    /**
     * ç¨Žçއ
     */
    @ApiModelProperty("税率")
    @Excel(name = "税率")
    private Integer taxRate;
    /**
     * å‘票类型
     */
    @ApiModelProperty("发票类型")
    @Excel(name = "发票类型")
    private String invoiceType;
    /**
     * å¼€ç¥¨æ—¥æœŸ
     */
    @ApiModelProperty("开票日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "开票日期")
    private LocalDate issueDate;
    /**
     * é‡‘额(不含税)
     */
    @ApiModelProperty("金额(不含税)")
    @Excel(name = "金额(不含税)")
    private BigDecimal taxExclusivelPrice;
    /**
     * ç¨Žé¢
     */
    @ApiModelProperty("税额")
    @Excel(name = "税额")
    private BigDecimal taxPrice;
    /**
     * ä»·ç¨Žåˆè®¡
     */
    @ApiModelProperty("价税合计")
    @Excel(name = "价税合计")
    private BigDecimal taxInclusivePrice;
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    @Excel(name = "备注")
    private String remark;
    /**
     * å‘票内容
     */
    @ApiModelProperty("发票内容")
    @Excel(name = "发票内容")
    private String invoiceContent;
    /**
     * ä¾›åº”商id
     */
    @ApiModelProperty("供应商id")
    private Integer supplierId;
    /**
     * å…³è”上传的发票附件id
     */
    @ApiModelProperty("关联上传的发票附件id")
    private Integer storageAttachmentId;
    /**
     * å…³è”入库单id(多选)
     */
    @ApiModelProperty("关联入库单id(多选)")
    private String stockInRecordIds;
    /**
     * çŠ¶æ€ 0启用 1禁用
     */
    @ApiModelProperty("状态 0启用 1禁用")
    @Excel(name = "状态", readConverterExp = "0=正常,1=作废")
    private Integer status;
}
src/main/java/com/ruoyi/account/pojo/purchase/AccountPurchasePayment.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
package com.ruoyi.account.pojo.purchase;
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 lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--付款单
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 04:14:51
 */
@Getter
@Setter
@ToString
@TableName("account_purchase_payment")
@ApiModel(value = "AccountPurchasePayment对象", description = "财务管理--付款单")
public class AccountPurchasePayment implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * å…³è”付款申请id
     */
    @ApiModelProperty("关联付款申请id")
    private Integer accountPaymentApplicationId;
    /**
     * åˆ›å»ºäºº
     */
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹äºº
     */
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    /**
     * éƒ¨é—¨ID
     */
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * ä¾›åº”商id
     */
    @ApiModelProperty("供应商id")
    private Integer supplierId;
    /**
     * ä»˜æ¬¾æ—¥æœŸ
     */
    @ApiModelProperty("付款日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "付款日期")
    private LocalDate paymentDate;
    /**
     * ä»˜æ¬¾æ–¹å¼
     */
    @ApiModelProperty("付款方式")
    @Excel(name = "付款方式",dictType = "checkout_payment")
    private String paymentMethod;
    /**
     * ä»˜æ¬¾é‡‘额
     */
    @ApiModelProperty("付款金额")
    @Excel(name = "付款金额")
    private BigDecimal paymentAmount;
    /**
     * ä»˜æ¬¾å•号
     */
    @ApiModelProperty("付款单号")
    @Excel(name = "付款单号")
    private String paymentNumber;
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    @Excel(name = "备注")
    private String remark;
}
src/main/java/com/ruoyi/account/pojo/sales/AccountInvoiceApplication.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,138 @@
package com.ruoyi.account.pojo.sales;
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 lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--开票申请
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 01:38:32
 */
@Getter
@Setter
@ToString
@TableName("account_invoice_application")
@ApiModel(value = "AccountInvoiceApplication对象", description = "财务管理--开票申请")
public class AccountInvoiceApplication implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * åˆ›å»ºäºº
     */
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹äºº
     */
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    /**
     * éƒ¨é—¨ID
     */
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * å®¢æˆ·id
     */
    @ApiModelProperty("客户id")
    private Integer customerId;
    /**
     * å…³è”出库单id(多选)
     */
    @ApiModelProperty("关联出库单id(多选)")
    private String stockOutRecordIds;
    /**
     * å¼€ç¥¨ç”³è¯·å•号
     */
    @ApiModelProperty("开票申请单号")
    @Excel(name = "开票申请单号")
    private String invoiceApplicationNo;
    /**
     * å‘票类型
     */
    @ApiModelProperty("发票类型")
    @Excel(name = "发票类型")
    private String invoiceType;
    /**
     * ç”³è¯·æ—¥æœŸ
     */
    @ApiModelProperty("申请日期")
    @Excel(name = "申请日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate applyDate;
    /**
     * å‘票内容
     */
    @ApiModelProperty("发票内容")
    @Excel(name = "发票内容")
    private String invoiceContent;
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    @Excel(name = "备注")
    private String remark;
    /**
     * å®¡æ ¸çŠ¶æ€:0待审核1审核通过2审核不通过
     */
    @ApiModelProperty("审核状态:0待审核1审核通过2审核不通过")
    @Excel(name = "审核状态",readConverterExp = "0=待审核,1=审核通过,2=审核不通过")
    private Integer status;
    @ApiModelProperty("开票金额")
    @Excel(name = "开票金额")
    private BigDecimal invoiceAmount;
    @ApiModelProperty("税率")
    @Excel(name = "税率")
    private BigDecimal taxRate;
}
src/main/java/com/ruoyi/account/pojo/sales/AccountSalesCollection.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
package com.ruoyi.account.pojo.sales;
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 lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--收款单
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:49:56
 */
@Getter
@Setter
@ToString
@TableName("account_sales_collection")
@ApiModel(value = "AccountSalesCollection对象", description = "财务管理--收款单")
public class AccountSalesCollection implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * å…³è”出库单id(多选)
     */
    @ApiModelProperty("关联出库单id(多选)")
    private String stockOutRecordIds;
    /**
     * åˆ›å»ºäºº
     */
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹äºº
     */
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    /**
     * éƒ¨é—¨ID
     */
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * å®¢æˆ·id
     */
    @ApiModelProperty("客户id")
    private Integer customerId;
    /**
     * æ”¶æ¬¾æ—¥æœŸ
     */
    @ApiModelProperty("收款日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "收款日期")
    private LocalDate collectionDate;
    /**
     * æ”¶æ¬¾é‡‘额
     */
    @ApiModelProperty("收款金额")
    @Excel(name = "收款金额")
    private BigDecimal collectionAmount;
    /**
     * æ”¶æ¬¾æ–¹å¼
     */
    @ApiModelProperty("收款方式")
    @Excel(name = "收款方式",dictType = "payment_methods")
    private String collectionMethod;
    /**
     * æ”¶æ¬¾å•号
     */
    @ApiModelProperty("收款单号")
    @Excel(name = "收款单号")
    private String collectionNumber;
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    @Excel(name = "备注")
    private String remark;
}
src/main/java/com/ruoyi/account/pojo/sales/AccountSalesInvoice.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,168 @@
package com.ruoyi.account.pojo.sales;
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 lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--销项发票
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:10:20
 */
@Getter
@Setter
@ToString
@TableName("account_sales_invoice")
@ApiModel(value = "AccountSalesInvoice对象", description = "财务管理--销项发票")
public class AccountSalesInvoice implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    /**
     * å…³è”开票申请id
     */
    @ApiModelProperty("关联开票申请id")
    private Integer accountInvoiceApplicationId;
    /**
     * åˆ›å»ºäºº
     */
    @ApiModelProperty("创建人")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹äºº
     */
    @ApiModelProperty("修改人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @ApiModelProperty("修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    /**
     * éƒ¨é—¨ID
     */
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * å‘票号码
     */
    @ApiModelProperty("发票号码")
    @Excel(name = "发票号码")
    private String invoiceNumber;
    /**
     * ç¨Žçއ
     */
    @ApiModelProperty("税率")
    @Excel(name = "税率")
    private BigDecimal taxRate;
    /**
     * å‘票类型
     */
    @ApiModelProperty("发票类型")
    @Excel(name = "发票类型")
    private String invoiceType;
    /**
     * å¼€ç¥¨æ—¥æœŸ
     */
    @ApiModelProperty("开票日期")
    @Excel(name = "开票日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate issueDate;
    /**
     * é‡‘额(不含税)
     */
    @ApiModelProperty("金额(不含税)")
    @Excel(name = "金额(不含税)")
    private BigDecimal taxExclusivelPrice;
    /**
     * ç¨Žé¢
     */
    @ApiModelProperty("税额")
    @Excel(name = "税额")
    private BigDecimal taxPrice;
    /**
     * ä»·ç¨Žåˆè®¡
     */
    @ApiModelProperty("价税合计")
    @Excel(name = "价税合计")
    private BigDecimal taxInclusivePrice;
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    @Excel(name = "备注")
    private String remark;
    /**
     * å‘票内容
     */
    @ApiModelProperty("发票内容")
    @Excel(name = "发票内容")
    private String invoiceContent;
    /**
     * å®¢æˆ·id
     */
    @ApiModelProperty("客户id")
    private Integer customerId;
    /**
     * å…³è”上传的发票附件id
     */
    @ApiModelProperty("关联上传的发票附件id")
    private Integer storageAttachmentId;
    /**
     * çŠ¶æ€ 0启用 1禁用
     */
    @ApiModelProperty("状态 0启用 1禁用")
    @Excel(name = "状态", readConverterExp = "0=正常,1=作废")
    private Integer status;
}
src/main/java/com/ruoyi/account/service/AccountExpenseService.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/service/AccountFileService.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/service/AccountIncomeService.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/service/AccountStatementDetailsService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.account.service;
import com.ruoyi.account.pojo.AccountStatementDetails;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--对账单明细 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 10:12:42
 */
public interface AccountStatementDetailsService extends IService<AccountStatementDetails> {
}
src/main/java/com/ruoyi/account/service/AccountStatementService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.account.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.account.bean.dto.StatementAccountDto;
import com.ruoyi.account.bean.vo.StatementAccountVo;
import com.ruoyi.account.pojo.AccountStatement;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <p>
 *  æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 09:42:47
 */
public interface AccountStatementService extends IService<AccountStatement> {
    StatementAccountVo getAccountStatementDetailsByMonth(StatementAccountDto statementAccountDto);
    boolean addAccountStatement(StatementAccountVo statementAccountVo);
    boolean deleteAccountStatement(List<Long> ids);
    IPage<StatementAccountVo> listPageAccountStatement(Page page, StatementAccountDto statementAccountDto);
    void exportAccountStatement(HttpServletResponse response, StatementAccountDto statementAccountDto);
}
src/main/java/com/ruoyi/account/service/AccountingService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.account.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.AccountReportDto;
import com.ruoyi.account.bean.vo.AccountReportVo;
import com.ruoyi.framework.web.domain.AjaxResult;
public interface AccountingService {
    AjaxResult total(Integer year);
    AjaxResult deviceTypeDistribution(Integer year);
    AjaxResult calculateDepreciation(Page page, Integer year);
    AccountReportVo getAccountStatementDetailsByMonth(AccountReportDto accountReportDto);
}
src/main/java/com/ruoyi/account/service/BorrowInfoService.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/service/impl/AccountExpenseServiceImpl.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/service/impl/AccountFileServiceImpl.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/service/impl/AccountIncomeServiceImpl.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/service/impl/AccountStatementDetailsServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.account.service.impl;
import com.ruoyi.account.pojo.AccountStatementDetails;
import com.ruoyi.account.mapper.AccountStatementDetailsMapper;
import com.ruoyi.account.service.AccountStatementDetailsService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--对账单明细 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 10:12:42
 */
@Service
public class AccountStatementDetailsServiceImpl extends ServiceImpl<AccountStatementDetailsMapper, AccountStatementDetails> implements AccountStatementDetailsService {
}
src/main/java/com/ruoyi/account/service/impl/AccountStatementServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,298 @@
package com.ruoyi.account.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
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.account.bean.dto.StatementAccountDto;
import com.ruoyi.account.bean.dto.purchase.PurchaseInboundDto;
import com.ruoyi.account.bean.dto.purchase.PurchaseReturnDto;
import com.ruoyi.account.bean.dto.sales.SalesOutboundDto;
import com.ruoyi.account.bean.dto.sales.SalesReturnDto;
import com.ruoyi.account.bean.vo.StatementAccountVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseReturnVo;
import com.ruoyi.account.bean.vo.sales.SalesOutboundVo;
import com.ruoyi.account.bean.vo.sales.SalesReturnVo;
import com.ruoyi.account.mapper.AccountStatementDetailsMapper;
import com.ruoyi.account.mapper.AccountStatementMapper;
import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper;
import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
import com.ruoyi.account.pojo.AccountStatement;
import com.ruoyi.account.pojo.AccountStatementDetails;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.ruoyi.account.service.AccountStatementService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
import com.ruoyi.stock.mapper.StockInRecordMapper;
import com.ruoyi.stock.mapper.StockOutRecordMapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
 * <p>
 *  æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 09:42:47
 */
@Service
@RequiredArgsConstructor
@Transactional(rollbackFor = Exception.class)
public class AccountStatementServiceImpl extends ServiceImpl<AccountStatementMapper, AccountStatement> implements AccountStatementService {
    private final AccountStatementMapper accountStatementMapper;
    private final AccountSalesCollectionMapper accountSalesCollectionMapper;
    private final StockOutRecordMapper stockOutRecordMapper;
    private final StockInRecordMapper stockInRecordMapper;
    private final ReturnManagementMapper returnManagementMapper;
    private final AccountStatementDetailsMapper accountStatementDetailsMapper;
    private final AccountPurchasePaymentMapper accountPurchasePaymentMapper;
    private final PurchaseReturnOrdersMapper purchaseReturnOrdersMapper;
    private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd");
    @Override
    public StatementAccountVo getAccountStatementDetailsByMonth(StatementAccountDto statementAccountDto) {
        //对账月份转换成开始日期和结束日期区间
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
        YearMonth yearMonth = YearMonth.parse(statementAccountDto.getStatementMonth(), formatter);
        statementAccountDto.setStartDate(yearMonth.atDay(1));
        statementAccountDto.setEndDate(yearMonth.atEndOfMonth());
        if (statementAccountDto.getAccountType() == 1){
            //应收对账--Customer
            return getAccountStatementDetailsByCustomerAndMonth(statementAccountDto);
        }else {
            //应付对账--SupplierManage
            return getAccountStatementDetailsBySupplierAndMonth(statementAccountDto);
        }
    }
    @Override
    public boolean addAccountStatement(StatementAccountVo statementAccountVo) {
        //同一客户或者同一供应商,一个月份只能有一个对账单
        List<AccountStatement> accountStatements = accountStatementMapper.selectList(Wrappers.<AccountStatement>lambdaQuery()
                .eq(AccountStatement::getStatementMonth, statementAccountVo.getStatementMonth())
                .eq(AccountStatement::getAccountType, statementAccountVo.getAccountType())
                .eq(AccountStatement::getCustomerId, statementAccountVo.getCustomerId()));
        if (CollectionUtils.isNotEmpty(accountStatements)){
            throw new ServiceException("同一客户或者同一供应商,一个月份只能有一个对账单");
        }
        AccountStatement accountStatement = new AccountStatement();
        BeanUtils.copyProperties(statementAccountVo, accountStatement);
        accountStatement.setStatementNumber(genStatementAccountNo());
        boolean save = save(accountStatement);
        statementAccountVo.getAccountStatementDetails().stream().forEach(accountStatementDetails -> {
            accountStatementDetails.setAccountStatementId(accountStatement.getId());
            //添加对账单明细
            accountStatementDetailsMapper.insert(accountStatementDetails);
        });
        return save;
    }
    @Override
    public boolean deleteAccountStatement(List<Long> ids) {
        //删除对账单明细
        accountStatementDetailsMapper.delete(Wrappers.<AccountStatementDetails>lambdaQuery().in(AccountStatementDetails::getAccountStatementId, ids));
        return removeByIds(ids);
    }
    @Override
    public IPage<StatementAccountVo> listPageAccountStatement(Page page, StatementAccountDto statementAccountDto) {
        return accountStatementMapper.listPageAccountStatement(page, statementAccountDto);
    }
    @Override
    public void exportAccountStatement(HttpServletResponse response, StatementAccountDto statementAccountDto) {
        List<StatementAccountVo> list = accountStatementMapper.listPageAccountStatement(new Page(1,-1),statementAccountDto).getRecords();
        ExcelUtil<StatementAccountVo> util = new ExcelUtil<>(StatementAccountVo.class);
        util.exportExcel(response, list , "对账单");
    }
    //根据客户和月份获取对账详情(销售)
    private StatementAccountVo getAccountStatementDetailsByCustomerAndMonth(StatementAccountDto statementAccountDto) {
        StatementAccountVo statementAccountVo = new StatementAccountVo();
        statementAccountVo.setAccountType(1);//应收对账
        List<AccountStatementDetails> accountStatementDetailsList = new ArrayList<>();
        /*查询出库明细*/
        SalesOutboundDto salesOutboundDto = new SalesOutboundDto();
        salesOutboundDto.setCustomerId(statementAccountDto.getCustomerId());
        salesOutboundDto.setStartDate(statementAccountDto.getStartDate());
        salesOutboundDto.setEndDate(statementAccountDto.getEndDate());
        List<SalesOutboundVo> salesOutboundVos = stockOutRecordMapper.listPageAccountSales(new Page(1, -1), salesOutboundDto).getRecords();
        salesOutboundVos.stream().forEach(salesOutboundVo -> {
            AccountStatementDetails accountStatementDetails = new AccountStatementDetails();
            //数据日期=出库日期
            accountStatementDetails.setOccurrenceDate(salesOutboundVo.getShippingDate());
            //单据编号=出库单号
            accountStatementDetails.setReceiptNumber(salesOutboundVo.getOutboundBatches());
            //类型=出库
            accountStatementDetails.setType(1);
            //金额=出库金额
            accountStatementDetails.setAmount(salesOutboundVo.getOutboundAmount());
            //备注
            accountStatementDetails.setRemark("产品销售出库,产品:"+salesOutboundVo.getProductName());
            accountStatementDetailsList.add(accountStatementDetails);
        });
        /*查询收款明细*/
        List<AccountSalesCollection> accountSalesCollections = accountSalesCollectionMapper.selectList(Wrappers.<AccountSalesCollection>lambdaQuery()
                .eq(AccountSalesCollection::getCustomerId, statementAccountDto.getCustomerId())
                .between(AccountSalesCollection::getCollectionDate, statementAccountDto.getStartDate(), statementAccountDto.getEndDate()));
        accountSalesCollections.stream().forEach(accountSalesCollection -> {
            AccountStatementDetails accountStatementDetails = new AccountStatementDetails();
            //数据日期=收款日期
            accountStatementDetails.setOccurrenceDate(accountSalesCollection.getCollectionDate());
            //单据编号=收款单号
            accountStatementDetails.setReceiptNumber(accountSalesCollection.getCollectionNumber());
            //类型=收款
            accountStatementDetails.setType(3);
            //金额=收款金额
            accountStatementDetails.setAmount(accountSalesCollection.getCollectionAmount());
            //备注
            accountStatementDetails.setRemark("客户回款,备注:"+accountSalesCollection.getRemark());
            accountStatementDetailsList.add(accountStatementDetails);
        });
        /*查询退货明细*/
        SalesReturnDto salesReturnDto = new SalesReturnDto();
        salesReturnDto.setCustomerId(statementAccountDto.getCustomerId());
        salesReturnDto.setStartDate(statementAccountDto.getStartDate());
        salesReturnDto.setEndDate(statementAccountDto.getEndDate());
        List<SalesReturnVo> salesReturnVos = returnManagementMapper.listPageAccountSalesReturn(new Page(1, -1), salesReturnDto).getRecords();
        salesReturnVos.stream().forEach(salesReturnVo -> {
            AccountStatementDetails accountStatementDetails = new AccountStatementDetails();
            //数据日期=退货日期
            accountStatementDetails.setOccurrenceDate(salesReturnVo.getMakeTime().toLocalDate());
            //单据编号=退货单号
            accountStatementDetails.setReceiptNumber(salesReturnVo.getReturnNo());
            //类型=退货
            accountStatementDetails.setType(5);
            //金额=退款金额
            accountStatementDetails.setAmount(salesReturnVo.getRefundAmount());
            //备注
            accountStatementDetails.setRemark("产品退货,原因:"+salesReturnVo.getReturnReason());
            accountStatementDetailsList.add(accountStatementDetails);
        });
        //期初余额=上个月的期末余额
        statementAccountVo.setOpeningBalance(BigDecimal.ZERO);
        List<AccountStatement> accountStatements = accountStatementMapper.selectList(Wrappers.<AccountStatement>lambdaQuery()
                .eq(AccountStatement::getAccountType, 1)
                .eq(AccountStatement::getCustomerId, statementAccountDto.getCustomerId())
                .eq(AccountStatement::getStatementMonth,
                        YearMonth.parse(statementAccountDto.getStatementMonth()).minusMonths(1).toString()));
        if (CollectionUtils.isNotEmpty(accountStatements)){
            statementAccountVo.setOpeningBalance(accountStatements.get(accountStatements.size() - 1).getClosingBalance());
        }
        //本期应收=出库-退货金额累计
        statementAccountVo.setCurrentPlan(salesOutboundVos.stream().map(SalesOutboundVo::getOutboundAmount).reduce(BigDecimal.ZERO, BigDecimal::add)
                .subtract(salesReturnVos.stream().map(SalesReturnVo::getRefundAmount).reduce(BigDecimal.ZERO, BigDecimal::add)));
        //本期收款=收款金额累计
        statementAccountVo.setCurrentActually(accountSalesCollections.stream().map(AccountSalesCollection::getCollectionAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
        //期末余额=期初+应收-收款
        statementAccountVo.setClosingBalance(statementAccountVo.getOpeningBalance().add(statementAccountVo.getCurrentPlan()).subtract(statementAccountVo.getCurrentActually()));
        statementAccountVo.setAccountStatementDetails(accountStatementDetailsList);
        return statementAccountVo;
    }
    //根据供应商和月份获取对账详情(采购)
    private StatementAccountVo getAccountStatementDetailsBySupplierAndMonth(StatementAccountDto statementAccountDto) {
        StatementAccountVo statementAccountVo = new StatementAccountVo();
        statementAccountVo.setAccountType(2);//应付对账
        List<AccountStatementDetails> accountStatementDetailsList = new ArrayList<>();
        /*查询入库明细*/
        PurchaseInboundDto purchaseInboundDto = new PurchaseInboundDto();
        purchaseInboundDto.setSupplierId(statementAccountDto.getCustomerId());
        purchaseInboundDto.setStartDate(statementAccountDto.getStartDate());
        purchaseInboundDto.setEndDate(statementAccountDto.getEndDate());
        List<PurchaseInboundVo> purchaseInboundVos = stockInRecordMapper.listPageAccountPurchase(new Page(1, -1), purchaseInboundDto).getRecords();
        purchaseInboundVos.stream().forEach(purchaseInboundVo -> {
            AccountStatementDetails accountStatementDetails = new AccountStatementDetails();
            //数据日期=入库日期
            accountStatementDetails.setOccurrenceDate(purchaseInboundVo.getInboundDate());
            //单据编号=入库单号
            accountStatementDetails.setReceiptNumber(purchaseInboundVo.getInboundBatches());
            //类型=入库
            accountStatementDetails.setType(2);
            //金额=入库金额
            accountStatementDetails.setAmount(purchaseInboundVo.getInboundAmount());
            //备注
            accountStatementDetails.setRemark("产品采购入库,产品:"+purchaseInboundVo.getProductName());
            accountStatementDetailsList.add(accountStatementDetails);
        });
        /*查询付款明细*/
        List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectList(Wrappers.<AccountPurchasePayment>lambdaQuery()
                .eq(AccountPurchasePayment::getSupplierId, statementAccountDto.getCustomerId())
                .between(AccountPurchasePayment::getPaymentDate, statementAccountDto.getStartDate(), statementAccountDto.getEndDate()));
        accountPurchasePayments.stream().forEach(accountPurchasePayment -> {
            AccountStatementDetails accountStatementDetails = new AccountStatementDetails();
            //数据日期=付款日期
            accountStatementDetails.setOccurrenceDate(accountPurchasePayment.getPaymentDate());
            //单据编号=付款单号
            accountStatementDetails.setReceiptNumber(accountPurchasePayment.getPaymentNumber());
            //类型=付款
            accountStatementDetails.setType(4);
            //金额=付款金额
            accountStatementDetails.setAmount(accountPurchasePayment.getPaymentAmount());
            //备注
            accountStatementDetails.setRemark("支付货款,备注:"+accountPurchasePayment.getRemark());
            accountStatementDetailsList.add(accountStatementDetails);
        });
        /*查询退货明细*/
        PurchaseReturnDto purchaseReturnDto = new PurchaseReturnDto();
        purchaseReturnDto.setSupplierId(statementAccountDto.getCustomerId());
        purchaseReturnDto.setStartDate(statementAccountDto.getStartDate());
        purchaseReturnDto.setEndDate(statementAccountDto.getEndDate());
        List<PurchaseReturnVo> purchaseReturnVos = purchaseReturnOrdersMapper.listPageAccountPurchaseReturn(new Page(1, -1), purchaseReturnDto).getRecords();
        purchaseReturnVos.stream().forEach(purchaseReturnVo -> {
            AccountStatementDetails accountStatementDetails = new AccountStatementDetails();
            //数据日期=退货日期
            accountStatementDetails.setOccurrenceDate(purchaseReturnVo.getPreparedAt().toLocalDate());
            //单据编号=退货单号
            accountStatementDetails.setReceiptNumber(purchaseReturnVo.getReturnNo());
            //类型=退货
            accountStatementDetails.setType(5);
            //金额=退款金额
            accountStatementDetails.setAmount(purchaseReturnVo.getTotalAmount());
            //备注
            accountStatementDetails.setRemark("产品退货,退货方式:"+purchaseReturnVo.getReturnType());
            accountStatementDetailsList.add(accountStatementDetails);
        });
        //期初余额=上个月的期末余额
        statementAccountVo.setOpeningBalance(BigDecimal.ZERO);
        List<AccountStatement> accountStatements = accountStatementMapper.selectList(Wrappers.<AccountStatement>lambdaQuery()
                .eq(AccountStatement::getAccountType, 2)
                .eq(AccountStatement::getCustomerId, statementAccountDto.getCustomerId())
                .eq(AccountStatement::getStatementMonth,
                        YearMonth.parse(statementAccountDto.getStatementMonth()).minusMonths(1).toString()));
        if (CollectionUtils.isNotEmpty(accountStatements)){
            statementAccountVo.setOpeningBalance(accountStatements.get(accountStatements.size() - 1).getClosingBalance());
        }
        //本期应付=入库-退货金额累计
        statementAccountVo.setCurrentPlan(purchaseInboundVos.stream().map(PurchaseInboundVo::getInboundAmount).reduce(BigDecimal.ZERO, BigDecimal::add)
                .subtract(purchaseReturnVos.stream().map(PurchaseReturnVo::getTotalAmount).reduce(BigDecimal.ZERO, BigDecimal::add)));
        //本期付款=付款金额累计
        statementAccountVo.setCurrentActually(accountPurchasePayments.stream().map(AccountPurchasePayment::getPaymentAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
        //期末余额=期初+应收-收款
        statementAccountVo.setClosingBalance(statementAccountVo.getOpeningBalance().add(statementAccountVo.getCurrentPlan()).subtract(statementAccountVo.getCurrentActually()));
        statementAccountVo.setAccountStatementDetails(accountStatementDetailsList);
        return statementAccountVo;
    }
    private String genStatementAccountNo() {
        return "DZ" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10);
    }
}
src/main/java/com/ruoyi/account/service/impl/AccountingServiceImpl.java
@@ -3,27 +3,47 @@
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.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.AccountReportDto;
import com.ruoyi.account.bean.dto.DeviceTypeDetail;
import com.ruoyi.account.bean.dto.DeviceTypeDistributionVO;
import com.ruoyi.account.mapper.BorrowInfoMapper;
import com.ruoyi.account.pojo.BorrowInfo;
import com.ruoyi.account.bean.dto.purchase.PurchaseInboundDto;
import com.ruoyi.account.bean.dto.purchase.PurchaseReturnDto;
import com.ruoyi.account.bean.dto.sales.SalesOutboundDto;
import com.ruoyi.account.bean.dto.sales.SalesReturnDto;
import com.ruoyi.account.bean.vo.AccountReportVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseReturnVo;
import com.ruoyi.account.bean.vo.sales.SalesOutboundVo;
import com.ruoyi.account.bean.vo.sales.SalesReturnVo;
import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper;
import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.ruoyi.account.service.AccountingService;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.procurementrecord.mapper.CustomStorageMapper;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper;
import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
import com.ruoyi.procurementrecord.pojo.CustomStorage;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
import com.ruoyi.stock.mapper.StockInRecordMapper;
import com.ruoyi.stock.mapper.StockOutRecordMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Year;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
@@ -35,15 +55,22 @@
@Service
@Slf4j
@RequiredArgsConstructor
public class AccountingServiceImpl {
public class AccountingServiceImpl implements AccountingService {
    private final DeviceLedgerMapper deviceLedgerMapper;
    private final BorrowInfoMapper borrowInfoMapper;
    private final CustomStorageMapper customStorageMapper;
    private final ProcurementRecordMapper procurementRecordMapper;
    private final ProcurementRecordOutMapper procurementRecordOutMapper;
    private final AccountSalesCollectionMapper accountSalesCollectionMapper;
    private final AccountPurchasePaymentMapper accountPurchasePaymentMapper;
    private final StockOutRecordMapper stockOutRecordMapper;
    private final ReturnManagementMapper returnManagementMapper;
    private final StockInRecordMapper stockInRecordMapper;
    private final PurchaseReturnOrdersMapper purchaseReturnOrdersMapper;
    public R<?> total(Integer year) {
    @Override
    public AjaxResult total(Integer year) {
        Map<String,Object> map = new HashMap<>();
        map.put("deprAmount",0); // æŠ˜æ—§é‡‘额
        map.put("deviceTotal",0); // è®¾å¤‡æ€»æ•°
@@ -75,17 +102,8 @@
            map.put("netValue",reduce.subtract(total));
        }
        // è´Ÿå€º
        LambdaQueryWrapper<BorrowInfo> borrowInfoLambdaQueryWrapper = new LambdaQueryWrapper<>();
        borrowInfoLambdaQueryWrapper.like(BorrowInfo::getCreateTime,year)
                .eq(BorrowInfo::getStatus,1);
        List<BorrowInfo> borrowInfos = borrowInfoMapper.selectList(borrowInfoLambdaQueryWrapper);
        if(CollectionUtils.isNotEmpty(borrowInfos)){
            BigDecimal reduce = borrowInfos.stream()
                    .map(BorrowInfo::getBorrowAmount)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            map.put("debt",reduce);
        }
        map.put("debt",BigDecimal.ZERO);
        // åº“存资产
        LambdaQueryWrapper<ProcurementRecordStorage> procurementRecordStorageLambdaQueryWrapper = new LambdaQueryWrapper<>();
        procurementRecordStorageLambdaQueryWrapper.like(ProcurementRecordStorage::getCreateTime,year);
@@ -150,7 +168,7 @@
            });
        }
        map.put("inventoryValue",procurementRecordTotal.add(customStorageTotal));
        return R.ok(map);
        return AjaxResult.success( map);
    }
    /**
@@ -245,7 +263,8 @@
        return totalDepreciation.setScale(2, BigDecimal.ROUND_HALF_UP);
    }
    public R<?> deviceTypeDistribution(Integer year) {
    @Override
    public AjaxResult deviceTypeDistribution(Integer year) {
        // 2. ç»„装返回VO
       DeviceTypeDistributionVO vo = new DeviceTypeDistributionVO();
       List<DeviceTypeDetail> details = deviceLedgerMapper.getDeviceTypeDistributionByYear( year);
@@ -265,10 +284,11 @@
                   .collect(Collectors.toList()));
           vo.setTotalCount(vo.getCategories().size());
       }
        return R.ok(vo);
        return AjaxResult.success(vo);
    }
    public R<?> calculateDepreciation(Page page, Integer year) {
    @Override
    public AjaxResult calculateDepreciation(Page page, Integer year) {
        LambdaQueryWrapper<DeviceLedger> deviceLedgerLambdaQueryWrapper = new LambdaQueryWrapper<>();
        deviceLedgerLambdaQueryWrapper.like(DeviceLedger::getCreateTime,year)
                .eq(DeviceLedger::getIsDepr,1);
@@ -277,6 +297,161 @@
            record.setDeprAmount(calculatePreciseDepreciation(record));
            record.setNetValue(record.getTaxIncludingPriceTotal().subtract(record.getDeprAmount()));
        }
        return R.ok(deviceLedgerIPage);
        return AjaxResult.success(deviceLedgerIPage);
    }
    @Override
    public AccountReportVo getAccountStatementDetailsByMonth(AccountReportDto accountReportDto) {
        AccountReportVo accountReportVo = new AccountReportVo();
        LocalDate start = accountReportDto.getEntryDateStart();
        LocalDate end = accountReportDto.getEntryDateEnd();
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
        // ========== 1. é¡¶éƒ¨å¡ç‰‡æ•°æ® ==========
        // 1.1 æ€»è¥æ”¶ = æ”¶æ¬¾å•总金额
        List<AccountSalesCollection> accountSalesCollections = accountSalesCollectionMapper.selectList(
                Wrappers.<AccountSalesCollection>lambdaQuery()
                        .between(AccountSalesCollection::getCollectionDate, start, end)
        );
        BigDecimal totalIncome = Optional.of(
                accountSalesCollections.stream()
                        .map(AccountSalesCollection::getCollectionAmount)
                        .filter(Objects::nonNull)
                        .reduce(BigDecimal.ZERO, BigDecimal::add)
        ).orElse(BigDecimal.ZERO);
        accountReportVo.setTotalIncome(totalIncome);
        // 1.2 æ€»æ”¯å‡º = ä»˜æ¬¾å•总金额
        List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectList(
                Wrappers.<AccountPurchasePayment>lambdaQuery()
                        .between(AccountPurchasePayment::getPaymentDate, start, end)
        );
        BigDecimal totalExpense = Optional.of(
                accountPurchasePayments.stream()
                        .map(AccountPurchasePayment::getPaymentAmount)
                        .filter(Objects::nonNull)
                        .reduce(BigDecimal.ZERO, BigDecimal::add)
        ).orElse(BigDecimal.ZERO);
        accountReportVo.setTotalExpense(totalExpense);
        // 1.3 åº”收账款 = é”€å”®å‡ºåº“金额合计 - é”€å”®é€€è´§é‡‘额合计
        SalesOutboundDto salesOutboundDto = new SalesOutboundDto();
        salesOutboundDto.setStartDate(accountReportDto.getEntryDateStart());
        salesOutboundDto.setEndDate(accountReportDto.getEntryDateEnd());
        List<SalesOutboundVo> salesOutboundVos = stockOutRecordMapper.listPageAccountSales(new Page(1, -1), salesOutboundDto).getRecords();
        BigDecimal salesOutAmount = Optional.of(
                salesOutboundVos.stream()
                        .map(SalesOutboundVo::getOutboundAmount)
                        .filter(Objects::nonNull)
                        .reduce(BigDecimal.ZERO, BigDecimal::add)
        ).orElse(BigDecimal.ZERO);
        SalesReturnDto salesReturnDto = new SalesReturnDto();
        salesReturnDto.setStartDate(accountReportDto.getEntryDateStart());
        salesReturnDto.setEndDate(accountReportDto.getEntryDateEnd());
        List<SalesReturnVo> salesReturnVos = returnManagementMapper.listPageAccountSalesReturn(new Page(1, -1), salesReturnDto).getRecords();
        BigDecimal salesReturnAmount = Optional.of(
                salesReturnVos.stream()
                        .map(SalesReturnVo::getRefundAmount)
                        .filter(Objects::nonNull)
                        .reduce(BigDecimal.ZERO, BigDecimal::add)
        ).orElse(BigDecimal.ZERO);
        accountReportVo.setAccountsReceivable(salesOutAmount.subtract(salesReturnAmount));
        // 1.4 åº”付账款 = é‡‡è´­å…¥åº“金额合计 - é‡‡è´­é€€è´§é‡‘额合计
        PurchaseInboundDto purchaseInboundDto = new PurchaseInboundDto();
        purchaseInboundDto.setStartDate(accountReportDto.getEntryDateStart());
        purchaseInboundDto.setEndDate(accountReportDto.getEntryDateEnd());
        List<PurchaseInboundVo> purchaseInboundVos = stockInRecordMapper.listPageAccountPurchase(new Page(1, -1), purchaseInboundDto).getRecords();
        BigDecimal purchaseInAmount = Optional.of(
                purchaseInboundVos.stream()
                        .map(PurchaseInboundVo::getInboundAmount)
                        .filter(Objects::nonNull)
                        .reduce(BigDecimal.ZERO, BigDecimal::add)
        ).orElse(BigDecimal.ZERO);
        PurchaseReturnDto purchaseReturnDto = new PurchaseReturnDto();
        purchaseReturnDto.setStartDate(accountReportDto.getEntryDateStart());
        purchaseReturnDto.setEndDate(accountReportDto.getEntryDateEnd());
        List<PurchaseReturnVo> purchaseReturnVos = purchaseReturnOrdersMapper.listPageAccountPurchaseReturn(new Page(1, -1), purchaseReturnDto).getRecords();
        BigDecimal purchaseReturnAmount = Optional.of(
                purchaseReturnVos.stream()
                        .map(PurchaseReturnVo::getTotalAmount)
                        .filter(Objects::nonNull)
                        .reduce(BigDecimal.ZERO, BigDecimal::add)
        ).orElse(BigDecimal.ZERO);
        accountReportVo.setAccountsPayable(purchaseInAmount.subtract(purchaseReturnAmount));
        // 1.5 å‡€åˆ©æ¶¦ = æ€»è¥æ”¶ - æ€»æ”¯å‡º
        BigDecimal netProfit = totalIncome.subtract(totalExpense);
        accountReportVo.setNetRevenue(netProfit);
        // ========== 2. æŠ˜çº¿å›¾ï¼šæœˆåº¦è¥æ”¶/支出/净利润趋势 ==========
        Map<String, BigDecimal> monthIncomeMap = new HashMap<>();
        Map<String, BigDecimal> monthExpenseMap = new HashMap<>();
        // æœˆåº¦è¥æ”¶
        accountSalesCollections.forEach(item -> {
            String month = item.getCollectionDate().format(monthFormatter);
            monthIncomeMap.put(month, monthIncomeMap.getOrDefault(month, BigDecimal.ZERO)
                    .add(Optional.ofNullable(item.getCollectionAmount()).orElse(BigDecimal.ZERO)));
        });
        // æœˆåº¦æ”¯å‡º
        accountPurchasePayments.forEach(item -> {
            String month = item.getPaymentDate().format(monthFormatter);
            monthExpenseMap.put(month, monthExpenseMap.getOrDefault(month, BigDecimal.ZERO)
                    .add(Optional.ofNullable(item.getPaymentAmount()).orElse(BigDecimal.ZERO)));
        });
        // ç”Ÿæˆè¿žç»­æœˆä»½åˆ—表
        List<String> monthList = new ArrayList<>();
        LocalDate current = start.withDayOfMonth(1);
        while (!current.isAfter(end.withDayOfMonth(1))) {
            monthList.add(current.format(monthFormatter));
            current = current.plusMonths(1);
        }
        // ç»„装趋势数据
        List<AccountReportVo.MonthlyTrendVO> trendList = new ArrayList<>();
        for (String month : monthList) {
            BigDecimal income = monthIncomeMap.getOrDefault(month, BigDecimal.ZERO);
            BigDecimal expense = monthExpenseMap.getOrDefault(month, BigDecimal.ZERO);
            AccountReportVo.MonthlyTrendVO trend = new AccountReportVo.MonthlyTrendVO();
            trend.setMonth(month);
            trend.setIncome(income);
            trend.setExpense(expense);
            trend.setProfit(income.subtract(expense));
            trendList.add(trend);
        }
        accountReportVo.setMonthlyTrendList(trendList);
        // ========== 3. æŸ±çŠ¶å›¾ï¼šæœˆåº¦åº”æ”¶/应付数据 ==========
        Map<String, BigDecimal> monthReceivableMap = new HashMap<>();
        Map<String, BigDecimal> monthPayableMap = new HashMap<>();
        // æœˆåº¦åº”收(销售出库-退货)
        salesOutboundVos.forEach(item -> {
            String month = item.getShippingDate().format(monthFormatter);
            monthReceivableMap.put(month, monthReceivableMap.getOrDefault(month, BigDecimal.ZERO)
                    .add(Optional.ofNullable(item.getOutboundAmount()).orElse(BigDecimal.ZERO)));
        });
        salesReturnVos.forEach(item -> {
            String month = item.getMakeTime().format(monthFormatter);
            monthReceivableMap.put(month, monthReceivableMap.getOrDefault(month, BigDecimal.ZERO)
                    .subtract(Optional.ofNullable(item.getRefundAmount()).orElse(BigDecimal.ZERO)));
        });
        // æœˆåº¦åº”付(采购入库-退货)
        purchaseInboundVos.forEach(item -> {
            String month = item.getInboundDate().format(monthFormatter);
            monthPayableMap.put(month, monthPayableMap.getOrDefault(month, BigDecimal.ZERO)
                    .add(Optional.ofNullable(item.getInboundAmount()).orElse(BigDecimal.ZERO)));
        });
        purchaseReturnVos.forEach(item -> {
            String month = item.getPreparedAt().format(monthFormatter);
            monthPayableMap.put(month, monthPayableMap.getOrDefault(month, BigDecimal.ZERO)
                    .subtract(Optional.ofNullable(item.getTotalAmount()).orElse(BigDecimal.ZERO)));
        });
        // ç»„装应收应付数据
        List<AccountReportVo.ReceivablePayableVO> rpList = new ArrayList<>();
        for (String month : monthList) {
            AccountReportVo.ReceivablePayableVO rp = new AccountReportVo.ReceivablePayableVO();
            rp.setMonth(month);
            rp.setReceivable(monthReceivableMap.getOrDefault(month, BigDecimal.ZERO));
            rp.setPayable(monthPayableMap.getOrDefault(month, BigDecimal.ZERO));
            rpList.add(rp);
        }
        accountReportVo.setReceivablePayableList(rpList);
        return accountReportVo;
    }
}
src/main/java/com/ruoyi/account/service/impl/BorrowInfoServiceImpl.java
ÎļþÒÑɾ³ý
src/main/java/com/ruoyi/account/service/impl/financial/AccountSubjectServiceImpl.java
@@ -174,7 +174,7 @@
        if (accountSubjectDto != null && accountSubjectDto.getStatus() != null) {
            queryWrapper.eq(AccountSubject::getStatus, accountSubjectDto.getStatus());
        }
        queryWrapper.orderByAsc(AccountSubject::getSubjectCode).orderByAsc(AccountSubject::getId);
        queryWrapper.orderByDesc(AccountSubject::getSubjectCode).orderByDesc(AccountSubject::getId);
        return queryWrapper;
    }
@@ -296,8 +296,8 @@
        }
        List<AccountSubject> sortedSubjects = new ArrayList<>(subjects);
        sortedSubjects.sort(Comparator
                .comparing(AccountSubject::getSubjectCode, Comparator.nullsLast(String::compareTo))
                .thenComparing(AccountSubject::getId, Comparator.nullsLast(Long::compareTo)));
                .comparing(AccountSubject::getSubjectCode, Comparator.nullsFirst(String::compareTo)).reversed()
                .thenComparing(AccountSubject::getId, Comparator.nullsFirst(Long::compareTo)).reversed());
        Map<Long, AccountSubjectVo> subjectVoMap = new LinkedHashMap<>();
        for (AccountSubject subject : sortedSubjects) {
src/main/java/com/ruoyi/account/service/impl/purchase/AccountPaymentApplicationServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
package com.ruoyi.account.service.impl.purchase;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
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.account.bean.dto.purchase.AccountPaymentApplicationDto;
import com.ruoyi.account.bean.vo.purchase.AccountPaymentApplicationVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo;
import com.ruoyi.account.mapper.purchase.AccountPaymentApplicationMapper;
import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper;
import com.ruoyi.account.pojo.purchase.AccountPaymentApplication;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
import com.ruoyi.account.service.purchase.AccountPaymentApplicationService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--付款申请 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 03:44:22
 */
@Service
@RequiredArgsConstructor
public class AccountPaymentApplicationServiceImpl extends ServiceImpl<AccountPaymentApplicationMapper, AccountPaymentApplication> implements AccountPaymentApplicationService {
    private final AccountPaymentApplicationMapper accountPaymentApplicationMapper;
    private final AccountPurchasePaymentMapper accountPurchasePaymentMapper;
    private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmss");
    @Override
    public IPage<AccountPaymentApplicationVo> listPageAccountPaymentApplication(Page page, AccountPaymentApplicationDto accountPaymentApplicationDto) {
        return accountPaymentApplicationMapper.listPageAccountPaymentApplication(page, accountPaymentApplicationDto);
    }
    @Override
    public List<PurchaseInboundVo> getInboundBatchesBySupplier(Integer supplierId) {
        return accountPaymentApplicationMapper.getInboundBatchesBySupplier(supplierId);
    }
    @Override
    public boolean addAccountPaymentApplication(AccountPaymentApplication accountPaymentApplication) {
        if (StringUtils.isEmpty(accountPaymentApplication.getInvoiceApplicationNo())) {
            accountPaymentApplication.setInvoiceApplicationNo(genPaymentApplicationNo());
        }
        String stockInRecordIds= accountPaymentApplication.getStockInRecordIds();
        if (stockInRecordIds != null && !stockInRecordIds.isEmpty()) {
            List<Long> ids = Arrays.stream(stockInRecordIds.split(","))
                    .map(Long::valueOf)
                    .toList();
            if (accountPaymentApplicationMapper.existsByStockInRecordId(ids)){
                throw new ServiceException("存在重复的入库单");
            }
        }
        return save(accountPaymentApplication);
    }
    @Override
    public boolean deleteAccountPaymentApplication(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            throw new ServiceException("删除失败,请选择要删除的数据");
        }
        //判断是否已经有对应的付款单,如果有则无法删除
        List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectList(Wrappers.<AccountPurchasePayment>lambdaQuery().in(AccountPurchasePayment::getAccountPaymentApplicationId, ids));
        if (CollectionUtils.isNotEmpty(accountPurchasePayments)){
            throw new ServiceException("删除失败,已经有关联的付款单");
        }
        //删除开票申请
        return removeBatchByIds(ids);
    }
    @Override
    public void exportAccountPaymentApplication(HttpServletResponse response, AccountPaymentApplicationDto accountPaymentApplicationDto) {
        List<AccountPaymentApplicationVo> list = accountPaymentApplicationMapper.listPageAccountPaymentApplication(new Page(1,-1),accountPaymentApplicationDto).getRecords();
        ExcelUtil<AccountPaymentApplicationVo> util = new ExcelUtil<>(AccountPaymentApplicationVo.class);
        util.exportExcel(response, list , "付款申请");
    }
    private String genPaymentApplicationNo() {
        return "FK" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10);
    }
}
src/main/java/com/ruoyi/account/service/impl/purchase/AccountPurchaseInvoiceServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.ruoyi.account.service.impl.purchase;
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.bean.dto.purchase.AccountPurchaseInvoiceDto;
import com.ruoyi.account.bean.vo.purchase.AccountPurchaseInvoiceVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo;
import com.ruoyi.account.mapper.purchase.AccountPurchaseInvoiceMapper;
import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice;
import com.ruoyi.account.service.purchase.AccountPurchaseInvoiceService;
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
import com.ruoyi.common.utils.poi.ExcelUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--进项发票 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 03:06:17
 */
@Service
@RequiredArgsConstructor
public class AccountPurchaseInvoiceServiceImpl extends ServiceImpl<AccountPurchaseInvoiceMapper, AccountPurchaseInvoice> implements AccountPurchaseInvoiceService {
    private final AccountPurchaseInvoiceMapper accountPurchaseInvoiceMapper;
    private final StorageAttachmentMapper storageAttachmentMapper;
    @Override
    public IPage<AccountPurchaseInvoiceVo> listPageAccountPurchaseInvoice(Page page, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto) {
        return accountPurchaseInvoiceMapper.listPageAccountPurchaseInvoice(page, accountPurchaseInvoiceDto);
    }
    @Override
    public boolean deleteAccountPurchaseInvoice(List<Long> ids) {
        List<AccountPurchaseInvoice> accountPurchaseInvoices = accountPurchaseInvoiceMapper.selectByIds(ids);
        //删除附件
        storageAttachmentMapper.deleteBatchIds(accountPurchaseInvoices.stream().map(AccountPurchaseInvoice::getStorageAttachmentId).toList());
        return removeBatchByIds(ids);
    }
    @Override
    public void exportAccountPurchaseInvoice(HttpServletResponse response, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto) {
        List<AccountPurchaseInvoiceVo> list = accountPurchaseInvoiceMapper.listPageAccountPurchaseInvoice(new Page(1,-1),accountPurchaseInvoiceDto).getRecords();
        ExcelUtil<AccountPurchaseInvoiceVo> util = new ExcelUtil<>(AccountPurchaseInvoiceVo.class);
        util.exportExcel(response, list , "进项发票");
    }
    @Override
    public List<PurchaseInboundVo> getInboundBatchesBySupplier(Integer supplierId) {
        return accountPurchaseInvoiceMapper.getInboundBatchesBySupplier(supplierId);
    }
}
src/main/java/com/ruoyi/account/service/impl/purchase/AccountPurchasePaymentServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
package com.ruoyi.account.service.impl.purchase;
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.github.xiaoymin.knife4j.core.util.CollectionUtils;
import com.ruoyi.account.bean.dto.purchase.AccountPurchasePaymentDto;
import com.ruoyi.account.bean.vo.purchase.AccountPurchasePaymentVo;
import com.ruoyi.account.mapper.AccountStatementDetailsMapper;
import com.ruoyi.account.mapper.purchase.AccountPaymentApplicationMapper;
import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper;
import com.ruoyi.account.pojo.AccountStatementDetails;
import com.ruoyi.account.pojo.purchase.AccountPaymentApplication;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
import com.ruoyi.account.service.purchase.AccountPurchasePaymentService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Random;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--付款单 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 04:14:51
 */
@Service
@RequiredArgsConstructor
public class AccountPurchasePaymentServiceImpl extends ServiceImpl<AccountPurchasePaymentMapper, AccountPurchasePayment> implements AccountPurchasePaymentService {
    private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmss");
    private final AccountPurchasePaymentMapper accountPurchasePaymentMapper;
    private final AccountStatementDetailsMapper accountStatementDetailsMapper;
    private final AccountPaymentApplicationMapper accountPaymentApplicationMapper;
    @Override
    public IPage<AccountPurchasePaymentVo> listPageAccountPurchasePayment(Page page, AccountPurchasePaymentDto accountPurchasePaymentDto) {
        return accountPurchasePaymentMapper.listPageAccountPurchasePayment(page, accountPurchasePaymentDto);
    }
    @Override
    public boolean addAccountPurchasePayment(AccountPurchasePayment accountPurchasePayment) {
        if (StringUtils.isEmpty(accountPurchasePayment.getPaymentNumber())) {
            accountPurchasePayment.setPaymentNumber(genAccountPurchasePaymentNo());
        }
        //校验累计付款金额不能超过申请金额
        AccountPaymentApplication accountPaymentApplication = accountPaymentApplicationMapper.selectById(accountPurchasePayment.getAccountPaymentApplicationId());
        List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectList(Wrappers.<AccountPurchasePayment>lambdaQuery().eq(AccountPurchasePayment::getAccountPaymentApplicationId, accountPurchasePayment.getAccountPaymentApplicationId()));
        BigDecimal totalPaymentAmount = accountPurchasePayments.stream().map(AccountPurchasePayment::getPaymentAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
        if (accountPaymentApplication.getPaymentAmount().compareTo(totalPaymentAmount) < 0) {
            throw new ServiceException("累计付款金额不能超过申请金额");
        }
        return save(accountPurchasePayment);
    }
    @Override
    public void exportAccountPurchasePayment(HttpServletResponse response, AccountPurchasePaymentDto accountPurchasePaymentDto) {
        List<AccountPurchasePaymentVo> list = accountPurchasePaymentMapper.listPageAccountPurchasePayment(new Page(1,-1),accountPurchasePaymentDto).getRecords();
        ExcelUtil<AccountPurchasePaymentVo> util = new ExcelUtil<>(AccountPurchasePaymentVo.class);
        util.exportExcel(response, list , "付款单");
    }
    @Override
    public boolean deleteAccountPurchasePayment(List<Long> ids) {
        //如果该付款单已经生成对账单则无法删除
        List<AccountPurchasePayment> accountPurchasePayments = accountPurchasePaymentMapper.selectByIds(ids);
        List<String> strings = accountPurchasePayments.stream().map(AccountPurchasePayment::getPaymentNumber).toList();
        List<AccountStatementDetails> accountStatementDetails = accountStatementDetailsMapper.selectList(Wrappers.<AccountStatementDetails>lambdaQuery()
                .in(AccountStatementDetails::getReceiptNumber, strings));
        if (CollectionUtils.isNotEmpty(accountStatementDetails)){
            throw new ServiceException("该付款单已经生成对账单,无法删除");
        }
        return removeByIds(ids);
    }
    private String genAccountPurchasePaymentNo() {
        return "SK" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10);
    }
}
src/main/java/com/ruoyi/account/service/impl/sales/AccountInvoiceApplicationServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
package com.ruoyi.account.service.impl.sales;
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.account.bean.dto.sales.AccountInvoiceApplicationDto;
import com.ruoyi.account.bean.vo.sales.AccountInvoiceApplicationVo;
import com.ruoyi.account.bean.vo.sales.SalesOutboundVo;
import com.ruoyi.account.mapper.sales.AccountInvoiceApplicationMapper;
import com.ruoyi.account.mapper.sales.AccountSalesInvoiceMapper;
import com.ruoyi.account.pojo.sales.AccountInvoiceApplication;
import com.ruoyi.account.pojo.sales.AccountSalesInvoice;
import com.ruoyi.account.service.sales.AccountInvoiceApplicationService;
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--开票申请 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 01:38:32
 */
@Service
@RequiredArgsConstructor
public class AccountInvoiceApplicationServiceImpl extends ServiceImpl<AccountInvoiceApplicationMapper, AccountInvoiceApplication> implements AccountInvoiceApplicationService {
    private final AccountInvoiceApplicationMapper accountInvoiceApplicationMapper;
    private final AccountSalesInvoiceMapper accountSalesInvoiceMapper;
    private final StorageAttachmentMapper storageAttachmentMapper;
    private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmss");
    @Override
    public IPage<AccountInvoiceApplicationVo> listPageAccountInvoiceApplication(Page page, AccountInvoiceApplicationDto accountInvoiceApplicationDto) {
        return accountInvoiceApplicationMapper.listPageAccountInvoiceApplication(page, accountInvoiceApplicationDto);
    }
    @Override
    public List<SalesOutboundVo> getOutboundBatchesByCustomer(Integer customerId) {
        return accountInvoiceApplicationMapper.getOutboundBatchesByCustomer(customerId);
    }
    @Override
    public boolean addAccountInvoiceApplication(AccountInvoiceApplication accountInvoiceApplication) {
        if (StringUtils.isEmpty(accountInvoiceApplication.getInvoiceApplicationNo())) {
            accountInvoiceApplication.setInvoiceApplicationNo(genInvoiceApplicationNo());
        }
        String stockOutRecordIds = accountInvoiceApplication.getStockOutRecordIds();
        if (stockOutRecordIds != null && !stockOutRecordIds.isEmpty()) {
            List<Long> ids = Arrays.stream(stockOutRecordIds.split(","))
                    .map(Long::valueOf)
                    .toList();
            if (accountInvoiceApplicationMapper.existsByStockOutRecordId(ids)){
                throw new ServiceException("存在重复的出库单");
            }
        }
        return save(accountInvoiceApplication);
    }
    @Override
    public void exportAccountInvoiceApplication(HttpServletResponse response, AccountInvoiceApplicationDto accountInvoiceApplicationDto) {
        List<AccountInvoiceApplicationVo> list = accountInvoiceApplicationMapper.listPageAccountInvoiceApplication(new Page(1,-1),accountInvoiceApplicationDto).getRecords();
        ExcelUtil<AccountInvoiceApplicationVo> util = new ExcelUtil<>(AccountInvoiceApplicationVo.class);
        util.exportExcel(response, list , "开票申请");
    }
    @Override
    public boolean deleteAccountInvoiceApplication(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            throw new ServiceException("删除失败,请选择要删除的数据");
        }
        //删除相关附件
        List<AccountSalesInvoice> accountSalesInvoices = accountSalesInvoiceMapper.selectList(Wrappers.<AccountSalesInvoice>lambdaQuery().in(AccountSalesInvoice::getAccountInvoiceApplicationId,ids));
        storageAttachmentMapper.deleteBatchIds(accountSalesInvoices.stream().map(AccountSalesInvoice::getStorageAttachmentId).toList());
        //删除相关的销项发票
        accountSalesInvoiceMapper.delete(Wrappers.<AccountSalesInvoice>lambdaQuery().in(AccountSalesInvoice::getAccountInvoiceApplicationId,ids));
        //删除开票申请
        return removeBatchByIds(ids);
    }
    private String genInvoiceApplicationNo() {
        return "KP" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10);
    }
}
src/main/java/com/ruoyi/account/service/impl/sales/AccountSalesCollectionServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
package com.ruoyi.account.service.impl.sales;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
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.account.bean.dto.sales.AccountSalesCollectionDto;
import com.ruoyi.account.bean.vo.sales.AccountSalesCollectionVo;
import com.ruoyi.account.bean.vo.sales.SalesOutboundVo;
import com.ruoyi.account.mapper.AccountStatementDetailsMapper;
import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
import com.ruoyi.account.pojo.AccountStatementDetails;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.ruoyi.account.service.sales.AccountSalesCollectionService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--收款单 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:49:56
 */
@Service
@RequiredArgsConstructor
public class AccountSalesCollectionServiceImpl extends ServiceImpl<AccountSalesCollectionMapper, AccountSalesCollection> implements AccountSalesCollectionService {
    private final AccountSalesCollectionMapper accountSalesCollectionMapper;
    private final AccountStatementDetailsMapper accountStatementDetailsMapper;
    private static final DateTimeFormatter CODE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMddHHmmss");
    @Override
    public IPage<AccountSalesCollectionVo> listPageAccountSalesCollection(Page page, AccountSalesCollectionDto accountSalesCollectionDto) {
        return accountSalesCollectionMapper.listPageAccountSalesCollection(page, accountSalesCollectionDto);
    }
    @Override
    public boolean addAccountSalesCollection(AccountSalesCollection accountSalesCollection) {
        if (StringUtils.isEmpty(accountSalesCollection.getCollectionNumber())) {
            accountSalesCollection.setCollectionNumber(genAccountSalesCollectionNo());
        }
        String stockOutRecordIds = accountSalesCollection.getStockOutRecordIds();
        if (stockOutRecordIds != null && !stockOutRecordIds.isEmpty()) {
            List<Long> ids = Arrays.stream(stockOutRecordIds.split(","))
                    .map(Long::valueOf)
                    .toList();
            if (accountSalesCollectionMapper.existsByStockOutRecordId(ids)){
                throw new ServiceException("存在重复的出库单");
            }
        }
        return save(accountSalesCollection);
    }
    @Override
    public void exportAccountSalesCollection(HttpServletResponse response, AccountSalesCollectionDto accountSalesCollectionDto) {
        List<AccountSalesCollectionVo> list = accountSalesCollectionMapper.listPageAccountSalesCollection(new Page(1,-1),accountSalesCollectionDto).getRecords();
        ExcelUtil<AccountSalesCollectionVo> util = new ExcelUtil<>(AccountSalesCollectionVo.class);
        util.exportExcel(response, list , "收款单");
    }
    @Override
    public boolean deleteAccountSalesCollection(List<Long> ids) {
        //如果该收款单已经生成对账单则无法删除
        List<AccountSalesCollection> accountSalesCollections = accountSalesCollectionMapper.selectByIds(ids);
        List<String> strings = accountSalesCollections.stream().map(AccountSalesCollection::getCollectionNumber).toList();
        List<AccountStatementDetails> accountStatementDetails = accountStatementDetailsMapper.selectList(Wrappers.<AccountStatementDetails>lambdaQuery()
                .in(AccountStatementDetails::getReceiptNumber, strings));
        if (CollectionUtils.isNotEmpty(accountStatementDetails)){
            throw new ServiceException("该收款单已经生成对账单,无法删除");
        }
        return removeByIds(ids);
    }
    @Override
    public List<SalesOutboundVo> getOutboundBatchesByCustomer(Integer customerId) {
        return accountSalesCollectionMapper.getOutboundBatchesByCustomer(customerId);
    }
    private String genAccountSalesCollectionNo() {
        return "SK" + LocalDateTime.now().format(CODE_TIME_FORMATTER) + new Random().nextInt(10);
    }
}
src/main/java/com/ruoyi/account/service/impl/sales/AccountSalesInvoiceServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package com.ruoyi.account.service.impl.sales;
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.bean.dto.sales.AccountSalesInvoiceDto;
import com.ruoyi.account.bean.vo.sales.AccountSalesInvoiceVo;
import com.ruoyi.account.mapper.sales.AccountSalesInvoiceMapper;
import com.ruoyi.account.pojo.sales.AccountSalesInvoice;
import com.ruoyi.account.service.sales.AccountSalesInvoiceService;
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
import com.ruoyi.common.utils.poi.ExcelUtil;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--销项发票 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:10:20
 */
@Service
@RequiredArgsConstructor
public class AccountSalesInvoiceServiceImpl extends ServiceImpl<AccountSalesInvoiceMapper, AccountSalesInvoice> implements AccountSalesInvoiceService {
    private final AccountSalesInvoiceMapper accountSalesInvoiceMapper;
    private final StorageAttachmentMapper storageAttachmentMapper;
    @Override
    public IPage<AccountSalesInvoiceVo> listPageAccountSalesInvoice(Page page, AccountSalesInvoiceDto accountSalesInvoiceDto) {
        return accountSalesInvoiceMapper.listPageAccountSalesInvoice(page, accountSalesInvoiceDto);
    }
    @Override
    public void exportAccountSalesInvoice(HttpServletResponse response, AccountSalesInvoiceDto accountSalesInvoiceDto) {
        List<AccountSalesInvoiceVo> list = accountSalesInvoiceMapper.listPageAccountSalesInvoice(new Page(1,-1),accountSalesInvoiceDto).getRecords();
        ExcelUtil<AccountSalesInvoiceVo> util = new ExcelUtil<>(AccountSalesInvoiceVo.class);
        util.exportExcel(response, list , "销项发票");
    }
    @Override
    public boolean deleteAccountSalesInvoice(List<Long> ids) {
        List<AccountSalesInvoice> accountSalesInvoices = accountSalesInvoiceMapper.selectByIds(ids);
        //删除附件
        storageAttachmentMapper.deleteBatchIds(accountSalesInvoices.stream().map(AccountSalesInvoice::getStorageAttachmentId).toList());
        return removeBatchByIds(ids);
    }
}
src/main/java/com/ruoyi/account/service/purchase/AccountPaymentApplicationService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.ruoyi.account.service.purchase;
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.account.bean.dto.purchase.AccountPaymentApplicationDto;
import com.ruoyi.account.bean.vo.purchase.AccountPaymentApplicationVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo;
import com.ruoyi.account.pojo.purchase.AccountPaymentApplication;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--付款申请 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 03:44:22
 */
public interface AccountPaymentApplicationService extends IService<AccountPaymentApplication> {
    IPage<AccountPaymentApplicationVo> listPageAccountPaymentApplication(Page page, AccountPaymentApplicationDto accountPaymentApplicationDto);
    List<PurchaseInboundVo> getInboundBatchesBySupplier(Integer supplierId);
    boolean addAccountPaymentApplication(AccountPaymentApplication accountPaymentApplication);
    boolean deleteAccountPaymentApplication(List<Long> ids);
    void exportAccountPaymentApplication(HttpServletResponse response, AccountPaymentApplicationDto accountPaymentApplicationDto);
}
src/main/java/com/ruoyi/account/service/purchase/AccountPurchaseInvoiceService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package com.ruoyi.account.service.purchase;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.purchase.AccountPurchaseInvoiceDto;
import com.ruoyi.account.bean.vo.purchase.AccountPurchaseInvoiceVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo;
import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice;
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--进项发票 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 03:06:17
 */
public interface AccountPurchaseInvoiceService extends IService<AccountPurchaseInvoice> {
    IPage<AccountPurchaseInvoiceVo> listPageAccountPurchaseInvoice(Page page, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto);
    boolean deleteAccountPurchaseInvoice(List<Long> ids);
    void exportAccountPurchaseInvoice(HttpServletResponse response, AccountPurchaseInvoiceDto accountPurchaseInvoiceDto);
    List<PurchaseInboundVo> getInboundBatchesBySupplier(Integer supplierId);
}
src/main/java/com/ruoyi/account/service/purchase/AccountPurchasePaymentService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package com.ruoyi.account.service.purchase;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.purchase.AccountPurchasePaymentDto;
import com.ruoyi.account.bean.vo.purchase.AccountPurchasePaymentVo;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--付款单 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-19 04:14:51
 */
public interface AccountPurchasePaymentService extends IService<AccountPurchasePayment> {
    IPage<AccountPurchasePaymentVo> listPageAccountPurchasePayment(Page page, AccountPurchasePaymentDto accountPurchasePaymentDto);
    boolean addAccountPurchasePayment(AccountPurchasePayment accountPurchasePayment);
    void exportAccountPurchasePayment(HttpServletResponse response, AccountPurchasePaymentDto accountPurchasePaymentDto);
    boolean deleteAccountPurchasePayment(List<Long> ids);
}
src/main/java/com/ruoyi/account/service/sales/AccountInvoiceApplicationService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.ruoyi.account.service.sales;
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.account.bean.dto.sales.AccountInvoiceApplicationDto;
import com.ruoyi.account.bean.vo.sales.AccountInvoiceApplicationVo;
import com.ruoyi.account.bean.vo.sales.SalesOutboundVo;
import com.ruoyi.account.pojo.sales.AccountInvoiceApplication;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--开票申请 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 01:38:32
 */
public interface AccountInvoiceApplicationService extends IService<AccountInvoiceApplication> {
    IPage<AccountInvoiceApplicationVo> listPageAccountInvoiceApplication(Page page, AccountInvoiceApplicationDto accountInvoiceApplicationDto);
    List<SalesOutboundVo> getOutboundBatchesByCustomer(Integer customerId);
    boolean addAccountInvoiceApplication(AccountInvoiceApplication accountInvoiceApplication);
    void exportAccountInvoiceApplication(HttpServletResponse response, AccountInvoiceApplicationDto accountInvoiceApplicationDto);
    boolean deleteAccountInvoiceApplication(List<Long> ids);
}
src/main/java/com/ruoyi/account/service/sales/AccountSalesCollectionService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.ruoyi.account.service.sales;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.sales.AccountSalesCollectionDto;
import com.ruoyi.account.bean.vo.sales.AccountSalesCollectionVo;
import com.ruoyi.account.bean.vo.sales.SalesOutboundVo;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--收款单 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:49:56
 */
public interface AccountSalesCollectionService extends IService<AccountSalesCollection> {
    IPage<AccountSalesCollectionVo> listPageAccountSalesCollection(Page page, AccountSalesCollectionDto accountSalesCollectionDto);
    boolean addAccountSalesCollection(AccountSalesCollection accountSalesCollection);
    void exportAccountSalesCollection(HttpServletResponse response, AccountSalesCollectionDto accountSalesCollectionDto);
    boolean deleteAccountSalesCollection(List<Long> ids);
    List<SalesOutboundVo> getOutboundBatchesByCustomer(Integer customerId);
}
src/main/java/com/ruoyi/account/service/sales/AccountSalesInvoiceService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.account.service.sales;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.sales.AccountSalesInvoiceDto;
import com.ruoyi.account.bean.vo.sales.AccountSalesInvoiceVo;
import com.ruoyi.account.pojo.sales.AccountSalesInvoice;
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * <p>
 * è´¢åŠ¡ç®¡ç†--销项发票 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:10:20
 */
public interface AccountSalesInvoiceService extends IService<AccountSalesInvoice> {
    IPage<AccountSalesInvoiceVo> listPageAccountSalesInvoice(Page page, AccountSalesInvoiceDto accountSalesInvoiceDto);
    void exportAccountSalesInvoice(HttpServletResponse response, AccountSalesInvoiceDto accountSalesInvoiceDto);
    boolean deleteAccountSalesInvoice(List<Long> ids);
}
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesNearExpiryController.java
@@ -7,7 +7,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
@@ -36,9 +36,9 @@
    @PostMapping("/add")
    @Operation(summary = "新增临期售后")
    @Log(title = "新增临期售后", businessType = BusinessType.INSERT)
    public R<?> add(@RequestBody AfterSalesNearExpiry entity) {
    public AjaxResult add(@RequestBody AfterSalesNearExpiry entity) {
        afterSalesNearExpiryService.add(entity);
        return R.ok(null, "添加成功");
        return AjaxResult.success("添加成功");
    }
    /**
@@ -47,9 +47,9 @@
    @PostMapping("/update")
    @Operation(summary = "更新临期售后")
    @Log(title = "更新临期售后", businessType = BusinessType.UPDATE)
    public R<?> update(@RequestBody AfterSalesNearExpiry entity) {
    public AjaxResult update(@RequestBody AfterSalesNearExpiry entity) {
        afterSalesNearExpiryService.update(entity);
        return R.ok(null, "更新成功");
        return AjaxResult.success("更新成功");
    }
    /**
@@ -58,9 +58,9 @@
    @DeleteMapping("/delete")
    @Operation(summary = "删除临期售后")
    @Log(title = "删除临期售后", businessType = BusinessType.DELETE)
    public R<?> delete(Long[] ids) {
    public AjaxResult delete(Long[] ids) {
        afterSalesNearExpiryService.delete(ids);
        return R.ok(null, "删除成功");
        return AjaxResult.success("删除成功");
    }
    /**
@@ -69,9 +69,9 @@
    @GetMapping("/listPage")
    @Operation(summary = "分页查询临期售后")
    @Log(title = "分页查询临期售后", businessType = BusinessType.OTHER)
    public R<?> listPage(Page<AfterSalesNearExpiry> page, AfterSalesNearExpiry entity) {
    public AjaxResult listPage(Page<AfterSalesNearExpiry> page, AfterSalesNearExpiry entity) {
        IPage<AfterSalesNearExpiry> listPage = afterSalesNearExpiryService.listPage(page, entity);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
}
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesServiceController.java
@@ -10,7 +10,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.sales.dto.SalesLedgerDto;
@@ -45,9 +45,9 @@
    @GetMapping("/listPage")
    @Operation(summary = "售后服务-分页查询")
    @Log(title = "售后服务-分页查询", businessType = BusinessType.OTHER)
    public R<?> listPage(Page page, AfterSalesServiceNewDto afterSalesService) {
    public AjaxResult listPage(Page page, AfterSalesServiceNewDto afterSalesService) {
        IPage<AfterSalesServiceNewDto> listPage = afterSalesServiceService.listPage(page, afterSalesService);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
    @Log(title = "售后服务-反馈登记", businessType = BusinessType.EXPORT)
@@ -85,14 +85,14 @@
    @PostMapping("/add")
    @Operation(summary = "售后服务-新增")
    @Log(title = "售后服务-新增", businessType = BusinessType.INSERT)
    public R<?> add(@RequestBody AfterSalesServiceNewDto afterSalesServiceNewDto) {
        return afterSalesServiceService.addAfterSalesServiceDto(afterSalesServiceNewDto) ? R.ok() : R.fail();
    public AjaxResult add(@RequestBody AfterSalesServiceNewDto afterSalesServiceNewDto) {
        return afterSalesServiceService.addAfterSalesServiceDto(afterSalesServiceNewDto) ? AjaxResult.success() : AjaxResult.error();
    }
    @PostMapping("/update")
    @Operation(summary = "售后服务-修改")
    @Log(title = "售后服务-修改", businessType = BusinessType.UPDATE)
    public R<?> update(@RequestBody AfterSalesServiceNewDto afterSalesServiceNewDto) {
    public AjaxResult update(@RequestBody AfterSalesServiceNewDto afterSalesServiceNewDto) {
        if (afterSalesServiceNewDto.getProductModelIdList() != null && afterSalesServiceNewDto.getProductModelIdList().isEmpty() ) {
            String productModelIds = afterSalesServiceNewDto.getProductModelIdList().stream()
                    .map(String::valueOf)
@@ -100,24 +100,24 @@
            afterSalesServiceNewDto.setProductModelIds(productModelIds);
        }
        boolean update = afterSalesServiceService.updateById(afterSalesServiceNewDto);
        return update ? R.ok() : R.fail();
        return update ? AjaxResult.success() : AjaxResult.error();
    }
    @DeleteMapping("/delete")
    @Operation(summary = "售后服务-删除")
    @Log(title = "售后服务-删除", businessType = BusinessType.DELETE)
    public R<?> delete(@RequestBody List<Long> ids) {
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return R.fail("请传入要删除的ID");
            return AjaxResult.error("请传入要删除的ID");
        }
        boolean delete = afterSalesServiceService.removeByIds(ids);
        return delete ? R.ok() : R.fail();
        return delete ? AjaxResult.success() : AjaxResult.error();
    }
    @PostMapping("/dispose")
    @Operation(summary = "售后服务-处理")
    @Log(title = "售后服务-处理", businessType = BusinessType.UPDATE)
    public R<?> dispose(@RequestBody AfterSalesService afterSalesService) {
    public AjaxResult dispose(@RequestBody AfterSalesService afterSalesService) {
        AfterSalesService byId = afterSalesServiceService.getById(afterSalesService.getId());
        if(byId == null) throw new RuntimeException("未找到该数据");
        if(byId.getStatus().equals(2)) throw new RuntimeException("该数据已处理");
@@ -126,28 +126,28 @@
        afterSalesService.setDisposeNickName(sysUser.getNickName());
        afterSalesService.setStatus(2);
        boolean update = afterSalesServiceService.updateById(afterSalesService);
        return update ? R.ok() : R.fail();
        return update ? AjaxResult.success() : AjaxResult.error();
    }
    @GetMapping("listSalesLedger")
    @Operation(summary = "售后服务-获取销售台账")
    public R<?> listSalesLedger(SalesLedgerDto salesLedgerDto, Page page) {
    public AjaxResult listSalesLedger(SalesLedgerDto salesLedgerDto, Page page) {
        IPage<SalesLedgerDto> list = salesLedgerService.listSalesLedger(salesLedgerDto,page);
        return R.ok(list);
        return AjaxResult.success(list);
    }
    @GetMapping("getById")
    @Operation(summary = "售后服务-根据id获取详情")
    public R<?> getById(Long id) {
        return R.ok(afterSalesServiceService.getAfterSalesServiceNewDtoById(id));
    public AjaxResult getById(Long id) {
        return AjaxResult.success(afterSalesServiceService.getAfterSalesServiceNewDtoById(id));
    }
    @Operation(summary = "售后服务-统计工单情况")
    @GetMapping("count")
    public R<?> count() {
        return R.ok(afterSalesServiceService.countAfterSalesService());
    public AjaxResult count() {
        return AjaxResult.success(afterSalesServiceService.countAfterSalesService());
    }
}
src/main/java/com/ruoyi/aftersalesservice/controller/AfterSalesServiceFileController.java
@@ -6,7 +6,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
@@ -34,24 +34,24 @@
    @PostMapping("/upload")
    @Operation(summary = "售后服务-文件上传")
    @Log(title = "售后服务-文件上传", businessType = BusinessType.INSERT)
    public R<?> fileUpload(@RequestParam("file") MultipartFile file,
    public AjaxResult fileUpload(@RequestParam("file") MultipartFile file,
                                 @RequestParam("id") Long afterSalesServiceId) {
        afterSalesServiceFileService.fileUpload(file, afterSalesServiceId);
        return R.ok(null, "上传成功");
        return AjaxResult.success("上传成功");
    }
    @GetMapping("/listPage")
    @Operation(summary = "售后处理-售后附件列表")
    @Log(title = "售后处理-售后附件列表", businessType = BusinessType.OTHER)
    public R<?> fileList(Page<AfterSalesServiceFile> page, Long afterSalesServiceId) {
        return R.ok(afterSalesServiceFileService.fileList(page, afterSalesServiceId));
    public AjaxResult fileList(Page<AfterSalesServiceFile> page, Long afterSalesServiceId) {
        return AjaxResult.success(afterSalesServiceFileService.fileList(page, afterSalesServiceId));
    }
    @DeleteMapping("/del/{fileId}")
    @Operation(summary = "售后处理-删除附件")
    @Log(title = "售后处理-删除附件", businessType = BusinessType.DELETE)
    public R<?> delFile(@PathVariable Long fileId) {
    public AjaxResult delFile(@PathVariable Long fileId) {
        afterSalesServiceFileService.delFile(fileId);
        return R.ok(null, "删除成功!");
        return AjaxResult.success("删除成功!");
    }
}
src/main/java/com/ruoyi/aftersalesservice/pojo/AfterSalesService.java
@@ -115,7 +115,6 @@
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
src/main/java/com/ruoyi/aftersalesservice/service/impl/AfterSalesServiceServiceImpl.java
@@ -11,6 +11,8 @@
import com.ruoyi.aftersalesservice.service.AfterSalesServiceService;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import java.time.LocalDateTime;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysUser;
@@ -69,7 +71,7 @@
        if(sysUser == null) throw new RuntimeException("审核人不存在");
        afterSalesServiceNewDto.setCheckNickName(sysUser.getNickName());
        if (StringUtils.isEmpty(afterSalesServiceNewDto.getAfterSalesServiceNo())) {
            String string = OrderUtils.countAfterServiceTodayByCreateTime(afterSalesServiceMapper, "SH_");
            String string = OrderUtils.countAfterServiceTodayByCreateTime(afterSalesServiceMapper, "SH_", afterSalesServiceNewDto.getCreateTime() != null ? afterSalesServiceNewDto.getCreateTime() : LocalDateTime.now());
            afterSalesServiceNewDto.setAfterSalesServiceNo(string);
        }
        return this.save(afterSalesServiceNewDto);
src/main/java/com/ruoyi/ai/assistant/FinancialAgent.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
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 reactor.core.publisher.Flux;
import static dev.langchain4j.service.spring.AiServiceWiringMode.EXPLICIT;
@AiService(
        wiringMode = EXPLICIT,
        streamingChatModel = "qwenStreamingChatModel",
        chatMemoryProvider = "chatMemoryProviderFinancial",
        tools = "financialAgentTools"
)
public interface FinancialAgent {
    @SystemMessage(fromResource = "financial-agent-prompt.txt")
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("currentDate") String currentDate);
}
src/main/java/com/ruoyi/ai/assistant/FinancialIntentExecutor.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,284 @@
package com.ruoyi.ai.assistant;
import com.ruoyi.ai.tools.FinancialAgentTools;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Component
public class FinancialIntentExecutor {
    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final Pattern LIMIT_PATTERN = Pattern.compile("(?:前|最近|展示|返回)?\\s*(\\d{1,2})\\s*(?:条|个|名)");
    private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
    private static final Pattern RELATIVE_DAY_PATTERN = Pattern.compile("(?:近|最近)\\s*(\\d{1,3})\\s*天");
    private static final Pattern FUTURE_MONTH_PATTERN = Pattern.compile("(?:未来|后续|接下来)\\s*(\\d{1,2})\\s*(?:个)?月");
    private final FinancialAgentTools financialAgentTools;
    public FinancialIntentExecutor(FinancialAgentTools financialAgentTools) {
        this.financialAgentTools = financialAgentTools;
    }
    public String tryExecute(String memoryId, String message) {
        if (!StringUtils.hasText(message)) {
            return null;
        }
        String text = message.trim();
        String quickPromptResponse = tryExecuteQuickPrompt(memoryId, text);
        if (StringUtils.hasText(quickPromptResponse)) {
            return quickPromptResponse;
        }
        DateRange dateRange = extractDateRange(text);
        Integer limit = extractLimit(text);
        String keyword = extractKeyword(text);
        String startDate = dateRange.startDate();
        String endDate = dateRange.endDate();
        String timeRange = dateRange.label();
        if (containsAny(text, "成本核算", "产品成本", "工序成本", "人工成本", "折旧", "材料损耗", "成本最高")) {
            return financialAgentTools.calculateIntelligentCost(memoryId, startDate, endDate, timeRange, keyword, limit);
        }
        if (containsAny(text, "利润分析", "订单利润", "亏损订单", "低利润", "最赚钱客户", "哪个客户最赚钱",
                "客户最赚钱", "利润最高客户", "利润贡献最高", "利润下降")) {
            return financialAgentTools.analyzeOrderProfit(memoryId, startDate, endDate, timeRange, keyword, limit);
        }
        if (containsAny(text, "库存资金", "库存积压", "呆滞库存", "资金占用", "周转率", "库存周转")) {
            return financialAgentTools.analyzeInventoryCapital(memoryId, startDate, endDate, timeRange, keyword, limit);
        }
        if (containsAny(text, "现金流", "回款风险", "付款压力", "资金缺口", "应收", "应付", "回款预测")) {
            return financialAgentTools.forecastCashFlow(memoryId, startDate, endDate, timeRange, extractForecastMonths(text));
        }
        if (containsAny(text, "异常预警", "经营异常", "风险预警", "成本异常", "利润异常", "回款异常", "订单风险")) {
            return financialAgentTools.detectBusinessAnomalies(memoryId, startDate, endDate, timeRange, limit);
        }
        if (containsAny(text, "驾驶舱", "经营看板", "经营总览", "经营仪表盘", "经营大盘")) {
            return financialAgentTools.getBusinessCockpit(memoryId, startDate, endDate, timeRange);
        }
        if (containsAny(text, "日报", "周报", "经营报告", "分析报告")) {
            return financialAgentTools.generateOperationReport(memoryId, startDate, endDate, timeRange,
                    containsAny(text, "周报") ? "weekly" : "daily");
        }
        if (containsAny(text, "业财融合", "业财联动", "口径", "指标解释", "为什么")) {
            return financialAgentTools.retrieveFinancialKnowledge(memoryId, text);
        }
        return null;
    }
    private String tryExecuteQuickPrompt(String memoryId, String text) {
        String normalized = normalizeForMatch(text);
        if ("生成本周经营周报利润与现金流".equals(normalized) || "生成本周经营周报".equals(normalized) || "生成周报".equals(normalized)) {
            DateRange range = weekRange();
            return financialAgentTools.generateOperationReport(memoryId, range.startDate(), range.endDate(), range.label(), "weekly");
        }
        if ("分析本月利润下降原因".equals(normalized)) {
            DateRange range = monthRange();
            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
        }
        if ("近30天哪个客户利润贡献最高".equals(normalized)) {
            DateRange range = recentDaysRange(30);
            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
        }
        if ("查看本月经营驾驶舱".equals(normalized) || "查看经营驾驶舱".equals(normalized)) {
            DateRange range = monthRange();
            return financialAgentTools.getBusinessCockpit(memoryId, range.startDate(), range.endDate(), range.label());
        }
        if ("查询近30天亏损订单".equals(normalized)) {
            DateRange range = recentDaysRange(30);
            return financialAgentTools.analyzeOrderProfit(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
        }
        if ("分析近30天库存资金占用".equals(normalized)) {
            DateRange range = recentDaysRange(30);
            return financialAgentTools.analyzeInventoryCapital(memoryId, range.startDate(), range.endDate(), range.label(), null, null);
        }
        if ("预测未来3个月现金流".equals(normalized)) {
            return financialAgentTools.forecastCashFlow(memoryId, null, null, null, 3);
        }
        if ("哪个工序成本最高".equals(normalized)) {
            return financialAgentTools.calculateIntelligentCost(memoryId, null, null, null, null, null);
        }
        return null;
    }
    private boolean containsAny(String text, String... keywords) {
        for (String keyword : keywords) {
            if (text.contains(keyword)) {
                return true;
            }
        }
        return false;
    }
    private Integer extractLimit(String text) {
        Matcher matcher = LIMIT_PATTERN.matcher(text);
        return matcher.find() ? Integer.parseInt(matcher.group(1)) : null;
    }
    private Integer extractForecastMonths(String text) {
        Matcher matcher = FUTURE_MONTH_PATTERN.matcher(text);
        return matcher.find() ? Integer.parseInt(matcher.group(1)) : null;
    }
    private DateRange extractDateRange(String text) {
        Matcher matcher = DATE_PATTERN.matcher(text);
        if (matcher.find()) {
            String first = matcher.group(1);
            String second = matcher.find() ? matcher.group(1) : first;
            return buildDateRange(first, second, first + "至" + second);
        }
        if (text.contains("本月")) {
            return monthRange();
        }
        if (text.contains("上月")) {
            return lastMonthRange();
        }
        if (text.contains("今年") || text.contains("本年")) {
            return yearRange();
        }
        if (text.contains("本周")) {
            return weekRange();
        }
        Matcher relativeDayMatcher = RELATIVE_DAY_PATTERN.matcher(text);
        if (relativeDayMatcher.find()) {
            int days = Integer.parseInt(relativeDayMatcher.group(1));
            return recentDaysRange(days);
        }
        return new DateRange(null, null, null);
    }
    private DateRange buildDateRange(String start, String end, String label) {
        LocalDate startDate = parseDate(start);
        LocalDate endDate = parseDate(end);
        if (startDate == null || endDate == null) {
            return new DateRange(null, null, null);
        }
        if (startDate.isAfter(endDate)) {
            LocalDate temp = startDate;
            startDate = endDate;
            endDate = temp;
        }
        return new DateRange(formatDate(startDate), formatDate(endDate), label);
    }
    private DateRange recentDaysRange(int days) {
        LocalDate end = LocalDate.now();
        int safeDays = Math.max(days, 1);
        LocalDate start = end.minusDays(safeDays - 1L);
        return new DateRange(formatDate(start), formatDate(end), "近" + safeDays + "天");
    }
    private DateRange monthRange() {
        LocalDate today = LocalDate.now();
        return new DateRange(formatDate(today.withDayOfMonth(1)), formatDate(today), "本月");
    }
    private DateRange weekRange() {
        LocalDate today = LocalDate.now();
        LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L);
        return new DateRange(formatDate(start), formatDate(today), "本周");
    }
    private DateRange lastMonthRange() {
        YearMonth lastMonth = YearMonth.now().minusMonths(1);
        return new DateRange(formatDate(lastMonth.atDay(1)), formatDate(lastMonth.atEndOfMonth()), "上月");
    }
    private DateRange yearRange() {
        LocalDate today = LocalDate.now();
        return new DateRange(formatDate(today.withDayOfYear(1)), formatDate(today), "今年");
    }
    private LocalDate parseDate(String text) {
        try {
            return LocalDate.parse(text, DATE_FMT);
        } catch (Exception ignored) {
            return null;
        }
    }
    private String formatDate(LocalDate date) {
        return date == null ? null : date.format(DATE_FMT);
    }
    private String normalizeForMatch(String text) {
        if (!StringUtils.hasText(text)) {
            return "";
        }
        return text.replace("(", "")
                .replace(")", "")
                .replace("(", "")
                .replace(")", "")
                .replace(",", "")
                .replace(",", "")
                .replace("。", "")
                .replace(".", "")
                .replace("!", "")
                .replace("!", "")
                .replace("?", "")
                .replace("?", "")
                .replace(":", "")
                .replace(":", "")
                .replace(";", "")
                .replace(";", "")
                .replace(" ", "")
                .trim();
    }
    private String extractKeyword(String text) {
        String cleaned = text
                .replaceAll("\\d{4}-\\d{2}-\\d{2}", "")
                .replaceAll("(?:近|最近)\\s*\\d{1,3}\\s*天", "")
                .replaceAll("(?:前|最近|展示|返回)?\\s*\\d{1,2}\\s*(?:条|个|名)", "")
                .replace("查询", "")
                .replace("查看", "")
                .replace("看下", "")
                .replace("看看", "")
                .replace("帮我", "")
                .replace("请", "")
                .replace("一个", "")
                .replace("为什么", "")
                .replace("哪个客户最赚钱", "")
                .replace("最近哪个客户最赚钱", "")
                .replace("本月哪个客户最赚钱", "")
                .replace("近30天哪个客户最赚钱", "")
                .replace("最赚钱客户", "")
                .replace("客户最赚钱", "")
                .replace("哪个客户利润最高", "")
                .replace("利润最高客户", "")
                .replace("哪个客户利润贡献最高", "")
                .replace("利润贡献最高客户", "")
                .replace("本月", "")
                .replace("本周", "")
                .replace("本年", "")
                .replace("今年", "")
                .replace("上月", "")
                .replace("近30天", "")
                .replace("近7天", "")
                .replace("近90天", "")
                .replace("前10条", "")
                .replace("最近10条", "")
                .replace("前20条", "")
                .replace("最近20条", "")
                .replace("订单利润分析", "")
                .replace("利润分析", "")
                .replace("库存资金分析", "")
                .replace("现金流预测", "")
                .replace("经营驾驶舱", "")
                .replace("日报", "")
                .replace("周报", "")
                .replace("异常预警", "")
                .replace("条", "")
                .trim();
        return cleaned.length() >= 2 ? cleaned : null;
    }
    private record DateRange(String startDate, String endDate, String label) {
    }
}
src/main/java/com/ruoyi/ai/config/FinancialAgentConfig.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 FinancialAgentConfig {
    @Bean
    ChatMemoryProvider chatMemoryProviderFinancial(MongoChatMemoryStore mongoChatMemoryStore) {
        return memoryId -> MessageWindowChatMemory.builder()
                .id(memoryId)
                .maxMessages(40)
                .chatMemoryStore(mongoChatMemoryStore)
                .build();
    }
}
src/main/java/com/ruoyi/ai/context/AiSessionUserContext.java
@@ -4,6 +4,8 @@
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -11,18 +13,26 @@
public class AiSessionUserContext {
    private final Map<String, LoginUser> loginUserByMemoryId = new ConcurrentHashMap<>();
    private final Map<String, Instant> lastAccessTimeByMemoryId = new ConcurrentHashMap<>();
    private static final Duration SESSION_TIMEOUT = Duration.ofHours(24);
    public void bind(String memoryId, LoginUser loginUser) {
        if (!StringUtils.hasText(memoryId) || loginUser == null) {
            return;
        }
        loginUserByMemoryId.put(memoryId, loginUser);
        lastAccessTimeByMemoryId.put(memoryId, Instant.now());
    }
    public LoginUser get(String memoryId) {
        if (!StringUtils.hasText(memoryId)) {
            return null;
        }
        if (isExpired(memoryId)) {
            remove(memoryId);
            return null;
        }
        lastAccessTimeByMemoryId.put(memoryId, Instant.now());
        return loginUserByMemoryId.get(memoryId);
    }
@@ -31,5 +41,25 @@
            return;
        }
        loginUserByMemoryId.remove(memoryId);
        lastAccessTimeByMemoryId.remove(memoryId);
    }
    public void cleanExpiredSessions() {
        Instant now = Instant.now();
        lastAccessTimeByMemoryId.entrySet().removeIf(entry -> {
            boolean expired = Duration.between(entry.getValue(), now).compareTo(SESSION_TIMEOUT) > 0;
            if (expired) {
                loginUserByMemoryId.remove(entry.getKey());
            }
            return expired;
        });
    }
    private boolean isExpired(String memoryId) {
        Instant lastAccess = lastAccessTimeByMemoryId.get(memoryId);
        if (lastAccess == null) {
            return true;
        }
        return Duration.between(lastAccess, Instant.now()).compareTo(SESSION_TIMEOUT) > 0;
    }
}
src/main/java/com/ruoyi/ai/controller/FinancialAiController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
package com.ruoyi.ai.controller;
import com.ruoyi.ai.assistant.FinancialAgent;
import com.ruoyi.ai.assistant.FinancialIntentExecutor;
import com.ruoyi.ai.bean.ChatForm;
import com.ruoyi.ai.context.AiSessionUserContext;
import com.ruoyi.ai.service.AiChatSessionService;
import com.ruoyi.ai.store.MongoChatMemoryStore;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.UserMessage;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Tag(name = "财务智能体")
@RestController
@RequestMapping("/financial-ai")
public class FinancialAiController extends BaseController {
    private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai");
    private final FinancialAgent financialAgent;
    private final FinancialIntentExecutor financialIntentExecutor;
    private final AiSessionUserContext aiSessionUserContext;
    private final MongoChatMemoryStore mongoChatMemoryStore;
    private final AiChatSessionService aiChatSessionService;
    public FinancialAiController(FinancialAgent financialAgent,
                                 FinancialIntentExecutor financialIntentExecutor,
                                 AiSessionUserContext aiSessionUserContext,
                                 MongoChatMemoryStore mongoChatMemoryStore,
                                 AiChatSessionService aiChatSessionService) {
        this.financialAgent = financialAgent;
        this.financialIntentExecutor = financialIntentExecutor;
        this.aiSessionUserContext = aiSessionUserContext;
        this.mongoChatMemoryStore = mongoChatMemoryStore;
        this.aiChatSessionService = aiChatSessionService;
    }
    @Operation(summary = "财务智能体对话")
    @PostMapping(value = "/chat", produces = "text/stream;charset=utf-8")
    public Flux<String> chat(@RequestBody ChatForm chatForm) {
        if (!StringUtils.hasText(chatForm.getMemoryId())) {
            return Flux.just("memoryId不能为空");
        }
        if (!StringUtils.hasText(chatForm.getMessage())) {
            return Flux.just("message不能为空");
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        String memoryId = chatForm.getMemoryId();
        String userMessage = chatForm.getMessage();
        aiSessionUserContext.bind(memoryId, loginUser);
        aiChatSessionService.touchSession(memoryId, loginUser, userMessage);
        String directResponse = financialIntentExecutor.tryExecute(memoryId, userMessage);
        if (StringUtils.isNotEmpty(directResponse)) {
            mongoChatMemoryStore.appendMessages(
                    memoryId,
                    List.of(UserMessage.from(userMessage), AiMessage.from(directResponse))
            );
            aiChatSessionService.refreshSessionStats(memoryId, loginUser);
            return Flux.just(directResponse);
        }
        if (isBusinessDataIntent(userMessage)) {
            String noGuessResponse = "未识别到可执行的数据查询条件。为保证结果准确,当前不会推测或编造数据,请补充明确时间范围、客户、供应商或单号后再查询。";
            mongoChatMemoryStore.appendMessages(
                    memoryId,
                    List.of(UserMessage.from(userMessage), AiMessage.from(noGuessResponse))
            );
            aiChatSessionService.refreshSessionStats(memoryId, loginUser);
            return Flux.just(noGuessResponse);
        }
        return financialAgent.chat(memoryId, userMessage, currentDateForPrompt())
                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
    }
    @Operation(summary = "财务智能体会话列表")
    @GetMapping("/history/sessions")
    public AjaxResult listSessions() {
        return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
    }
    @Operation(summary = "财务智能体会话消息")
    @GetMapping("/history/messages/{memoryId}")
    public AjaxResult listMessages(@PathVariable String memoryId) {
        return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
    }
    @Operation(summary = "删除财务智能体会话")
    @DeleteMapping("/history/{memoryId}")
    public AjaxResult deleteSession(@PathVariable String memoryId) {
        aiSessionUserContext.remove(memoryId);
        return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
    }
    private String currentDateForPrompt() {
        return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT);
    }
    private boolean isBusinessDataIntent(String message) {
        if (!StringUtils.hasText(message)) {
            return false;
        }
        String text = message.trim();
        return containsAny(text,
                "查询", "查看", "统计", "分析", "建议", "成本核算", "产品成本", "工序成本",
                "订单利润", "亏损订单", "低利润", "库存资金", "库存积压", "呆滞库存",
                "现金流", "回款风险", "付款压力", "资金缺口", "应收", "应付",
                "异常预警", "经营异常", "风险预警", "驾驶舱", "经营看板", "经营总览",
                "日报", "周报", "经营报告", "分析报告", "业财融合", "口径", "指标解释");
    }
    private boolean containsAny(String text, String... keywords) {
        for (String keyword : keywords) {
            if (text.contains(keyword)) {
                return true;
            }
        }
        return false;
    }
}
src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java
@@ -10,7 +10,7 @@
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.R;
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;
@@ -89,22 +89,21 @@
    @Operation(summary = "制造会话列表")
    @GetMapping("/history/sessions")
    public R<?> listSessions() {
        return R.ok(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
    public AjaxResult listSessions() {
        return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
    }
    @Operation(summary = "制造会话消息")
    @GetMapping("/history/messages/{memoryId}")
    public R<?> listMessages(@PathVariable String memoryId) {
        return R.ok(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
    public AjaxResult listMessages(@PathVariable String memoryId) {
        return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
    }
    @Operation(summary = "删除制造会话")
    @DeleteMapping("/history/{memoryId}")
    public R<?> deleteSession(@PathVariable String memoryId) {
    public AjaxResult deleteSession(@PathVariable String memoryId) {
        aiSessionUserContext.remove(memoryId);
        aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser());
        return R.ok();
        return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
    }
    private String currentDateForPrompt() {
src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
@@ -6,10 +6,17 @@
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
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;
@@ -42,28 +49,28 @@
    @Operation(summary = "采购多文件分析确认处理")
    @PostMapping("/analyze-files/confirm")
    public R confirmAnalyzeResult(@RequestBody PurchaseAiConfirmRequest request) {
    public AjaxResult confirmAnalyzeResult(@RequestBody PurchaseAiConfirmRequest request) {
        return purchaseAiService.confirmAnalyzeResult(request);
    }
    @Operation(summary = "采购会话列表")
    @GetMapping("/history/sessions")
    public R listSessions() {
    public AjaxResult listSessions() {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        return R.ok(purchaseAiService.listSessions(loginUser));
        return success(purchaseAiService.listSessions(loginUser));
    }
    @Operation(summary = "采购会话消息")
    @GetMapping("/history/messages/{memoryId}")
    public R listMessages(@PathVariable String memoryId) {
    public AjaxResult listMessages(@PathVariable String memoryId) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        return R.ok(purchaseAiService.listMessages(memoryId, loginUser));
        return success(purchaseAiService.listMessages(memoryId, loginUser));
    }
    @Operation(summary = "删除采购会话")
    @DeleteMapping("/history/{memoryId}")
    public R deleteSession(@PathVariable String memoryId) {
    public AjaxResult deleteSession(@PathVariable String memoryId) {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        return R.ok(purchaseAiService.deleteSession(memoryId, loginUser));
        return toAjax(purchaseAiService.deleteSession(memoryId, loginUser));
    }
}
src/main/java/com/ruoyi/ai/controller/SalesAiController.java
@@ -10,7 +10,7 @@
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.R;
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;
@@ -99,22 +99,21 @@
    @Operation(summary = "销售助手会话列表")
    @GetMapping("/history/sessions")
    public R<?> listSessions() {
        return R.ok(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
    public AjaxResult listSessions() {
        return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
    }
    @Operation(summary = "销售助手会话消息")
    @GetMapping("/history/messages/{memoryId}")
    public R<?> listMessages(@PathVariable String memoryId) {
        return R.ok(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
    public AjaxResult listMessages(@PathVariable String memoryId) {
        return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
    }
    @Operation(summary = "删除销售助手会话")
    @DeleteMapping("/history/{memoryId}")
    public R<?> deleteSession(@PathVariable String memoryId) {
    public AjaxResult deleteSession(@PathVariable String memoryId) {
        aiSessionUserContext.remove(memoryId);
        aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser());
        return R.ok();
        return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
    }
    private boolean isBusinessDataIntent(String message) {
src/main/java/com/ruoyi/ai/controller/XiaozhiController.java
@@ -12,12 +12,19 @@
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.R;
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.*;
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;
@@ -151,21 +158,21 @@
    @Operation(summary = "会话列表")
    @GetMapping("/history/sessions")
    public R listSessions() {
        return R.ok(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
    public AjaxResult listSessions() {
        return success(aiChatSessionService.listCurrentUserSessions(SecurityUtils.getLoginUser()));
    }
    @Operation(summary = "会话消息")
    @GetMapping("/history/messages/{memoryId}")
    public R listMessages(@PathVariable String memoryId) {
        return R.ok(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
    public AjaxResult listMessages(@PathVariable String memoryId) {
        return success(aiChatSessionService.listCurrentUserMessages(memoryId, SecurityUtils.getLoginUser()));
    }
    @Operation(summary = "删除会话")
    @DeleteMapping("/history/{memoryId}")
    public R deleteSession(@PathVariable String memoryId) {
    public AjaxResult deleteSession(@PathVariable String memoryId) {
        aiSessionUserContext.remove(memoryId);
        return R.ok(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
        return toAjax(aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser()));
    }
    private boolean isApproveTodoBusinessIntent(String message) {
src/main/java/com/ruoyi/ai/schedule/AiSessionCleanupTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.ai.schedule;
import com.ruoyi.ai.context.AiSessionUserContext;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class AiSessionCleanupTask {
    private final AiSessionUserContext aiSessionUserContext;
    public AiSessionCleanupTask(AiSessionUserContext aiSessionUserContext) {
        this.aiSessionUserContext = aiSessionUserContext;
    }
    @Scheduled(cron = "0 0 2 * * ?")
    public void cleanupExpiredSessions() {
        try {
            aiSessionUserContext.cleanExpiredSessions();
        } catch (Exception e) {
            System.err.println("清理过期AI会话失败: " + e.getMessage());
        }
    }
}
src/main/java/com/ruoyi/ai/service/PurchaseAiService.java
@@ -18,16 +18,20 @@
import com.ruoyi.basic.service.StorageBlobService;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.R;
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.*;
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;
@@ -42,12 +46,21 @@
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.file.Files;
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.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
import java.nio.file.Files;
@Service
public class PurchaseAiService {
@@ -67,7 +80,6 @@
    private final AiFileTextExtractor aiFileTextExtractor;
    private final ObjectMapper objectMapper;
    private final IPurchaseLedgerService purchaseLedgerService;
    private final IPaymentRegistrationService paymentRegistrationService;
    private final PurchaseReturnOrdersService purchaseReturnOrdersService;
    private final StorageBlobService storageBlobService;
    private final SupplierManageMapper supplierManageMapper;
@@ -81,7 +93,6 @@
                                AiFileTextExtractor aiFileTextExtractor,
                                 ObjectMapper objectMapper,
                                 IPurchaseLedgerService purchaseLedgerService,
                                 IPaymentRegistrationService paymentRegistrationService,
                                 PurchaseReturnOrdersService purchaseReturnOrdersService,
                                 StorageBlobService storageBlobService,
                                 SupplierManageMapper supplierManageMapper,
@@ -94,7 +105,6 @@
        this.aiFileTextExtractor = aiFileTextExtractor;
        this.objectMapper = objectMapper;
        this.purchaseLedgerService = purchaseLedgerService;
        this.paymentRegistrationService = paymentRegistrationService;
        this.purchaseReturnOrdersService = purchaseReturnOrdersService;
        this.storageBlobService = storageBlobService;
        this.supplierManageMapper = supplierManageMapper;
@@ -206,24 +216,23 @@
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
    }
    public R confirmAnalyzeResult(PurchaseAiConfirmRequest request) {
    public AjaxResult confirmAnalyzeResult(PurchaseAiConfirmRequest request) {
        if (request == null || !StringUtils.hasText(request.getBusinessType())) {
            return R.fail("businessType不能为空");
            return AjaxResult.error("businessType不能为空");
        }
        if (request.getPayload() == null || request.getPayload().isEmpty()) {
            return R.fail("payload不能为空");
            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 -> R.fail("暂不支持该业务类型: " + businessType);
                default -> AjaxResult.error("暂不支持该业务类型: " + businessType);
            };
        } catch (Exception ex) {
            return R.fail(toCustomerMessage(ex));
            return AjaxResult.error(toCustomerMessage(ex));
        }
    }
@@ -523,8 +532,8 @@
                è¾“出要求:
                1. åªè¾“出合法 JSON,不要 Markdown,不要额外解释。
                2. JSON é¡¶å±‚字段固定为:
                   - ok: boolean
                   - businessType: purchase_ledger | payment_registration | purchase_return_order | unknown
                   - success: boolean
                   - businessType: purchase_ledger  | purchase_return_order | unknown
                   - action: confirm_required
                   - description: ä¸­æ–‡è¯´æ˜Ž
                   - confidence: 0到1的小数
@@ -549,7 +558,7 @@
                     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。
                4. å¦‚果可判断为付款登记,businessType ä½¿ç”¨ payload.records ä¸ºä»˜æ¬¾ç™»è®°æ•°ç»„,字段尽量包含 purchaseLedgerId、salesLedgerProductId、currentPaymentAmount、paymentMethod、paymentDate。
                5. å¦‚果可判断为采购退货,businessType ä½¿ç”¨ purchase_return_order,payload æŒ‰ PurchaseReturnOrderDto ç»„织,明细放 purchaseReturnOrderProductsDtos。
                6. ç¼ºå°‘业务处理必须字段时,不要编造 ID,把字段放入 missingFields,并仍返回可确认的草稿数据。
                7. æ‰€æœ‰ä¸­æ–‡å†…容直接保留,不要转义成 Unicode。
@@ -559,33 +568,33 @@
                """.formatted(message, fileContent);
    }
    private R processPurchaseLedger(Map<String, Object> payload) throws Exception {
    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);
        R ledgerResult = validatePurchaseLedger(dto, 0);
        AjaxResult ledgerResult = validatePurchaseLedger(dto, 0);
        if (ledgerResult != null) {
            return ledgerResult;
        }
        R supplierResult = fillSupplierIdByName(dto);
        AjaxResult supplierResult = fillSupplierIdByName(dto);
        if (supplierResult != null) {
            return supplierResult;
        }
        R productResult = validatePurchaseProducts(dto.getProductData(), 0);
        AjaxResult productResult = validatePurchaseProducts(dto.getProductData(), 0);
        if (productResult != null) {
            return productResult;
        }
        int result = purchaseLedgerService.addOrEditPurchase(dto);
        return R.ok( result,"采购台账已处理");
        return AjaxResult.success("采购台账已处理", result);
    }
    private R processPurchaseLedgerBatch(Map<String, Object> payload) throws Exception {
    private AjaxResult processPurchaseLedgerBatch(Map<String, Object> payload) throws Exception {
        List<Map<String, Object>> purchaseLedgers = toMapList(payload.get("purchaseLedgers"));
        if (purchaseLedgers.isEmpty()) {
            return R.fail("purchaseLedgers不能为空");
            return AjaxResult.error("purchaseLedgers不能为空");
        }
        List<Map<String, Object>> topLevelProductData = toMapList(payload.get("productData"));
@@ -593,11 +602,11 @@
        for (int i = 0; i < purchaseLedgers.size(); i++) {
            Map<String, Object> ledgerMap = normalizePurchaseLedgerMap(purchaseLedgers.get(i));
            PurchaseLedgerDto dto = objectMapper.convertValue(ledgerMap, PurchaseLedgerDto.class);
            R ledgerResult = validatePurchaseLedger(dto, i);
            AjaxResult ledgerResult = validatePurchaseLedger(dto, i);
            if (ledgerResult != null) {
                return ledgerResult;
            }
            R supplierResult = fillSupplierIdByName(dto);
            AjaxResult supplierResult = fillSupplierIdByName(dto);
            if (supplierResult != null) {
                return supplierResult;
            }
@@ -607,7 +616,7 @@
                products = matchProductsForLedger(ledgerMap, dto, topLevelProductData, purchaseLedgers.size() == 1);
                dto.setProductData(products);
            }
            R productResult = validatePurchaseProducts(products, i);
            AjaxResult productResult = validatePurchaseProducts(products, i);
            if (productResult != null) {
                return productResult;
            }
@@ -622,7 +631,7 @@
            item.put("result", result);
            results.add(item);
        }
        return R.ok( results,"采购台账已批量处理");
        return AjaxResult.success("采购台账已批量处理", results);
    }
    private List<SalesLedgerProduct> matchProductsForLedger(Map<String, Object> ledgerMap,
@@ -828,7 +837,7 @@
        }
    }
    private R validatePurchaseProducts(List<SalesLedgerProduct> products, int ledgerIndex) {
    private AjaxResult validatePurchaseProducts(List<SalesLedgerProduct> products, int ledgerIndex) {
        if (products == null || products.isEmpty()) {
            return null;
        }
@@ -836,34 +845,34 @@
            SalesLedgerProduct product = products.get(i);
            String prefix = "第" + (ledgerIndex + 1) + "个采购台账的第" + (i + 1) + "条产品";
            if (!StringUtils.hasText(product.getProductCategory())) {
                return R.fail(prefix + "缺少产品名称,请补充后再确认");
                return AjaxResult.error(prefix + "缺少产品名称,请补充后再确认");
            }
            if (!StringUtils.hasText(product.getSpecificationModel())) {
                return R.fail(prefix + "缺少规格型号,请补充后再确认");
                return AjaxResult.error(prefix + "缺少规格型号,请补充后再确认");
            }
            if (!StringUtils.hasText(product.getUnit())) {
                return R.fail(prefix + "缺少单位,请补充后再确认");
                return AjaxResult.error(prefix + "缺少单位,请补充后再确认");
            }
            if (product.getQuantity() == null) {
                return R.fail(prefix + "缺少数量");
                return AjaxResult.error(prefix + "缺少数量");
            }
            if (product.getTaxInclusiveUnitPrice() == null) {
                return R.fail(prefix + "缺少含税单价,请补充后再确认");
                return AjaxResult.error(prefix + "缺少含税单价,请补充后再确认");
            }
            if (product.getTaxInclusiveTotalPrice() == null) {
                return R.fail(prefix + "缺少含税总价,请补充后再确认");
                return AjaxResult.error(prefix + "缺少含税总价,请补充后再确认");
            }
        }
        return null;
    }
    private R validatePurchaseLedger(PurchaseLedgerDto dto, int ledgerIndex) {
    private AjaxResult validatePurchaseLedger(PurchaseLedgerDto dto, int ledgerIndex) {
        String prefix = "第" + (ledgerIndex + 1) + "个采购台账";
        if (!StringUtils.hasText(dto.getPurchaseContractNumber())) {
            return R.fail(prefix + "缺少采购合同号,请补充后再确认");
            return AjaxResult.error(prefix + "缺少采购合同号,请补充后再确认");
        }
        if (dto.getSupplierId() == null && !StringUtils.hasText(dto.getSupplierName())) {
            return R.fail(prefix + "缺少供应商名称,请补充后再确认");
            return AjaxResult.error(prefix + "缺少供应商名称,请补充后再确认");
        }
        return null;
    }
@@ -1034,40 +1043,27 @@
        return "处理失败:" + message;
    }
    private R fillSupplierIdByName(PurchaseLedgerDto dto) {
    private AjaxResult fillSupplierIdByName(PurchaseLedgerDto dto) {
        if (dto.getSupplierId() != null) {
            return null;
        }
        if (!StringUtils.hasText(dto.getSupplierName())) {
            return R.fail("供应商ID不能为空;未识别到供应商名称,无法自动匹配供应商ID");
            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 R.fail("未找到供应商:" + dto.getSupplierName() + ",请先维护供应商或手动选择供应商ID");
            return AjaxResult.error("未找到供应商:" + dto.getSupplierName() + ",请先维护供应商或手动选择供应商ID");
        }
        dto.setSupplierId(supplier.getId());
        return null;
    }
    private R 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 R.ok( result,"付款登记已处理");
    }
    private R processPurchaseReturnOrder(Map<String, Object> payload) {
    private AjaxResult processPurchaseReturnOrder(Map<String, Object> payload) {
        PurchaseReturnOrderDto dto = objectMapper.convertValue(payload, PurchaseReturnOrderDto.class);
        Boolean result = purchaseReturnOrdersService.add(dto);
        return R.ok( result,"采购退货单已处理");
        return AjaxResult.success("采购退货单已处理", result);
    }
}
src/main/java/com/ruoyi/ai/tools/ApproveTodoTools.java
@@ -22,11 +22,10 @@
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.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
@@ -46,7 +45,7 @@
    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_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private final ApproveProcessMapper approveProcessMapper;
@@ -740,7 +739,10 @@
    }
    private String formatDate(Date value) {
        return value == null ? "" : DATE_FORMAT.format(value);
        if (value == null) {
            return "";
        }
        return value.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(DATE_FORMATTER);
    }
    private long countByStatus(List<ApproveProcess> processes, int status) {
@@ -824,8 +826,9 @@
    private Date parseDate(String dateText) {
        try {
            return DATE_FORMAT.parse(dateText);
        } catch (ParseException e) {
            LocalDate localDate = LocalDate.parse(dateText, DATE_FORMATTER);
            return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
        } catch (Exception e) {
            throw new IllegalArgumentException("日期格式必须是 yyyy-MM-dd");
        }
    }
src/main/java/com/ruoyi/ai/tools/FinancialAgentTools.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2311 @@
package com.ruoyi.ai.tools;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.ruoyi.account.mapper.AccountStatementMapper;
import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper;
import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
import com.ruoyi.account.pojo.AccountStatement;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.ruoyi.account.service.impl.AccountingServiceImpl;
import com.ruoyi.ai.context.AiSessionUserContext;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.mapper.SupplierManageMapper;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.pojo.SupplierManage;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.mapper.DeviceRepairMapper;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.device.pojo.DeviceRepair;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordMapper;
import com.ruoyi.procurementrecord.mapper.ProcurementRecordOutMapper;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordOut;
import com.ruoyi.procurementrecord.pojo.ProcurementRecordStorage;
import com.ruoyi.production.mapper.ProductionAccountMapper;
import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
import com.ruoyi.production.mapper.ProductionOrderMapper;
import com.ruoyi.production.mapper.ProductionPlanMapper;
import com.ruoyi.production.mapper.ProductionProductMainMapper;
import com.ruoyi.production.mapper.ProductionProductOutputMapper;
import com.ruoyi.production.pojo.ProductionAccount;
import com.ruoyi.production.pojo.ProductionOperationTask;
import com.ruoyi.production.pojo.ProductionOrder;
import com.ruoyi.production.pojo.ProductionPlan;
import com.ruoyi.production.pojo.ProductionProductMain;
import com.ruoyi.production.pojo.ProductionProductOutput;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.pojo.StockInventory;
import com.ruoyi.technology.mapper.TechnologyOperationMapper;
import com.ruoyi.technology.pojo.TechnologyOperation;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.ToolMemoryId;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Component
public class FinancialAgentTools {
    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final Pattern RELATIVE_PATTERN = Pattern.compile("(近|最近)?\\s*(\\d+)\\s*(天|周|个月|月|å¹´)");
    private static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2})");
    private static final BigDecimal ONE_HUNDRED = new BigDecimal("100");
    private static final int DEFAULT_LIMIT = 10;
    private static final int MAX_LIMIT = 50;
    private static final BigDecimal DEFAULT_FALLBACK_MATERIAL_COST_RATE = new BigDecimal("0.60");
    private static final BigDecimal DEFAULT_LABOR_COST_RATE = new BigDecimal("0.15");
    private static final BigDecimal DEFAULT_OVERHEAD_COST_RATE = new BigDecimal("0.10");
    private static final BigDecimal SME_RECEIVABLE_RISK_THRESHOLD = new BigDecimal("500000");
    private static final BigDecimal SME_INVENTORY_RISK_THRESHOLD = new BigDecimal("1000000");
    private static final BigDecimal SME_PROFIT_WARNING_RATE = new BigDecimal("0.08");
    private final SalesLedgerMapper salesLedgerMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final ProductionAccountMapper productionAccountMapper;
    private final ProductionProductMainMapper productionProductMainMapper;
    private final ProductionOperationTaskMapper productionOperationTaskMapper;
    private final ProductionOrderMapper productionOrderMapper;
    private final ProductionPlanMapper productionPlanMapper;
    private final ProductionProductOutputMapper productionProductOutputMapper;
    private final TechnologyOperationMapper technologyOperationMapper;
    private final DeviceLedgerMapper deviceLedgerMapper;
    private final DeviceRepairMapper deviceRepairMapper;
    private final ProcurementRecordMapper procurementRecordMapper;
    private final ProcurementRecordOutMapper procurementRecordOutMapper;
    private final StockInventoryMapper stockInventoryMapper;
    private final AccountSalesCollectionMapper accountSalesCollectionMapper;
    private final AccountPurchasePaymentMapper accountPurchasePaymentMapper;
    private final AccountStatementMapper accountStatementMapper;
    private final CustomerMapper customerMapper;
    private final SupplierManageMapper supplierManageMapper;
    private final ProductModelMapper productModelMapper;
    private final ProductMapper productMapper;
    private final AiSessionUserContext aiSessionUserContext;
    public FinancialAgentTools(SalesLedgerMapper salesLedgerMapper,
                               SalesLedgerProductMapper salesLedgerProductMapper,
                               ProductionAccountMapper productionAccountMapper,
                               ProductionProductMainMapper productionProductMainMapper,
                               ProductionOperationTaskMapper productionOperationTaskMapper,
                               ProductionOrderMapper productionOrderMapper,
                               ProductionPlanMapper productionPlanMapper,
                               ProductionProductOutputMapper productionProductOutputMapper,
                               TechnologyOperationMapper technologyOperationMapper,
                               DeviceLedgerMapper deviceLedgerMapper,
                               DeviceRepairMapper deviceRepairMapper,
                               ProcurementRecordMapper procurementRecordMapper,
                               ProcurementRecordOutMapper procurementRecordOutMapper,
                               StockInventoryMapper stockInventoryMapper,
                               AccountSalesCollectionMapper accountSalesCollectionMapper,
                               AccountPurchasePaymentMapper accountPurchasePaymentMapper,
                               AccountStatementMapper accountStatementMapper,
                               CustomerMapper customerMapper,
                               SupplierManageMapper supplierManageMapper,
                               ProductModelMapper productModelMapper,
                               ProductMapper productMapper,
                               AiSessionUserContext aiSessionUserContext) {
        this.salesLedgerMapper = salesLedgerMapper;
        this.salesLedgerProductMapper = salesLedgerProductMapper;
        this.productionAccountMapper = productionAccountMapper;
        this.productionProductMainMapper = productionProductMainMapper;
        this.productionOperationTaskMapper = productionOperationTaskMapper;
        this.productionOrderMapper = productionOrderMapper;
        this.productionPlanMapper = productionPlanMapper;
        this.productionProductOutputMapper = productionProductOutputMapper;
        this.technologyOperationMapper = technologyOperationMapper;
        this.deviceLedgerMapper = deviceLedgerMapper;
        this.deviceRepairMapper = deviceRepairMapper;
        this.procurementRecordMapper = procurementRecordMapper;
        this.procurementRecordOutMapper = procurementRecordOutMapper;
        this.stockInventoryMapper = stockInventoryMapper;
        this.accountSalesCollectionMapper = accountSalesCollectionMapper;
        this.accountPurchasePaymentMapper = accountPurchasePaymentMapper;
        this.accountStatementMapper = accountStatementMapper;
        this.customerMapper = customerMapper;
        this.supplierManageMapper = supplierManageMapper;
        this.productModelMapper = productModelMapper;
        this.productMapper = productMapper;
        this.aiSessionUserContext = aiSessionUserContext;
    }
    @Tool(name = "财务知识检索", value = "按财务经营问题检索业财融合知识片段与指标口径,作为RAG上下文。")
    public String retrieveFinancialKnowledge(@ToolMemoryId String memoryId,
                                             @P(value = "问题或关键词,例如利润下降、库存周转、资金缺口") String question) {
        List<KnowledgeDoc> knowledgeDocs = financeKnowledgeBase();
        String normalized = normalizeForMatch(question);
        List<KnowledgeDoc> ranked = knowledgeDocs.stream()
                .sorted(Comparator.comparingInt((KnowledgeDoc doc) -> keywordHitCount(doc.keywords(), normalized)).reversed())
                .filter(doc -> keywordHitCount(doc.keywords(), normalized) > 0 || !StringUtils.hasText(normalized))
                .limit(5)
                .toList();
        List<Map<String, Object>> items = ranked.stream().map(doc -> {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("topic", doc.topic());
            map.put("knowledge", doc.knowledge());
            map.put("relatedTables", doc.relatedTables());
            map.put("suggestedQuestions", doc.suggestedQuestions());
            return map;
        }).toList();
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("question", safe(question));
        summary.put("hitCount", items.size());
        summary.put("retrievalMode", "keyword_rag");
        return jsonResponse(true, "financial_rag_knowledge", "已返回财务知识检索结果", summary, Map.of("items", items), Map.of());
    }
    @Tool(name = "智能成本核算", value = "自动核算产品成本、工序成本、人工成本、设备折旧、材料损耗与订单利润。")
    public String calculateIntelligentCost(@ToolMemoryId String memoryId,
                                           @P(value = "开始日期 yyyy-MM-dd", required = false) String startDate,
                                           @P(value = "结束日期 yyyy-MM-dd", required = false) String endDate,
                                           @P(value = "时间范围描述,如本月、近30天", required = false) String timeRange,
                                           @P(value = "关键词,可匹配合同号/客户/项目", required = false) String keyword,
                                           @P(value = "返回条数,默认10,最大50", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        if (loginUser == null) {
            return jsonResponse(false, "financial_cost_accounting", "用户信息获取失败", Map.of(), Map.of(), Map.of());
        }
        DateRange range = resolveDateRange(startDate, endDate, timeRange, "近30天");
        AnalysisBundle bundle = buildOrderProfitBundle(loginUser, range, keyword, limit);
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("orderCount", bundle.orderMetrics().size());
        summary.put("totalRevenue", bundle.totalRevenue());
        summary.put("totalMaterialCost", bundle.totalMaterialCost());
        summary.put("totalLaborCost", bundle.totalLaborCost());
        summary.put("totalDepreciationCost", bundle.totalDepreciationCost());
        summary.put("totalScrapCost", bundle.totalScrapCost());
        summary.put("totalCost", bundle.totalCost());
        summary.put("totalProfit", bundle.totalProfit());
        summary.put("profitRate", toPercent(rate(bundle.totalProfit(), bundle.totalRevenue())));
        List<Map<String, Object>> orderItems = bundle.orderMetrics().stream()
                .map(this::toOrderCostItem)
                .toList();
        List<Map<String, Object>> processItems = bundle.processCostRanking().entrySet().stream()
                .map(entry -> {
                    Map<String, Object> map = new LinkedHashMap<>();
                    map.put("processName", entry.getKey());
                    map.put("cost", entry.getValue());
                    return map;
                }).toList();
        List<Map<String, Object>> topCustomerItems = buildCustomerProfitTop(bundle.orderMetrics(), 5);
        Map<String, Object> charts = new LinkedHashMap<>();
        charts.put("costCompositionPieOption",
                buildCostCompositionPie(bundle.totalMaterialCost(), bundle.totalLaborCost(), bundle.totalDepreciationCost(), bundle.totalScrapCost()));
        charts.put("orderProfitBarOption", buildOrderProfitBar(bundle.orderMetrics()));
        charts.put("processCostBarOption", buildProcessCostBar(bundle.processCostRanking()));
        return jsonResponse(true, "financial_cost_accounting", "已完成智能成本核算", summary,
                Map.of(
                        "orders", orderItems,
                        "processCostRanking", processItems,
                        "topCustomers", topCustomerItems
                ),
                charts
        );
    }
    @Tool(name = "订单利润分析", value = "识别低利润/亏损订单,输出原因分析和优化建议。")
    public String analyzeOrderProfit(@ToolMemoryId String memoryId,
                                     @P(value = "开始日期 yyyy-MM-dd", required = false) String startDate,
                                     @P(value = "结束日期 yyyy-MM-dd", required = false) String endDate,
                                     @P(value = "时间范围描述,如本月、近30天", required = false) String timeRange,
                                     @P(value = "关键词,可匹配合同号/客户/项目", required = false) String keyword,
                                     @P(value = "返回条数,默认10,最大50", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        if (loginUser == null) {
            return jsonResponse(false, "financial_order_profit_analysis", "用户信息获取失败", Map.of(), Map.of(), Map.of());
        }
        DateRange range = resolveDateRange(startDate, endDate, timeRange, "近30天");
        AnalysisBundle bundle = buildOrderProfitBundle(loginUser, range, keyword, limit);
        List<OrderProfitMetric> metrics = bundle.orderMetrics();
        List<OrderProfitMetric> riskyOrders = metrics.stream()
                .filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0 || item.profitRate().compareTo(SME_PROFIT_WARNING_RATE) < 0)
                .sorted(Comparator.comparing(OrderProfitMetric::profitRate)
                        .thenComparing(OrderProfitMetric::profit))
                .toList();
        Map<String, BigDecimal> customerProfitMap = new LinkedHashMap<>();
        for (OrderProfitMetric metric : metrics) {
            customerProfitMap.merge(metric.customerName(), metric.profit(), BigDecimal::add);
        }
        Map.Entry<String, BigDecimal> topCustomer = customerProfitMap.entrySet().stream()
                .max(Map.Entry.comparingByValue())
                .orElse(Map.entry("暂无数据", BigDecimal.ZERO));
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("orderCount", metrics.size());
        summary.put("lossOrderCount", metrics.stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count());
        summary.put("lowProfitOrderCount", riskyOrders.size());
        summary.put("avgProfitRate", toPercent(avgRate(metrics)));
        summary.put("topCustomerByProfit", topCustomer.getKey());
        summary.put("topCustomerProfit", topCustomer.getValue());
        List<Map<String, Object>> riskyItems = riskyOrders.stream()
                .limit(normalizeLimit(limit))
                .map(this::toRiskOrderItem)
                .toList();
        Map<String, Object> charts = new LinkedHashMap<>();
        charts.put("profitDistributionOption", buildProfitDistributionBar(metrics));
        charts.put("lossOrderTrendOption", buildLossOrderTrendLine(metrics));
        charts.put("customerProfitTopOption", buildCustomerProfitBar(customerProfitMap));
        return jsonResponse(true, "financial_order_profit_analysis", "已完成订单利润分析", summary,
                Map.of(
                        "riskOrders", riskyItems,
                        "allOrders", metrics.stream().map(this::toOrderCostItem).toList(),
                        "customerProfitTop", buildCustomerProfitTop(metrics, 10)
                ),
                charts
        );
    }
    @Tool(name = "库存资金分析", value = "分析库存积压、呆滞库存、资金占用与周转率。")
    public String analyzeInventoryCapital(@ToolMemoryId String memoryId,
                                          @P(value = "开始日期 yyyy-MM-dd", required = false) String startDate,
                                          @P(value = "结束日期 yyyy-MM-dd", required = false) String endDate,
                                          @P(value = "时间范围描述,如本月、近30天", required = false) String timeRange,
                                          @P(value = "关键词,可匹配产品名称/型号", required = false) String keyword,
                                          @P(value = "返回条数,默认10,最大50", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, timeRange, "近30天");
        int finalLimit = normalizeLimit(limit);
        List<StockInventory> inventoryRows = queryStockInventory(loginUser);
        if (inventoryRows.isEmpty()) {
            return jsonResponse(true, "financial_inventory_capital_analysis", "当前无库存数据",
                    rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of());
        }
        Set<Long> modelIds = inventoryRows.stream()
                .map(StockInventory::getProductModelId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Long, ProductModel> productModelMap = queryProductModels(modelIds);
        Map<Long, Product> productMap = queryProducts(productModelMap.values());
        Map<Long, BigDecimal> avgUnitCostByModelId = queryAverageUnitCostByModel(loginUser, modelIds);
        OutboundStats outboundStats = queryOutboundStats(loginUser, modelIds, range);
        List<InventoryMetric> metrics = buildInventoryMetrics(inventoryRows, productModelMap, productMap, avgUnitCostByModelId, outboundStats)
                .stream()
                .filter(metric -> matchInventoryKeyword(metric, keyword))
                .sorted(Comparator.comparing(InventoryMetric::inventoryValue).reversed())
                .toList();
        BigDecimal totalInventoryValue = metrics.stream().map(InventoryMetric::inventoryValue).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal stagnantValue = metrics.stream()
                .filter(metric -> metric.stagnantDays() >= 90)
                .map(InventoryMetric::inventoryValue)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        long stagnantCount = metrics.stream().filter(metric -> metric.stagnantDays() >= 90).count();
        long overstockCount = metrics.stream().filter(InventoryMetric::overstock).count();
        BigDecimal totalOutboundCost = outboundStats.totalOutboundCost();
        BigDecimal turnoverDays = totalOutboundCost.compareTo(BigDecimal.ZERO) > 0
                ? totalInventoryValue.multiply(BigDecimal.valueOf(daysBetween(range.start(), range.end()) + 1L))
                .divide(totalOutboundCost, 2, RoundingMode.HALF_UP)
                : BigDecimal.ZERO;
        List<Map<String, Object>> items = metrics.stream()
                .limit(finalLimit)
                .map(this::toInventoryItem)
                .toList();
        Map<String, Object> summary = rangeSummary(range, metrics.size(), keyword);
        summary.put("totalInventoryValue", totalInventoryValue);
        summary.put("stagnantValue", stagnantValue);
        summary.put("stagnantCount", stagnantCount);
        summary.put("overstockCount", overstockCount);
        summary.put("turnoverDays", turnoverDays);
        summary.put("capitalOccupation", totalInventoryValue);
        summary.put("totalOutboundCost", totalOutboundCost);
        Map<String, Object> charts = new LinkedHashMap<>();
        charts.put("inventoryValueTopOption", buildInventoryTopBar(metrics));
        charts.put("inventoryAgingPieOption", buildInventoryAgingPie(metrics));
        charts.put("inventoryTurnoverGauge", buildTurnoverGauge(turnoverDays));
        return jsonResponse(true, "financial_inventory_capital_analysis", "已完成库存资金分析", summary, Map.of("items", items), charts);
    }
    @Tool(name = "应收应付与现金流预测", value = "预测未来现金流、回款风险、付款压力与资金缺口。")
    public String forecastCashFlow(@ToolMemoryId String memoryId,
                                   @P(value = "开始日期 yyyy-MM-dd", required = false) String startDate,
                                   @P(value = "结束日期 yyyy-MM-dd", required = false) String endDate,
                                   @P(value = "时间范围描述,如近90天、本年", required = false) String timeRange,
                                   @P(value = "预测月份数,默认3,最大6", required = false) Integer forecastMonths) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, timeRange, "近90天");
        int months = forecastMonths == null || forecastMonths <= 0 ? 3 : Math.min(forecastMonths, 6);
        List<AccountSalesCollection> collections = queryCollections(loginUser, range);
        List<AccountPurchasePayment> payments = queryPayments(loginUser, range);
        List<MonthlyCashFlow> monthlyActual = buildMonthlyCashFlow(range, collections, payments);
        List<MonthlyCashFlow> monthlyForecast = forecastMonthlyCashFlow(monthlyActual, months);
        StatementSnapshot snapshot = buildStatementSnapshot(loginUser);
        BigDecimal receivableTotal = snapshot.receivableTotal();
        BigDecimal payableTotal = snapshot.payableTotal();
        BigDecimal forecastNetSum = monthlyForecast.stream().map(MonthlyCashFlow::netFlow).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal coverage = receivableTotal.add(maxZero(forecastNetSum));
        BigDecimal fundGap = maxZero(payableTotal.subtract(coverage));
        Map<String, String> customerNameMap = queryCustomerNameMap(snapshot.receivableTop().stream().map(StatementMetric::entityId).collect(Collectors.toSet()));
        Map<String, String> supplierNameMap = querySupplierNameMap(snapshot.payableTop().stream().map(StatementMetric::entityId).collect(Collectors.toSet()));
        List<Map<String, Object>> receivableRiskItems = snapshot.receivableTop().stream().map(item -> toStatementRiskItem(item, customerNameMap, "customer")).toList();
        List<Map<String, Object>> payablePressureItems = snapshot.payableTop().stream().map(item -> toStatementRiskItem(item, supplierNameMap, "supplier")).toList();
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("actualIncomeTotal", collections.stream().map(AccountSalesCollection::getCollectionAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
        summary.put("actualExpenseTotal", payments.stream().map(AccountPurchasePayment::getPaymentAmount).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
        summary.put("receivableBalance", receivableTotal);
        summary.put("payableBalance", payableTotal);
        summary.put("forecastNetSum", forecastNetSum);
        summary.put("fundGap", fundGap);
        summary.put("forecastMonths", months);
        summary.put("collectionRiskLevel", riskLevelByAmount(receivableTotal));
        summary.put("paymentPressureLevel", riskLevelByAmount(payableTotal));
        Map<String, Object> charts = new LinkedHashMap<>();
        charts.put("cashFlowTrendOption", buildCashflowTrend(monthlyActual, monthlyForecast));
        charts.put("receivablePayableBarOption", buildReceivablePayableBar(receivableTotal, payableTotal));
        charts.put("fundGapGaugeOption", buildFundGapGauge(fundGap));
        return jsonResponse(true, "financial_cashflow_forecast", "已完成应收应付与现金流预测", summary,
                Map.of(
                        "actualMonthly", monthlyActual.stream().map(this::toMonthlyCashFlowItem).toList(),
                        "forecastMonthly", monthlyForecast.stream().map(this::toMonthlyCashFlowItem).toList(),
                        "receivableRiskTop", receivableRiskItems,
                        "payablePressureTop", payablePressureItems
                ),
                charts
        );
    }
    @Tool(name = "经营异常预警", value = "识别成本异常、利润异常、回款异常、订单风险、库存异常。")
    public String detectBusinessAnomalies(@ToolMemoryId String memoryId,
                                          @P(value = "开始日期 yyyy-MM-dd", required = false) String startDate,
                                          @P(value = "结束日期 yyyy-MM-dd", required = false) String endDate,
                                          @P(value = "时间范围描述,如近30天", required = false) String timeRange,
                                          @P(value = "返回条数,默认10,最大50", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, timeRange, "近30天");
        int finalLimit = normalizeLimit(limit);
        AnalysisBundle currentBundle = buildOrderProfitBundle(loginUser, range, null, Math.max(finalLimit, 30));
        DateRange prevRange = previousSameLengthRange(range);
        AnalysisBundle prevBundle = buildOrderProfitBundle(loginUser, prevRange, null, 50);
        BigDecimal currentCostRate = rate(currentBundle.totalCost(), currentBundle.totalRevenue());
        BigDecimal prevCostRate = rate(prevBundle.totalCost(), prevBundle.totalRevenue());
        BigDecimal costRateDiff = currentCostRate.subtract(prevCostRate);
        StatementSnapshot snapshot = buildStatementSnapshot(loginUser);
        List<InventoryMetric> inventoryMetrics = buildInventoryMetrics(
                queryStockInventory(loginUser),
                queryProductModels(Collections.emptySet()),
                Map.of(),
                queryAverageUnitCostByModel(loginUser, Collections.emptySet()),
                queryOutboundStats(loginUser, Collections.emptySet(), range)
        );
        List<Map<String, Object>> anomalyItems = new ArrayList<>();
        if (costRateDiff.compareTo(new BigDecimal("0.10")) > 0) {
            anomalyItems.add(anomalyItem("high", "成本异常", "单位收入成本率较上周期上升超过10%", Map.of(
                    "currentCostRate", toPercent(currentCostRate),
                    "previousCostRate", toPercent(prevCostRate),
                    "delta", toPercent(costRateDiff)
            )));
        }
        long lossCount = currentBundle.orderMetrics().stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count();
        if (lossCount > 0) {
            anomalyItems.add(anomalyItem("high", "利润异常", "检测到亏损订单", Map.of("lossOrderCount", lossCount)));
        }
        if (snapshot.receivableTotal().compareTo(snapshot.payableTotal().multiply(new BigDecimal("1.2"))) > 0) {
            anomalyItems.add(anomalyItem("medium", "回款异常", "应收余额显著高于应付,回款压力偏大", Map.of(
                    "receivableBalance", snapshot.receivableTotal(),
                    "payableBalance", snapshot.payableTotal()
            )));
        }
        long overdueOrderCount = currentBundle.orderMetrics().stream()
                .filter(item -> item.deliveryDate() != null && item.deliveryDate().isBefore(LocalDate.now()) && item.profitRate().compareTo(new BigDecimal("0.10")) < 0)
                .count();
        if (overdueOrderCount > 0) {
            anomalyItems.add(anomalyItem("medium", "订单风险", "存在低利润且交付已逾期订单", Map.of("overdueRiskOrderCount", overdueOrderCount)));
        }
        long stagnantCount = inventoryMetrics.stream().filter(item -> item.stagnantDays() >= 90).count();
        if (stagnantCount > 0) {
            anomalyItems.add(anomalyItem("medium", "库存异常", "存在超过90天未周转库存", Map.of("stagnantCount", stagnantCount)));
        }
        List<Map<String, Object>> topAnomalies = anomalyItems.stream().limit(finalLimit).toList();
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("anomalyCount", topAnomalies.size());
        summary.put("highRiskCount", topAnomalies.stream().filter(item -> "high".equals(item.get("riskLevel"))).count());
        summary.put("mediumRiskCount", topAnomalies.stream().filter(item -> "medium".equals(item.get("riskLevel"))).count());
        Map<String, Object> charts = new LinkedHashMap<>();
        charts.put("anomalyLevelPieOption", buildAnomalyLevelPie(topAnomalies));
        charts.put("anomalyTypeBarOption", buildAnomalyTypeBar(topAnomalies));
        return jsonResponse(true, "financial_business_anomaly_warning", "已完成经营异常预警分析", summary,
                Map.of("items", topAnomalies), charts);
    }
    @Tool(name = "AI经营驾驶舱", value = "实时展示产值、利润、库存、回款、设备利用率、订单利润率等核心经营指标。")
    public String getBusinessCockpit(@ToolMemoryId String memoryId,
                                     @P(value = "开始日期 yyyy-MM-dd", required = false) String startDate,
                                     @P(value = "结束日期 yyyy-MM-dd", required = false) String endDate,
                                     @P(value = "时间范围描述,如本月、近30天", required = false) String timeRange) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, timeRange, "本月");
        AnalysisBundle profitBundle = buildOrderProfitBundle(loginUser, range, null, 30);
        StatementSnapshot snapshot = buildStatementSnapshot(loginUser);
        List<StockInventory> inventories = queryStockInventory(loginUser);
        BigDecimal inventoryValue = estimateInventoryValue(loginUser, inventories);
        long deviceTotal = countDevices(loginUser);
        long repairingCount = countRepairingDevices(loginUser);
        BigDecimal deviceUtilization = deviceTotal > 0
                ? new BigDecimal(deviceTotal - repairingCount).multiply(ONE_HUNDRED).divide(new BigDecimal(deviceTotal), 2, RoundingMode.HALF_UP)
                : BigDecimal.ZERO;
        BigDecimal outputValue = profitBundle.totalRevenue();
        BigDecimal profitRate = rate(profitBundle.totalProfit(), profitBundle.totalRevenue());
        BigDecimal collectionRate = snapshot.receivableTotal().compareTo(BigDecimal.ZERO) > 0
                ? ONE_HUNDRED.subtract(rate(snapshot.receivableTotal(), snapshot.receivableTotal().add(snapshot.payableTotal())).multiply(ONE_HUNDRED))
                : BigDecimal.ZERO;
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("outputValue", outputValue);
        summary.put("profit", profitBundle.totalProfit());
        summary.put("profitRate", toPercent(profitRate));
        summary.put("inventoryValue", inventoryValue);
        summary.put("receivableBalance", snapshot.receivableTotal());
        summary.put("payableBalance", snapshot.payableTotal());
        summary.put("collectionRate", toPercent(collectionRate.divide(ONE_HUNDRED, 4, RoundingMode.HALF_UP)));
        summary.put("deviceUtilizationRate", deviceUtilization + "%");
        summary.put("orderProfitRate", toPercent(avgRate(profitBundle.orderMetrics())));
        Map<String, Object> indicators = new LinkedHashMap<>();
        indicators.put("产值", outputValue);
        indicators.put("利润", profitBundle.totalProfit());
        indicators.put("库存资金占用", inventoryValue);
        indicators.put("应收余额", snapshot.receivableTotal());
        indicators.put("设备利用率", deviceUtilization + "%");
        indicators.put("订单平均利润率", toPercent(avgRate(profitBundle.orderMetrics())));
        Map<String, Object> charts = new LinkedHashMap<>();
        charts.put("kpiCardData", indicators);
        charts.put("profitTrendOption", buildOrderProfitBar(profitBundle.orderMetrics()));
        charts.put("receivablePayableBarOption", buildReceivablePayableBar(snapshot.receivableTotal(), snapshot.payableTotal()));
        charts.put("inventoryProfitGaugeOption", buildInventoryProfitGauge(inventoryValue, profitBundle.totalProfit()));
        return jsonResponse(true, "financial_business_cockpit", "已生成AI经营驾驶舱数据", summary,
                Map.of(
                        "orderProfitTop", profitBundle.orderMetrics().stream()
                                .sorted(Comparator.comparing(OrderProfitMetric::profit).reversed())
                                .limit(10)
                                .map(this::toOrderCostItem)
                                .toList(),
                        "indicators", indicators
                ),
                charts
        );
    }
    @Tool(name = "日报周报自动生成", value = "自动输出经营分析日报/周报与风险建议。")
    public String generateOperationReport(@ToolMemoryId String memoryId,
                                          @P(value = "开始日期 yyyy-MM-dd", required = false) String startDate,
                                          @P(value = "结束日期 yyyy-MM-dd", required = false) String endDate,
                                          @P(value = "时间范围描述,如今天、本周", required = false) String timeRange,
                                          @P(value = "报告类型 daily/weekly", required = false) String reportType) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, timeRange,
                "weekly".equalsIgnoreCase(reportType) ? "本周" : "今天");
        String type = "weekly".equalsIgnoreCase(reportType) ? "weekly" : "daily";
        AnalysisBundle bundle = buildOrderProfitBundle(loginUser, range, null, 30);
        StatementSnapshot snapshot = buildStatementSnapshot(loginUser);
        BigDecimal inventoryValue = estimateInventoryValue(loginUser, queryStockInventory(loginUser));
        long lossCount = bundle.orderMetrics().stream().filter(item -> item.profit().compareTo(BigDecimal.ZERO) < 0).count();
        List<String> conclusions = new ArrayList<>();
        conclusions.add("营收" + bundle.totalRevenue() + ",利润" + bundle.totalProfit() + ",利润率" + toPercent(rate(bundle.totalProfit(), bundle.totalRevenue())) + "。");
        conclusions.add("应收余额" + snapshot.receivableTotal() + ",应付余额" + snapshot.payableTotal() + ",库存资金占用" + inventoryValue + "。");
        if (lossCount > 0) {
            conclusions.add("发现亏损订单" + lossCount + "个,建议优先复核材料损耗和工序人工效率。");
        } else {
            conclusions.add("当前未发现亏损订单,建议持续跟踪低于8%利润率订单。");
        }
        if (snapshot.receivableTotal().compareTo(snapshot.payableTotal()) > 0) {
            conclusions.add("回款压力偏高,建议针对高应收客户执行分层催收与账期优化。");
        } else {
            conclusions.add("资金压力可控,建议保持付款计划与采购节奏联动。");
        }
        List<Map<String, Object>> riskSuggestions = new ArrayList<>();
        if (lossCount > 0) {
            riskSuggestions.add(riskSuggestion("利润风险", "高", "复核亏损订单BOM和工序工资定额,必要时调整报价与交付节奏。"));
        }
        if (snapshot.receivableTotal().compareTo(SME_RECEIVABLE_RISK_THRESHOLD) > 0) {
            riskSuggestions.add(riskSuggestion("回款风险", "中", "对应收TOP客户建立周度回款计划,并设置预警阈值。"));
        }
        if (inventoryValue.compareTo(SME_INVENTORY_RISK_THRESHOLD) > 0) {
            riskSuggestions.add(riskSuggestion("库存风险", "中", "对高金额呆滞库存执行降价、替代和生产消耗策略。"));
        }
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("reportType", type);
        summary.put("timeRange", range.label());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("orderCount", bundle.orderMetrics().size());
        summary.put("lossOrderCount", lossCount);
        summary.put("riskSuggestionCount", riskSuggestions.size());
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("headline", "weekly".equals(type) ? "经营周报" : "经营日报");
        data.put("conclusions", conclusions);
        data.put("riskSuggestions", riskSuggestions);
        data.put("orderProfitTop", bundle.orderMetrics().stream()
                .sorted(Comparator.comparing(OrderProfitMetric::profitRate))
                .limit(10)
                .map(this::toRiskOrderItem)
                .toList());
        Map<String, Object> charts = new LinkedHashMap<>();
        charts.put("reportProfitBarOption", buildOrderProfitBar(bundle.orderMetrics()));
        charts.put("reportReceivablePayableOption", buildReceivablePayableBar(snapshot.receivableTotal(), snapshot.payableTotal()));
        return jsonResponse(true, "financial_operation_report", "已自动生成经营分析报告", summary, data, charts);
    }
    private AnalysisBundle buildOrderProfitBundle(LoginUser loginUser, DateRange range, String keyword, Integer limit) {
        List<SalesLedger> ledgers = querySalesLedgers(loginUser, range, keyword, limit);
        if (ledgers.isEmpty()) {
            return AnalysisBundle.empty();
        }
        List<Long> ledgerIds = ledgers.stream().map(SalesLedger::getId).filter(Objects::nonNull).toList();
        List<SalesLedgerProduct> ledgerProducts = queryLedgerProducts(loginUser, ledgerIds);
        Map<Long, List<SalesLedgerProduct>> productsByLedgerId = ledgerProducts.stream()
                .collect(Collectors.groupingBy(SalesLedgerProduct::getSalesLedgerId));
        MaterialCostResult materialCostResult = calculateMaterialCost(loginUser, range, ledgerProducts);
        ProductionCostContext productionCostContext = calculateProductionCost(loginUser, range, ledgers, ledgerProducts, materialCostResult.avgUnitCostByModelId());
        BigDecimal totalDepreciation = calculateTotalDepreciation(loginUser);
        BigDecimal totalRevenue = ledgers.stream()
                .map(SalesLedger::getContractAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        Map<Long, BigDecimal> depreciationCostByLedger = allocateDepreciation(ledgers, totalDepreciation, totalRevenue);
        List<OrderProfitMetric> metrics = new ArrayList<>();
        for (SalesLedger ledger : ledgers) {
            BigDecimal revenue = defaultDecimal(ledger.getContractAmount());
            BigDecimal materialCost = materialCostResult.materialCostByLedgerId().getOrDefault(ledger.getId(), fallbackMaterialCost(productsByLedgerId.get(ledger.getId()), revenue));
            BigDecimal laborCost = productionCostContext.laborCostByLedgerId().getOrDefault(ledger.getId(), BigDecimal.ZERO);
            BigDecimal scrapCost = productionCostContext.scrapCostByLedgerId().getOrDefault(ledger.getId(), BigDecimal.ZERO);
            BigDecimal depreciationCost = depreciationCostByLedger.getOrDefault(ledger.getId(), BigDecimal.ZERO);
            BigDecimal totalCost = materialCost.add(laborCost).add(scrapCost).add(depreciationCost);
            BigDecimal profit = revenue.subtract(totalCost);
            BigDecimal profitRate = rate(profit, revenue);
            String riskLevel = profit.compareTo(BigDecimal.ZERO) < 0
                    ? "high"
                    : (profitRate.compareTo(new BigDecimal("0.08")) < 0 ? "medium" : "low");
            List<String> reasons = buildProfitReasons(revenue, materialCost, laborCost, scrapCost, profit, profitRate);
            String suggestion = buildProfitSuggestion(riskLevel, reasons);
            metrics.add(new OrderProfitMetric(
                    ledger.getId(),
                    safe(ledger.getSalesContractNo()),
                    safe(ledger.getCustomerName()),
                    safe(ledger.getProjectName()),
                    toLocalDate(ledger.getEntryDate()),
                    ledger.getDeliveryDate(),
                    revenue,
                    materialCost,
                    laborCost,
                    depreciationCost,
                    scrapCost,
                    totalCost,
                    profit,
                    profitRate,
                    riskLevel,
                    reasons,
                    suggestion
            ));
        }
        metrics.sort(Comparator.comparing(OrderProfitMetric::entryDate, Comparator.nullsLast(Comparator.reverseOrder()))
                .thenComparing(OrderProfitMetric::ledgerId, Comparator.nullsLast(Comparator.reverseOrder())));
        BigDecimal totalMaterialCost = metrics.stream().map(OrderProfitMetric::materialCost).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalLaborCost = metrics.stream().map(OrderProfitMetric::laborCost).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalScrapCost = metrics.stream().map(OrderProfitMetric::scrapCost).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalDepreciationCost = metrics.stream().map(OrderProfitMetric::depreciationCost).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalCost = metrics.stream().map(OrderProfitMetric::totalCost).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalProfit = metrics.stream().map(OrderProfitMetric::profit).reduce(BigDecimal.ZERO, BigDecimal::add);
        return new AnalysisBundle(
                metrics,
                productionCostContext.processCostRanking(),
                totalRevenue,
                totalMaterialCost,
                totalLaborCost,
                totalDepreciationCost,
                totalScrapCost,
                totalCost,
                totalProfit
        );
    }
    private MaterialCostResult calculateMaterialCost(LoginUser loginUser, DateRange range, List<SalesLedgerProduct> ledgerProducts) {
        if (ledgerProducts.isEmpty()) {
            return new MaterialCostResult(Map.of(), Map.of());
        }
        List<Long> ledgerProductIds = ledgerProducts.stream().map(SalesLedgerProduct::getId).filter(Objects::nonNull).toList();
        Set<Long> productModelIds = ledgerProducts.stream().map(SalesLedgerProduct::getProductModelId).filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, Long> productIdToLedgerId = ledgerProducts.stream()
                .filter(item -> item.getId() != null && item.getSalesLedgerId() != null)
                .collect(Collectors.toMap(SalesLedgerProduct::getId, SalesLedgerProduct::getSalesLedgerId, (a, b) -> a));
        Map<Long, BigDecimal> avgUnitCostByModelId = queryAverageUnitCostByModel(loginUser, productModelIds);
        LambdaQueryWrapper<ProcurementRecordOut> outWrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(outWrapper, loginUser.getTenantId(), ProcurementRecordOut::getTenantId);
        applyDeptFilter(outWrapper, loginUser.getCurrentDeptId(), ProcurementRecordOut::getDeptId);
        outWrapper.eq(ProcurementRecordOut::getType, 2)
                .in(ProcurementRecordOut::getSalesLedgerProductId, ledgerProductIds);
        if (range.hasDateFilter()) {
            outWrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
                    .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
        }
        List<ProcurementRecordOut> outList = defaultList(procurementRecordOutMapper.selectList(outWrapper));
        Set<Integer> storageIds = outList.stream()
                .map(ProcurementRecordOut::getProcurementRecordStorageId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Integer, ProcurementRecordStorage> storageMap = storageIds.isEmpty()
                ? Map.of()
                : defaultList(procurementRecordMapper.selectBatchIds(storageIds)).stream()
                .collect(Collectors.toMap(ProcurementRecordStorage::getId, item -> item, (a, b) -> a));
        Map<Long, BigDecimal> materialCostByLedgerId = new HashMap<>();
        for (ProcurementRecordOut out : outList) {
            Long ledgerId = productIdToLedgerId.get(out.getSalesLedgerProductId());
            if (ledgerId == null) {
                continue;
            }
            ProcurementRecordStorage storage = storageMap.get(out.getProcurementRecordStorageId());
            BigDecimal unitPrice = storage == null ? BigDecimal.ZERO : defaultDecimal(storage.getUnitPrice());
            BigDecimal quantity = defaultDecimal(out.getInboundNum());
            BigDecimal cost = quantity.multiply(unitPrice);
            materialCostByLedgerId.merge(ledgerId, cost, BigDecimal::add);
        }
        return new MaterialCostResult(materialCostByLedgerId, avgUnitCostByModelId);
    }
    private ProductionCostContext calculateProductionCost(LoginUser loginUser,
                                                          DateRange range,
                                                          List<SalesLedger> ledgers,
                                                          List<SalesLedgerProduct> ledgerProducts,
                                                          Map<Long, BigDecimal> avgUnitCostByModelId) {
        if (ledgers.isEmpty()) {
            return ProductionCostContext.empty();
        }
        Set<Long> ledgerIds = ledgers.stream().map(SalesLedger::getId).filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, Set<Long>> productModelToLedgerIds = new HashMap<>();
        for (SalesLedgerProduct product : ledgerProducts) {
            if (product.getProductModelId() == null || product.getSalesLedgerId() == null) {
                continue;
            }
            productModelToLedgerIds.computeIfAbsent(product.getProductModelId(), key -> new HashSet<>()).add(product.getSalesLedgerId());
        }
        LambdaQueryWrapper<ProductionPlan> planWrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(planWrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId);
        planWrapper.in(ProductionPlan::getSalesLedgerId, ledgerIds);
        List<ProductionPlan> plans = defaultList(productionPlanMapper.selectList(planWrapper));
        Map<Long, Long> planIdToLedgerId = plans.stream()
                .filter(item -> item.getId() != null && item.getSalesLedgerId() != null)
                .collect(Collectors.toMap(ProductionPlan::getId, ProductionPlan::getSalesLedgerId, (a, b) -> a));
        LambdaQueryWrapper<ProductionOrder> orderWrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(orderWrapper, loginUser.getCurrentDeptId(), ProductionOrder::getDeptId);
        if (range.hasDateFilter()) {
            orderWrapper.ge(ProductionOrder::getCreateTime, range.start().atStartOfDay().minusMonths(2))
                    .lt(ProductionOrder::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
        }
        List<ProductionOrder> orders = defaultList(productionOrderMapper.selectList(orderWrapper));
        Map<Long, Set<Long>> orderIdToLedgerIds = new HashMap<>();
        for (ProductionOrder order : orders) {
            Set<Long> orderLedgers = new HashSet<>();
            for (Long planId : parseIdList(order.getProductionPlanIds())) {
                Long ledgerId = planIdToLedgerId.get(planId);
                if (ledgerId != null) {
                    orderLedgers.add(ledgerId);
                }
            }
            if (orderLedgers.isEmpty() && order.getProductModelId() != null) {
                orderLedgers.addAll(productModelToLedgerIds.getOrDefault(order.getProductModelId(), Set.of()));
            }
            if (!orderLedgers.isEmpty()) {
                orderIdToLedgerIds.put(order.getId(), orderLedgers);
            }
        }
        if (orderIdToLedgerIds.isEmpty()) {
            return ProductionCostContext.empty();
        }
        LambdaQueryWrapper<ProductionOperationTask> taskWrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(taskWrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId);
        taskWrapper.in(ProductionOperationTask::getProductionOrderId, orderIdToLedgerIds.keySet());
        List<ProductionOperationTask> tasks = defaultList(productionOperationTaskMapper.selectList(taskWrapper));
        Map<Long, Long> taskIdToOrderId = tasks.stream()
                .filter(item -> item.getId() != null && item.getProductionOrderId() != null)
                .collect(Collectors.toMap(ProductionOperationTask::getId, ProductionOperationTask::getProductionOrderId, (a, b) -> a));
        if (taskIdToOrderId.isEmpty()) {
            return ProductionCostContext.empty();
        }
        LambdaQueryWrapper<ProductionProductMain> mainWrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(mainWrapper, loginUser.getCurrentDeptId(), ProductionProductMain::getDeptId);
        mainWrapper.in(ProductionProductMain::getProductionOperationTaskId, taskIdToOrderId.keySet());
        if (range.hasDateFilter()) {
            mainWrapper.ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay().minusMonths(2))
                    .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay().plusMonths(1));
        }
        List<ProductionProductMain> mainList = defaultList(productionProductMainMapper.selectList(mainWrapper));
        Map<Long, Set<Long>> mainIdToLedgers = new HashMap<>();
        for (ProductionProductMain main : mainList) {
            Long orderId = taskIdToOrderId.get(main.getProductionOperationTaskId());
            if (orderId == null) {
                continue;
            }
            Set<Long> ledgerSet = orderIdToLedgerIds.get(orderId);
            if (ledgerSet == null || ledgerSet.isEmpty()) {
                continue;
            }
            mainIdToLedgers.put(main.getId(), ledgerSet);
        }
        if (mainIdToLedgers.isEmpty()) {
            return ProductionCostContext.empty();
        }
        LambdaQueryWrapper<ProductionAccount> accountWrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(accountWrapper, loginUser.getCurrentDeptId(), ProductionAccount::getDeptId);
        accountWrapper.in(ProductionAccount::getProductionProductMainId, mainIdToLedgers.keySet());
        if (range.hasDateFilter()) {
            accountWrapper.ge(ProductionAccount::getSchedulingDate, range.start().atStartOfDay())
                    .lt(ProductionAccount::getSchedulingDate, range.end().plusDays(1).atStartOfDay());
        }
        List<ProductionAccount> accountList = defaultList(productionAccountMapper.selectList(accountWrapper));
        Map<String, BigDecimal> salaryQuotaByOperation = defaultList(technologyOperationMapper.selectList(new LambdaQueryWrapper<TechnologyOperation>()
                        .select(TechnologyOperation::getName, TechnologyOperation::getSalaryQuota)))
                .stream()
                .filter(item -> StringUtils.hasText(item.getName()))
                .collect(Collectors.toMap(TechnologyOperation::getName, item -> defaultDecimal(item.getSalaryQuota()), (a, b) -> a));
        Map<Long, BigDecimal> laborCostByLedger = new HashMap<>();
        Map<String, BigDecimal> processCostMap = new HashMap<>();
        for (ProductionAccount account : accountList) {
            Set<Long> ledgerSet = mainIdToLedgers.get(account.getProductionProductMainId());
            if (ledgerSet == null || ledgerSet.isEmpty()) {
                continue;
            }
            BigDecimal cost = estimateLaborCost(account, salaryQuotaByOperation);
            if (cost.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            BigDecimal split = cost.divide(new BigDecimal(ledgerSet.size()), 4, RoundingMode.HALF_UP);
            for (Long ledgerId : ledgerSet) {
                laborCostByLedger.merge(ledgerId, split, BigDecimal::add);
            }
            processCostMap.merge(safe(account.getTechnologyOperationName()), cost, BigDecimal::add);
        }
        LambdaQueryWrapper<ProductionProductOutput> outputWrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(outputWrapper, loginUser.getCurrentDeptId(), ProductionProductOutput::getDeptId);
        outputWrapper.in(ProductionProductOutput::getProductionProductMainId, mainIdToLedgers.keySet());
        if (range.hasDateFilter()) {
            outputWrapper.ge(ProductionProductOutput::getCreateTime, range.start().atStartOfDay())
                    .lt(ProductionProductOutput::getCreateTime, range.end().plusDays(1).atStartOfDay());
        }
        List<ProductionProductOutput> outputList = defaultList(productionProductOutputMapper.selectList(outputWrapper));
        Map<Long, BigDecimal> scrapCostByLedger = new HashMap<>();
        for (ProductionProductOutput output : outputList) {
            Set<Long> ledgerSet = mainIdToLedgers.get(output.getProductionProductMainId());
            if (ledgerSet == null || ledgerSet.isEmpty()) {
                continue;
            }
            BigDecimal scrapQty = defaultDecimal(output.getScrapQty());
            if (scrapQty.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            BigDecimal unitCost = avgUnitCostByModelId.getOrDefault(output.getProductModelId(), BigDecimal.ZERO);
            BigDecimal scrapCost = scrapQty.multiply(unitCost);
            BigDecimal split = scrapCost.divide(new BigDecimal(ledgerSet.size()), 4, RoundingMode.HALF_UP);
            for (Long ledgerId : ledgerSet) {
                scrapCostByLedger.merge(ledgerId, split, BigDecimal::add);
            }
        }
        Map<String, BigDecimal> processCostRanking = processCostMap.entrySet().stream()
                .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
                .limit(10)
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new));
        return new ProductionCostContext(laborCostByLedger, scrapCostByLedger, processCostRanking);
    }
    private List<SalesLedger> querySalesLedgers(LoginUser loginUser, DateRange range, String keyword, Integer limit) {
        LambdaQueryWrapper<SalesLedger> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), SalesLedger::getTenantId);
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesLedger::getDeptId);
        if (StringUtils.hasText(keyword)) {
            wrapper.and(w -> w.like(SalesLedger::getSalesContractNo, keyword)
                    .or().like(SalesLedger::getCustomerContractNo, keyword)
                    .or().like(SalesLedger::getCustomerName, keyword)
                    .or().like(SalesLedger::getProjectName, keyword)
                    .or().like(SalesLedger::getSalesman, keyword));
        }
        if (range.hasDateFilter()) {
            wrapper.ge(SalesLedger::getEntryDate, toDate(range.start()))
                    .lt(SalesLedger::getEntryDate, toExclusiveEndDate(range.end()));
        }
        wrapper.orderByDesc(SalesLedger::getEntryDate, SalesLedger::getId);
        if (limit != null && limit > 0) {
            wrapper.last("limit " + normalizeLimit(limit));
        }
        return defaultList(salesLedgerMapper.selectList(wrapper));
    }
    private List<SalesLedgerProduct> queryLedgerProducts(LoginUser loginUser, List<Long> ledgerIds) {
        if (ledgerIds == null || ledgerIds.isEmpty()) {
            return List.of();
        }
        LambdaQueryWrapper<SalesLedgerProduct> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesLedgerProduct::getDeptId);
        wrapper.in(SalesLedgerProduct::getSalesLedgerId, ledgerIds)
                .eq(SalesLedgerProduct::getType, 1);
        return defaultList(salesLedgerProductMapper.selectList(wrapper));
    }
    private List<StockInventory> queryStockInventory(LoginUser loginUser) {
        LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId);
        return defaultList(stockInventoryMapper.selectList(wrapper));
    }
    private Map<Long, ProductModel> queryProductModels(Set<Long> modelIds) {
        if (modelIds == null || modelIds.isEmpty()) {
            return defaultList(productModelMapper.selectList(null)).stream()
                    .filter(item -> item.getId() != null)
                    .collect(Collectors.toMap(ProductModel::getId, item -> item, (a, b) -> a));
        }
        LambdaQueryWrapper<ProductModel> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(ProductModel::getId, modelIds);
        return defaultList(productModelMapper.selectList(wrapper)).stream()
                .filter(item -> item.getId() != null)
                .collect(Collectors.toMap(ProductModel::getId, item -> item, (a, b) -> a));
    }
    private Map<Long, Product> queryProducts(Collection<ProductModel> models) {
        Set<Long> productIds = models == null ? Set.of() : models.stream()
                .map(ProductModel::getProductId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (productIds.isEmpty()) {
            return Map.of();
        }
        LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(Product::getId, productIds);
        return defaultList(productMapper.selectList(wrapper)).stream()
                .filter(item -> item.getId() != null)
                .collect(Collectors.toMap(Product::getId, item -> item, (a, b) -> a));
    }
    private Map<Long, BigDecimal> queryAverageUnitCostByModel(LoginUser loginUser, Set<Long> productModelIds) {
        LambdaQueryWrapper<ProcurementRecordStorage> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementRecordStorage::getTenantId);
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementRecordStorage::getDeptId);
        wrapper.in(ProcurementRecordStorage::getType, List.of(1, 2));
        if (productModelIds != null && !productModelIds.isEmpty()) {
            wrapper.in(ProcurementRecordStorage::getProductModelId, productModelIds);
        }
        List<ProcurementRecordStorage> rows = defaultList(procurementRecordMapper.selectList(wrapper));
        Map<Long, BigDecimal> amountByModel = new HashMap<>();
        Map<Long, BigDecimal> qtyByModel = new HashMap<>();
        for (ProcurementRecordStorage row : rows) {
            if (row.getProductModelId() == null) {
                continue;
            }
            BigDecimal qty = defaultDecimal(row.getInboundNum());
            if (qty.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            BigDecimal amount = defaultDecimal(row.getUnitPrice()).multiply(qty);
            amountByModel.merge(row.getProductModelId(), amount, BigDecimal::add);
            qtyByModel.merge(row.getProductModelId(), qty, BigDecimal::add);
        }
        Map<Long, BigDecimal> result = new HashMap<>();
        for (Map.Entry<Long, BigDecimal> entry : amountByModel.entrySet()) {
            BigDecimal qty = qtyByModel.get(entry.getKey());
            if (qty == null || qty.compareTo(BigDecimal.ZERO) <= 0) {
                continue;
            }
            result.put(entry.getKey(), entry.getValue().divide(qty, 6, RoundingMode.HALF_UP));
        }
        return result;
    }
    private OutboundStats queryOutboundStats(LoginUser loginUser, Set<Long> productModelIds, DateRange range) {
        LambdaQueryWrapper<ProcurementRecordOut> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementRecordOut::getTenantId);
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementRecordOut::getDeptId);
        if (productModelIds != null && !productModelIds.isEmpty()) {
            wrapper.in(ProcurementRecordOut::getProductModelId, productModelIds);
        }
        if (range.hasDateFilter()) {
            wrapper.ge(ProcurementRecordOut::getCreateTime, range.start().atStartOfDay())
                    .lt(ProcurementRecordOut::getCreateTime, range.end().plusDays(1).atStartOfDay());
        }
        List<ProcurementRecordOut> outList = defaultList(procurementRecordOutMapper.selectList(wrapper));
        if (outList.isEmpty()) {
            return OutboundStats.empty();
        }
        Set<Integer> storageIds = outList.stream()
                .map(ProcurementRecordOut::getProcurementRecordStorageId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map<Integer, ProcurementRecordStorage> storageMap = storageIds.isEmpty()
                ? Map.of()
                : defaultList(procurementRecordMapper.selectBatchIds(storageIds)).stream()
                .collect(Collectors.toMap(ProcurementRecordStorage::getId, item -> item, (a, b) -> a));
        Map<Long, BigDecimal> outboundQtyByModel = new HashMap<>();
        Map<Long, LocalDateTime> lastOutboundTimeByModel = new HashMap<>();
        BigDecimal totalOutboundCost = BigDecimal.ZERO;
        for (ProcurementRecordOut out : outList) {
            Long modelId = out.getProductModelId();
            if (modelId == null) {
                continue;
            }
            BigDecimal qty = defaultDecimal(out.getInboundNum());
            outboundQtyByModel.merge(modelId, qty, BigDecimal::add);
            if (out.getCreateTime() != null) {
                LocalDateTime existing = lastOutboundTimeByModel.get(modelId);
                if (existing == null || out.getCreateTime().isAfter(existing)) {
                    lastOutboundTimeByModel.put(modelId, out.getCreateTime());
                }
            }
            ProcurementRecordStorage storage = storageMap.get(out.getProcurementRecordStorageId());
            BigDecimal unitPrice = storage == null ? BigDecimal.ZERO : defaultDecimal(storage.getUnitPrice());
            totalOutboundCost = totalOutboundCost.add(unitPrice.multiply(qty));
        }
        return new OutboundStats(outboundQtyByModel, lastOutboundTimeByModel, totalOutboundCost);
    }
    private List<InventoryMetric> buildInventoryMetrics(List<StockInventory> inventoryRows,
                                                        Map<Long, ProductModel> productModelMap,
                                                        Map<Long, Product> productMap,
                                                        Map<Long, BigDecimal> avgUnitCostByModelId,
                                                        OutboundStats outboundStats) {
        Map<Long, InventoryMetricBuilder> metricBuilderByModel = new HashMap<>();
        for (StockInventory row : inventoryRows) {
            if (row.getProductModelId() == null) {
                continue;
            }
            InventoryMetricBuilder builder = metricBuilderByModel.computeIfAbsent(row.getProductModelId(), InventoryMetricBuilder::new);
            builder.addQuantity(maxZero(defaultDecimal(row.getQualitity()).subtract(defaultDecimal(row.getLockedQuantity()))));
            builder.addLockedQuantity(defaultDecimal(row.getLockedQuantity()));
            builder.addWarnNum(defaultDecimal(row.getWarnNum()));
            if (row.getCreateTime() != null) {
                builder.updateFirstInTime(row.getCreateTime());
            }
        }
        List<InventoryMetric> result = new ArrayList<>();
        LocalDate today = LocalDate.now();
        for (InventoryMetricBuilder builder : metricBuilderByModel.values()) {
            Long modelId = builder.modelId();
            ProductModel model = productModelMap.get(modelId);
            Product product = model == null ? null : productMap.get(model.getProductId());
            BigDecimal unitCost = avgUnitCostByModelId.getOrDefault(modelId, BigDecimal.ZERO);
            BigDecimal value = builder.quantity().multiply(unitCost);
            LocalDateTime lastOutTime = outboundStats.lastOutboundTimeByModel().get(modelId);
            long stagnantDays;
            if (lastOutTime != null) {
                stagnantDays = daysBetween(lastOutTime.toLocalDate(), today);
            } else if (builder.firstInTime() != null) {
                stagnantDays = daysBetween(builder.firstInTime().toLocalDate(), today);
            } else {
                stagnantDays = 0;
            }
            BigDecimal outQty = outboundStats.outboundQtyByModel().getOrDefault(modelId, BigDecimal.ZERO);
            boolean overstock = builder.warnNum().compareTo(BigDecimal.ZERO) > 0
                    && builder.quantity().compareTo(builder.warnNum().multiply(new BigDecimal("3"))) > 0;
            result.add(new InventoryMetric(
                    modelId,
                    product == null ? "未知产品" : safe(product.getProductName()),
                    model == null ? "未知型号" : safe(model.getModel()),
                    builder.quantity(),
                    builder.lockedQuantity(),
                    unitCost,
                    value,
                    outQty,
                    stagnantDays,
                    overstock
            ));
        }
        return result;
    }
    private List<AccountSalesCollection> queryCollections(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<AccountSalesCollection> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountSalesCollection::getDeptId);
        if (range.hasDateFilter()) {
            wrapper.ge(AccountSalesCollection::getCollectionDate, range.start())
                    .le(AccountSalesCollection::getCollectionDate, range.end());
        }
        wrapper.orderByAsc(AccountSalesCollection::getCollectionDate);
        return defaultList(accountSalesCollectionMapper.selectList(wrapper));
    }
    private List<AccountPurchasePayment> queryPayments(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<AccountPurchasePayment> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountPurchasePayment::getDeptId);
        if (range.hasDateFilter()) {
            wrapper.ge(AccountPurchasePayment::getPaymentDate, range.start())
                    .le(AccountPurchasePayment::getPaymentDate, range.end());
        }
        wrapper.orderByAsc(AccountPurchasePayment::getPaymentDate);
        return defaultList(accountPurchasePaymentMapper.selectList(wrapper));
    }
    private List<MonthlyCashFlow> buildMonthlyCashFlow(DateRange range,
                                                       List<AccountSalesCollection> collections,
                                                       List<AccountPurchasePayment> payments) {
        Map<YearMonth, BigDecimal> incomeByMonth = new LinkedHashMap<>();
        Map<YearMonth, BigDecimal> expenseByMonth = new LinkedHashMap<>();
        DateRange monthlyRange = range.hasDateFilter() ? range : inferCashFlowRange(collections, payments);
        if (!monthlyRange.hasDateFilter()) {
            return List.of();
        }
        YearMonth startMonth = YearMonth.from(monthlyRange.start());
        YearMonth endMonth = YearMonth.from(monthlyRange.end());
        for (YearMonth month = startMonth; !month.isAfter(endMonth); month = month.plusMonths(1)) {
            incomeByMonth.put(month, BigDecimal.ZERO);
            expenseByMonth.put(month, BigDecimal.ZERO);
        }
        for (AccountSalesCollection row : collections) {
            if (row.getCollectionDate() == null) {
                continue;
            }
            YearMonth month = YearMonth.from(row.getCollectionDate());
            if (incomeByMonth.containsKey(month)) {
                incomeByMonth.put(month, incomeByMonth.get(month).add(defaultDecimal(row.getCollectionAmount())));
            }
        }
        for (AccountPurchasePayment row : payments) {
            if (row.getPaymentDate() == null) {
                continue;
            }
            YearMonth month = YearMonth.from(row.getPaymentDate());
            if (expenseByMonth.containsKey(month)) {
                expenseByMonth.put(month, expenseByMonth.get(month).add(defaultDecimal(row.getPaymentAmount())));
            }
        }
        List<MonthlyCashFlow> result = new ArrayList<>();
        for (YearMonth month : incomeByMonth.keySet()) {
            BigDecimal income = incomeByMonth.get(month);
            BigDecimal expense = expenseByMonth.getOrDefault(month, BigDecimal.ZERO);
            result.add(new MonthlyCashFlow(month.toString(), income, expense, income.subtract(expense)));
        }
        return result;
    }
    private DateRange inferCashFlowRange(List<AccountSalesCollection> collections,
                                         List<AccountPurchasePayment> payments) {
        LocalDate min = null;
        LocalDate max = null;
        for (AccountSalesCollection row : defaultList(collections)) {
            if (row.getCollectionDate() == null) {
                continue;
            }
            min = min == null || row.getCollectionDate().isBefore(min) ? row.getCollectionDate() : min;
            max = max == null || row.getCollectionDate().isAfter(max) ? row.getCollectionDate() : max;
        }
        for (AccountPurchasePayment row : defaultList(payments)) {
            if (row.getPaymentDate() == null) {
                continue;
            }
            min = min == null || row.getPaymentDate().isBefore(min) ? row.getPaymentDate() : min;
            max = max == null || row.getPaymentDate().isAfter(max) ? row.getPaymentDate() : max;
        }
        return min == null || max == null ? new DateRange(null, null, "全部") : new DateRange(min, max, "全部");
    }
    private List<MonthlyCashFlow> forecastMonthlyCashFlow(List<MonthlyCashFlow> actual, int forecastMonths) {
        if (actual.isEmpty()) {
            List<MonthlyCashFlow> defaults = new ArrayList<>();
            YearMonth now = YearMonth.now();
            for (int i = 1; i <= forecastMonths; i++) {
                YearMonth month = now.plusMonths(i);
                defaults.add(new MonthlyCashFlow(month.toString(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO));
            }
            return defaults;
        }
        List<BigDecimal> series = actual.stream().map(MonthlyCashFlow::netFlow).toList();
        BigDecimal avg = series.stream().reduce(BigDecimal.ZERO, BigDecimal::add)
                .divide(new BigDecimal(series.size()), 4, RoundingMode.HALF_UP);
        BigDecimal slope = BigDecimal.ZERO;
        if (series.size() > 1) {
            slope = series.get(series.size() - 1).subtract(series.get(0))
                    .divide(new BigDecimal(series.size() - 1), 4, RoundingMode.HALF_UP);
        }
        YearMonth lastMonth = YearMonth.parse(actual.get(actual.size() - 1).month());
        List<MonthlyCashFlow> forecast = new ArrayList<>();
        for (int i = 1; i <= forecastMonths; i++) {
            YearMonth month = lastMonth.plusMonths(i);
            BigDecimal net = avg.add(slope.multiply(new BigDecimal(i))).setScale(2, RoundingMode.HALF_UP);
            BigDecimal income = net.compareTo(BigDecimal.ZERO) >= 0 ? net : BigDecimal.ZERO;
            BigDecimal expense = net.compareTo(BigDecimal.ZERO) >= 0 ? BigDecimal.ZERO : net.abs();
            forecast.add(new MonthlyCashFlow(month.toString(), income, expense, net));
        }
        return forecast;
    }
    private StatementSnapshot buildStatementSnapshot(LoginUser loginUser) {
        LambdaQueryWrapper<AccountStatement> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountStatement::getDeptId);
        wrapper.orderByDesc(AccountStatement::getStatementMonth);
        List<AccountStatement> rows = defaultList(accountStatementMapper.selectList(wrapper));
        if (rows.isEmpty()) {
            return StatementSnapshot.empty();
        }
        Map<String, AccountStatement> latestByEntity = new HashMap<>();
        for (AccountStatement row : rows) {
            if (row.getAccountType() == null || row.getCustomerId() == null || !StringUtils.hasText(row.getStatementMonth())) {
                continue;
            }
            String key = row.getAccountType() + "::" + row.getCustomerId();
            AccountStatement existing = latestByEntity.get(key);
            if (existing == null || row.getStatementMonth().compareTo(existing.getStatementMonth()) > 0) {
                latestByEntity.put(key, row);
            }
        }
        BigDecimal receivableTotal = BigDecimal.ZERO;
        BigDecimal payableTotal = BigDecimal.ZERO;
        List<StatementMetric> receivableMetrics = new ArrayList<>();
        List<StatementMetric> payableMetrics = new ArrayList<>();
        for (AccountStatement row : latestByEntity.values()) {
            BigDecimal closing = defaultDecimal(row.getClosingBalance());
            if (Objects.equals(row.getAccountType(), 1)) {
                receivableTotal = receivableTotal.add(closing);
                receivableMetrics.add(new StatementMetric(String.valueOf(row.getCustomerId()), closing,
                        defaultDecimal(row.getCurrentPlan()), defaultDecimal(row.getCurrentActually()), safe(row.getStatementMonth())));
            } else if (Objects.equals(row.getAccountType(), 2)) {
                payableTotal = payableTotal.add(closing);
                payableMetrics.add(new StatementMetric(String.valueOf(row.getCustomerId()), closing,
                        defaultDecimal(row.getCurrentPlan()), defaultDecimal(row.getCurrentActually()), safe(row.getStatementMonth())));
            }
        }
        receivableMetrics.sort(Comparator.comparing(StatementMetric::closingBalance).reversed());
        payableMetrics.sort(Comparator.comparing(StatementMetric::closingBalance).reversed());
        return new StatementSnapshot(
                receivableTotal,
                payableTotal,
                receivableMetrics.stream().limit(10).toList(),
                payableMetrics.stream().limit(10).toList()
        );
    }
    private BigDecimal calculateTotalDepreciation(LoginUser loginUser) {
        LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId);
        wrapper.eq(DeviceLedger::getIsDepr, 1);
        List<DeviceLedger> devices = defaultList(deviceLedgerMapper.selectList(wrapper));
        BigDecimal total = BigDecimal.ZERO;
        for (DeviceLedger device : devices) {
            total = total.add(defaultDecimal(AccountingServiceImpl.calculatePreciseDepreciation(device)));
        }
        return total;
    }
    private Map<Long, BigDecimal> allocateDepreciation(List<SalesLedger> ledgers, BigDecimal totalDepreciation, BigDecimal totalRevenue) {
        if (ledgers.isEmpty() || totalDepreciation.compareTo(BigDecimal.ZERO) <= 0) {
            return Map.of();
        }
        Map<Long, BigDecimal> result = new HashMap<>();
        if (totalRevenue.compareTo(BigDecimal.ZERO) <= 0) {
            BigDecimal avg = totalDepreciation.divide(new BigDecimal(ledgers.size()), 4, RoundingMode.HALF_UP);
            for (SalesLedger ledger : ledgers) {
                result.put(ledger.getId(), avg);
            }
            return result;
        }
        for (SalesLedger ledger : ledgers) {
            BigDecimal revenue = defaultDecimal(ledger.getContractAmount());
            BigDecimal ratio = revenue.divide(totalRevenue, 6, RoundingMode.HALF_UP);
            result.put(ledger.getId(), totalDepreciation.multiply(ratio));
        }
        return result;
    }
    private BigDecimal fallbackMaterialCost(List<SalesLedgerProduct> products, BigDecimal revenue) {
        if (products != null && !products.isEmpty()) {
            BigDecimal productAmount = products.stream()
                    .map(SalesLedgerProduct::getTaxExclusiveTotalPrice)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            if (productAmount.compareTo(BigDecimal.ZERO) > 0) {
                return productAmount;
            }
        }
        BigDecimal materialCost = revenue.multiply(DEFAULT_FALLBACK_MATERIAL_COST_RATE);
        BigDecimal laborCost = revenue.multiply(DEFAULT_LABOR_COST_RATE);
        BigDecimal overheadCost = revenue.multiply(DEFAULT_OVERHEAD_COST_RATE);
        return materialCost.add(laborCost).add(overheadCost);
    }
    private BigDecimal estimateTotalCost(BigDecimal revenue, List<SalesLedgerProduct> products) {
        if (revenue == null || revenue.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        BigDecimal materialCost = BigDecimal.ZERO;
        if (products != null && !products.isEmpty()) {
            materialCost = products.stream()
                    .map(SalesLedgerProduct::getTaxExclusiveTotalPrice)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
        }
        if (materialCost.compareTo(BigDecimal.ZERO) <= 0) {
            materialCost = revenue.multiply(DEFAULT_FALLBACK_MATERIAL_COST_RATE);
        }
        BigDecimal laborCost = revenue.multiply(DEFAULT_LABOR_COST_RATE);
        BigDecimal overheadCost = revenue.multiply(DEFAULT_OVERHEAD_COST_RATE);
        return materialCost.add(laborCost).add(overheadCost);
    }
    private Map<String, String> queryCustomerNameMap(Set<String> idSet) {
        if (idSet == null || idSet.isEmpty()) {
            return Map.of();
        }
        Set<Long> ids = idSet.stream()
                .map(this::toLongOrNull)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (ids.isEmpty()) {
            return Map.of();
        }
        LambdaQueryWrapper<Customer> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(Customer::getId, ids);
        return defaultList(customerMapper.selectList(wrapper)).stream()
                .collect(Collectors.toMap(item -> String.valueOf(item.getId()), Customer::getCustomerName, (a, b) -> a));
    }
    private Map<String, String> querySupplierNameMap(Set<String> idSet) {
        if (idSet == null || idSet.isEmpty()) {
            return Map.of();
        }
        Set<Long> ids = idSet.stream()
                .map(this::toLongOrNull)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (ids.isEmpty()) {
            return Map.of();
        }
        LambdaQueryWrapper<SupplierManage> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(SupplierManage::getId, ids);
        return defaultList(supplierManageMapper.selectList(wrapper)).stream()
                .collect(Collectors.toMap(item -> String.valueOf(item.getId()), SupplierManage::getSupplierName, (a, b) -> a));
    }
    private String riskLevelByAmount(BigDecimal amount) {
        if (amount.compareTo(new BigDecimal("5000000")) >= 0) {
            return "high";
        }
        if (amount.compareTo(new BigDecimal("1000000")) >= 0) {
            return "medium";
        }
        return "low";
    }
    private long countDevices(LoginUser loginUser) {
        LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId);
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId);
        return deviceLedgerMapper.selectCount(wrapper);
    }
    private long countRepairingDevices(LoginUser loginUser) {
        LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId);
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId);
        wrapper.in(DeviceRepair::getStatus, List.of(0, 3));
        return deviceRepairMapper.selectCount(wrapper);
    }
    private BigDecimal estimateInventoryValue(LoginUser loginUser, List<StockInventory> inventories) {
        if (inventories == null || inventories.isEmpty()) {
            return BigDecimal.ZERO;
        }
        Set<Long> modelIds = inventories.stream().map(StockInventory::getProductModelId).filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, BigDecimal> costMap = queryAverageUnitCostByModel(loginUser, modelIds);
        BigDecimal total = BigDecimal.ZERO;
        for (StockInventory inventory : inventories) {
            BigDecimal qty = maxZero(defaultDecimal(inventory.getQualitity()).subtract(defaultDecimal(inventory.getLockedQuantity())));
            BigDecimal unit = costMap.getOrDefault(inventory.getProductModelId(), BigDecimal.ZERO);
            total = total.add(qty.multiply(unit));
        }
        return total;
    }
    private DateRange previousSameLengthRange(DateRange range) {
        if (!range.hasDateFilter()) {
            return new DateRange(null, null, "全部");
        }
        long days = daysBetween(range.start(), range.end()) + 1L;
        LocalDate prevEnd = range.start().minusDays(1);
        LocalDate prevStart = prevEnd.minusDays(days - 1L);
        return new DateRange(prevStart, prevEnd, prevStart + "至" + prevEnd);
    }
    private List<String> buildProfitReasons(BigDecimal revenue,
                                            BigDecimal materialCost,
                                            BigDecimal laborCost,
                                            BigDecimal scrapCost,
                                            BigDecimal profit,
                                            BigDecimal profitRate) {
        List<String> reasons = new ArrayList<>();
        BigDecimal materialRate = rate(materialCost, revenue);
        if (materialRate.compareTo(new BigDecimal("0.70")) >= 0) {
            reasons.add("材料成本占比超过70%");
        } else if (materialRate.compareTo(new BigDecimal("0.55")) >= 0) {
            reasons.add("材料成本占比偏高");
        }
        BigDecimal laborRate = rate(laborCost, revenue);
        if (laborRate.compareTo(new BigDecimal("0.20")) >= 0) {
            reasons.add("人工成本占比超过20%");
        } else if (laborRate.compareTo(new BigDecimal("0.12")) >= 0) {
            reasons.add("人工成本增长偏快");
        }
        BigDecimal scrapRate = rate(scrapCost, revenue);
        if (scrapRate.compareTo(new BigDecimal("0.05")) >= 0) {
            reasons.add("报废损耗占比偏高");
        }
        if (profit.compareTo(BigDecimal.ZERO) < 0) {
            reasons.add("订单处于亏损状态");
        } else if (profitRate.compareTo(new BigDecimal("0.08")) < 0) {
            reasons.add("利润率低于8%");
        }
        if (reasons.isEmpty()) {
            reasons.add("成本结构处于合理区间");
        }
        return reasons;
    }
    private String buildProfitSuggestion(String riskLevel, List<String> reasons) {
        if ("high".equals(riskLevel)) {
            return "优先复核BOM用量与工序定额,必要时调整报价和付款条款,并限制超账期交付。";
        }
        if ("medium".equals(riskLevel)) {
            return "建议优化采购批次和工序排产,提升一次合格率并同步执行毛利预警。";
        }
        if (reasons.stream().anyMatch(item -> item.contains("材料"))) {
            return "保持材料采购成本看板,按周跟踪主要材料单价波动。";
        }
        return "维持当前经营节奏,继续跟踪订单利润率和回款效率。";
    }
    private Map<String, Object> toOrderCostItem(OrderProfitMetric metric) {
        Map<String, Object> item = new LinkedHashMap<>();
        item.put("ledgerId", metric.ledgerId());
        item.put("salesContractNo", metric.salesContractNo());
        item.put("customerName", metric.customerName());
        item.put("projectName", metric.projectName());
        item.put("entryDate", formatDate(metric.entryDate()));
        item.put("deliveryDate", formatDate(metric.deliveryDate()));
        item.put("revenue", metric.revenue());
        item.put("materialCost", metric.materialCost());
        item.put("laborCost", metric.laborCost());
        item.put("depreciationCost", metric.depreciationCost());
        item.put("scrapCost", metric.scrapCost());
        item.put("totalCost", metric.totalCost());
        item.put("profit", metric.profit());
        item.put("profitRate", toPercent(metric.profitRate()));
        item.put("riskLevel", metric.riskLevel());
        item.put("reasons", metric.reasons());
        item.put("suggestion", metric.suggestion());
        return item;
    }
    private Map<String, Object> toRiskOrderItem(OrderProfitMetric metric) {
        Map<String, Object> map = toOrderCostItem(metric);
        map.put("priority", "high".equals(metric.riskLevel()) ? "high" : ("medium".equals(metric.riskLevel()) ? "medium" : "low"));
        return map;
    }
    private List<Map<String, Object>> buildCustomerProfitTop(List<OrderProfitMetric> metrics, int topN) {
        Map<String, BigDecimal> customerProfitMap = new HashMap<>();
        Map<String, BigDecimal> customerRevenueMap = new HashMap<>();
        for (OrderProfitMetric metric : metrics) {
            customerProfitMap.merge(metric.customerName(), metric.profit(), BigDecimal::add);
            customerRevenueMap.merge(metric.customerName(), metric.revenue(), BigDecimal::add);
        }
        return customerProfitMap.entrySet().stream()
                .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
                .limit(topN)
                .map(entry -> {
                    Map<String, Object> map = new LinkedHashMap<>();
                    BigDecimal revenue = customerRevenueMap.getOrDefault(entry.getKey(), BigDecimal.ZERO);
                    map.put("customerName", entry.getKey());
                    map.put("profit", entry.getValue());
                    map.put("revenue", revenue);
                    map.put("profitRate", toPercent(rate(entry.getValue(), revenue)));
                    return map;
                })
                .toList();
    }
    private Map<String, Object> toInventoryItem(InventoryMetric metric) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("productModelId", metric.modelId());
        map.put("productName", metric.productName());
        map.put("model", metric.modelName());
        map.put("quantity", metric.quantity());
        map.put("lockedQuantity", metric.lockedQuantity());
        map.put("avgUnitCost", metric.avgUnitCost());
        map.put("inventoryValue", metric.inventoryValue());
        map.put("outboundQuantity", metric.outboundQuantity());
        map.put("stagnantDays", metric.stagnantDays());
        map.put("overstock", metric.overstock());
        map.put("riskLevel", metric.stagnantDays() >= 90 ? "high" : (metric.stagnantDays() >= 30 ? "medium" : "low"));
        return map;
    }
    private boolean matchInventoryKeyword(InventoryMetric metric, String keyword) {
        if (!StringUtils.hasText(keyword)) {
            return true;
        }
        return metric.productName().contains(keyword.trim()) || metric.modelName().contains(keyword.trim());
    }
    private Map<String, Object> toMonthlyCashFlowItem(MonthlyCashFlow flow) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("month", flow.month());
        map.put("income", flow.income());
        map.put("expense", flow.expense());
        map.put("netFlow", flow.netFlow());
        return map;
    }
    private Map<String, Object> toStatementRiskItem(StatementMetric metric, Map<String, String> nameMap, String type) {
        BigDecimal actualRate = rate(metric.actualAmount(), metric.planAmount());
        Map<String, Object> map = new LinkedHashMap<>();
        map.put(type + "Id", metric.entityId());
        map.put(type + "Name", safe(nameMap.get(metric.entityId())));
        map.put("statementMonth", metric.statementMonth());
        map.put("closingBalance", metric.closingBalance());
        map.put("planAmount", metric.planAmount());
        map.put("actualAmount", metric.actualAmount());
        map.put("actualRate", toPercent(actualRate));
        map.put("riskLevel", metric.closingBalance().compareTo(new BigDecimal("1000000")) > 0 || actualRate.compareTo(new BigDecimal("0.50")) < 0 ? "high" : "medium");
        return map;
    }
    private Map<String, Object> anomalyItem(String level, String type, String message, Map<String, Object> detail) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("riskLevel", level);
        map.put("type", type);
        map.put("message", message);
        map.put("detail", detail);
        return map;
    }
    private Map<String, Object> riskSuggestion(String type, String level, String suggestion) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("type", type);
        map.put("level", level);
        map.put("suggestion", suggestion);
        return map;
    }
    private Map<String, Object> buildCostCompositionPie(BigDecimal material, BigDecimal labor, BigDecimal depreciation, BigDecimal scrap) {
        List<Map<String, Object>> data = List.of(
                Map.of("name", "材料成本", "value", material),
                Map.of("name", "人工成本", "value", labor),
                Map.of("name", "折旧成本", "value", depreciation),
                Map.of("name", "损耗成本", "value", scrap)
        );
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "成本构成", "left", "center"));
        option.put("tooltip", Map.of("trigger", "item"));
        option.put("series", List.of(Map.of("name", "成本构成", "type", "pie", "radius", "60%", "data", data)));
        return option;
    }
    private Map<String, Object> buildOrderProfitBar(List<OrderProfitMetric> metrics) {
        List<OrderProfitMetric> top = metrics.stream()
                .sorted(Comparator.comparing(OrderProfitMetric::profit))
                .limit(10)
                .toList();
        List<String> xData = top.stream().map(OrderProfitMetric::salesContractNo).toList();
        List<BigDecimal> yData = top.stream().map(OrderProfitMetric::profit).toList();
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "订单利润分布", "left", "center"));
        option.put("tooltip", Map.of("trigger", "axis"));
        option.put("xAxis", Map.of("type", "category", "data", xData));
        option.put("yAxis", Map.of("type", "value"));
        option.put("series", List.of(Map.of("name", "利润", "type", "bar", "data", yData)));
        return option;
    }
    private Map<String, Object> buildProcessCostBar(Map<String, BigDecimal> processCosts) {
        List<String> xData = new ArrayList<>(processCosts.keySet());
        List<BigDecimal> yData = new ArrayList<>(processCosts.values());
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "工序成本排名", "left", "center"));
        option.put("tooltip", Map.of("trigger", "axis"));
        option.put("xAxis", Map.of("type", "category", "data", xData));
        option.put("yAxis", Map.of("type", "value"));
        option.put("series", List.of(Map.of("name", "成本", "type", "bar", "data", yData)));
        return option;
    }
    private Map<String, Object> buildProfitDistributionBar(List<OrderProfitMetric> metrics) {
        List<OrderProfitMetric> sorted = metrics.stream()
                .sorted(Comparator.comparing(OrderProfitMetric::profitRate))
                .limit(15)
                .toList();
        List<String> xData = sorted.stream().map(OrderProfitMetric::salesContractNo).toList();
        List<BigDecimal> yData = sorted.stream().map(metric -> metric.profitRate().multiply(ONE_HUNDRED).setScale(2, RoundingMode.HALF_UP)).toList();
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "订单利润率分布", "left", "center"));
        option.put("tooltip", Map.of("trigger", "axis"));
        option.put("xAxis", Map.of("type", "category", "data", xData));
        option.put("yAxis", Map.of("type", "value", "name", "%"));
        option.put("series", List.of(Map.of("name", "利润率", "type", "bar", "data", yData)));
        return option;
    }
    private Map<String, Object> buildLossOrderTrendLine(List<OrderProfitMetric> metrics) {
        Map<String, Long> lossByDate = new LinkedHashMap<>();
        List<OrderProfitMetric> sorted = metrics.stream()
                .filter(metric -> metric.entryDate() != null)
                .sorted(Comparator.comparing(OrderProfitMetric::entryDate))
                .toList();
        for (OrderProfitMetric metric : sorted) {
            String day = formatDate(metric.entryDate());
            long inc = metric.profit().compareTo(BigDecimal.ZERO) < 0 ? 1L : 0L;
            lossByDate.merge(day, inc, Long::sum);
        }
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "亏损订单趋势", "left", "center"));
        option.put("tooltip", Map.of("trigger", "axis"));
        option.put("xAxis", Map.of("type", "category", "data", new ArrayList<>(lossByDate.keySet())));
        option.put("yAxis", Map.of("type", "value"));
        option.put("series", List.of(Map.of("name", "亏损订单数", "type", "line", "smooth", true, "data", new ArrayList<>(lossByDate.values()))));
        return option;
    }
    private Map<String, Object> buildCustomerProfitBar(Map<String, BigDecimal> customerProfitMap) {
        List<Map.Entry<String, BigDecimal>> top = customerProfitMap.entrySet().stream()
                .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
                .limit(10)
                .toList();
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "客户利润贡献TOP10", "left", "center"));
        option.put("tooltip", Map.of("trigger", "axis"));
        option.put("xAxis", Map.of("type", "category", "data", top.stream().map(Map.Entry::getKey).toList()));
        option.put("yAxis", Map.of("type", "value"));
        option.put("series", List.of(Map.of("name", "利润", "type", "bar", "data", top.stream().map(Map.Entry::getValue).toList())));
        return option;
    }
    private Map<String, Object> buildInventoryTopBar(List<InventoryMetric> metrics) {
        List<InventoryMetric> top = metrics.stream()
                .sorted(Comparator.comparing(InventoryMetric::inventoryValue).reversed())
                .limit(10)
                .toList();
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "库存资金占用TOP10", "left", "center"));
        option.put("tooltip", Map.of("trigger", "axis"));
        option.put("xAxis", Map.of("type", "category", "data", top.stream().map(item -> item.productName() + "/" + item.modelName()).toList()));
        option.put("yAxis", Map.of("type", "value"));
        option.put("series", List.of(Map.of("name", "资金占用", "type", "bar", "data", top.stream().map(InventoryMetric::inventoryValue).toList())));
        return option;
    }
    private Map<String, Object> buildInventoryAgingPie(List<InventoryMetric> metrics) {
        long normal = metrics.stream().filter(item -> item.stagnantDays() < 30).count();
        long slow = metrics.stream().filter(item -> item.stagnantDays() >= 30 && item.stagnantDays() < 90).count();
        long stagnant = metrics.stream().filter(item -> item.stagnantDays() >= 90).count();
        List<Map<String, Object>> data = List.of(
                Map.of("name", "正常", "value", normal),
                Map.of("name", "缓慢", "value", slow),
                Map.of("name", "呆滞", "value", stagnant)
        );
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "库存库龄分布", "left", "center"));
        option.put("tooltip", Map.of("trigger", "item"));
        option.put("series", List.of(Map.of("type", "pie", "radius", "60%", "data", data)));
        return option;
    }
    private Map<String, Object> buildTurnoverGauge(BigDecimal turnoverDays) {
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "库存周转天数", "left", "center"));
        option.put("series", List.of(Map.of(
                "type", "gauge",
                "min", 0,
                "max", 180,
                "detail", Map.of("formatter", "{value}天"),
                "data", List.of(Map.of("value", turnoverDays, "name", "周转天数"))
        )));
        return option;
    }
    private Map<String, Object> buildCashflowTrend(List<MonthlyCashFlow> actual, List<MonthlyCashFlow> forecast) {
        List<String> labels = new ArrayList<>();
        List<BigDecimal> netActual = new ArrayList<>();
        List<BigDecimal> netForecast = new ArrayList<>();
        for (MonthlyCashFlow point : actual) {
            labels.add(point.month());
            netActual.add(point.netFlow());
            netForecast.add(null);
        }
        for (MonthlyCashFlow point : forecast) {
            labels.add(point.month());
            netActual.add(null);
            netForecast.add(point.netFlow());
        }
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "现金流趋势(实际+预测)", "left", "center"));
        option.put("tooltip", Map.of("trigger", "axis"));
        option.put("xAxis", Map.of("type", "category", "data", labels));
        option.put("yAxis", Map.of("type", "value"));
        option.put("series", List.of(
                Map.of("name", "实际净现金流", "type", "line", "smooth", true, "data", netActual),
                Map.of("name", "预测净现金流", "type", "line", "smooth", true, "data", netForecast)
        ));
        return option;
    }
    private Map<String, Object> buildReceivablePayableBar(BigDecimal receivable, BigDecimal payable) {
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "应收应付余额对比", "left", "center"));
        option.put("tooltip", Map.of("trigger", "axis"));
        option.put("xAxis", Map.of("type", "category", "data", List.of("应收", "应付")));
        option.put("yAxis", Map.of("type", "value"));
        option.put("series", List.of(Map.of("name", "余额", "type", "bar", "data", List.of(receivable, payable))));
        return option;
    }
    private Map<String, Object> buildFundGapGauge(BigDecimal fundGap) {
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "资金缺口", "left", "center"));
        option.put("series", List.of(Map.of(
                "type", "gauge",
                "min", 0,
                "max", 10000000,
                "detail", Map.of("formatter", "{value}"),
                "data", List.of(Map.of("value", fundGap, "name", "资金缺口"))
        )));
        return option;
    }
    private Map<String, Object> buildAnomalyLevelPie(List<Map<String, Object>> anomalies) {
        long high = anomalies.stream().filter(item -> "high".equals(item.get("riskLevel"))).count();
        long medium = anomalies.stream().filter(item -> "medium".equals(item.get("riskLevel"))).count();
        long low = anomalies.stream().filter(item -> "low".equals(item.get("riskLevel"))).count();
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "异常等级分布", "left", "center"));
        option.put("tooltip", Map.of("trigger", "item"));
        option.put("series", List.of(Map.of("type", "pie", "radius", "60%", "data", List.of(
                Map.of("name", "高风险", "value", high),
                Map.of("name", "中风险", "value", medium),
                Map.of("name", "低风险", "value", low)
        ))));
        return option;
    }
    private Map<String, Object> buildAnomalyTypeBar(List<Map<String, Object>> anomalies) {
        Map<String, Long> countByType = new LinkedHashMap<>();
        for (Map<String, Object> anomaly : anomalies) {
            countByType.merge(String.valueOf(anomaly.get("type")), 1L, Long::sum);
        }
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "异常类型分布", "left", "center"));
        option.put("tooltip", Map.of("trigger", "axis"));
        option.put("xAxis", Map.of("type", "category", "data", new ArrayList<>(countByType.keySet())));
        option.put("yAxis", Map.of("type", "value"));
        option.put("series", List.of(Map.of("name", "异常数", "type", "bar", "data", new ArrayList<>(countByType.values()))));
        return option;
    }
    private Map<String, Object> buildInventoryProfitGauge(BigDecimal inventoryValue, BigDecimal profit) {
        BigDecimal ratio = inventoryValue.compareTo(BigDecimal.ZERO) <= 0
                ? BigDecimal.ZERO
                : profit.divide(inventoryValue, 4, RoundingMode.HALF_UP).multiply(ONE_HUNDRED);
        Map<String, Object> option = new LinkedHashMap<>();
        option.put("title", Map.of("text", "利润/库存资金比", "left", "center"));
        option.put("series", List.of(Map.of(
                "type", "gauge",
                "min", -100,
                "max", 100,
                "detail", Map.of("formatter", "{value}%"),
                "data", List.of(Map.of("value", ratio.setScale(2, RoundingMode.HALF_UP), "name", "利润资金比"))
        )));
        return option;
    }
    private int normalizeLimit(Integer limit) {
        if (limit == null || limit <= 0) {
            return DEFAULT_LIMIT;
        }
        return Math.min(limit, MAX_LIMIT);
    }
    private DateRange resolveDateRange(String startDate, String endDate, String timeRange, String defaultLabel) {
        LocalDate today = LocalDate.now();
        LocalDate explicitStart = parseLocalDate(startDate);
        LocalDate explicitEnd = parseLocalDate(endDate);
        if (explicitStart != null || explicitEnd != null) {
            LocalDate start = explicitStart != null ? explicitStart : explicitEnd;
            LocalDate end = explicitEnd != null ? explicitEnd : explicitStart;
            if (start.isAfter(end)) {
                LocalDate temp = start;
                start = end;
                end = temp;
            }
            return new DateRange(start, end, start + "至" + end);
        }
        if (!StringUtils.hasText(timeRange)) {
            return new DateRange(null, null, "全部");
        }
        String text = timeRange.trim();
        if (text.contains("今天")) {
            return new DateRange(today, today, "今天");
        }
        if (text.contains("昨天") || text.contains("昨日")) {
            LocalDate day = today.minusDays(1);
            return new DateRange(day, day, "昨天");
        }
        if (text.contains("本周")) {
            LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1L);
            return new DateRange(start, today, "本周");
        }
        if (text.contains("上周")) {
            LocalDate thisWeekStart = today.minusDays(today.getDayOfWeek().getValue() - 1L);
            LocalDate start = thisWeekStart.minusWeeks(1);
            LocalDate end = thisWeekStart.minusDays(1);
            return new DateRange(start, end, "上周");
        }
        if (text.contains("本月")) {
            return new DateRange(today.withDayOfMonth(1), today, "本月");
        }
        if (text.contains("上月")) {
            YearMonth lastMonth = YearMonth.from(today).minusMonths(1);
            return new DateRange(lastMonth.atDay(1), lastMonth.atEndOfMonth(), "上月");
        }
        if (text.contains("今年") || text.contains("本年")) {
            return new DateRange(today.withDayOfYear(1), today, "今年");
        }
        Matcher relativeMatcher = RELATIVE_PATTERN.matcher(text);
        if (relativeMatcher.find()) {
            int amount = Integer.parseInt(relativeMatcher.group(2));
            String unit = relativeMatcher.group(3);
            LocalDate start = switch (unit) {
                case "天" -> today.minusDays(Math.max(amount - 1L, 0));
                case "周" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1);
                case "个月", "月" -> today.minusMonths(Math.max(amount, 1)).plusDays(1);
                case "å¹´" -> today.minusYears(Math.max(amount, 1)).plusDays(1);
                default -> today.minusDays(29);
            };
            return new DateRange(start, today, "近" + amount + unit);
        }
        Matcher dateMatcher = DATE_PATTERN.matcher(text);
        if (dateMatcher.find()) {
            LocalDate start = parseLocalDate(dateMatcher.group(1));
            LocalDate end = dateMatcher.find() ? parseLocalDate(dateMatcher.group(1)) : start;
            if (start != null && end != null) {
                if (start.isAfter(end)) {
                    LocalDate temp = start;
                    start = end;
                    end = temp;
                }
                return new DateRange(start, end, start + "至" + end);
            }
        }
        return new DateRange(null, null, "全部");
    }
    private LocalDate parseLocalDate(String text) {
        if (!StringUtils.hasText(text)) {
            return null;
        }
        try {
            return LocalDate.parse(text.trim(), DATE_FMT);
        } catch (Exception ignored) {
            return null;
        }
    }
    private Date toDate(LocalDate localDate) {
        return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
    }
    private Date toExclusiveEndDate(LocalDate localDate) {
        return Date.from(localDate.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
    }
    private LocalDate toLocalDate(Date date) {
        if (date == null) {
            return null;
        }
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    }
    private String formatDate(LocalDate date) {
        return date == null ? "" : date.format(DATE_FMT);
    }
    private String displayDate(LocalDate date) {
        return date == null ? "" : date.format(DATE_FMT);
    }
    private long daysBetween(LocalDate start, LocalDate end) {
        if (start == null || end == null || start.isAfter(end)) {
            return 0;
        }
        return end.toEpochDay() - start.toEpochDay();
    }
    private BigDecimal defaultDecimal(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
    private BigDecimal maxZero(BigDecimal value) {
        return value == null || value.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : value;
    }
    private BigDecimal rate(BigDecimal numerator, BigDecimal denominator) {
        if (denominator == null || denominator.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        return defaultDecimal(numerator).divide(denominator, 6, RoundingMode.HALF_UP);
    }
    private String toPercent(BigDecimal decimal) {
        if (decimal == null) {
            return "0.00%";
        }
        BigDecimal rate = decimal.multiply(ONE_HUNDRED).setScale(2, RoundingMode.HALF_UP);
        return rate.toPlainString() + "%";
    }
    private BigDecimal avgRate(List<OrderProfitMetric> metrics) {
        if (metrics == null || metrics.isEmpty()) {
            return BigDecimal.ZERO;
        }
        BigDecimal sum = metrics.stream().map(OrderProfitMetric::profitRate).reduce(BigDecimal.ZERO, BigDecimal::add);
        return sum.divide(new BigDecimal(metrics.size()), 6, RoundingMode.HALF_UP);
    }
    private BigDecimal estimateLaborCost(ProductionAccount account, Map<String, BigDecimal> salaryQuotaByOperation) {
        BigDecimal salaryQuota = salaryQuotaByOperation.getOrDefault(safe(account.getTechnologyOperationName()), BigDecimal.ZERO);
        BigDecimal finishedNum = defaultDecimal(account.getFinishedNum());
        BigDecimal workHours = defaultDecimal(account.getWorkHours());
        if (salaryQuota.compareTo(BigDecimal.ZERO) > 0 && finishedNum.compareTo(BigDecimal.ZERO) > 0) {
            return finishedNum.multiply(salaryQuota);
        }
        if (salaryQuota.compareTo(BigDecimal.ZERO) > 0 && workHours.compareTo(BigDecimal.ZERO) > 0) {
            return workHours.multiply(salaryQuota);
        }
        if (workHours.compareTo(BigDecimal.ZERO) > 0) {
            return workHours;
        }
        return finishedNum;
    }
    private List<Long> parseIdList(String raw) {
        if (!StringUtils.hasText(raw)) {
            return List.of();
        }
        String text = raw.replace("[", "").replace("]", "").replace(" ", "");
        if (!StringUtils.hasText(text)) {
            return List.of();
        }
        List<Long> result = new ArrayList<>();
        for (String part : text.split(",")) {
            if (!StringUtils.hasText(part)) {
                continue;
            }
            try {
                result.add(Long.parseLong(part.trim()));
            } catch (Exception ignored) {
            }
        }
        return result;
    }
    private int keywordHitCount(List<String> keywords, String question) {
        if (!StringUtils.hasText(question) || keywords == null) {
            return 0;
        }
        int count = 0;
        for (String keyword : keywords) {
            if (question.contains(keyword)) {
                count++;
            }
        }
        return count;
    }
    private String normalizeForMatch(String text) {
        if (!StringUtils.hasText(text)) {
            return "";
        }
        return text.replace(",", "")
                .replace(",", "")
                .replace("。", "")
                .replace(".", "")
                .replace("!", "")
                .replace("!", "")
                .replace("?", "")
                .replace("?", "")
                .replace(":", "")
                .replace(":", "")
                .replace(";", "")
                .replace(";", "")
                .replace(" ", "")
                .trim();
    }
    private String safe(Object value) {
        return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' ').trim();
    }
    private LoginUser currentLoginUser(String memoryId) {
        LoginUser loginUser = aiSessionUserContext.get(memoryId);
        if (loginUser != null) {
            return loginUser;
        }
        return SecurityUtils.getLoginUser();
    }
    private Map<String, Object> rangeSummary(DateRange range, int count, String keyword) {
        Map<String, Object> summary = new LinkedHashMap<>();
        summary.put("timeRange", range.label());
        summary.put("startDate", displayDate(range.start()));
        summary.put("endDate", displayDate(range.end()));
        summary.put("count", count);
        summary.put("keyword", safe(keyword));
        return summary;
    }
    private Long toLongOrNull(String value) {
        if (!StringUtils.hasText(value)) {
            return null;
        }
        try {
            return Long.valueOf(value.trim());
        } catch (Exception ignored) {
            return null;
        }
    }
    private <T> List<T> defaultList(List<T> list) {
        return list == null ? List.of() : list;
    }
    private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, SFunction<T, Long> field) {
        if (tenantId != null) {
            wrapper.eq(field, tenantId);
        }
    }
    private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, SFunction<T, Long> field) {
        if (deptId != null) {
            wrapper.eq(field, deptId);
        }
    }
    private List<KnowledgeDoc> financeKnowledgeBase() {
        return List.of(
                new KnowledgeDoc(
                        "利润下降分析框架",
                        List.of("利润下降", "亏损订单", "毛利率", "净利率"),
                        "先看收入端(订单结构、单价、交付延迟),再看成本端(材料、人工、折旧、损耗),最后看现金端(回款、账期、坏账风险)。",
                        List.of("sales_ledger", "sales_ledger_product", "production_account", "device_ledger", "account_statement"),
                        List.of("为什么本月利润下降?", "哪些订单亏损最严重?", "成本上升来自哪个工序?")
                ),
                new KnowledgeDoc(
                        "库存资金占用诊断",
                        List.of("库存积压", "呆滞库存", "周转率", "资金占用"),
                        "库存资金诊断重点看:库存价值、近30天出库成本、呆滞天数、超储比例,形成去库存与采购节奏联动策略。",
                        List.of("stock_inventory", "procurement_record_storage", "procurement_record_out"),
                        List.of("哪些物料资金占用最高?", "哪些库存超过90天未周转?", "库存周转天数是否异常?")
                ),
                new KnowledgeDoc(
                        "现金流与账款风险",
                        List.of("现金流", "应收", "应付", "回款", "资金缺口"),
                        "现金流判断要结合收款、付款、应收应付余额与预测净流量,重点关注高余额客户和高集中付款供应商。",
                        List.of("account_sales_collection", "account_purchase_payment", "account_statement"),
                        List.of("未来三个月是否有资金缺口?", "哪个客户回款风险最高?", "付款压力最大的是哪些供应商?")
                ),
                new KnowledgeDoc(
                        "业财一体化口径",
                        List.of("业财融合", "业财联动", "口径", "驾驶舱"),
                        "订单利润口径=销售收入-材料成本-人工成本-设备折旧-损耗成本;经营驾驶舱联动订单、生产、库存、设备、账款数据。",
                        List.of("sales_ledger", "production_operation_task", "production_product_main", "device_ledger", "stock_inventory", "account_statement"),
                        List.of("订单利润率如何计算?", "经营驾驶舱核心指标有哪些?")
                )
        );
    }
    private String jsonResponse(boolean success,
                                String type,
                                String description,
                                Map<String, Object> summary,
                                Map<String, Object> data,
                                Map<String, Object> charts) {
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("success", success);
        result.put("type", type);
        result.put("description", description);
        result.put("summary", summary == null ? Map.of() : summary);
        result.put("data", data == null ? Map.of() : data);
        result.put("charts", charts == null ? Map.of() : charts);
        return JSON.toJSONString(result);
    }
    private record DateRange(LocalDate start, LocalDate end, String label) {
        private boolean hasDateFilter() {
            return start != null && end != null;
        }
    }
    private record OrderProfitMetric(Long ledgerId,
                                     String salesContractNo,
                                     String customerName,
                                     String projectName,
                                     LocalDate entryDate,
                                     LocalDate deliveryDate,
                                     BigDecimal revenue,
                                     BigDecimal materialCost,
                                     BigDecimal laborCost,
                                     BigDecimal depreciationCost,
                                     BigDecimal scrapCost,
                                     BigDecimal totalCost,
                                     BigDecimal profit,
                                     BigDecimal profitRate,
                                     String riskLevel,
                                     List<String> reasons,
                                     String suggestion) {
    }
    private record AnalysisBundle(List<OrderProfitMetric> orderMetrics,
                                  Map<String, BigDecimal> processCostRanking,
                                  BigDecimal totalRevenue,
                                  BigDecimal totalMaterialCost,
                                  BigDecimal totalLaborCost,
                                  BigDecimal totalDepreciationCost,
                                  BigDecimal totalScrapCost,
                                  BigDecimal totalCost,
                                  BigDecimal totalProfit) {
        private static AnalysisBundle empty() {
            return new AnalysisBundle(List.of(), Map.of(), BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO,
                    BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);
        }
    }
    private record MaterialCostResult(Map<Long, BigDecimal> materialCostByLedgerId,
                                      Map<Long, BigDecimal> avgUnitCostByModelId) {
    }
    private record ProductionCostContext(Map<Long, BigDecimal> laborCostByLedgerId,
                                         Map<Long, BigDecimal> scrapCostByLedgerId,
                                         Map<String, BigDecimal> processCostRanking) {
        private static ProductionCostContext empty() {
            return new ProductionCostContext(Map.of(), Map.of(), Map.of());
        }
    }
    private record InventoryMetric(Long modelId,
                                   String productName,
                                   String modelName,
                                   BigDecimal quantity,
                                   BigDecimal lockedQuantity,
                                   BigDecimal avgUnitCost,
                                   BigDecimal inventoryValue,
                                   BigDecimal outboundQuantity,
                                   long stagnantDays,
                                   boolean overstock) {
    }
    private static class InventoryMetricBuilder {
        private final Long modelId;
        private BigDecimal quantity = BigDecimal.ZERO;
        private BigDecimal lockedQuantity = BigDecimal.ZERO;
        private BigDecimal warnNum = BigDecimal.ZERO;
        private LocalDateTime firstInTime;
        private InventoryMetricBuilder(Long modelId) {
            this.modelId = modelId;
        }
        private void addQuantity(BigDecimal quantity) {
            this.quantity = this.quantity.add(quantity);
        }
        private void addLockedQuantity(BigDecimal lockedQuantity) {
            this.lockedQuantity = this.lockedQuantity.add(lockedQuantity);
        }
        private void addWarnNum(BigDecimal warnNum) {
            this.warnNum = this.warnNum.max(warnNum);
        }
        private void updateFirstInTime(LocalDateTime createTime) {
            if (this.firstInTime == null || createTime.isBefore(this.firstInTime)) {
                this.firstInTime = createTime;
            }
        }
        private Long modelId() {
            return modelId;
        }
        private BigDecimal quantity() {
            return quantity;
        }
        private BigDecimal lockedQuantity() {
            return lockedQuantity;
        }
        private BigDecimal warnNum() {
            return warnNum;
        }
        private LocalDateTime firstInTime() {
            return firstInTime;
        }
    }
    private record OutboundStats(Map<Long, BigDecimal> outboundQtyByModel,
                                 Map<Long, LocalDateTime> lastOutboundTimeByModel,
                                 BigDecimal totalOutboundCost) {
        private static OutboundStats empty() {
            return new OutboundStats(Map.of(), Map.of(), BigDecimal.ZERO);
        }
    }
    private record MonthlyCashFlow(String month, BigDecimal income, BigDecimal expense, BigDecimal netFlow) {
    }
    private record StatementMetric(String entityId,
                                   BigDecimal closingBalance,
                                   BigDecimal planAmount,
                                   BigDecimal actualAmount,
                                   String statementMonth) {
    }
    private record StatementSnapshot(BigDecimal receivableTotal,
                                     BigDecimal payableTotal,
                                     List<StatementMetric> receivableTop,
                                     List<StatementMetric> payableTop) {
        private static StatementSnapshot empty() {
            return new StatementSnapshot(BigDecimal.ZERO, BigDecimal.ZERO, List.of(), List.of());
        }
    }
    private record KnowledgeDoc(String topic,
                                List<String> keywords,
                                String knowledge,
                                List<String> relatedTables,
                                List<String> suggestedQuestions) {
    }
}
src/main/java/com/ruoyi/ai/tools/PurchaseAgentTools.java
@@ -2,23 +2,29 @@
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.account.mapper.purchase.AccountPaymentApplicationMapper;
import com.ruoyi.account.mapper.purchase.AccountPurchaseInvoiceMapper;
import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper;
import com.ruoyi.account.pojo.purchase.AccountPaymentApplication;
import com.ruoyi.account.pojo.purchase.AccountPurchaseInvoice;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
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.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.purchase.pojo.PurchaseReturnOrders;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.stock.mapper.StockInRecordMapper;
import com.ruoyi.stock.pojo.StockInRecord;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.ToolMemoryId;
@@ -27,15 +33,9 @@
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.*;
import java.util.stream.Collectors;
@Component
@@ -47,29 +47,38 @@
    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 AccountPurchasePaymentMapper accountPurchasePaymentMapper;
    private final AccountPaymentApplicationMapper accountPaymentApplicationMapper;
    private final AccountPurchaseInvoiceMapper accountPurchaseInvoiceMapper;
    private final StockInRecordMapper stockInRecordMapper;
    private final QualityInspectMapper qualityInspectMapper;
    private final AiSessionUserContext aiSessionUserContext;
    public PurchaseAgentTools(PurchaseLedgerMapper purchaseLedgerMapper,
                              PaymentRegistrationMapper paymentRegistrationMapper,
                              InvoicePurchaseMapper invoicePurchaseMapper,
                              PurchaseReturnOrdersMapper purchaseReturnOrdersMapper,
                              SalesLedgerProductMapper salesLedgerProductMapper,
                              ProcurementRecordMapper procurementRecordMapper,
                              InboundManagementMapper inboundManagementMapper,
                              AccountPurchasePaymentMapper accountPurchasePaymentMapper,
                              AccountPaymentApplicationMapper accountPaymentApplicationMapper,
                              AccountPurchaseInvoiceMapper accountPurchaseInvoiceMapper,
                              StockInRecordMapper stockInRecordMapper,
                              QualityInspectMapper qualityInspectMapper,
                              AiSessionUserContext aiSessionUserContext) {
        this.purchaseLedgerMapper = purchaseLedgerMapper;
        this.paymentRegistrationMapper = paymentRegistrationMapper;
        this.invoicePurchaseMapper = invoicePurchaseMapper;
        this.purchaseReturnOrdersMapper = purchaseReturnOrdersMapper;
        this.salesLedgerProductMapper = salesLedgerProductMapper;
        this.procurementRecordMapper = procurementRecordMapper;
        this.inboundManagementMapper = inboundManagementMapper;
        this.accountPurchasePaymentMapper = accountPurchasePaymentMapper;
        this.accountPaymentApplicationMapper = accountPaymentApplicationMapper;
        this.accountPurchaseInvoiceMapper = accountPurchaseInvoiceMapper;
        this.stockInRecordMapper = stockInRecordMapper;
        this.qualityInspectMapper = qualityInspectMapper;
        this.aiSessionUserContext = aiSessionUserContext;
    }
@@ -131,8 +140,8 @@
        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<AccountPurchasePayment> payments = queryPayments(loginUser, range);
        List<AccountPurchaseInvoice> invoices = queryInvoices(loginUser, range);
        List<PurchaseReturnOrders> returns = queryReturns(loginUser, range);
        BigDecimal contractAmount = ledgers.stream()
@@ -140,11 +149,11 @@
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal paymentAmount = payments.stream()
                .map(PaymentRegistration::getCurrentPaymentAmount)
                .map(AccountPurchasePayment::getPaymentAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal invoiceAmount = invoices.stream()
                .map(InvoicePurchase::getInvoiceAmount)
                .map(this::invoiceAmountOf)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal returnAmount = returns.stream()
@@ -280,9 +289,15 @@
                                           @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()
        List<PurchaseLedger> matchedLedgers = queryLedgers(loginUser, range).stream()
                .filter(ledger -> matchLedgerKeyword(ledger, keyword))
                .map(ledger -> toPendingPaymentItem(loginUser, ledger))
                .collect(Collectors.toList());
        Map<Long, BigDecimal> paidAmountByLedgerId = sumPaymentAmountByLedgerId(loginUser, matchedLedgers.stream()
                .map(PurchaseLedger::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList()));
        List<Map<String, Object>> items = matchedLedgers.stream()
                .map(ledger -> toPendingPaymentItem(ledger, paidAmountByLedgerId.getOrDefault(ledger.getId(), BigDecimal.ZERO)))
                .filter(Objects::nonNull)
                .sorted(Comparator.comparing(item -> (BigDecimal) item.get("pendingAmount"), Comparator.reverseOrder()))
                .limit(normalizeLimit(limit))
@@ -423,27 +438,58 @@
        return map;
    }
    private Map<String, Object> toPendingPaymentItem(LoginUser loginUser, PurchaseLedger ledger) {
    private Map<String, Object> toPendingPaymentItem(PurchaseLedger ledger, BigDecimal paidAmount) {
        BigDecimal contractAmount = defaultDecimal(ledger.getContractAmount());
        BigDecimal paidAmount = sumPaymentAmount(loginUser, ledger.getId());
        BigDecimal pendingAmount = contractAmount.subtract(paidAmount);
        BigDecimal safePaidAmount = defaultDecimal(paidAmount);
        BigDecimal pendingAmount = contractAmount.subtract(safePaidAmount);
        if (pendingAmount.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }
        Map<String, Object> item = toLedgerItem(ledger);
        item.put("paidAmount", paidAmount);
        item.put("paidAmount", safePaidAmount);
        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<Long, BigDecimal> sumPaymentAmountByLedgerId(LoginUser loginUser, List<Long> purchaseLedgerIds) {
        if (purchaseLedgerIds == null || purchaseLedgerIds.isEmpty()) {
            return Map.of();
        }
        List<AccountPurchasePayment> payments = queryPayments(loginUser);
        if (payments.isEmpty()) {
            return Map.of();
        }
        Map<Integer, AccountPaymentApplication> applicationById = queryPaymentApplications(payments);
        if (applicationById.isEmpty()) {
            return Map.of();
        }
        Map<Long, StockInRecord> stockInRecordById = queryStockInRecords(applicationById.values());
        Map<Long, Long> purchaseLedgerIdByQualityInspectId = queryPurchaseLedgerIdByQualityInspectId(stockInRecordById.values());
        Set<Long> targetLedgerIdSet = new HashSet<>(purchaseLedgerIds);
        Map<Long, BigDecimal> result = new HashMap<>();
        for (AccountPurchasePayment payment : payments) {
            if (payment.getAccountPaymentApplicationId() == null) {
                continue;
            }
            AccountPaymentApplication application = applicationById.get(payment.getAccountPaymentApplicationId());
            if (application == null) {
                continue;
            }
            Set<Long> ledgerIds = resolvePurchaseLedgerIds(application, stockInRecordById, purchaseLedgerIdByQualityInspectId);
            if (ledgerIds.isEmpty()) {
                continue;
            }
            BigDecimal amount = defaultDecimal(payment.getPaymentAmount());
            for (Long ledgerId : ledgerIds) {
                if (targetLedgerIdSet.contains(ledgerId)) {
                    result.merge(ledgerId, amount, BigDecimal::add);
                }
            }
        }
        return result;
    }
    private Map<String, Object> toReturnItem(PurchaseReturnOrders item) {
@@ -473,25 +519,132 @@
        if (value instanceof Number number) {
            return new BigDecimal(String.valueOf(number));
        }
        return BigDecimal.ZERO;
        try {
            return new BigDecimal(String.valueOf(value));
        } catch (Exception ignored) {
            return BigDecimal.ZERO;
        }
    }
    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 BigDecimal invoiceAmountOf(AccountPurchaseInvoice invoice) {
        if (invoice == null) {
            return BigDecimal.ZERO;
        }
        BigDecimal amount = defaultDecimal(invoice.getTaxInclusivePrice());
        if (amount.compareTo(BigDecimal.ZERO) > 0) {
            return amount;
        }
        return defaultDecimal(invoice.getTaxExclusivelPrice()).add(defaultDecimal(invoice.getTaxPrice()));
    }
    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<AccountPurchasePayment> queryPayments(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<AccountPurchasePayment> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountPurchasePayment::getDeptId);
        wrapper.ge(AccountPurchasePayment::getPaymentDate, range.start())
                .le(AccountPurchasePayment::getPaymentDate, range.end())
                .orderByDesc(AccountPurchasePayment::getPaymentDate, AccountPurchasePayment::getId);
        return defaultList(accountPurchasePaymentMapper.selectList(wrapper));
    }
    private List<AccountPurchasePayment> queryPayments(LoginUser loginUser) {
        LambdaQueryWrapper<AccountPurchasePayment> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountPurchasePayment::getDeptId);
        return defaultList(accountPurchasePaymentMapper.selectList(wrapper));
    }
    private List<AccountPurchaseInvoice> queryInvoices(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<AccountPurchaseInvoice> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountPurchaseInvoice::getDeptId);
        wrapper.ge(AccountPurchaseInvoice::getIssueDate, range.start())
                .le(AccountPurchaseInvoice::getIssueDate, range.end())
                .orderByDesc(AccountPurchaseInvoice::getIssueDate, AccountPurchaseInvoice::getId);
        return defaultList(accountPurchaseInvoiceMapper.selectList(wrapper));
    }
    private Map<Integer, AccountPaymentApplication> queryPaymentApplications(List<AccountPurchasePayment> payments) {
        List<Integer> ids = payments.stream()
                .map(AccountPurchasePayment::getAccountPaymentApplicationId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (ids.isEmpty()) {
            return Map.of();
        }
        return defaultList(accountPaymentApplicationMapper.selectBatchIds(ids)).stream()
                .filter(item -> item.getId() != null)
                .collect(Collectors.toMap(AccountPaymentApplication::getId, item -> item, (a, b) -> a));
    }
    private Map<Long, StockInRecord> queryStockInRecords(Collection<AccountPaymentApplication> applications) {
        Set<Long> stockInRecordIds = new HashSet<>();
        for (AccountPaymentApplication application : applications) {
            stockInRecordIds.addAll(parseLongIds(application.getStockInRecordIds()));
        }
        if (stockInRecordIds.isEmpty()) {
            return Map.of();
        }
        return defaultList(stockInRecordMapper.selectBatchIds(stockInRecordIds)).stream()
                .filter(item -> item.getId() != null)
                .collect(Collectors.toMap(StockInRecord::getId, item -> item, (a, b) -> a));
    }
    private Map<Long, Long> queryPurchaseLedgerIdByQualityInspectId(Collection<StockInRecord> stockInRecords) {
        Set<Long> qualityInspectIds = stockInRecords.stream()
                .filter(Objects::nonNull)
                .filter(item -> item.getRecordId() != null && "10".equals(safe(item.getRecordType()).trim()))
                .map(StockInRecord::getRecordId)
                .collect(Collectors.toSet());
        if (qualityInspectIds.isEmpty()) {
            return Map.of();
        }
        return defaultList(qualityInspectMapper.selectBatchIds(qualityInspectIds)).stream()
                .filter(item -> item.getId() != null && item.getPurchaseLedgerId() != null)
                .collect(Collectors.toMap(QualityInspect::getId, QualityInspect::getPurchaseLedgerId, (a, b) -> a));
    }
    private Set<Long> resolvePurchaseLedgerIds(AccountPaymentApplication application,
                                               Map<Long, StockInRecord> stockInRecordById,
                                               Map<Long, Long> purchaseLedgerIdByQualityInspectId) {
        Set<Long> result = new LinkedHashSet<>();
        for (Long stockInRecordId : parseLongIds(application.getStockInRecordIds())) {
            StockInRecord stockInRecord = stockInRecordById.get(stockInRecordId);
            if (stockInRecord == null || stockInRecord.getRecordId() == null) {
                continue;
            }
            if (stockInRecord.getApprovalStatus() != null && stockInRecord.getApprovalStatus() != 1) {
                continue;
            }
            String recordType = safe(stockInRecord.getRecordType()).trim();
            if ("7".equals(recordType)) {
                result.add(stockInRecord.getRecordId());
            } else if ("10".equals(recordType)) {
                Long purchaseLedgerId = purchaseLedgerIdByQualityInspectId.get(stockInRecord.getRecordId());
                if (purchaseLedgerId != null) {
                    result.add(purchaseLedgerId);
                }
            }
        }
        return result;
    }
    private List<Long> parseLongIds(String raw) {
        if (!StringUtils.hasText(raw)) {
            return List.of();
        }
        List<Long> result = new ArrayList<>();
        for (String part : raw.split(",")) {
            if (!StringUtils.hasText(part)) {
                continue;
            }
            try {
                result.add(Long.parseLong(part.trim()));
            } catch (Exception ignored) {
            }
        }
        return result;
    }
    private List<PurchaseReturnOrders> queryReturns(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<PurchaseReturnOrders> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), PurchaseReturnOrders::getDeptId);
src/main/java/com/ruoyi/ai/tools/SalesAgentTools.java
@@ -3,24 +3,22 @@
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.ruoyi.account.mapper.SalesReceiptReturnMapper;
import com.ruoyi.account.pojo.SalesReceiptReturn;
import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.ruoyi.ai.context.AiSessionUserContext;
import com.ruoyi.basic.dto.CustomerDto;
import com.ruoyi.basic.mapper.CustomerMapper;
import com.ruoyi.basic.vo.CustomerVo;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.sales.dto.InvoiceLedgerDto;
import com.ruoyi.sales.mapper.InvoiceLedgerMapper;
import com.ruoyi.sales.mapper.ReceiptPaymentMapper;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.mapper.SalesQuotationMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.ReceiptPayment;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.stock.mapper.StockOutRecordMapper;
import com.ruoyi.stock.pojo.StockOutRecord;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import dev.langchain4j.agent.tool.ToolMemoryId;
@@ -34,16 +32,7 @@
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -62,26 +51,23 @@
    private final SalesLedgerMapper salesLedgerMapper;
    private final SalesQuotationMapper salesQuotationMapper;
    private final ShippingInfoMapper shippingInfoMapper;
    private final ReceiptPaymentMapper receiptPaymentMapper;
    private final InvoiceLedgerMapper invoiceLedgerMapper;
    private final SalesReceiptReturnMapper salesReceiptReturnMapper;
    private final AccountSalesCollectionMapper accountSalesCollectionMapper;
    private final StockOutRecordMapper stockOutRecordMapper;
    private final AiSessionUserContext aiSessionUserContext;
    public SalesAgentTools(CustomerMapper customerMapper,
                           SalesLedgerMapper salesLedgerMapper,
                           SalesQuotationMapper salesQuotationMapper,
                           ShippingInfoMapper shippingInfoMapper,
                           ReceiptPaymentMapper receiptPaymentMapper,
                           InvoiceLedgerMapper invoiceLedgerMapper,
                           SalesReceiptReturnMapper salesReceiptReturnMapper,
                           AccountSalesCollectionMapper accountSalesCollectionMapper,
                           StockOutRecordMapper stockOutRecordMapper,
                           AiSessionUserContext aiSessionUserContext) {
        this.customerMapper = customerMapper;
        this.salesLedgerMapper = salesLedgerMapper;
        this.salesQuotationMapper = salesQuotationMapper;
        this.shippingInfoMapper = shippingInfoMapper;
        this.receiptPaymentMapper = receiptPaymentMapper;
        this.invoiceLedgerMapper = invoiceLedgerMapper;
        this.salesReceiptReturnMapper = salesReceiptReturnMapper;
        this.accountSalesCollectionMapper = accountSalesCollectionMapper;
        this.stockOutRecordMapper = stockOutRecordMapper;
        this.aiSessionUserContext = aiSessionUserContext;
    }
@@ -261,36 +247,35 @@
                                   @P(value = "返回条数,默认10,最大30", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, null);
        LambdaQueryWrapper<SalesReceiptReturn> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), SalesReceiptReturn::getDeptId);
        LambdaQueryWrapper<AccountSalesCollection> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountSalesCollection::getDeptId);
        if (StringUtils.hasText(keyword)) {
            wrapper.and(w -> w.like(SalesReceiptReturn::getRefundId, keyword)
                    .or().like(SalesReceiptReturn::getTransactionNo, keyword)
                    .or().like(SalesReceiptReturn::getPaymentAccountName, keyword));
            wrapper.and(w -> w.like(AccountSalesCollection::getCollectionNumber, keyword)
                    .or().like(AccountSalesCollection::getCollectionMethod, keyword)
                    .or().like(AccountSalesCollection::getRemark, keyword));
        }
        wrapper.ge(SalesReceiptReturn::getCreateTime, range.start().atStartOfDay())
                .le(SalesReceiptReturn::getCreateTime, range.end().atTime(23, 59, 59))
                .orderByDesc(SalesReceiptReturn::getCreateTime, SalesReceiptReturn::getId)
        wrapper.ge(AccountSalesCollection::getCollectionDate, range.start())
                .le(AccountSalesCollection::getCollectionDate, range.end())
                .orderByDesc(AccountSalesCollection::getCollectionDate, AccountSalesCollection::getId)
                .last("limit " + normalizeLimit(limit));
        List<SalesReceiptReturn> rows = defaultList(salesReceiptReturnMapper.selectList(wrapper));
        List<AccountSalesCollection> rows = defaultList(accountSalesCollectionMapper.selectList(wrapper));
        BigDecimal returnAmount = rows.stream()
                .map(SalesReceiptReturn::getActualAmount)
                .map(AccountSalesCollection::getCollectionAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        List<Map<String, Object>> items = rows.stream().map(item -> {
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("id", item.getId());
            map.put("refundId", safe(item.getRefundId()));
            map.put("paymentAccount", safe(item.getPaymentAccount()));
            map.put("paymentAccountName", safe(item.getPaymentAccountName()));
            map.put("paymentMethod", item.getPaymentMethod());
            map.put("actualAmount", item.getActualAmount());
            map.put("fee", item.getFee());
            map.put("discountAmount", item.getDiscountAmount());
            map.put("transactionNo", safe(item.getTransactionNo()));
            map.put("createTime", formatDateTime(item.getCreateTime()));
            map.put("refundId", safe(item.getCollectionNumber()));
            map.put("collectionNumber", safe(item.getCollectionNumber()));
            map.put("paymentMethod", safe(item.getCollectionMethod()));
            map.put("actualAmount", item.getCollectionAmount());
            map.put("collectionAmount", item.getCollectionAmount());
            map.put("customerId", item.getCustomerId());
            map.put("remark", safe(item.getRemark()));
            map.put("createTime", formatDate(item.getCollectionDate()));
            return map;
        }).collect(Collectors.toList());
@@ -307,55 +292,59 @@
                                           @P(value = "返回条数,默认10,最大30", required = false) Integer limit) {
        LoginUser loginUser = currentLoginUser(memoryId);
        DateRange range = resolveDateRange(startDate, endDate, null);
        LambdaQueryWrapper<ReceiptPayment> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), ReceiptPayment::getTenantId);
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ReceiptPayment::getDeptId);
        wrapper.ge(ReceiptPayment::getReceiptPaymentDate, range.start())
                .le(ReceiptPayment::getReceiptPaymentDate, range.end())
                .orderByDesc(ReceiptPayment::getReceiptPaymentDate, ReceiptPayment::getId);
        List<ReceiptPayment> payments = defaultList(receiptPaymentMapper.selectList(wrapper));
        if (payments.isEmpty()) {
            return jsonResponse(true, "sales_customer_interaction_list", "未查询到客户往来记录", rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of());
        List<AccountSalesCollection> collections = queryCollections(loginUser, range);
        if (collections.isEmpty()) {
            return jsonResponse(true, "sales_customer_interaction_list", "no_customer_interactions", rangeSummary(range, 0, keyword), Map.of("items", List.of()), Map.of());
        }
        List<Long> ledgerIds = payments.stream()
                .map(ReceiptPayment::getSalesLedgerId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        Map<Integer, Set<Long>> ledgerIdsByCollectionId = mapCollectionLedgerIds(loginUser, collections);
        Set<Long> ledgerIds = ledgerIdsByCollectionId.values().stream()
                .flatMap(Collection::stream)
                .collect(Collectors.toSet());
        Map<Long, SalesLedger> ledgerMap = defaultList(salesLedgerMapper.selectBatchIds(ledgerIds)).stream()
                .filter(ledger -> tenantMatched(ledger.getTenantId(), loginUser.getTenantId()))
                .collect(Collectors.toMap(SalesLedger::getId, item -> item, (a, b) -> a, LinkedHashMap::new));
        List<ReceiptPayment> filtered = payments.stream()
                .filter(item -> matchInteractionKeyword(item, ledgerMap.get(item.getSalesLedgerId()), keyword))
                .limit(normalizeLimit(limit))
                .collect(Collectors.toList());
        int finalLimit = normalizeLimit(limit);
        List<Map<String, Object>> items = new ArrayList<>();
        for (AccountSalesCollection collection : collections) {
            Set<Long> relatedLedgerIds = ledgerIdsByCollectionId.get(collection.getId());
            if (relatedLedgerIds == null || relatedLedgerIds.isEmpty()) {
                if (!matchInteractionKeyword(collection, null, keyword)) {
                    continue;
                }
                items.add(toInteractionItem(collection, null));
                if (items.size() >= finalLimit) {
                    break;
                }
                continue;
            }
            for (Long ledgerId : relatedLedgerIds) {
                SalesLedger ledger = ledgerMap.get(ledgerId);
                if (ledger == null || !matchInteractionKeyword(collection, ledger, keyword)) {
                    continue;
                }
                items.add(toInteractionItem(collection, ledger));
                if (items.size() >= finalLimit) {
                    break;
                }
            }
            if (items.size() >= finalLimit) {
                break;
            }
        }
        BigDecimal totalReceiptAmount = filtered.stream()
                .map(ReceiptPayment::getReceiptPaymentAmount)
                .filter(Objects::nonNull)
        BigDecimal totalReceiptAmount = items.stream()
                .map(item -> asBigDecimal(item.get("receiptPaymentAmount")))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        List<Map<String, Object>> items = filtered.stream().map(item -> {
            SalesLedger ledger = ledgerMap.get(item.getSalesLedgerId());
            Map<String, Object> map = new LinkedHashMap<>();
            map.put("id", item.getId());
            map.put("salesLedgerId", item.getSalesLedgerId());
            map.put("salesContractNo", ledger == null ? "" : safe(ledger.getSalesContractNo()));
            map.put("customerName", ledger == null ? "" : safe(ledger.getCustomerName()));
            map.put("projectName", ledger == null ? "" : safe(ledger.getProjectName()));
            map.put("receiptPaymentDate", formatDate(item.getReceiptPaymentDate()));
            map.put("receiptPaymentAmount", item.getReceiptPaymentAmount());
            map.put("receiptPaymentType", safe(item.getReceiptPaymentType()));
            map.put("registrant", safe(item.getRegistrant()));
            return map;
        }).collect(Collectors.toList());
        Map<String, Object> summary = rangeSummary(range, items.size(), keyword);
        summary.put("totalReceiptAmount", totalReceiptAmount);
        summary.put("customerCount", items.stream().map(item -> String.valueOf(item.get("customerName"))).filter(StringUtils::hasText).distinct().count());
        return jsonResponse(true, "sales_customer_interaction_list", "已返回客户往来明细", summary, Map.of("items", items), Map.of());
        summary.put("customerCount", items.stream()
                .map(item -> String.valueOf(item.get("customerName")))
                .filter(StringUtils::hasText)
                .distinct()
                .count());
        return jsonResponse(true, "sales_customer_interaction_list", "ok", summary, Map.of("items", items), Map.of());
    }
    @Tool(name = "查询发货台账", value = "按关键词和时间范围查询发货台账")
@@ -426,7 +415,7 @@
        List<SalesLedger> ledgers = querySalesLedgers(loginUser, range);
        List<SalesQuotation> quotations = querySalesQuotations(loginUser, range);
        List<ShippingInfo> shippings = queryShippings(loginUser, range);
        List<ReceiptPayment> receipts = queryReceipts(loginUser, range);
        BigDecimal contractAmountTotal = ledgers.stream()
                .map(SalesLedger::getContractAmount)
@@ -436,11 +425,6 @@
                .map(SalesQuotation::getTotalAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal receivedAmountTotal = receipts.stream()
                .map(ReceiptPayment::getReceiptPaymentAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal pendingAmountTotal = maxZero(contractAmountTotal.subtract(receivedAmountTotal));
        long shippingCount = shippings.size();
        long shippedCount = shippings.stream().filter(item -> isShippedStatus(item.getStatus())).count();
@@ -460,11 +444,12 @@
        summary.put("shipRate", shipRate);
        summary.put("contractAmountTotal", contractAmountTotal);
        summary.put("quotationAmountTotal", quotationAmountTotal);
        summary.put("receivedAmountTotal", receivedAmountTotal);
        summary.put("pendingAmountTotal", pendingAmountTotal);
        summary.put("receivedAmountTotal", BigDecimal.ZERO);
        summary.put("pendingAmountTotal", BigDecimal.ZERO);
        Map<String, Object> charts = new LinkedHashMap<>();
        charts.put("amountBarOption", buildAmountBarOption(contractAmountTotal, quotationAmountTotal, receivedAmountTotal, pendingAmountTotal));
//        charts.put("amountBarOption", buildAmountBarOption(contractAmountTotal, quotationAmountTotal, receivedAmountTotal, pendingAmountTotal));
        charts.put("amountBarOption", buildAmountBarOption(contractAmountTotal, quotationAmountTotal, BigDecimal.ONE, BigDecimal.ONE));
        charts.put("shippingPieOption", buildShippingPieOption(shippedCount, Math.max(shippingCount - shippedCount, 0)));
        charts.put("customerTopBarOption", buildCustomerTopBarOption(topCustomers));
        charts.put("contractTrendLineOption", buildContractTrendLineOption(trendData.labels(), trendData.values()));
@@ -858,28 +843,6 @@
        return defaultList(shippingInfoMapper.selectList(wrapper));
    }
    private List<ReceiptPayment> queryReceipts(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<ReceiptPayment> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), ReceiptPayment::getTenantId);
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ReceiptPayment::getDeptId);
        if (range != null) {
            wrapper.ge(ReceiptPayment::getReceiptPaymentDate, range.start())
                    .le(ReceiptPayment::getReceiptPaymentDate, range.end());
        }
        return defaultList(receiptPaymentMapper.selectList(wrapper));
    }
    private List<ReceiptPayment> queryReceiptsByLedgerIds(LoginUser loginUser, List<Long> ledgerIds) {
        if (ledgerIds == null || ledgerIds.isEmpty()) {
            return List.of();
        }
        LambdaQueryWrapper<ReceiptPayment> wrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(wrapper, loginUser.getTenantId(), ReceiptPayment::getTenantId);
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ReceiptPayment::getDeptId);
        wrapper.in(ReceiptPayment::getSalesLedgerId, ledgerIds);
        return defaultList(receiptPaymentMapper.selectList(wrapper));
    }
    private List<ShippingInfo> queryShippingsByLedgerIds(LoginUser loginUser, List<Long> ledgerIds) {
        if (ledgerIds == null || ledgerIds.isEmpty()) {
            return List.of();
@@ -896,24 +859,213 @@
            return Map.of();
        }
        Map<Long, BigDecimal> result = new HashMap<>();
        for (InvoiceLedgerDto item : defaultList(invoiceLedgerMapper.invoicedTotal(ledgerIds))) {
            if (item.getSalesLedgerId() == null) {
                continue;
            }
            result.merge(item.getSalesLedgerId().longValue(), defaultDecimal(item.getInvoiceTotal()), BigDecimal::add);
        }
        return result;
    }
    private Map<Long, BigDecimal> sumReceiptAmounts(LoginUser loginUser, List<Long> ledgerIds) {
        if (ledgerIds == null || ledgerIds.isEmpty()) {
            return Map.of();
        }
        List<SalesLedger> ledgers = defaultList(salesLedgerMapper.selectBatchIds(ledgerIds)).stream()
                .filter(ledger -> tenantMatched(ledger.getTenantId(), loginUser.getTenantId()))
                .collect(Collectors.toList());
        if (ledgers.isEmpty()) {
            return Map.of();
        }
        Set<Integer> customerIds = ledgers.stream()
                .map(SalesLedger::getCustomerId)
                .filter(Objects::nonNull)
                .map(Long::intValue)
                .collect(Collectors.toSet());
        if (customerIds.isEmpty()) {
            return Map.of();
        }
        LambdaQueryWrapper<AccountSalesCollection> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountSalesCollection::getDeptId);
        wrapper.in(AccountSalesCollection::getCustomerId, customerIds);
        List<AccountSalesCollection> collections = defaultList(accountSalesCollectionMapper.selectList(wrapper));
        if (collections.isEmpty()) {
            return Map.of();
        }
        Map<Integer, Set<Long>> ledgerIdsByCollectionId = mapCollectionLedgerIds(loginUser, collections);
        Map<Long, List<Long>> ledgerIdsByCustomerId = ledgers.stream()
                .filter(item -> item.getId() != null && item.getCustomerId() != null)
                .collect(Collectors.groupingBy(item -> item.getCustomerId().longValue(),
                        Collectors.mapping(SalesLedger::getId, Collectors.toList())));
        Set<Long> targetLedgerIdSet = new HashSet<>(ledgerIds);
        Map<Long, BigDecimal> result = new HashMap<>();
        for (ReceiptPayment item : queryReceiptsByLedgerIds(loginUser, ledgerIds)) {
            if (item.getSalesLedgerId() == null) {
        for (AccountSalesCollection collection : collections) {
            BigDecimal amount = defaultDecimal(collection.getCollectionAmount());
            if (amount.compareTo(BigDecimal.ZERO) == 0) {
                continue;
            }
            result.merge(item.getSalesLedgerId(), defaultDecimal(item.getReceiptPaymentAmount()), BigDecimal::add);
            Set<Long> relatedLedgerIds = ledgerIdsByCollectionId.getOrDefault(collection.getId(), Set.of());
            if (!relatedLedgerIds.isEmpty()) {
                for (Long ledgerId : relatedLedgerIds) {
                    if (targetLedgerIdSet.contains(ledgerId)) {
                        result.merge(ledgerId, amount, BigDecimal::add);
                    }
                }
                continue;
            }
            if (collection.getCustomerId() == null) {
                continue;
            }
            List<Long> customerLedgerIds = ledgerIdsByCustomerId.get(collection.getCustomerId().longValue());
            if (customerLedgerIds == null || customerLedgerIds.isEmpty()) {
                continue;
            }
            for (Long ledgerId : customerLedgerIds) {
                if (targetLedgerIdSet.contains(ledgerId)) {
                    result.merge(ledgerId, amount, BigDecimal::add);
                }
            }
        }
        return result;
    }
    private List<AccountSalesCollection> queryCollections(LoginUser loginUser, DateRange range) {
        LambdaQueryWrapper<AccountSalesCollection> wrapper = new LambdaQueryWrapper<>();
        applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), AccountSalesCollection::getDeptId);
        if (range != null) {
            wrapper.ge(AccountSalesCollection::getCollectionDate, range.start())
                    .le(AccountSalesCollection::getCollectionDate, range.end());
        }
        wrapper.orderByDesc(AccountSalesCollection::getCollectionDate, AccountSalesCollection::getId);
        return defaultList(accountSalesCollectionMapper.selectList(wrapper));
    }
    private Map<Integer, Set<Long>> mapCollectionLedgerIds(LoginUser loginUser, List<AccountSalesCollection> collections) {
        Map<Integer, Set<Long>> result = new HashMap<>();
        if (collections == null || collections.isEmpty()) {
            return result;
        }
        Map<Integer, List<Long>> stockOutRecordIdsByCollection = new HashMap<>();
        Set<Long> allStockOutRecordIds = new HashSet<>();
        for (AccountSalesCollection collection : collections) {
            if (collection.getId() == null) {
                continue;
            }
            List<Long> stockOutRecordIds = parseLongIds(collection.getStockOutRecordIds());
            if (stockOutRecordIds.isEmpty()) {
                continue;
            }
            stockOutRecordIdsByCollection.put(collection.getId(), stockOutRecordIds);
            allStockOutRecordIds.addAll(stockOutRecordIds);
        }
        if (allStockOutRecordIds.isEmpty()) {
            return result;
        }
        List<StockOutRecord> stockOutRecords = defaultList(stockOutRecordMapper.selectList(new LambdaQueryWrapper<StockOutRecord>()
                .in(StockOutRecord::getId, allStockOutRecordIds)));
        if (stockOutRecords.isEmpty()) {
            return result;
        }
        Map<Long, StockOutRecord> stockOutRecordMap = stockOutRecords.stream()
                .filter(item -> item.getId() != null)
                .collect(Collectors.toMap(StockOutRecord::getId, item -> item, (a, b) -> a));
        Set<Long> shippingIds = stockOutRecords.stream()
                .filter(this::isSalesOutboundRecord)
                .map(StockOutRecord::getRecordId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (shippingIds.isEmpty()) {
            return result;
        }
        LambdaQueryWrapper<ShippingInfo> shippingWrapper = new LambdaQueryWrapper<>();
        applyTenantFilter(shippingWrapper, loginUser.getTenantId(), ShippingInfo::getTenantId);
        applyDeptFilter(shippingWrapper, loginUser.getCurrentDeptId(), ShippingInfo::getDeptId);
        shippingWrapper.in(ShippingInfo::getId, shippingIds);
        Map<Long, Long> ledgerIdByShippingId = defaultList(shippingInfoMapper.selectList(shippingWrapper)).stream()
                .filter(item -> item.getId() != null && item.getSalesLedgerId() != null)
                .collect(Collectors.toMap(ShippingInfo::getId, ShippingInfo::getSalesLedgerId, (a, b) -> a));
        for (Map.Entry<Integer, List<Long>> entry : stockOutRecordIdsByCollection.entrySet()) {
            Set<Long> ledgerIds = new LinkedHashSet<>();
            for (Long stockOutRecordId : entry.getValue()) {
                StockOutRecord stockOutRecord = stockOutRecordMap.get(stockOutRecordId);
                if (!isSalesOutboundRecord(stockOutRecord)) {
                    continue;
                }
                Long ledgerId = ledgerIdByShippingId.get(stockOutRecord.getRecordId());
                if (ledgerId != null) {
                    ledgerIds.add(ledgerId);
                }
            }
            if (!ledgerIds.isEmpty()) {
                result.put(entry.getKey(), ledgerIds);
            }
        }
        return result;
    }
    private boolean isSalesOutboundRecord(StockOutRecord stockOutRecord) {
        if (stockOutRecord == null || !StringUtils.hasText(stockOutRecord.getRecordType())) {
            return false;
        }
        if (stockOutRecord.getApprovalStatus() != null && stockOutRecord.getApprovalStatus() != 1) {
            return false;
        }
        return "13".equals(stockOutRecord.getRecordType().trim());
    }
    private List<Long> parseLongIds(String raw) {
        if (!StringUtils.hasText(raw)) {
            return List.of();
        }
        List<Long> result = new ArrayList<>();
        for (String part : raw.split(",")) {
            if (!StringUtils.hasText(part)) {
                continue;
            }
            try {
                result.add(Long.parseLong(part.trim()));
            } catch (Exception ignored) {
            }
        }
        return result;
    }
    private boolean matchInteractionKeyword(AccountSalesCollection collection, SalesLedger ledger, String keyword) {
        if (!StringUtils.hasText(keyword)) {
            return true;
        }
        String text = keyword.trim();
        if (safe(collection.getCollectionNumber()).contains(text)
                || safe(collection.getCollectionMethod()).contains(text)
                || safe(collection.getRemark()).contains(text)) {
            return true;
        }
        if (ledger == null) {
            return false;
        }
        return safe(ledger.getSalesContractNo()).contains(text)
                || safe(ledger.getCustomerName()).contains(text)
                || safe(ledger.getProjectName()).contains(text);
    }
    private Map<String, Object> toInteractionItem(AccountSalesCollection collection, SalesLedger ledger) {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("id", collection.getId());
        map.put("salesLedgerId", ledger == null ? null : ledger.getId());
        map.put("salesContractNo", ledger == null ? "" : safe(ledger.getSalesContractNo()));
        map.put("customerName", ledger == null ? "" : safe(ledger.getCustomerName()));
        map.put("projectName", ledger == null ? "" : safe(ledger.getProjectName()));
        map.put("receiptPaymentDate", formatDate(collection.getCollectionDate()));
        map.put("receiptPaymentAmount", collection.getCollectionAmount());
        map.put("receiptPaymentType", safe(collection.getCollectionMethod()));
        map.put("collectionNumber", safe(collection.getCollectionNumber()));
        map.put("registrant", collection.getCreateUser());
        map.put("remark", safe(collection.getRemark()));
        return map;
    }
    private boolean isLedgerFullyShipped(Long ledgerId, Map<Long, List<ShippingInfo>> shippingByLedgerId) {
@@ -952,17 +1104,6 @@
                || safe(customer.getContactPhone()).contains(text)
                || safe(customer.getCompanyPhone()).contains(text)
                || safe(customer.getUsageUserName()).contains(text);
    }
    private boolean matchInteractionKeyword(ReceiptPayment payment, SalesLedger ledger, String keyword) {
        if (!StringUtils.hasText(keyword)) {
            return true;
        }
        String text = keyword.trim();
        return safe(payment.getRegistrant()).contains(text)
                || (ledger != null && (safe(ledger.getCustomerName()).contains(text)
                || safe(ledger.getSalesContractNo()).contains(text)
                || safe(ledger.getProjectName()).contains(text)));
    }
    private boolean matchLedgerCustomerKeyword(SalesLedger ledger, String keyword) {
@@ -1150,6 +1291,23 @@
        return value == null ? BigDecimal.ZERO : value;
    }
    private BigDecimal asBigDecimal(Object value) {
        if (value == null) {
            return BigDecimal.ZERO;
        }
        if (value instanceof BigDecimal decimal) {
            return decimal;
        }
        if (value instanceof Number number) {
            return new BigDecimal(String.valueOf(number));
        }
        try {
            return new BigDecimal(String.valueOf(value));
        } catch (Exception ignored) {
            return BigDecimal.ZERO;
        }
    }
    private BigDecimal maxZero(BigDecimal value) {
        return value == null || value.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : value;
    }
src/main/java/com/ruoyi/approve/bean/dto/ApprovalInstanceDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.ruoyi.approve.bean.dto;
import com.ruoyi.approve.pojo.ApprovalInstance;
import com.ruoyi.basic.dto.StorageBlobDTO;
import lombok.Data;
import java.util.List;
@Data
public class ApprovalInstanceDto extends ApprovalInstance {
    private String approveAction;
    private String approveComment;
    private String createTimeEnd;
    private String createTimeStart;
    private List<StorageBlobDTO> storageBlobDTOs;
}
src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.approve.bean.dto;
import com.ruoyi.approve.pojo.ApprovalTemplate;
import lombok.Data;
import java.util.List;
@Data
public class ApprovalTemplateDto  extends ApprovalTemplate {
    private List<ApprovalTemplateNodeDto> nodes;
}
src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeApproverDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
package com.ruoyi.approve.bean.dto;
import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
import lombok.Data;
@Data
public class ApprovalTemplateNodeApproverDto extends ApprovalTemplateNodeApprover {
}
src/main/java/com/ruoyi/approve/bean/dto/ApprovalTemplateNodeDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.approve.bean.dto;
import com.ruoyi.approve.pojo.ApprovalTemplateNode;
import lombok.Data;
import java.util.List;
@Data
public class ApprovalTemplateNodeDto extends ApprovalTemplateNode {
    private List<ApprovalTemplateNodeApproverDto> approvers;
}
src/main/java/com/ruoyi/approve/bean/dto/FinReimbursementDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package com.ruoyi.approve.bean.dto;
import com.ruoyi.approve.pojo.FinReimbursement;
import com.ruoyi.approve.pojo.FinReimbursementDetail;
import com.ruoyi.approve.pojo.FinReimbursementTravel;
import com.ruoyi.basic.dto.StorageBlobDTO;
import lombok.Data;
import java.util.List;
@Data
public class FinReimbursementDto extends FinReimbursement {
    private String createTimeStart;
    private String createTimeEnd;
    private FinReimbursementTravel  travel;
    private List<FinReimbursementDetail> details;
    private List<ApprovalTemplateNodeDto> nodes;
    private List<StorageBlobDTO> storageBlobDTOs;
}
src/main/java/com/ruoyi/approve/bean/vo/ApprovalInstanceVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package com.ruoyi.approve.bean.vo;
import com.ruoyi.approve.pojo.ApprovalInstance;
import com.ruoyi.approve.pojo.ApprovalRecord;
import com.ruoyi.approve.pojo.ApprovalTask;
import com.ruoyi.basic.dto.StorageBlobVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
public class ApprovalInstanceVo extends ApprovalInstance {
    //当前用户是否可以审批
    @Schema(description = "当前用户是否可以审批")
    private Boolean isApprove;
    //审批流程
    private List<ApprovalTask> tasks;
    //审批记录
    private List<ApprovalRecord>  records;
    @Schema(description = "业务名称")
    private String businessName;
    private List<StorageBlobVO> storageBlobVOList;
}
src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeApproverVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
package com.ruoyi.approve.bean.vo;
import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
import lombok.Data;
@Data
public class ApprovalTemplateNodeApproverVo extends ApprovalTemplateNodeApprover {
}
src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateNodeVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.approve.bean.vo;
import com.ruoyi.approve.pojo.ApprovalTemplateNode;
import lombok.Data;
import java.util.List;
@Data
public class ApprovalTemplateNodeVo extends ApprovalTemplateNode {
    private List<ApprovalTemplateNodeApproverVo> approvers;
}
src/main/java/com/ruoyi/approve/bean/vo/ApprovalTemplateVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.ruoyi.approve.bean.vo;
import com.ruoyi.approve.pojo.ApprovalTemplate;
import lombok.Data;
import java.util.List;
@Data
public class ApprovalTemplateVo extends ApprovalTemplate {
    private List<ApprovalTemplateNodeVo> nodes;
    private String createdUserName;
}
src/main/java/com/ruoyi/approve/bean/vo/ApproveGetAndUpdateVo.java
@@ -9,6 +9,7 @@
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@@ -45,6 +46,14 @@
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "出差开始时间")
    private LocalDateTime startDateTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "出差结束时间")
    private LocalDateTime endDateTime;
    private BigDecimal price;
    private String location;
src/main/java/com/ruoyi/approve/bean/vo/ApproveProcessVO.java
@@ -8,6 +8,7 @@
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
@@ -58,6 +59,14 @@
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "出差开始时间")
    private LocalDateTime startDateTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "出差结束时间")
    private LocalDateTime endDateTime;
    private BigDecimal price;
    private String location;
src/main/java/com/ruoyi/approve/bean/vo/FinReimbursementVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.approve.bean.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.approve.pojo.*;
import com.ruoyi.basic.dto.StorageBlobVO;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
@Data
public class FinReimbursementVo extends FinReimbursement {
    private String createTimeStart;
    private String createTimeEnd;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime startTime;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime endTime;
    private FinReimbursementTravel travel;
    private List<FinReimbursementDetail> details;
    //审批流程
    private List<ApprovalTask> tasks;
    //审批记录
    private List<ApprovalRecord>  records;
    private List<StorageBlobVO> storageBlobVOList;
}
src/main/java/com/ruoyi/approve/controller/ApprovalInstanceController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
package com.ruoyi.approve.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
import com.ruoyi.approve.bean.vo.ApprovalInstanceVo;
import com.ruoyi.approve.service.ApprovalInstanceService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * å®¡æ‰¹å®žä¾‹è¡¨ å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:27:46
 */
@RestController
@RequestMapping("/approvalInstance")
@Tag(name = "审批实例表")
@AllArgsConstructor
public class ApprovalInstanceController extends BaseController {
    private final ApprovalInstanceService approvalInstanceService;
    @GetMapping("/listPage")
    @Operation(summary = "分页查询")
    @Log(title = "审批列表分页查询", businessType = BusinessType.OTHER)
    public R listPage(Page<ApprovalInstanceVo> page, ApprovalInstanceDto approvalInstanceDto) {
        return approvalInstanceService.listPage(page, approvalInstanceDto);
    }
    @PostMapping("/save")
    @Operation(summary = "保存")
    @Log(title = "审批列表保存", businessType = BusinessType.INSERT)
    public R save(@RequestBody ApprovalInstanceDto approvalInstanceDto) {
        return approvalInstanceService.add(approvalInstanceDto) ? R.ok() : R.fail();
    }
    @PutMapping("/update")
    @Operation(summary = "更新")
    @Log(title = "审批列表更新", businessType = BusinessType.UPDATE)
    public R update(@RequestBody ApprovalInstanceDto approvalInstanceDto) {
        return approvalInstanceService.update(approvalInstanceDto) ? R.ok() : R.fail();
    }
    @DeleteMapping("/delete")
    @Log(title = "审批列表删除", businessType = BusinessType.DELETE)
    @Operation(summary = "删除")
    public R delete(@RequestBody List<Long> ids) {
        return approvalInstanceService.delete(ids) ? R.ok() : R.fail();
    }
    @Operation(summary = "审批")
    @PostMapping("/approve")
    @Log(title = "审批列表审批", businessType = BusinessType.UPDATE)
    public R approve(@RequestBody ApprovalInstanceDto approvalInstanceDto) {
        return approvalInstanceService.approve(approvalInstanceDto);
    }
}
src/main/java/com/ruoyi/approve/controller/ApprovalInstanceNodeController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.approve.controller;
import com.ruoyi.framework.web.controller.BaseController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * å®¡æ‰¹èŠ‚ç‚¹å®žä¾‹è¡¨ å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:27:54
 */
@RestController
@RequestMapping("/approvalInstanceNode")
public class ApprovalInstanceNodeController extends BaseController {
}
src/main/java/com/ruoyi/approve/controller/ApprovalRecordController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * å®¡æ‰¹è®°å½•表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:28:21
 */
@RestController
@RequestMapping("/approvalRecord")
public class ApprovalRecordController {
}
src/main/java/com/ruoyi/approve/controller/ApprovalTaskController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * å®¡æ‰¹ä»»åŠ¡è¡¨ å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:32:37
 */
@RestController
@RequestMapping("/approvalTask")
public class ApprovalTaskController {
}
src/main/java/com/ruoyi/approve/controller/ApprovalTemplateController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package com.ruoyi.approve.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.ApprovalTemplateDto;
import com.ruoyi.approve.bean.vo.ApprovalTemplateVo;
import com.ruoyi.approve.service.ApprovalTemplateService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿è¡¨ å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ‹é“è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:08
 */
@RestController
@RequestMapping("/approvalTemplate")
@Tag(name = "审批模板表")
@AllArgsConstructor
public class ApprovalTemplateController  extends BaseController {
    private final ApprovalTemplateService approvalTemplateService;
    @GetMapping("/listPage")
    @Operation(summary = "分页查询")
    @Log(title = "审批模板分页查询", businessType = BusinessType.OTHER)
    public R listPage(Page<ApprovalTemplateVo> page, ApprovalTemplateDto approvalTemplateDto) {
        return R.ok(approvalTemplateService.listPage(page, approvalTemplateDto));
    }
    @PostMapping("/add")
    @Operation(summary = "添加")
    @Log(title = "添加审批模板", businessType = BusinessType.INSERT)
    public R add(@RequestBody ApprovalTemplateDto approvalTemplateDto) {
        return R.ok(approvalTemplateService.saveApprovalTemplateDto(approvalTemplateDto));
    }
    @PutMapping("/update")
    @Operation(summary = "修改")
    @Log(title = "修改审批模板", businessType = BusinessType.UPDATE)
    public R update(@RequestBody ApprovalTemplateDto approvalTemplateDto) {
        return R.ok(approvalTemplateService.updateApprovalTemplateDto(approvalTemplateDto));
    }
    @PostMapping("/delete")
    @Operation(summary = "删除")
    @Log(title = "删除审批模板", businessType = BusinessType.DELETE)
    public R delete(@RequestBody List<Long> ids) {
        return R.ok(approvalTemplateService.delete(ids));
    }
    @GetMapping("/list/{type}")
    @Operation(summary = "查询所有审批模板")
    public R list(@PathVariable("type") Integer type) {
        return R.ok(approvalTemplateService.listApprovalTemplateVo(type));
    }
    @GetMapping("/detail/{id}")
    @Operation(summary = "查询审批模板详情")
    @Log(title = "查询审批模板详情", businessType = BusinessType.OTHER)
    public R detail(@PathVariable("id") Long id) {
        return R.ok(approvalTemplateService.getApprovalTemplateVoById(id));
    }
}
src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeApproverController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿èŠ‚ç‚¹å®¡æ‰¹äººè¡¨ å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:30
 */
@RestController
@RequestMapping("/approvalTemplateNodeApprover")
public class ApprovalTemplateNodeApproverController {
}
src/main/java/com/ruoyi/approve/controller/ApprovalTemplateNodeController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿èŠ‚ç‚¹è¡¨ å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:19
 */
@RestController
@RequestMapping("/approvalTemplateNode")
public class ApprovalTemplateNodeController {
}
src/main/java/com/ruoyi/approve/controller/ApproveNodeController.java
@@ -2,8 +2,7 @@
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.approve.service.IApproveNodeService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
@@ -16,7 +15,7 @@
@RestController
@RequestMapping("/approveNode")
@AllArgsConstructor
public class ApproveNodeController extends BaseController {
public class ApproveNodeController {
    private IApproveNodeService approveNodeService;
@@ -27,8 +26,8 @@
     */
    @GetMapping("/details/{id}")
    @Operation(summary = "流程状态详情")
    public R<?> details(@PathVariable String id) {
        return R.ok(approveNodeService.details(id));
    public AjaxResult details(@PathVariable String id) {
        return AjaxResult.success(approveNodeService.details(id));
    }
    /**
@@ -39,9 +38,9 @@
    @PostMapping("/updateApproveNode")
    @Transactional(rollbackFor = Exception.class)
    @Operation(summary = "审批节点")
    public R<?> updateApproveNode(@RequestBody ApproveNode approveNode) throws IOException {
    public AjaxResult updateApproveNode(@RequestBody ApproveNode approveNode) throws IOException {
        approveNodeService.updateApproveNode(approveNode);
        return R.ok();
        return AjaxResult.success();
    }
    /**
@@ -50,9 +49,9 @@
     * @return
     */
    @PostMapping("/init")
    public R<?> init(String id) {
    public AjaxResult init(String id) {
        approveNodeService.initApproveNodes("",id,1L);
        return R.ok();
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java
@@ -9,9 +9,7 @@
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.domain.SysDept;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
@@ -27,7 +25,7 @@
@RequestMapping("/approveProcess")
@AllArgsConstructor
@Tag(name = "审批")
public class ApproveProcessController extends BaseController {
public class ApproveProcessController {
    private IApproveProcessService approveProcessService;
    /**、
@@ -35,13 +33,13 @@
     * @return
     */
    @GetMapping("/getDept")
    public R<?> getDept() {
    public AjaxResult getDept() {
        Long userId = SecurityUtils.getUserId();
        LoginUser user = SecurityUtils.getLoginUser();
        Long[] deptIds = SecurityUtils.getDeptId();
        List<SysDept> sysDeptList = approveProcessService.selectDeptListByDeptIds(deptIds);
        return R.ok(sysDeptList);
        return AjaxResult.success(sysDeptList);
    }
    /**
@@ -52,13 +50,13 @@
    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
    @Operation(summary = "添加审批")
    public R<?> add(@RequestBody ApproveProcessVO approveProcessVO) throws Exception {
    public AjaxResult add(@RequestBody ApproveProcessVO approveProcessVO) throws Exception {
        if (approveProcessVO == null) {
            return R.fail(HttpStatus.WARN,"参数不能为空");
            return AjaxResult.warn("参数不能为空");
        }
        approveProcessService.addApprove(approveProcessVO);
        return R.ok(null, "添加成功");
        return AjaxResult.success("添加成功");
    }
    /**
@@ -68,11 +66,11 @@
     */
    @GetMapping("/get")
    @Operation(summary = "审批详情")
    public R<?> get(ApproveGetAndUpdateVo approveGetAndUpdateVo){
    public AjaxResult get(ApproveGetAndUpdateVo approveGetAndUpdateVo){
        if (approveGetAndUpdateVo.getId() == null || approveGetAndUpdateVo.getId().isEmpty()) {
            return R.fail(HttpStatus.WARN,"参数不能为空");
            return AjaxResult.warn("参数不能为空");
        }
        return R.ok(approveProcessService.getApproveById(approveGetAndUpdateVo.getId()));
        return AjaxResult.success(approveProcessService.getApproveById(approveGetAndUpdateVo.getId()));
    }
    /**
@@ -83,12 +81,12 @@
    @PostMapping("/update")
    @Transactional(rollbackFor = Exception.class)
    @Operation(summary = "更新审批")
    public R<?> update(@RequestBody ApproveGetAndUpdateVo approveGetAndUpdateVo) throws IOException {
    public AjaxResult update(@RequestBody ApproveGetAndUpdateVo approveGetAndUpdateVo) throws IOException {
        if (approveGetAndUpdateVo == null) {
            return R.fail(HttpStatus.WARN,"参数不能为空");
            return AjaxResult.warn("参数不能为空");
        }
        approveProcessService.updateByApproveId(approveGetAndUpdateVo);
        return R.ok(null, "操作成功");
        return AjaxResult.success("操作成功");
    }
    /**
     * èŽ·å–å®¡æ‰¹åˆ—è¡¨
@@ -96,8 +94,8 @@
     */
    @GetMapping("/list")
    @Operation(summary = "获取审批列表")
    public R<?> list(Page page, ApproveProcess approveProcess) {
        return R.ok(approveProcessService.listAll(page, approveProcess));
    public AjaxResult list(Page page, ApproveProcess approveProcess) {
        return AjaxResult.success(approveProcessService.listAll(page, approveProcess));
    }
    /**
@@ -108,12 +106,12 @@
    @DeleteMapping("/deleteIds")
    @Operation(summary = "删除审批")
    @Transactional(rollbackFor = Exception.class)
    public R<?> deleteIds(@RequestBody List<Long> ids) {
    public AjaxResult deleteIds(@RequestBody List<Long> ids) {
        if (ids == null || ids.size() == 0) {
            return R.fail(HttpStatus.WARN,"参数不能为空");
            return AjaxResult.warn("参数不能为空");
        }
        approveProcessService.delApprove(ids);
        return R.ok(null, "操作成功");
        return AjaxResult.success("操作成功");
    }
    @Operation(summary = "公出管理导出")
src/main/java/com/ruoyi/approve/controller/FinReimbursementController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package com.ruoyi.approve.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.FinReimbursementDto;
import com.ruoyi.approve.bean.vo.FinReimbursementVo;
import com.ruoyi.approve.service.FinReimbursementService;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * æŠ¥é”€å•主表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:15
 */
@RestController
@RequestMapping("/finReimbursement")
@Tag(name = "报销单主表", description = "报销单主表")
@AllArgsConstructor
public class FinReimbursementController {
    private final FinReimbursementService finReimbursementService;
    @GetMapping("/listPage")
    @Operation(summary = "分页查询")
    public R listPage(Page<FinReimbursementVo> page, FinReimbursementDto finReimbursementDto) {
        return R.ok(finReimbursementService.listPage(finReimbursementDto, page));
    }
    @PostMapping("/save")
    @Operation(summary = "保存")
    public R save(@RequestBody FinReimbursementDto finReimbursementDto) {
        return R.ok(finReimbursementService.add(finReimbursementDto));
    }
    @GetMapping("/detail")
    @Operation(summary = "详情")
    public R detail(Long id) {
        return R.ok(finReimbursementService.detail(id));
    }
    @PostMapping("/update")
    @Operation(summary = "修改")
    public R update(@RequestBody FinReimbursementDto finReimbursementDto) {
        return R.ok(finReimbursementService.update(finReimbursementDto));
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除")
    public R delete(@RequestBody List<Long> ids) {
        return R.ok(finReimbursementService.delete(ids));
    }
}
src/main/java/com/ruoyi/approve/controller/FinReimbursementDetailController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * æŠ¥é”€å•明细表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:38
 */
@RestController
@RequestMapping("/finReimbursementDetail")
public class FinReimbursementDetailController {
}
src/main/java/com/ruoyi/approve/controller/FinReimbursementTravelController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * å·®æ—…报销扩展表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:47
 */
@RestController
@RequestMapping("/finReimbursementTravel")
public class FinReimbursementTravelController {
}
src/main/java/com/ruoyi/approve/controller/HolidaySettingsController.java
@@ -7,8 +7,7 @@
import com.ruoyi.approve.mapper.WorkingHoursSettingMapper;
import com.ruoyi.approve.pojo.*;
import com.ruoyi.approve.service.HolidaySettingsService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -18,7 +17,7 @@
@RestController
@RequestMapping("/holidaySettings")
@AllArgsConstructor
public class HolidaySettingsController extends BaseController {
public class HolidaySettingsController {
    private HolidaySettingsService holidaySettingsService;
    private AnnualLeaveSettingMapper annualLeaveSettingMapper;
    private OvertimeSettingMapper overtimeSettingMapper;
@@ -29,70 +28,70 @@
     * @return
     */
    @GetMapping("/getList")
    public R<?> getList(@RequestParam(defaultValue = "1") long current,
    public AjaxResult getList(@RequestParam(defaultValue = "1") long current,
                              @RequestParam(defaultValue = "50") long size, HolidaySettings holidaySettings) {
        Page page = new Page(current, size);
        return R.ok(holidaySettingsService.listpage(page,holidaySettings));
        return AjaxResult.success(holidaySettingsService.listpage(page,holidaySettings));
    }
    /**、
     * å¢žæ·»
     * @return
     */
    @PostMapping("/add")
    public R<?> add(@RequestBody HolidaySettings holidaySettings){
        return R.ok(holidaySettingsService.save(holidaySettings));
    public AjaxResult add(@RequestBody HolidaySettings holidaySettings){
        return AjaxResult.success(holidaySettingsService.save(holidaySettings));
    }
    /**
     * æ›´æ–°
     * @return
     */
    @PostMapping("/update")
    public R<?> update(@RequestBody HolidaySettings holidaySettings){
        return R.ok(holidaySettingsService.updateById(holidaySettings));
    public AjaxResult update(@RequestBody HolidaySettings holidaySettings){
        return AjaxResult.success(holidaySettingsService.updateById(holidaySettings));
    }
    /**
     * åˆ é™¤
     * @return
     */
    @DeleteMapping("/delete")
    public R<?> delete(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return R.fail("请传入要删除的ID");
        return R.ok(holidaySettingsService.removeByIds(ids));
    public AjaxResult delete(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
        return AjaxResult.success(holidaySettingsService.removeByIds(ids));
    }
    /**、
     * èŽ·å–å¹´å‡è§„åˆ™åˆ—è¡¨
     * @return
     */
    @GetMapping("/getAnnualLeaveSettingList")
    public R<?> getAnnualLeaveSettingList(@RequestParam(defaultValue = "1") long current,
    public AjaxResult getAnnualLeaveSettingList(@RequestParam(defaultValue = "1") long current,
                              @RequestParam(defaultValue = "50") long size, AnnualLeaveSetting annualLeaveSetting) {
        Page page = new Page(current, size);
        return R.ok(annualLeaveSettingMapper.listPage(page,annualLeaveSetting));
        return AjaxResult.success(annualLeaveSettingMapper.listPage(page,annualLeaveSetting));
    }
    /**、
     * å¢žæ·»å¹´å‡è§„则
     * @return
     */
    @PostMapping("/addAnnualLeaveSetting")
    public R<?> addAnnualLeaveSetting(@RequestBody AnnualLeaveSetting annualLeaveSetting){
        return R.ok(annualLeaveSettingMapper.insert(annualLeaveSetting));
    public AjaxResult addAnnualLeaveSetting(@RequestBody AnnualLeaveSetting annualLeaveSetting){
        return AjaxResult.success(annualLeaveSettingMapper.insert(annualLeaveSetting));
    }
    /**、
     * æ›´æ–°å¹´å‡è§„则
     * @return
     */
    @PostMapping("/updateAnnualLeaveSetting")
    public R<?> updateAnnualLeaveSetting(@RequestBody AnnualLeaveSetting annualLeaveSetting){
        return R.ok(annualLeaveSettingMapper.updateById(annualLeaveSetting));
    public AjaxResult updateAnnualLeaveSetting(@RequestBody AnnualLeaveSetting annualLeaveSetting){
        return AjaxResult.success(annualLeaveSettingMapper.updateById(annualLeaveSetting));
    }
    /**、
     * åˆ é™¤å¹´å‡è§„则
     * @return
     */
    @DeleteMapping("/deleteAnnualLeaveSetting")
    public R<?> deleteAnnualLeaveSetting(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return R.fail("请传入要删除的ID");
        return R.ok(annualLeaveSettingMapper.deleteBatchIds(ids));
    public AjaxResult deleteAnnualLeaveSetting(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
        return AjaxResult.success(annualLeaveSettingMapper.deleteBatchIds(ids));
    }
    /**、
@@ -100,70 +99,70 @@
     * @return
     */
    @GetMapping("/getOvertimeSettingList")
    public R<?> getOvertimeSettingList(@RequestParam(defaultValue = "1") long current,
    public AjaxResult getOvertimeSettingList(@RequestParam(defaultValue = "1") long current,
                              @RequestParam(defaultValue = "50") long size, OvertimeSetting overtimeSetting) {
        Page page = new Page(current, size);
        return R.ok(overtimeSettingMapper.listPage(page,overtimeSetting));
        return AjaxResult.success(overtimeSettingMapper.listPage(page,overtimeSetting));
    }
    /**、
     * å¢žæ·»åŠ ç­è§„åˆ™
     * @return
     */
    @PostMapping("/addOvertimeSetting")
    public R<?> addOvertimeSetting(@RequestBody OvertimeSetting overtimeSetting){
        return R.ok(overtimeSettingMapper.insert(overtimeSetting));
    public AjaxResult addOvertimeSetting(@RequestBody OvertimeSetting overtimeSetting){
        return AjaxResult.success(overtimeSettingMapper.insert(overtimeSetting));
    }
    /**、
     * æ›´æ–°åŠ ç­è§„åˆ™
     * @return
     */
    @PostMapping("/updateOvertimeSetting")
    public R<?> updateOvertimeSetting(@RequestBody OvertimeSetting overtimeSetting){
        return R.ok(overtimeSettingMapper.updateById(overtimeSetting));
    public AjaxResult updateOvertimeSetting(@RequestBody OvertimeSetting overtimeSetting){
        return AjaxResult.success(overtimeSettingMapper.updateById(overtimeSetting));
    }
    /**、
     * åˆ é™¤åŠ ç­è§„åˆ™
     * @return
     */
    @DeleteMapping("/deleteOvertimeSetting")
    public R<?> deleteOvertimeSetting(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return R.fail("请传入要删除的ID");
        return R.ok(overtimeSettingMapper.deleteBatchIds(ids));
    public AjaxResult deleteOvertimeSetting(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
        return AjaxResult.success(overtimeSettingMapper.deleteBatchIds(ids));
    }
    /**、
     * èŽ·å–ä¸Šç­æ—¶é—´è®¾ç½®-班制规则列表
     * @return
     */
    @GetMapping("/getWorkingHoursSettingList")
    public R<?> getWorkingHoursSettingList(@RequestParam(defaultValue = "1") long current,
    public AjaxResult getWorkingHoursSettingList(@RequestParam(defaultValue = "1") long current,
                              @RequestParam(defaultValue = "50") long size, WorkingHoursSetting workingHoursSetting) {
        Page page = new Page(current, size);
        return R.ok(workingHoursSettingMapper.listPage(page,workingHoursSetting));
        return AjaxResult.success(workingHoursSettingMapper.listPage(page,workingHoursSetting));
    }
    /**、
     * å¢žæ·»ç­åˆ¶è§„则
     * @return
     */
    @PostMapping("/addWorkingHoursSetting")
    public R<?> addWorkingHoursSetting(@RequestBody WorkingHoursSetting workingHoursSetting){
        return R.ok(workingHoursSettingMapper.insert(workingHoursSetting));
    public AjaxResult addWorkingHoursSetting(@RequestBody WorkingHoursSetting workingHoursSetting){
        return AjaxResult.success(workingHoursSettingMapper.insert(workingHoursSetting));
    }
    /**、
     * æ›´æ–°ç­åˆ¶è§„则
     * @return
     */
    @PostMapping("/updateWorkingHoursSetting")
    public R<?> updateWorkingHoursSetting(@RequestBody WorkingHoursSetting workingHoursSetting){
        return R.ok(workingHoursSettingMapper.updateById(workingHoursSetting));
    public AjaxResult updateWorkingHoursSetting(@RequestBody WorkingHoursSetting workingHoursSetting){
        return AjaxResult.success(workingHoursSettingMapper.updateById(workingHoursSetting));
    }
    /**、
     * åˆ é™¤ç­åˆ¶è§„则
     * @return
     */
    @DeleteMapping("/deleteWorkingHoursSetting")
    public R<?> deleteWorkingHoursSetting(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return R.fail("请传入要删除的ID");
        return R.ok(workingHoursSettingMapper.deleteBatchIds(ids));
    public AjaxResult deleteWorkingHoursSetting(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
        return AjaxResult.success(workingHoursSettingMapper.deleteBatchIds(ids));
    }
src/main/java/com/ruoyi/approve/controller/KnowledgeBaseController.java
@@ -5,8 +5,7 @@
import com.ruoyi.approve.pojo.KnowledgeBase;
import com.ruoyi.approve.service.KnowledgeBaseService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
@@ -19,7 +18,7 @@
@RequestMapping("/knowledgeBase")
@AllArgsConstructor
@Tag(name = "知识库管理")
public class KnowledgeBaseController extends BaseController {
public class KnowledgeBaseController {
    private KnowledgeBaseService knowledgeBaseService;
    /**、
@@ -27,35 +26,35 @@
     * @return
     */
    @GetMapping("/getList")
    public R<?> getList(@RequestParam(defaultValue = "1") long current,
    public AjaxResult getList(@RequestParam(defaultValue = "1") long current,
                              @RequestParam(defaultValue = "10") long size, KnowledgeBase knowledgeBase) {
        Page page = new Page(current, size);
        return R.ok(knowledgeBaseService.listpage(page,knowledgeBase));
        return AjaxResult.success(knowledgeBaseService.listpage(page,knowledgeBase));
    }
    /**、
     * å¢žæ·»
     * @return
     */
    @PostMapping("/add")
    public R<?> add(@RequestBody KnowledgeBase knowledgeBase){
        return R.ok(knowledgeBaseService.save(knowledgeBase));
    public AjaxResult add(@RequestBody KnowledgeBase knowledgeBase){
        return AjaxResult.success(knowledgeBaseService.save(knowledgeBase));
    }
    /**
     * æ›´æ–°
     * @return
     */
    @PostMapping("/update")
    public R<?> update(@RequestBody KnowledgeBase knowledgeBase){
        return R.ok(knowledgeBaseService.updateById(knowledgeBase));
    public AjaxResult update(@RequestBody KnowledgeBase knowledgeBase){
        return AjaxResult.success(knowledgeBaseService.updateById(knowledgeBase));
    }
    /**
     * åˆ é™¤
     * @return
     */
    @DeleteMapping("/delete")
    public R<?> delete(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return R.fail("请传入要删除的ID");
        return R.ok(knowledgeBaseService.removeByIds(ids));
    public AjaxResult delete(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
        return AjaxResult.success(knowledgeBaseService.removeByIds(ids));
    }
    @Operation(summary = "知识库管理导出")
src/main/java/com/ruoyi/approve/controller/NotificationManagementController.java
@@ -8,8 +8,7 @@
import com.ruoyi.approve.pojo.NotificationManagement;
import com.ruoyi.approve.pojo.OnlineMeeting;
import com.ruoyi.approve.service.NotificationManagementService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -19,7 +18,7 @@
@RestController
@RequestMapping("/notificationManagement")
@AllArgsConstructor
public class NotificationManagementController extends BaseController {
public class NotificationManagementController {
    private NotificationManagementService notificationManagementService ;
    private OnlineMeetingMapper onlineMeetingMapper;
    private FileSharingMapper fileSharingMapper;
@@ -28,35 +27,35 @@
     * @return
     */
    @GetMapping("/getList")
    public R<?> getList(@RequestParam(defaultValue = "1") long current,
    public AjaxResult getList(@RequestParam(defaultValue = "1") long current,
                              @RequestParam(defaultValue = "50") long size, NotificationManagement notificationManagement) {
        Page page = new Page(current, size);
        return R.ok(notificationManagementService.listpage(page,notificationManagement));
        return AjaxResult.success(notificationManagementService.listpage(page,notificationManagement));
    }
    /**、
     * å¢žæ·»
     * @return
     */
    @PostMapping("/add")
    public R<?> add(@RequestBody NotificationManagement notificationManagement){
        return R.ok(notificationManagementService.save(notificationManagement));
    public AjaxResult add(@RequestBody NotificationManagement notificationManagement){
        return AjaxResult.success(notificationManagementService.save(notificationManagement));
    }
    /**
     * æ›´æ–°
     * @return
     */
    @PostMapping("/update")
    public R<?> update(@RequestBody NotificationManagement notificationManagement){
        return R.ok(notificationManagementService.updateById(notificationManagement));
    public AjaxResult update(@RequestBody NotificationManagement notificationManagement){
        return AjaxResult.success(notificationManagementService.updateById(notificationManagement));
    }
    /**
     * åˆ é™¤
     * @return
     */
    @DeleteMapping("/delete")
    public R<?> delete(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return R.fail("请传入要删除的ID");
        return R.ok(notificationManagementService.removeByIds(ids));
    public AjaxResult delete(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
        return AjaxResult.success(notificationManagementService.removeByIds(ids));
    }
    /**
     *新增会议
@@ -64,16 +63,16 @@
     * @return
     */
    @PostMapping("/addOnlineMeeting")
    public R<?> addOnlineMeeting(@RequestBody OnlineMeeting onlineMeeting){
        return R.ok(onlineMeetingMapper.insert(onlineMeeting));
    public AjaxResult addOnlineMeeting(@RequestBody OnlineMeeting onlineMeeting){
        return AjaxResult.success(onlineMeetingMapper.insert(onlineMeeting));
    }
    /**
     *新增文件共享
     *
     */
    @PostMapping("/addFileSharing")
    public R<?> addFileSharing(@RequestBody FileSharing fileSharing){
        return R.ok(fileSharingMapper.insert(fileSharing));
    public AjaxResult addFileSharing(@RequestBody FileSharing fileSharing){
        return AjaxResult.success(fileSharingMapper.insert(fileSharing));
    }
}
src/main/java/com/ruoyi/approve/controller/RpaProcessAutomationController.java
@@ -5,8 +5,7 @@
import com.ruoyi.approve.pojo.RpaProcessAutomation;
import com.ruoyi.approve.service.RpaProcessAutomationService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
@@ -19,42 +18,42 @@
@RequestMapping("/rpaProcessAutomation")
@AllArgsConstructor
@Tag(name = "RPA流程自动化")
public class RpaProcessAutomationController extends BaseController {
public class RpaProcessAutomationController {
    private RpaProcessAutomationService rpaProcessAutomationService;
    /**、
     * èŽ·å–åˆ—è¡¨
     * @return
     */
    @GetMapping("/getList")
    public R<?> getList(@RequestParam(defaultValue = "1") long current,
    public AjaxResult getList(@RequestParam(defaultValue = "1") long current,
                              @RequestParam(defaultValue = "100") long size, RpaProcessAutomation rpaProcessAutomation) {
        Page page = new Page(current, size);
        return R.ok(rpaProcessAutomationService.listpage(page,rpaProcessAutomation));
        return AjaxResult.success(rpaProcessAutomationService.listpage(page,rpaProcessAutomation));
    }
    /**、
     * å¢žæ·»
     * @return
     */
    @PostMapping("/add")
    public R<?> add(@RequestBody RpaProcessAutomation rpaProcessAutomation){
        return R.ok(rpaProcessAutomationService.save(rpaProcessAutomation));
    public AjaxResult add(@RequestBody RpaProcessAutomation rpaProcessAutomation){
        return AjaxResult.success(rpaProcessAutomationService.save(rpaProcessAutomation));
    }
    /**
     * æ›´æ–°
     * @return
     */
    @PostMapping("/update")
    public R<?> update(@RequestBody RpaProcessAutomation rpaProcessAutomation){
        return R.ok(rpaProcessAutomationService.updateById(rpaProcessAutomation));
    public AjaxResult update(@RequestBody RpaProcessAutomation rpaProcessAutomation){
        return AjaxResult.success(rpaProcessAutomationService.updateById(rpaProcessAutomation));
    }
    /**
     * åˆ é™¤
     * @return
     */
    @DeleteMapping("/delete")
    public R<?> delete(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return R.fail("请传入要删除的ID");
        return R.ok(rpaProcessAutomationService.removeByIds(ids));
    public AjaxResult delete(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
        return AjaxResult.success(rpaProcessAutomationService.removeByIds(ids));
    }
    @Operation(summary = "RPA流程自动化导出")
src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
import com.ruoyi.approve.bean.vo.ApprovalInstanceVo;
import com.ruoyi.approve.pojo.ApprovalInstance;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
 * å®¡æ‰¹å®žä¾‹è¡¨ Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:27:46
 */
@Mapper
public interface ApprovalInstanceMapper extends BaseMapper<ApprovalInstance> {
    IPage<ApprovalInstanceVo> listPage(Page<ApprovalInstanceVo> page,@Param("ew") ApprovalInstanceDto approvalInstanceDto);
}
src/main/java/com/ruoyi/approve/mapper/ApprovalInstanceNodeMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.approve.pojo.ApprovalInstanceNode;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * å®¡æ‰¹èŠ‚ç‚¹å®žä¾‹è¡¨ Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:27:54
 */
@Mapper
public interface ApprovalInstanceNodeMapper extends BaseMapper<ApprovalInstanceNode> {
}
src/main/java/com/ruoyi/approve/mapper/ApprovalRecordMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.approve.pojo.ApprovalRecord;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * å®¡æ‰¹è®°å½•表 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:28:21
 */
@Mapper
public interface ApprovalRecordMapper extends BaseMapper<ApprovalRecord> {
}
src/main/java/com/ruoyi/approve/mapper/ApprovalTaskMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.approve.pojo.ApprovalTask;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * å®¡æ‰¹ä»»åŠ¡è¡¨ Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:32:37
 */
@Mapper
public interface ApprovalTaskMapper extends BaseMapper<ApprovalTask> {
}
src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.ApprovalTemplateDto;
import com.ruoyi.approve.bean.vo.ApprovalTemplateVo;
import com.ruoyi.approve.pojo.ApprovalTemplate;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿è¡¨ Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:08
 */
@Mapper
public interface ApprovalTemplateMapper extends BaseMapper<ApprovalTemplate> {
    IPage<ApprovalTemplateVo> listPage(Page<ApprovalTemplateVo> page,@Param("ew") ApprovalTemplateDto approvalTemplateDto);
}
src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeApproverMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿èŠ‚ç‚¹å®¡æ‰¹äººè¡¨ Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:30
 */
@Mapper
public interface ApprovalTemplateNodeApproverMapper extends BaseMapper<ApprovalTemplateNodeApprover> {
}
src/main/java/com/ruoyi/approve/mapper/ApprovalTemplateNodeMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.approve.pojo.ApprovalTemplateNode;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿èŠ‚ç‚¹è¡¨ Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:19
 */
@Mapper
public interface ApprovalTemplateNodeMapper extends BaseMapper<ApprovalTemplateNode> {
}
src/main/java/com/ruoyi/approve/mapper/FinReimbursementDetailMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.approve.pojo.FinReimbursementDetail;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * æŠ¥é”€å•明细表 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:38
 */
@Mapper
public interface FinReimbursementDetailMapper extends BaseMapper<FinReimbursementDetail> {
}
src/main/java/com/ruoyi/approve/mapper/FinReimbursementMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.FinReimbursementDto;
import com.ruoyi.approve.bean.vo.FinReimbursementVo;
import com.ruoyi.approve.pojo.FinReimbursement;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
 * æŠ¥é”€å•主表 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:15
 */
@Mapper
public interface FinReimbursementMapper extends BaseMapper<FinReimbursement> {
    IPage<FinReimbursementVo> listPage(@Param("ew") FinReimbursementDto finReimbursementDto, Page<FinReimbursementVo> page);
}
src/main/java/com/ruoyi/approve/mapper/FinReimbursementTravelMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.approve.pojo.FinReimbursementTravel;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * å·®æ—…报销扩展表 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:47
 */
@Mapper
public interface FinReimbursementTravelMapper extends BaseMapper<FinReimbursementTravel> {
}
src/main/java/com/ruoyi/approve/pojo/ApprovalInstance.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,151 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * å®¡æ‰¹å®žä¾‹è¡¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:27:46
 */
@Getter
@Setter
@ToString
@TableName("approval_instance")
@ApiModel(value = "ApprovalInstance对象", description = "审批实例表")
public class ApprovalInstance implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * å®¡æ‰¹å®žä¾‹ID
     */
    @Schema(description ="审批实例ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * å®¡æ‰¹ç¼–号
     */
    @Schema(description ="审批编号")
    private String instanceNo;
    /**
     * æ¨¡æ¿ID
     */
    @Schema(description ="模板ID")
    private Long templateId;
    /**
     * æ¨¡æ¿åç§°
     */
    @Schema(description ="模板名称")
    private String templateName;
    /**
     * ä¸šåŠ¡ID
     */
    @Schema(description ="业务ID")
    private Long businessId;
    /**
     * ä¸šåŠ¡ç±»åž‹
     */
    @Schema(description ="业务类型")
    private Long businessType;
    /**
     * å®¡æ‰¹æ ‡é¢˜
     */
    @Schema(description ="审批标题")
    private String title;
    /**
     * å®¡æ‰¹çŠ¶æ€
     */
    @Schema(description ="审批状态 PENDING - å¾…审批/进行中  APPROVED - å·²é€šè¿‡/已完成  REJECTED - å·²é©³å›ž")
    private String status;
    /**
     * å½“前审批级别
     */
    @Schema(description ="当前审批级别")
    private Integer currentLevel;
    /**
     * ç”³è¯·äººID
     */
    @Schema(description ="申请人ID")
    private Long applicantId;
    /**
     * ç”³è¯·äººåç§°
     */
    @Schema(description ="申请人名称")
    private String applicantName;
    /**
     * ç”³è¯·æ—¶é—´
     */
    @Schema(description ="申请时间")
    private LocalDateTime applyTime;
    /**
     * å®Œæˆæ—¶é—´
     */
    @Schema(description ="完成时间")
    private LocalDateTime finishTime;
    /**
     * åˆ›å»ºäºº
     */
    @Schema(description ="创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @Schema(description ="创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    /**
     * æ›´æ–°äºº
     */
    @Schema(description ="更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    /**
     * æ›´æ–°æ—¶é—´
     */
    @Schema(description ="更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * é€»è¾‘删除
     */
    @Schema(description ="逻辑删除")
    private Byte deleted;
    @Schema(description = "表单数据")
    private String formConfig;
}
src/main/java/com/ruoyi/approve/pojo/ApprovalInstanceNode.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * å®¡æ‰¹èŠ‚ç‚¹å®žä¾‹è¡¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:27:54
 */
@Getter
@Setter
@ToString
@TableName("approval_instance_node")
@ApiModel(value = "ApprovalInstanceNode对象", description = "审批节点实例表")
public class ApprovalInstanceNode implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * èŠ‚ç‚¹å®žä¾‹ID
     */
    @Schema(description ="节点实例ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * å®¡æ‰¹å®žä¾‹ID
     */
    @Schema(description ="审批实例ID")
    private Long instanceId;
    /**
     * å®¡æ‰¹çº§åˆ«
     */
    @Schema(description ="审批级别")
    private Integer levelNo;
    /**
     * å®¡æ‰¹ç±»åž‹
     */
    @Schema(description ="审批类型")
    private String approveType;
    /**
     * èŠ‚ç‚¹çŠ¶æ€
     */
    @Schema(description ="节点状态 PENDING - å¾…处理 APPROVED - å·²é€šè¿‡ REJECTED - å·²é©³å›ž")
    private String status;
    /**
     * å¼€å§‹æ—¶é—´
     */
    @Schema(description ="开始时间")
    private LocalDateTime startTime;
    /**
     * å®Œæˆæ—¶é—´
     */
    @Schema(description ="完成时间")
    private LocalDateTime finishTime;
    /**
     * åˆ›å»ºäºº
     */
    @Schema(description ="创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @Schema(description ="创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
     * æ›´æ–°äºº
     */
    @Schema(description ="更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    /**
     * æ›´æ–°æ—¶é—´
     */
    @Schema(description ="更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    /**
     * é€»è¾‘删除
     */
    @Schema(description ="逻辑删除")
    private Byte deleted;
}
src/main/java/com/ruoyi/approve/pojo/ApprovalRecord.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * å®¡æ‰¹è®°å½•表
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:28:21
 */
@Getter
@Setter
@ToString
@TableName("approval_record")
@ApiModel(value = "ApprovalRecord对象", description = "审批记录表")
public class ApprovalRecord implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * å®¡æ‰¹è®°å½•ID
     */
    @Schema(description ="审批记录ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * å®¡æ‰¹å®žä¾‹ID
     */
    @Schema(description ="审批实例ID")
    private Long instanceId;
    /**
     * èŠ‚ç‚¹å®žä¾‹ID
     */
    @Schema(description ="节点实例ID")
    private Long nodeId;
    /**
     * å®¡æ‰¹ä»»åŠ¡ID
     */
    @Schema(description ="审批任务ID")
    private Long taskId;
    /**
     * æ“ä½œäººID
     */
    @Schema(description ="操作人ID")
    private Long operatorId;
    /**
     * æ“ä½œäººåç§°
     */
    @Schema(description ="操作人名称")
    private String operatorName;
    /**
     * æ“ä½œç±»åž‹
     */
    @Schema(description ="操作类型")
    private String action;
    /**
     * å®¡æ‰¹æ„è§
     */
    @Schema(description ="审批意见")
    private String comment;
    /**
     * åˆ›å»ºäºº
     */
    @Schema(description ="创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @Schema(description ="创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
     * é€»è¾‘删除
     */
    @Schema(description ="逻辑删除")
    private Byte deleted;
}
src/main/java/com/ruoyi/approve/pojo/ApprovalTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,128 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * å®¡æ‰¹ä»»åŠ¡è¡¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:32:37
 */
@Getter
@Setter
@ToString
@TableName("approval_task")
@ApiModel(value = "ApprovalTask对象", description = "审批任务表")
public class ApprovalTask implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * å®¡æ‰¹ä»»åŠ¡ID
     */
    @Schema(description ="审批任务ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * å®¡æ‰¹å®žä¾‹ID
     */
    @Schema(description ="审批实例ID")
    private Long instanceId;
    /**
     * èŠ‚ç‚¹å®žä¾‹ID
     */
    @Schema(description ="节点实例ID")
    private Long nodeId;
    /**
     * å®¡æ‰¹çº§åˆ«
     */
    @Schema(description ="审批级别")
    private Integer levelNo;
    /**
     * å®¡æ‰¹äººID
     */
    @Schema(description ="审批人ID")
    private Long approverId;
    /**
     * å®¡æ‰¹äººåç§°
     */
    @Schema(description ="审批人名称")
    private String approverName;
    /**
     * ä»»åŠ¡çŠ¶æ€
     */
    @Schema(description ="任务状态 PENDING - å¾…审批 APPROVED - å·²åŒæ„  REJECTED - å·²æ‹’绝")
    private String taskStatus;
    /**
     * å®¡æ‰¹æ—¶é—´
     */
    @Schema(description ="审批时间")
    private LocalDateTime approveTime;
    /**
     * å®¡æ‰¹æ„è§
     */
    @Schema(description ="审批意见")
    private String comment;
    /**
     * æ˜¯å¦å·²è¯»
     */
    @Schema(description ="是否已读")
    private Byte isRead;
    /**
     * åˆ›å»ºäºº
     */
    @Schema(description ="创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @Schema(description ="创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
     * æ›´æ–°äºº
     */
    @Schema(description ="更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    /**
     * æ›´æ–°æ—¶é—´
     */
    @Schema(description ="更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    /**
     * é€»è¾‘删除
     */
    @Schema(description ="逻辑删除")
    private Byte deleted;
    @Schema(description ="部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/ApprovalTemplate.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿è¡¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:08
 */
@Getter
@Setter
@ToString
@TableName("approval_template")
@ApiModel(value = "ApprovalTemplate对象", description = "审批模板表")
public class ApprovalTemplate implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * æ¨¡æ¿ID
     */
    @Schema(description ="模板ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * æ¨¡æ¿åç§°
     */
    @Schema(description ="模板名称")
    private String templateName;
    /**
     * å¯ç”¨çŠ¶æ€ï¼š1启用,0停用
     */
    @Schema(description ="启用状态:1启用,0停用")
    private Byte enabled;
    /**
     * æ¨¡æ¿è¯´æ˜Ž
     */
    @Schema(description ="模板说明")
    private String description;
    /**
     * åˆ›å»ºäºº
     */
    @Schema(description ="创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @Schema(description ="创建时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    /**
     * æ›´æ–°äºº
     */
    @Schema(description ="更新人")
    @TableField(fill = FieldFill.UPDATE)
    private Long updateUser;
    /**
     * æ›´æ–°æ—¶é—´
     */
    @Schema(description ="更新时间")
    @TableField(fill = FieldFill.UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    /**
     * é€»è¾‘删除:0否,1是
     */
    @Schema(description ="逻辑删除:0否,1是")
    private Integer deleted;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @Schema(description = "表单配置")
    private String formConfig;
    @Schema(description = "模板类型:0系统内置,1自定义")
    private Integer templateType;
    @Schema(description = "业务类型")
    private Long businessType;
}
src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNode.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿èŠ‚ç‚¹è¡¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:19
 */
@Getter
@Setter
@ToString
@TableName("approval_template_node")
@ApiModel(value = "ApprovalTemplateNode对象", description = "审批模板节点表")
public class ApprovalTemplateNode implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * èŠ‚ç‚¹ID
     */
    @Schema(description ="节点ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * å®¡æ‰¹æ¨¡æ¿ID
     */
    @Schema(description ="审批模板ID")
    private Long templateId;
    /**
     * å®¡æ‰¹çº§åˆ«ï¼Œä»Ž1开始
     */
    @Schema(description ="审批级别,从1开始")
    private Integer levelNo;
    /**
     * å®¡æ‰¹æ–¹å¼ï¼šAND会签,OR或签
     */
    @Schema(description ="审批方式:AND会签,OR或签")
    private String approveType;
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/ApprovalTemplateNodeApprover.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,76 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿èŠ‚ç‚¹å®¡æ‰¹äººè¡¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:30
 */
@Getter
@Setter
@ToString
@TableName("approval_template_node_approver")
@ApiModel(value = "ApprovalTemplateNodeApprover对象", description = "审批模板节点审批人表")
public class ApprovalTemplateNodeApprover implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®ID
     */
    @Schema(description = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹ID
     */
    @Schema(description ="审批节点ID")
    private Long nodeId;
    /**
     * å®¡æ‰¹æ¨¡æ¿ID
     */
    @Schema(description ="审批模板ID")
    private Long templateId;
    /**
     * å®¡æ‰¹äººID
     */
    @Schema(description ="审批人ID")
    private Long approverId;
    /**
     * å®¡æ‰¹äººåç§°å†—ä½™
     */
    @Schema(description ="审批人名称冗余")
    private String approverName;
    /**
     * å®¡æ‰¹äººæŽ’序
     */
    @Schema(description ="审批人排序")
    private Integer sortNo;
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @TableField(fill = FieldFill.INSERT)
    private Long deleted ;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java
@@ -151,6 +151,16 @@
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date endDate;
    @Excel(name = "出差开始时间", dateFormat = "yyyy-MM-dd HH:mm", width = 30)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "出差开始时间")
    private LocalDateTime startDateTime;
    @Excel(name = "出差结束时间", dateFormat = "yyyy-MM-dd HH:mm", width = 30)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm")
    @Schema(description = "出差结束时间")
    private LocalDateTime endDateTime;
    private BigDecimal price;
    private String location;
src/main/java/com/ruoyi/approve/pojo/FinReimbursement.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,209 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <p>
 * æŠ¥é”€å•主表
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:15
 */
@Getter
@Setter
@ToString
@TableName("fin_reimbursement")
@ApiModel(value = "FinReimbursement对象", description = "报销单主表")
public class FinReimbursement implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®ID
     */
    @Schema(description = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * æŠ¥é”€å•号
     */
    @Schema(description = "报销单号")
    private String billNo;
    /**
     * æŠ¥é”€ç±»åž‹ï¼š1-差旅报销,2-费用报销
     */
    @Schema(description = "报销类型:1-差旅报销,2-费用报销")
    private Byte reimbursementType;
    /**
     * è´¹ç”¨ç±»åž‹ï¼šå·®æ—…è´¹/办公采购/业务招待/交通费/通讯费/其他
     */
    @Schema(description = "费用类型:差旅费/办公采购/业务招待/交通费/通讯费/其他")
    private String expenseType;
    /**
     * ç”³è¯·äººID
     */
    @Schema(description = "申请人ID")
    private Long applicantId;
    /**
     * å‘˜å·¥ç¼–号
     */
    @Schema(description = "员工编号")
    private String applicantCode;
    /**
     * å‘˜å·¥å§“名
     */
    @Schema(description = "员工姓名")
    private String applicantName;
    /**
     * ç”³è¯·éƒ¨é—¨ID
     */
    @Schema(description = "申请部门ID")
    private Long applicantDeptId;
    /**
     * ç”³è¯·éƒ¨é—¨åç§°
     */
    @Schema(description = "申请部门名称")
    private String applicantDeptName;
    /**
     * æŠ¥é”€åŽŸå› 
     */
    @Schema(description = "报销原因")
    private String reason;
    /**
     * ç”³è¯·é‡‘额
     */
    @Schema(description = "申请金额")
    private BigDecimal applyAmount;
    /**
     * æ˜Žç»†æ±‡æ€»é‡‘额
     */
    @Schema(description = "明细汇总金额")
    private BigDecimal detailTotalAmount;
    /**
     * æ”¶æ¬¾äºº
     */
    @Schema(description = "收款人")
    private String payeeName;
    /**
     * æ”¶æ¬¾è´¦å·
     */
    @Schema(description = "收款账号")
    private String payeeAccount;
    /**
     * å¼€æˆ·æ”¯è¡Œ
     */
    @Schema(description = "开户支行")
    private String payeeBank;
    /**
     * å®¡æ‰¹å®žä¾‹ID,对应 approval_instance.id
     */
    @Schema(description = "审批实例ID,对应 approval_instance.id")
    private Long approvalInstanceId;
    /**
     * å®¡æ‰¹æµç¨‹ID,对应 approve_process.id
     */
    @Schema(description = "审批流程ID,对应 approve_process.id")
    private Long approveProcessId;
    /**
     * å•据状态:DRAFT-草稿,IN_APPROVAL-审批中,APPROVED-审批通过,REJECTED-审批驳回,WITHDRAWN-已撤回,PAID-已付款
     */
    @Schema(description = "单据状态:DRAFT-草稿,IN_APPROVAL-审批中,APPROVED-审批通过,REJECTED-审批驳回,WITHDRAWN-已撤回,PAID-已付款")
    private String billStatus;
    /**
     * å®¡æ‰¹é€šè¿‡æ—¶é—´
     */
    @Schema(description = "审批通过时间")
    private LocalDateTime approvedTime;
    /**
     * ä»˜æ¬¾æ—¶é—´
     */
    @Schema(description = "付款时间")
    private LocalDateTime paidTime;
    /**
     * ç”Ÿæˆçš„财务支出记录ID,对应 account_expense.id
     */
    @Schema(description = "生成的财务支出记录ID,对应 account_expense.id")
    private Long accountExpenseId;
    /**
     * å¤‡æ³¨
     */
    @Schema(description = "备注")
    private String remark;
    /**
     * ç§Ÿæˆ·ID
     */
    @Schema(description = "租户ID")
    private Long tenantId;
    /**
     * åˆ›å»ºäºº
     */
    @Schema(description = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    /**
     * æ›´æ–°äºº
     */
    @Schema(description = "更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    /**
     * æ›´æ–°æ—¶é—´
     */
    @Schema(description = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    /**
     * å½’属部门ID
     */
    @Schema(description = "归属部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * é€»è¾‘删除:0-否,1-是
     */
    @Schema(description = "逻辑删除:0-否,1-是")
    private Byte deleted;
}
src/main/java/com/ruoyi/approve/pojo/FinReimbursementDetail.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,157 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * <p>
 * æŠ¥é”€å•明细表
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:38
 */
@Getter
@Setter
@ToString
@TableName("fin_reimbursement_detail")
@ApiModel(value = "FinReimbursementDetail对象", description = "报销单明细表")
public class FinReimbursementDetail implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®ID
     */
    @Schema(description = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * æŠ¥é”€å•ID,对应 fin_reimbursement.id
     */
    @Schema(description = "报销单ID,对应 fin_reimbursement.id")
    private Long reimbursementId;
    /**
     * æ˜Žç»†è¡Œå·
     */
    @Schema(description = "明细行号")
    private Integer rowNo;
    /**
     * å‘票日期
     */
    @Schema(description = "发票日期")
    private LocalDate invoiceDate;
    /**
     * è´¹ç”¨ç§‘ç›®
     */
    @Schema(description = "费用科目")
    private String expenseCategory;
    /**
     * é‡‘额
     */
    @Schema(description = "金额")
    private BigDecimal amount;
    /**
     * æè¿°
     */
    @Schema(description = "描述")
    private String description;
    /**
     * å‘票号码
     */
    @Schema(description = "发票号码")
    private String invoiceNo;
    /**
     * å‘票类型
     */
    @Schema(description = "发票类型")
    private String invoiceType;
    /**
     * ç¥¨é¢é‡‘额
     */
    @Schema(description = "票面金额")
    private BigDecimal invoiceAmount;
    /**
     * ç¨Žçއ
     */
    @Schema(description = "税率")
    private BigDecimal taxRate;
    /**
     * ç¨Žé¢
     */
    @Schema(description = "税额")
    private BigDecimal taxAmount;
    /**
     * å¤‡æ³¨
     */
    @Schema(description = "备注")
    private String remark;
    /**
     * ç§Ÿæˆ·ID
     */
    @Schema(description = "租户ID")
    private Long tenantId;
    /**
     * åˆ›å»ºäºº
     */
    @Schema(description = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
     * æ›´æ–°äºº
     */
    @Schema(description = "更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    /**
     * æ›´æ–°æ—¶é—´
     */
    @Schema(description = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    /**
     * å½’属部门ID
     */
    @Schema(description = "归属部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    /**
     * é€»è¾‘删除:0-否,1-是
     */
    @Schema(description = "逻辑删除:0-否,1-是")
    private Byte deleted;
}
src/main/java/com/ruoyi/approve/pojo/FinReimbursementTravel.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,162 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * <p>
 * å·®æ—…报销扩展表
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:47
 */
@Getter
@Setter
@ToString
@TableName("fin_reimbursement_travel")
@ApiModel(value = "FinReimbursementTravel对象", description = "差旅报销扩展表")
public class FinReimbursementTravel implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®ID
     */
    @Schema(description = "主键ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * æŠ¥é”€å•ID,对应 fin_reimbursement.id
     */
    @Schema(description = "报销单ID,对应 fin_reimbursement.id")
    private Long reimbursementId;
    /**
     * å‡ºå·®å¼€å§‹æ—¶é—´
     */
    @Schema(description = "出差开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime startTime;
    /**
     * å‡ºå·®ç»“束时间
     */
    @Schema(description = "出差结束时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime endTime;
    /**
     * å‡ºå·®å¤©æ•°
     */
    @Schema(description = "出差天数")
    private BigDecimal travelDays;
    /**
     * å‡ºå·®åœ°/出发城市
     */
    @Schema(description = "出差地/出发城市")
    private String departureCity;
    /**
     * ç›®çš„地/目的城市
     */
    @Schema(description = "目的地/目的城市")
    private String destinationCity;
    /**
     * é…’店标准
     */
    @Schema(description = "酒店标准")
    private BigDecimal hotelStandard;
    /**
     * ä½å®¿å¤©æ•°
     */
    @Schema(description = "住宿天数")
    private BigDecimal lodgingDays;
    /**
     * ç”Ÿæ´»è¡¥è´´
     */
    @Schema(description = "生活补贴")
    private BigDecimal mealAllowance;
    /**
     * äº¤é€šè¡¥è´´
     */
    @Schema(description = "交通补贴")
    private BigDecimal transportAllowance;
    /**
     * ä½å®¿é™é¢
     */
    @Schema(description = "住宿限额")
    private BigDecimal lodgingLimit;
    /**
     * ç‰¹æ‰¹æ ‡è®°æ–‡æœ¬ï¼Œå¦‚在标准范围内/超标特批
     */
    @Schema(description = "特批标记文本,如在标准范围内/超标特批")
    private String standardTag;
    /**
     * æ˜¯å¦åœ¨æ ‡å‡†å†…:1-是,0-否
     */
    @Schema(description = "是否在标准内:1-是,0-否")
    private Byte withinStandard;
    /**
     * ç§Ÿæˆ·ID
     */
    @Schema(description = "租户ID")
    private Long tenantId;
    /**
     * åˆ›å»ºäºº
     */
    @Schema(description = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @Schema(description = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    /**
     * æ›´æ–°äºº
     */
    @Schema(description = "更新人")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    /**
     * æ›´æ–°æ—¶é—´
     */
    @Schema(description = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    /**
     * å½’属部门ID
     */
    @Schema(description = "归属部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/approve/service/ApprovalInstanceNodeService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.approve.service;
import com.ruoyi.approve.pojo.ApprovalInstanceNode;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * å®¡æ‰¹èŠ‚ç‚¹å®žä¾‹è¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:27:54
 */
public interface ApprovalInstanceNodeService extends IService<ApprovalInstanceNode> {
}
src/main/java/com/ruoyi/approve/service/ApprovalInstanceService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package com.ruoyi.approve.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
import com.ruoyi.approve.bean.vo.ApprovalInstanceVo;
import com.ruoyi.approve.pojo.ApprovalInstance;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.framework.web.domain.R;
import java.util.List;
/**
 * <p>
 * å®¡æ‰¹å®žä¾‹è¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:27:46
 */
public interface ApprovalInstanceService extends IService<ApprovalInstance> {
    R listPage(Page<ApprovalInstanceVo> page, ApprovalInstanceDto approvalInstanceDto);
    Boolean add(ApprovalInstanceDto approvalInstanceDto);
    Boolean update(ApprovalInstanceDto approvalInstanceDto);
    Boolean delete(List<Long> ids);
    R approve(ApprovalInstanceDto approvalInstanceDto);
}
src/main/java/com/ruoyi/approve/service/ApprovalRecordService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.approve.service;
import com.ruoyi.approve.pojo.ApprovalRecord;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * å®¡æ‰¹è®°å½•表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:28:21
 */
public interface ApprovalRecordService extends IService<ApprovalRecord> {
}
src/main/java/com/ruoyi/approve/service/ApprovalTaskService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.approve.service;
import com.ruoyi.approve.pojo.ApprovalTask;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * å®¡æ‰¹ä»»åŠ¡è¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:32:37
 */
public interface ApprovalTaskService extends IService<ApprovalTask> {
}
src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeApproverService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.approve.service;
import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿èŠ‚ç‚¹å®¡æ‰¹äººè¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:30
 */
public interface ApprovalTemplateNodeApproverService extends IService<ApprovalTemplateNodeApprover> {
}
src/main/java/com/ruoyi/approve/service/ApprovalTemplateNodeService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.approve.service;
import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeDto;
import com.ruoyi.approve.pojo.ApprovalTemplateNode;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿èŠ‚ç‚¹è¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:19
 */
public interface ApprovalTemplateNodeService extends IService<ApprovalTemplateNode> {
    Boolean saveApprovalTemplateNode(Long id, List<ApprovalTemplateNodeDto> nodes);
}
src/main/java/com/ruoyi/approve/service/ApprovalTemplateService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.approve.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.ApprovalTemplateDto;
import com.ruoyi.approve.bean.vo.ApprovalTemplateVo;
import com.ruoyi.approve.pojo.ApprovalTemplate;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿è¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:08
 */
public interface ApprovalTemplateService extends IService<ApprovalTemplate> {
    IPage<ApprovalTemplateVo> listPage(Page<ApprovalTemplateVo> page, ApprovalTemplateDto approvalTemplateDto);
    Boolean saveApprovalTemplateDto(ApprovalTemplateDto approvalTemplateDto);
    Boolean updateApprovalTemplateDto(ApprovalTemplateDto approvalTemplateDto);
    Boolean delete(List<Long> ids);
    List<ApprovalTemplateVo> listApprovalTemplateVo(Integer type);
    ApprovalTemplateVo getApprovalTemplateVoById(Long id);
}
src/main/java/com/ruoyi/approve/service/FinReimbursementDetailService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.approve.service;
import com.ruoyi.approve.pojo.FinReimbursementDetail;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * æŠ¥é”€å•明细表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:38
 */
public interface FinReimbursementDetailService extends IService<FinReimbursementDetail> {
}
src/main/java/com/ruoyi/approve/service/FinReimbursementService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package com.ruoyi.approve.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.approve.bean.dto.FinReimbursementDto;
import com.ruoyi.approve.bean.vo.FinReimbursementVo;
import com.ruoyi.approve.pojo.FinReimbursement;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
 * <p>
 * æŠ¥é”€å•主表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:15
 */
public interface FinReimbursementService extends IService<FinReimbursement> {
    IPage<FinReimbursementVo> listPage(FinReimbursementDto finReimbursementDto, Page<FinReimbursementVo> page);
    Boolean add(FinReimbursementDto finReimbursementDto);
    FinReimbursementVo detail(Long id);
    Boolean update(FinReimbursementDto finReimbursementDto);
    Boolean delete(List<Long> ids);
}
src/main/java/com/ruoyi/approve/service/FinReimbursementTravelService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.approve.service;
import com.ruoyi.approve.pojo.FinReimbursementTravel;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * å·®æ—…报销扩展表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:47
 */
public interface FinReimbursementTravelService extends IService<FinReimbursementTravel> {
}
src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceNodeServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.approve.service.impl;
import com.ruoyi.approve.pojo.ApprovalInstanceNode;
import com.ruoyi.approve.mapper.ApprovalInstanceNodeMapper;
import com.ruoyi.approve.service.ApprovalInstanceNodeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * å®¡æ‰¹èŠ‚ç‚¹å®žä¾‹è¡¨ æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:27:54
 */
@Service
public class ApprovalInstanceNodeServiceImpl extends ServiceImpl<ApprovalInstanceNodeMapper, ApprovalInstanceNode> implements ApprovalInstanceNodeService {
}
src/main/java/com/ruoyi/approve/service/impl/ApprovalInstanceServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,753 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
import com.ruoyi.approve.bean.vo.ApprovalInstanceVo;
import com.ruoyi.approve.mapper.ApprovalInstanceMapper;
import com.ruoyi.approve.mapper.ApprovalTemplateNodeApproverMapper;
import com.ruoyi.approve.mapper.FinReimbursementMapper;
import com.ruoyi.approve.pojo.*;
import com.ruoyi.approve.service.*;
import com.ruoyi.approve.utils.ApproveProcessConfigNodeUtils;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsMapper;
import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsScopeDeptMapper;
import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsScopeUserMapper;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser;
import com.ruoyi.common.enums.*;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysUserDeptMapper;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.project.system.service.ISysNoticeService;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.quality.utils.QualityInspectHelper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.SalesQuotationMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.staff.mapper.HolidayApplicationMapper;
import com.ruoyi.staff.pojo.HolidayApplication;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
 * <p>
 * å®¡æ‰¹å®žä¾‹æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @since 2026-05-18 03:27:46
 */
@Service
@RequiredArgsConstructor
public class ApprovalInstanceServiceImpl extends ServiceImpl<ApprovalInstanceMapper, ApprovalInstance> implements ApprovalInstanceService {
    private static final String ENTERPRISE_NEWS_STATUS_PUBLISHED = "PUBLISHED";
    private static final String ENTERPRISE_NEWS_STATUS_REJECTED = "REJECTED";
    private final ApprovalInstanceMapper approvalInstanceMapper;
    private final ApproveProcessConfigNodeUtils approveProcessConfigNodeUtils;
    private final ApprovalInstanceNodeService approvalInstanceNodeService;
    private final ApprovalTaskService approvalTaskService;
    private final ApprovalRecordService approvalRecordService;
    private final ApprovalTemplateNodeService approvalTemplateNodeService;
    private final FinReimbursementMapper finReimbursementMapper;
    private final FileUtil fileUtil;
    private final ISysNoticeService sysNoticeService;
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final StockUtils stockUtils;
    private final SalesQuotationMapper salesQuotationMapper;
    private final ShippingInfoMapper shippingInfoMapper;
    private final QualityInspectHelper qualityInspectHelper;
    private final EnterpriseNewsScopeUserMapper enterpriseNewsScopeUserMapper;
    private final SysUserMapper sysUserMapper;
    private final SysUserDeptMapper sysUserDeptMapper;
    private final SysDeptMapper sysDeptMapper;
    private final HolidayApplicationMapper holidayApplicationMapper;
    private final EnterpriseNewsMapper enterpriseNewsMapper;
    private final EnterpriseNewsScopeDeptMapper enterpriseNewsScopeDeptMapper;
    private final ApprovalTemplateNodeApproverMapper approvalTemplateNodeApproverMapper;
    @Override
    public R listPage(Page<ApprovalInstanceVo> page, ApprovalInstanceDto approvalInstanceDto) {
        IPage<ApprovalInstanceVo> approvalInstanceVoIPage = approvalInstanceMapper.listPage(page, approvalInstanceDto);
        List<ApprovalInstanceVo> records = approvalInstanceVoIPage.getRecords();
        if (records == null || records.isEmpty()) {
            return R.ok(approvalInstanceVoIPage);
        }
        records.forEach(vo -> {
            vo.setBusinessName(TypeEnums.getLabelByValue(vo.getBusinessType()));
        });
        Long currentUserId = SecurityUtils.getUserId();
        List<Long> instanceIds = records.stream()
                .map(ApprovalInstanceVo::getId)
                .filter(id -> id != null)
                .distinct()
                .collect(Collectors.toList());
        if (!instanceIds.isEmpty()) {
            Map<Long, List<ApprovalRecord>> recordMap = approvalRecordService.list(
                    Wrappers.<ApprovalRecord>lambdaQuery()
                            .in(ApprovalRecord::getInstanceId, instanceIds)
                            .eq(ApprovalRecord::getDeleted, 0)
            ).stream().collect(Collectors.groupingBy(ApprovalRecord::getInstanceId));
            Map<Long, List<ApprovalTask>> taskMap = approvalTaskService.list(
                    Wrappers.<ApprovalTask>lambdaQuery()
                            .in(ApprovalTask::getInstanceId, instanceIds)
                            .eq(ApprovalTask::getDeleted, 0)
            ).stream().collect(Collectors.groupingBy(ApprovalTask::getInstanceId));
            for (ApprovalInstanceVo vo : records) {
                vo.setIsApprove(approveProcessConfigNodeUtils.isCurrentApprover(vo.getId(), currentUserId));
                vo.setRecords(recordMap.getOrDefault(vo.getId(), new ArrayList<>()));
                vo.setTasks(taskMap.getOrDefault(vo.getId(), new ArrayList<>()));
                vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.APPROVAL_INSTANCE, vo.getId()));
            }
        }
        return R.ok(approvalInstanceVoIPage);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean add(ApprovalInstanceDto approvalInstanceDto) {
        String instanceNo = OrderUtils.countTodayByCreateTime(approvalInstanceMapper, "SP", "instance_no", approvalInstanceDto.getCreateTime() != null ? approvalInstanceDto.getCreateTime() : LocalDateTime.now());
        approvalInstanceDto.setInstanceNo(instanceNo);
        approvalInstanceDto.setStatus("PENDING");
        approvalInstanceDto.setCurrentLevel(1);
        boolean saved = this.save(approvalInstanceDto);
        if (!saved) {
            return false;
        }
        approveProcessConfigNodeUtils.createCurrentNodeAndTasks(approvalInstanceDto);
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVAL_INSTANCE, approvalInstanceDto.getId(), approvalInstanceDto.getStorageBlobDTOs());
        sendApproveNotice(approvalInstanceDto, approveProcessConfigNodeUtils.getCurrentPendingTasks(approvalInstanceDto.getId()));
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(ApprovalInstanceDto approvalInstanceDto) {
        if (approvalInstanceDto == null || approvalInstanceDto.getId() == null) {
            return false;
        }
        // åˆ¤æ–­æ˜¯å¦æœ‰æ­£åœ¨è¿›è¡Œçš„审批任务,有则不允许修改
        long pendingTaskCount = approvalTaskService.count(
                Wrappers.<ApprovalTask>lambdaQuery()
                        .eq(ApprovalTask::getInstanceId, approvalInstanceDto.getId())
                        .eq(ApprovalTask::getTaskStatus, "PENDING")
                        .eq(ApprovalTask::getDeleted, 0)
        );
        if (pendingTaskCount > 0) {
            throw new ServiceException("该审批单有正在进行的审批任务,不允许修改");
        }
        boolean updated = this.updateById(approvalInstanceDto);
        if (!updated) {
            return false;
        }
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVAL_INSTANCE, approvalInstanceDto.getId(), approvalInstanceDto.getStorageBlobDTOs());
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean delete(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return false;
        }
        fileUtil.deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVAL_INSTANCE, ids);
        int instanceRows = approvalInstanceMapper.update(
                null,
                Wrappers.<ApprovalInstance>lambdaUpdate()
                        .in(ApprovalInstance::getId, ids)
                        .eq(ApprovalInstance::getDeleted, 0)
                        .set(ApprovalInstance::getDeleted, (byte) 1)
        );
        LambdaUpdateWrapper<ApprovalInstanceNode> nodeUpdateWrapper = Wrappers.lambdaUpdate();
        nodeUpdateWrapper.in(ApprovalInstanceNode::getInstanceId, ids)
                .eq(ApprovalInstanceNode::getDeleted, 0)
                .set(ApprovalInstanceNode::getDeleted, (byte) 1);
        approvalInstanceNodeService.update(nodeUpdateWrapper);
        LambdaUpdateWrapper<ApprovalTask> taskUpdateWrapper = Wrappers.lambdaUpdate();
        taskUpdateWrapper.in(ApprovalTask::getInstanceId, ids)
                .eq(ApprovalTask::getDeleted, 0)
                .set(ApprovalTask::getDeleted, (byte) 1);
        approvalTaskService.update(taskUpdateWrapper);
        LambdaUpdateWrapper<ApprovalRecord> recordUpdateWrapper = Wrappers.lambdaUpdate();
        recordUpdateWrapper.in(ApprovalRecord::getInstanceId, ids)
                .eq(ApprovalRecord::getDeleted, 0)
                .set(ApprovalRecord::getDeleted, (byte) 1);
        approvalRecordService.update(recordUpdateWrapper);
        return instanceRows > 0;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R approve(ApprovalInstanceDto approvalInstanceDto) {
        if (approvalInstanceDto == null || approvalInstanceDto.getId() == null) {
            return R.fail("审批实例 ID ä¸èƒ½ä¸ºç©º");
        }
        String approveAction = normalizeApproveAction(approvalInstanceDto.getApproveAction());
        if (approveAction == null) {
            return R.fail("审批动作只支持 APPROVED æˆ– REJECTED");
        }
        ApprovalInstance instance = getPendingApprovalInstance(approvalInstanceDto.getId());
        if (instance == null) {
            return R.fail("审批实例不存在");
        }
        ApprovalInstanceNode currentNode = approveProcessConfigNodeUtils.getCurrentNode(instance.getId());
        if (currentNode == null) {
            return R.fail("当前没有待处理的审批节点");
        }
        Long currentUserId = SecurityUtils.getUserId();
        ApprovalTask currentTask = approveProcessConfigNodeUtils.getCurrentUserTask(instance.getId(), currentUserId);
        if (currentTask == null) {
            return R.fail("当前用户没有可审批任务");
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        String operatorName = loginUser.getUser() != null ? loginUser.getUser().getNickName() : SecurityUtils.getUsername();
        LocalDateTime now = LocalDateTime.now();
        if (!updateCurrentTask(approvalInstanceDto, approveAction, currentTask, now)) {
            return R.fail("当前任务已被处理,请刷新后重试");
        }
        saveApprovalRecord(
                instance.getId(),
                currentNode.getId(),
                currentTask.getId(),
                currentUserId,
                operatorName,
                approveAction,
                approvalInstanceDto.getApproveComment()
        );
        //审批拒绝的处理
        if ("REJECTED".equals(approveAction)) {
            return rejectCurrentNode(instance, currentNode, now);
        }
        if (!approveProcessConfigNodeUtils.canProceedToNextLevel(instance.getId(), currentNode.getApproveType())) {
            return R.ok("审批成功,等待其他审批人处理");
        }
        return approveAndMoveNext(instance, currentNode, approvalInstanceDto, now);
    }
    private String normalizeApproveAction(String approveAction) {
        if (!StringUtils.hasText(approveAction)) {
            return null;
        }
        String normalizedAction = approveAction.trim().toUpperCase(Locale.ROOT);
        return "APPROVED".equals(normalizedAction) || "REJECTED".equals(normalizedAction)
                ? normalizedAction
                : null;
    }
    private ApprovalInstance getPendingApprovalInstance(Long instanceId) {
        return this.getOne(
                new LambdaQueryWrapper<ApprovalInstance>()
                        .eq(ApprovalInstance::getId, instanceId)
                        .eq(ApprovalInstance::getDeleted, 0)
                        .last("LIMIT 1")
        );
    }
    private boolean updateCurrentTask(ApprovalInstanceDto approvalInstanceDto,
                                      String approveAction,
                                      ApprovalTask currentTask,
                                      LocalDateTime now) {
        // ä»…允许待审批任务被成功处理一次,避免并发下重复审批成功。
        return approvalTaskService.update(
                Wrappers.<ApprovalTask>lambdaUpdate()
                        .eq(ApprovalTask::getId, currentTask.getId())
                        .eq(ApprovalTask::getTaskStatus, "PENDING")
                        .eq(ApprovalTask::getDeleted, 0)
                        .set(ApprovalTask::getTaskStatus, approveAction)
                        .set(ApprovalTask::getComment, approvalInstanceDto.getApproveComment())
                        .set(ApprovalTask::getApproveTime, now)
                        .set(ApprovalTask::getIsRead, (byte) 1)
        );
    }
    private R rejectCurrentNode(ApprovalInstance instance, ApprovalInstanceNode currentNode, LocalDateTime now) {
        if (!updateCurrentNodeStatus(currentNode.getId(), "REJECTED", now)) {
            return R.ok("当前节点已处理完成");
        }
        closePendingTasks(instance.getId(), currentNode.getId());
        instance.setStatus("REJECTED");
        instance.setFinishTime(now);
        this.updateById(instance);
        // é©³å›žå¯¹åº”的企业新闻, å·®æ—…报销
        if (instance.getBusinessType().equals(TypeEnums.ENTERPRISE_NEWS_APPROVAL.getCode())) {
            enterpriseNewsMapper.update(
                    new LambdaUpdateWrapper<EnterpriseNews>()
                            .eq(EnterpriseNews::getId, instance.getBusinessId())
                            .set(EnterpriseNews::getStatus, "REJECTED")
            );
        }else if (instance.getBusinessType().equals(TypeEnums.TRAVEL_REIMBURSEMENT_APPROVAL.getCode())||instance.getBusinessType().equals(TypeEnums.EXPENSE_APPROVAL.getCode())) {
            finReimbursementMapper.update(
                    new LambdaUpdateWrapper<FinReimbursement>()
                            .eq(FinReimbursement::getId, instance.getBusinessId())
                            .set(FinReimbursement::getBillStatus, "REJECTED")
            );
        }
        return R.ok("审批已驳回");
    }
    private R approveAndMoveNext(ApprovalInstance instance,
                                 ApprovalInstanceNode currentNode,
                                 ApprovalInstanceDto approvalInstanceDto,
                                 LocalDateTime now) {
        if (!updateCurrentNodeStatus(currentNode.getId(), "APPROVED", now)) {
            return R.ok("当前节点已处理完成");
        }
        closePendingTasks(instance.getId(), currentNode.getId());
        int nextLevel = currentNode.getLevelNo() + 1;
        ApprovalInstanceNode nextInstanceNode = approvalInstanceNodeService.getOne(
                new LambdaQueryWrapper<ApprovalInstanceNode>()
                        .eq(ApprovalInstanceNode::getInstanceId, instance.getId())
                        .eq(ApprovalInstanceNode::getLevelNo, nextLevel)
                        .eq(ApprovalInstanceNode::getDeleted, 0)
                        .orderByAsc(ApprovalInstanceNode::getId)
                        .last("LIMIT 1")
        );
        if (nextInstanceNode != null) {
            if (!activateNextInstanceNode(nextInstanceNode.getId(), now)) {
                return R.ok("下一审批节点已被激活,请刷新后重试");
            }
            instance.setCurrentLevel(nextLevel);
            instance.setStatus("PENDING");
            this.updateById(instance);
            List<ApprovalTask> nextTasks = approvalTaskService.list(
                    Wrappers.<ApprovalTask>lambdaQuery()
                            .eq(ApprovalTask::getInstanceId, instance.getId())
                            .eq(ApprovalTask::getNodeId, nextInstanceNode.getId())
                            .eq(ApprovalTask::getTaskStatus, "PENDING")
                            .eq(ApprovalTask::getDeleted, 0)
            );
            sendApproveNotice(instance, nextTasks);
            return R.ok("审批成功,已流转到下一节点");
        }
        ApprovalTemplateNode nextTemplateNode = approvalTemplateNodeService.getOne(
                new LambdaQueryWrapper<ApprovalTemplateNode>()
                        .eq(ApprovalTemplateNode::getTemplateId, instance.getTemplateId())
                        .eq(ApprovalTemplateNode::getLevelNo, nextLevel)
                        .orderByAsc(ApprovalTemplateNode::getId)
                        .last("LIMIT 1")
        );
        if (nextTemplateNode == null) {
            instance.setStatus("APPROVED");
            instance.setFinishTime(now);
            this.updateById(instance);
            handleBusinessAfterApprovalFinished(instance);
            return R.ok("审批已完成");
        }
        instance.setCurrentLevel(nextLevel);
        instance.setStatus("PENDING");
        this.updateById(instance);
        approveProcessConfigNodeUtils.createCurrentNodeAndTasks(instance, false);
        sendApproveNotice(instance, approveProcessConfigNodeUtils.getCurrentPendingTasks(approvalInstanceDto.getId()));
        return R.ok("审批成功,已流转到下一节点");
    }
    private boolean activateNextInstanceNode(Long nodeId, LocalDateTime now) {
        return approvalInstanceNodeService.update(
                Wrappers.<ApprovalInstanceNode>lambdaUpdate()
                        .eq(ApprovalInstanceNode::getId, nodeId)
                        .eq(ApprovalInstanceNode::getStatus, "WAITING")
                        .eq(ApprovalInstanceNode::getDeleted, 0)
                        .set(ApprovalInstanceNode::getStatus, "PENDING")
                        .set(ApprovalInstanceNode::getStartTime, now)
        );
    }
    private boolean updateCurrentNodeStatus(Long nodeId, String targetStatus, LocalDateTime now) {
        // ä»…允许一个请求将当前节点从待处理推进到目标状态,避免重复流转。
        return approvalInstanceNodeService.update(
                Wrappers.<ApprovalInstanceNode>lambdaUpdate()
                        .eq(ApprovalInstanceNode::getId, nodeId)
                        .eq(ApprovalInstanceNode::getStatus, "PENDING")
                        .eq(ApprovalInstanceNode::getDeleted, 0)
                        .set(ApprovalInstanceNode::getStatus, targetStatus)
                        .set(ApprovalInstanceNode::getFinishTime, now)
        );
    }
    private void handleBusinessAfterApprovalFinished(ApprovalInstance instance) {
        String status = instance.getStatus();
        Long businessType = instance.getBusinessType();
        if (TypeEnums.PURCHASE_APPROVAL.getCode().equals(businessType)) {
            handlePurchaseApprovalFinished(instance, status);
            return;
        }
        if (TypeEnums.QUOTATION_APPROVAL.getCode().equals(businessType)) {
            handleSalesQuotationApprovalFinished(instance, status);
            return;
        }
        if (TypeEnums.SHIPPING_APPROVAL.getCode().equals(businessType)) {
            handleShippingApprovalFinished(instance, status);
            return;
        }
        if (TypeEnums.TRAVEL_REIMBURSEMENT_APPROVAL.getCode().equals(businessType)
                || TypeEnums.EXPENSE_APPROVAL.getCode().equals(businessType)) {
            handleReimbursementApprovalFinished(instance, status);
            return;
        }
        // é©³å›žå¯¹åº”的企业新闻、加班申请、请假申请可以重新再提交
        if (TypeEnums.LEAVE_APPROVAL.getCode().equals(businessType)
                || TypeEnums.OVERTIME_APPROVAL.getCode().equals(businessType)) {
            handleHolidayApplicationApprovalFinished(instance, status);
            return;
        }
        if (TypeEnums.ENTERPRISE_NEWS_APPROVAL.getCode().equals(businessType)) {
            handleNewsApprovalFinished(instance, status);
        }
    }
    private void handleReimbursementApprovalFinished(ApprovalInstance instance, String status) {
        if (instance == null || instance.getBusinessId() == null) {
            return;
        }
        FinReimbursement reimbursement = new FinReimbursement();
        reimbursement.setId(instance.getBusinessId());
        if ("APPROVED".equals(status)) {
            reimbursement.setBillStatus("APPROVED");
            reimbursement.setApprovedTime(instance.getFinishTime());
        } else if ("REJECTED".equals(status)) {
            reimbursement.setBillStatus("REJECTED");
        } else if ("PENDING".equals(status)) {
            reimbursement.setBillStatus("IN_APPROVAL");
        } else {
            return;
        }
        finReimbursementMapper.updateById(reimbursement);
    }
    private void handleNewsApprovalFinished(ApprovalInstance instance, String status) {
        if (instance == null || instance.getBusinessId() == null) {
            return;
        }
        EnterpriseNews enterpriseNews = new EnterpriseNews();
        enterpriseNews.setId(instance.getBusinessId());
        if ("APPROVED".equals(status)) {
            enterpriseNews.setStatus(ENTERPRISE_NEWS_STATUS_PUBLISHED);
            enterpriseNewsMapper.updateById(enterpriseNews);
            sendEnterpriseNewsNotice(instance.getBusinessId());
            return;
        }
        if ("REJECTED".equals(status)) {
            enterpriseNews.setStatus(ENTERPRISE_NEWS_STATUS_REJECTED);
            enterpriseNewsMapper.updateById(enterpriseNews);
        }
    }
    private void handleHolidayApplicationApprovalFinished(ApprovalInstance instance, String status) {
        if (instance == null || instance.getBusinessId() == null) {
            return;
        }
        HolidayApplication holidayApplication = new HolidayApplication();
        holidayApplication.setId(instance.getBusinessId());
        if ("APPROVED".equals(status)) {
            holidayApplication.setStatus("APPROVED");
        } else if ("REJECTED".equals(status)) {
            holidayApplication.setStatus("REJECTED");
        } else if ("PENDING".equals(status)) {
            holidayApplication.setStatus("PENDING");
        } else {
            return;
        }
        holidayApplicationMapper.updateById(holidayApplication);
    }
    private void handlePurchaseApprovalFinished(ApprovalInstance instance, String status) {
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(
                new LambdaQueryWrapper<PurchaseLedger>()
                        .eq(PurchaseLedger::getId, instance.getBusinessId())
                        .last("limit 1")
        );
        if (purchaseLedger == null) {
            return;
        }
        if ("APPROVED".equals(status)) {
            purchaseLedger.setApprovalStatus(ApprovalStatusEnum.APPROVED.getCode());
            List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(
                    new QueryWrapper<SalesLedgerProduct>().lambda()
                            .eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId())
                            .eq(SalesLedgerProduct::getType, 2)
            );
            for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
                if (salesLedgerProduct.getIsChecked()) {
                    qualityInspectHelper.addQualityInspect(purchaseLedger, salesLedgerProduct);
                } else {
                    stockUtils.addStockWithBatchNo(
                            salesLedgerProduct.getProductModelId(),
                            salesLedgerProduct.getQuantity(),
                            StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(),
                            purchaseLedger.getId(),
                            purchaseLedger.getPurchaseContractNumber() + "-" + salesLedgerProduct.getId()
                    );
                }
            }
        } else if ("REJECTED".equals(status)) {
            purchaseLedger.setApprovalStatus(ApprovalStatusEnum.REJECTED.getCode());
        } else if ("PENDING".equals(status)) {
            purchaseLedger.setApprovalStatus(ApprovalStatusEnum.IN_PROGRESS.getCode());
        }
        purchaseLedgerMapper.updateById(purchaseLedger);
    }
    private void handleSalesQuotationApprovalFinished(ApprovalInstance instance, String status) {
        SalesQuotation salesQuote = salesQuotationMapper.selectOne(
                new LambdaQueryWrapper<SalesQuotation>()
                        .eq(SalesQuotation::getId, instance.getBusinessId())
                        .last("limit 1")
        );
        if (salesQuote == null) {
            return;
        }
        if ("APPROVED".equals(status)) {
            salesQuote.setStatus(SalesQuotationStatusEnum.APPROVED.getCode());
        } else if ("REJECTED".equals(status)) {
            salesQuote.setStatus(SalesQuotationStatusEnum.REJECTED.getCode());
        } else if ("PENDING".equals(status)) {
            salesQuote.setStatus(SalesQuotationStatusEnum.IN_PROGRESS.getCode());
        }
        salesQuotationMapper.updateById(salesQuote);
    }
    private void handleShippingApprovalFinished(ApprovalInstance instance, String status) {
        ShippingInfo shippingInfo = shippingInfoMapper.selectOne(
                new LambdaQueryWrapper<ShippingInfo>()
                        .eq(ShippingInfo::getId, instance.getTitle())
                        .orderByDesc(ShippingInfo::getCreateTime)
                        .last("limit 1")
        );
        if (shippingInfo == null) {
            return;
        }
        if ("APPROVED".equals(status)) {
            shippingInfo.setStatus(ShippingStatusEnum.APPROVED.getCode());
            shippingInfo.setShippingDate(new Date());
            stockUtils.shipmentStatus(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), shippingInfo.getId());
        } else if ("REJECTED".equals(status)) {
            stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
            shippingInfo.setStatus(ShippingStatusEnum.REJECTED.getCode());
        } else if ("PENDING".equals(status)) {
            shippingInfo.setStatus(ShippingStatusEnum.IN_PROGRESS.getCode());
        }
        shippingInfoMapper.updateById(shippingInfo);
    }
    private List<ApprovalTask> createNodeAndTasks(ApprovalInstance instance, ApprovalTemplateNode templateNode) {
        List<ApprovalTemplateNodeApprover> approvers = approvalTemplateNodeApproverMapper.selectList(
                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
                        .eq(ApprovalTemplateNodeApprover::getTemplateId, instance.getTemplateId())
                        .eq(ApprovalTemplateNodeApprover::getNodeId, templateNode.getId())
                        .eq(ApprovalTemplateNodeApprover::getDeleted, 0L)
                        .orderByAsc(ApprovalTemplateNodeApprover::getSortNo)
        );
        if (approvers == null || approvers.isEmpty()) {
            throw new RuntimeException("下一审批节点未配置审批人");
        }
        ApprovalInstanceNode instanceNode = new ApprovalInstanceNode();
        instanceNode.setInstanceId(instance.getId());
        instanceNode.setLevelNo(templateNode.getLevelNo());
        instanceNode.setApproveType(templateNode.getApproveType());
        instanceNode.setStatus("PENDING");
        instanceNode.setStartTime(LocalDateTime.now());
        instanceNode.setDeleted((byte) 0);
        approvalInstanceNodeService.save(instanceNode);
        List<ApprovalTask> taskList = new ArrayList<>(approvers.size());
        for (ApprovalTemplateNodeApprover approver : approvers) {
            ApprovalTask task = new ApprovalTask();
            task.setInstanceId(instance.getId());
            task.setNodeId(instanceNode.getId());
            task.setLevelNo(instanceNode.getLevelNo());
            task.setApproverId(approver.getApproverId());
            task.setApproverName(approver.getApproverName());
            task.setTaskStatus("PENDING");
            task.setIsRead((byte) 0);
            task.setDeleted((byte) 0);
            taskList.add(task);
        }
        approvalTaskService.saveBatch(taskList);
        return taskList;
    }
    private void sendApproveNotice(ApprovalInstance instance, List<ApprovalTask> tasks) {
        if (instance == null || tasks == null || tasks.isEmpty()) {
            return;
        }
        List<Long> approverIds = tasks.stream()
                .map(ApprovalTask::getApproverId)
                .filter(id -> id != null && id > 0)
                .distinct()
                .collect(Collectors.toList());
        if (approverIds.isEmpty()) {
            return;
        }
        String title = StringUtils.hasText(instance.getTemplateName()) ? instance.getTemplateName() : "审批提醒";
        String message = "审批单号 " + instance.getInstanceNo() + " éœ€è¦æ‚¨å®¡æ‰¹";
        String jumpPath = "/officeProcessAutomation/ApproveManage/approve-list?id=" + instance.getId();
        sysNoticeService.simpleNoticeByUser(title, message, approverIds, jumpPath);
    }
    private void sendEnterpriseNewsNotice(Long newsId) {
        EnterpriseNews enterpriseNews = enterpriseNewsMapper.selectById(newsId);
        if (enterpriseNews == null) {
            return;
        }
        List<Long> userIds = getEnterpriseNewsNoticeUserIds(enterpriseNews);
        if (userIds == null || userIds.isEmpty()) {
            return;
        }
        String title = "企业新闻";
        String message = "您有新的企业新闻《" + enterpriseNews.getTitle() + "》请及时查阅";
        String jumpPath = "/officeProcessAutomation/EnterpriseNews?id=" + newsId;
        sysNoticeService.simpleNoticeByUser(title, message, userIds, jumpPath);
    }
    private List<Long> getEnterpriseNewsNoticeUserIds(EnterpriseNews enterpriseNews) {
        if (enterpriseNews == null || !org.springframework.util.StringUtils.hasText(enterpriseNews.getReadScope())) {
            return Collections.emptyList();
        }
        String readScope = enterpriseNews.getReadScope().trim();
        if ("all".equals(readScope)) {
            return sysUserMapper.selectList(new LambdaQueryWrapper<SysUser>()
                            .select(SysUser::getUserId)
                            .eq(SysUser::getDelFlag, "0"))
                    .stream()
                    .map(SysUser::getUserId)
                    .filter(id -> id != null && id > 0)
                    .distinct()
                    .collect(Collectors.toList());
        }
        if ("dept".equals(readScope)) {
            List<Long> deptIds = enterpriseNewsScopeDeptMapper.selectList(
                            new LambdaQueryWrapper<EnterpriseNewsScopeDept>()
                                    .eq(EnterpriseNewsScopeDept::getNewsId, enterpriseNews.getId()))
                    .stream()
                    .map(EnterpriseNewsScopeDept::getDeptId)
                    .filter(id -> id != null && id > 0)
                    .distinct()
                    .collect(Collectors.toList());
            if (deptIds.isEmpty()) {
                return Collections.emptyList();
            }
            return sysUserDeptMapper.selectDistinctUserIdsByDeptIds(collectDeptIdsWithChildren(deptIds));
        }
        if ("custom".equals(readScope)) {
            return enterpriseNewsScopeUserMapper.selectList(
                            new LambdaQueryWrapper<EnterpriseNewsScopeUser>()
                                    .eq(EnterpriseNewsScopeUser::getNewsId, enterpriseNews.getId()))
                    .stream()
                    .map(EnterpriseNewsScopeUser::getUserId)
                    .filter(id -> id != null && id > 0)
                    .distinct()
                    .collect(Collectors.toList());
        }
        return Collections.emptyList();
    }
    private List<Long> collectDeptIdsWithChildren(List<Long> deptIds) {
        Set<Long> allDeptIds = new LinkedHashSet<>();
        for (Long deptId : deptIds) {
            if (deptId == null) {
                continue;
            }
            allDeptIds.add(deptId);
            List<SysDept> children = sysDeptMapper.selectChildrenDeptById(deptId);
            if (children != null && !children.isEmpty()) {
                for (SysDept child : children) {
                    if (child != null && child.getDeptId() != null) {
                        allDeptIds.add(child.getDeptId());
                    }
                }
            }
        }
        return new ArrayList<>(allDeptIds);
    }
    private void closePendingTasks(Long instanceId, Long nodeId) {
        LambdaUpdateWrapper<ApprovalTask> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.eq(ApprovalTask::getInstanceId, instanceId)
                .eq(ApprovalTask::getNodeId, nodeId)
                .eq(ApprovalTask::getTaskStatus, "PENDING")
                .eq(ApprovalTask::getDeleted, 0)
                .set(ApprovalTask::getDeleted, (byte) 1);
        approvalTaskService.update(updateWrapper);
    }
    private void saveApprovalRecord(Long instanceId,
                                    Long nodeId,
                                    Long taskId,
                                    Long operatorId,
                                    String operatorName,
                                    String action,
                                    String comment) {
        ApprovalRecord record = new ApprovalRecord();
        record.setInstanceId(instanceId);
        record.setNodeId(nodeId);
        record.setTaskId(taskId);
        record.setOperatorId(operatorId);
        record.setOperatorName(operatorName);
        record.setAction(action);
        record.setComment(comment);
        record.setDeleted((byte) 0);
        approvalRecordService.save(record);
    }
}
src/main/java/com/ruoyi/approve/service/impl/ApprovalRecordServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.approve.service.impl;
import com.ruoyi.approve.pojo.ApprovalRecord;
import com.ruoyi.approve.mapper.ApprovalRecordMapper;
import com.ruoyi.approve.service.ApprovalRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * å®¡æ‰¹è®°å½•表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:28:21
 */
@Service
public class ApprovalRecordServiceImpl extends ServiceImpl<ApprovalRecordMapper, ApprovalRecord> implements ApprovalRecordService {
}
src/main/java/com/ruoyi/approve/service/impl/ApprovalTaskServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.approve.service.impl;
import com.ruoyi.approve.pojo.ApprovalTask;
import com.ruoyi.approve.mapper.ApprovalTaskMapper;
import com.ruoyi.approve.service.ApprovalTaskService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * å®¡æ‰¹ä»»åŠ¡è¡¨ æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 03:32:37
 */
@Service
public class ApprovalTaskServiceImpl extends ServiceImpl<ApprovalTaskMapper, ApprovalTask> implements ApprovalTaskService {
}
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeApproverServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.approve.service.impl;
import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
import com.ruoyi.approve.mapper.ApprovalTemplateNodeApproverMapper;
import com.ruoyi.approve.service.ApprovalTemplateNodeApproverService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿èŠ‚ç‚¹å®¡æ‰¹äººè¡¨ æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:30
 */
@Service
public class ApprovalTemplateNodeApproverServiceImpl extends ServiceImpl<ApprovalTemplateNodeApproverMapper, ApprovalTemplateNodeApprover> implements ApprovalTemplateNodeApproverService {
}
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateNodeServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,62 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeApproverDto;
import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeDto;
import com.ruoyi.approve.mapper.ApprovalTemplateNodeMapper;
import com.ruoyi.approve.pojo.ApprovalTemplateNode;
import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
import com.ruoyi.approve.service.ApprovalTemplateNodeApproverService;
import com.ruoyi.approve.service.ApprovalTemplateNodeService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿èŠ‚ç‚¹è¡¨ æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-18 11:20:19
 */
@Service
@RequiredArgsConstructor
public class ApprovalTemplateNodeServiceImpl extends ServiceImpl<ApprovalTemplateNodeMapper, ApprovalTemplateNode> implements ApprovalTemplateNodeService {
    private final ApprovalTemplateNodeMapper approvalTemplateNodeMapper;
    private final ApprovalTemplateNodeApproverService approvalTemplateNodeApproverService;
    @Override
    public Boolean saveApprovalTemplateNode(Long templateId, List<ApprovalTemplateNodeDto> nodes) {
        if (nodes == null || nodes.isEmpty()) {
            throw new RuntimeException("节点列表不能为空");
        }
        List<ApprovalTemplateNodeApprover> approverList = new ArrayList<>();
        for (ApprovalTemplateNodeDto nodeDto : nodes) {
            ApprovalTemplateNode node = new ApprovalTemplateNode();
            BeanUtils.copyProperties(nodeDto, node);
            node.setTemplateId(templateId);
            approvalTemplateNodeMapper.insert(node);
            List<ApprovalTemplateNodeApproverDto> approvers = nodeDto.getApprovers();
            if (approvers == null || approvers.isEmpty()) {
                throw new RuntimeException("节点审批人不能为空");
            }
            for (ApprovalTemplateNodeApproverDto approverDto : approvers) {
                ApprovalTemplateNodeApprover approver = new ApprovalTemplateNodeApprover();
                BeanUtils.copyProperties(approverDto, approver);
                approver.setNodeId(node.getId());
                approver.setTemplateId(templateId);
                approver.setDeleted(0L);
                approverList.add(approver);
            }
        }
        approvalTemplateNodeApproverService.saveBatch(approverList);
        return true;
    }
}
src/main/java/com/ruoyi/approve/service/impl/ApprovalTemplateServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,250 @@
package com.ruoyi.approve.service.impl;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.bean.dto.ApprovalTemplateDto;
import com.ruoyi.approve.bean.vo.ApprovalTemplateNodeApproverVo;
import com.ruoyi.approve.bean.vo.ApprovalTemplateNodeVo;
import com.ruoyi.approve.bean.vo.ApprovalTemplateVo;
import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
import com.ruoyi.approve.mapper.ApprovalTemplateNodeApproverMapper;
import com.ruoyi.approve.pojo.ApprovalTemplate;
import com.ruoyi.approve.pojo.ApprovalTemplateNode;
import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
import com.ruoyi.approve.service.ApprovalTemplateNodeService;
import com.ruoyi.approve.service.ApprovalTemplateService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * <p>
 * å®¡æ‰¹æ¨¡æ¿æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @since 2026-05-18 11:20:08
 */
@Service
@RequiredArgsConstructor
public class ApprovalTemplateServiceImpl extends ServiceImpl<ApprovalTemplateMapper, ApprovalTemplate> implements ApprovalTemplateService {
    private final ApprovalTemplateMapper approvalTemplateMapper;
    private final ApprovalTemplateNodeService approvalTemplateNodeService;
    private final ApprovalTemplateNodeApproverMapper approvalTemplateNodeApproverMapper;
    @Override
    public IPage<ApprovalTemplateVo> listPage(Page<ApprovalTemplateVo> page, ApprovalTemplateDto approvalTemplateDto) {
        IPage<ApprovalTemplateVo> approvalTemplateVoIPage = approvalTemplateMapper.listPage(page, approvalTemplateDto);
        fillTemplateVoNodes(approvalTemplateVoIPage.getRecords());
        return approvalTemplateVoIPage;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean saveApprovalTemplateDto(ApprovalTemplateDto approvalTemplateDto) {
        approvalTemplateMapper.insert(approvalTemplateDto);
        approvalTemplateNodeService.remove(
                new LambdaQueryWrapper<ApprovalTemplateNode>()
                        .eq(ApprovalTemplateNode::getTemplateId, approvalTemplateDto.getId())
        );
        approvalTemplateNodeApproverMapper.delete(
                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
                        .eq(ApprovalTemplateNodeApprover::getTemplateId, approvalTemplateDto.getId())
        );
        return approvalTemplateNodeService.saveApprovalTemplateNode(
                approvalTemplateDto.getId(),
                approvalTemplateDto.getNodes()
        );
    }
    @Override
    public Boolean updateApprovalTemplateDto(ApprovalTemplateDto approvalTemplateDto) {
        approvalTemplateMapper.updateById(approvalTemplateDto);
        approvalTemplateNodeService.remove(
                new LambdaQueryWrapper<ApprovalTemplateNode>()
                        .eq(ApprovalTemplateNode::getTemplateId, approvalTemplateDto.getId())
        );
        approvalTemplateNodeApproverMapper.delete(
                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
                        .eq(ApprovalTemplateNodeApprover::getTemplateId, approvalTemplateDto.getId())
        );
        return approvalTemplateNodeService.saveApprovalTemplateNode(
                approvalTemplateDto.getId(),
                approvalTemplateDto.getNodes()
        );
    }
    @Transactional(rollbackFor = Exception.class)
    public Boolean delete(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return false;
        }
        ApprovalTemplate updateEntity = new ApprovalTemplate();
        updateEntity.setDeleted(1);
        LambdaUpdateWrapper<ApprovalTemplate> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.in(ApprovalTemplate::getId, ids)
                .eq(ApprovalTemplate::getDeleted, 0);
        int rows = approvalTemplateMapper.update(updateEntity, updateWrapper);
        return rows == ids.size();
    }
    @Override
    public List<ApprovalTemplateVo> listApprovalTemplateVo(Integer type) {
        List<ApprovalTemplate> templateList = this.list(
                new LambdaQueryWrapper<ApprovalTemplate>()
                        .eq(ApprovalTemplate::getDeleted, 0)
                        .eq(ApprovalTemplate::getEnabled, 1)
                        .orderByDesc(ApprovalTemplate::getTemplateType)
                        .orderByDesc(ApprovalTemplate::getId)
        );
        if (CollUtil.isEmpty(templateList)) {
            return Collections.emptyList();
        }
        List<ApprovalTemplateVo> templateVos = templateList.stream()
                .map(template -> {
                    ApprovalTemplateVo templateVo = new ApprovalTemplateVo();
                    BeanUtils.copyProperties(template, templateVo);
                    return templateVo;
                })
                .collect(Collectors.toList());
        fillTemplateVoNodes(templateVos);
        return templateVos;
    }
    @Override
    public ApprovalTemplateVo getApprovalTemplateVoById(Long id) {
        if (id == null) {
            throw new IllegalArgumentException("参数 id ä¸èƒ½ä¸ºç©º");
        }
        ApprovalTemplate template = this.getOne(
                new LambdaQueryWrapper<ApprovalTemplate>()
                        .eq(ApprovalTemplate::getId, id)
                        .eq(ApprovalTemplate::getDeleted, 0)
        );
        if (template == null) {
            throw new IllegalArgumentException("模板不存在");
        }
        List<ApprovalTemplateNode> nodeList = approvalTemplateNodeService.list(
                new LambdaQueryWrapper<ApprovalTemplateNode>()
                        .eq(ApprovalTemplateNode::getTemplateId, id)
                        .orderByAsc(ApprovalTemplateNode::getLevelNo)
        );
        List<ApprovalTemplateNodeApprover> approverList = approvalTemplateNodeApproverMapper.selectList(
                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
                        .eq(ApprovalTemplateNodeApprover::getTemplateId, id)
                        .eq(ApprovalTemplateNodeApprover::getDeleted, 0L)
        );
        Map<Long, List<ApprovalTemplateNode>> nodeMap = nodeList.stream()
                .collect(Collectors.groupingBy(ApprovalTemplateNode::getTemplateId));
        Map<Long, List<ApprovalTemplateNodeApprover>> approverMap = approverList.stream()
                .collect(Collectors.groupingBy(ApprovalTemplateNodeApprover::getNodeId));
        return buildTemplateVo(template, nodeMap, approverMap);
    }
    /**
     * æ‰¹é‡å¡«å……模板节点及节点审批人,避免循环查库。
     */
    private void fillTemplateVoNodes(List<ApprovalTemplateVo> templateVos) {
        if (CollUtil.isEmpty(templateVos)) {
            return;
        }
        List<Long> templateIds = templateVos.stream()
                .map(ApprovalTemplateVo::getId)
                .collect(Collectors.toList());
        List<ApprovalTemplateNode> nodeList = approvalTemplateNodeService.list(
                new LambdaQueryWrapper<ApprovalTemplateNode>()
                        .in(ApprovalTemplateNode::getTemplateId, templateIds)
                        .orderByAsc(ApprovalTemplateNode::getLevelNo)
        );
        List<ApprovalTemplateNodeApprover> approverList = approvalTemplateNodeApproverMapper.selectList(
                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
                        .in(ApprovalTemplateNodeApprover::getTemplateId, templateIds)
                        .eq(ApprovalTemplateNodeApprover::getDeleted, 0L)
        );
        Map<Long, List<ApprovalTemplateNode>> nodeMap = nodeList.stream()
                .collect(Collectors.groupingBy(ApprovalTemplateNode::getTemplateId));
        Map<Long, List<ApprovalTemplateNodeApprover>> approverMap = approverList.stream()
                .collect(Collectors.groupingBy(ApprovalTemplateNodeApprover::getNodeId));
        templateVos.forEach(templateVo -> templateVo.setNodes(
                nodeMap.getOrDefault(templateVo.getId(), Collections.emptyList())
                        .stream()
                        .sorted(Comparator.comparing(
                                ApprovalTemplateNode::getLevelNo,
                                Comparator.nullsLast(Integer::compareTo)
                        ))
                        .map(node ->  buildNodeVo(node, approverMap))
                        .collect(Collectors.toList())
        ));
    }
    private ApprovalTemplateVo buildTemplateVo(ApprovalTemplate template,
                                               Map<Long, List<ApprovalTemplateNode>> nodeMap,
                                               Map<Long, List<ApprovalTemplateNodeApprover>> approverMap) {
        ApprovalTemplateVo templateVo = new ApprovalTemplateVo();
        BeanUtils.copyProperties(template, templateVo);
        List<ApprovalTemplateNodeVo> nodeVos = nodeMap
                .getOrDefault(template.getId(), Collections.emptyList())
                .stream()
                .sorted(Comparator.comparing(
                        ApprovalTemplateNode::getLevelNo,
                        Comparator.nullsLast(Integer::compareTo)
                ))
                .map(node -> buildNodeVo(node, approverMap))
                .collect(Collectors.toList());
        templateVo.setNodes(nodeVos);
        return templateVo;
    }
    private ApprovalTemplateNodeVo buildNodeVo(ApprovalTemplateNode node,
                                               Map<Long, List<ApprovalTemplateNodeApprover>> approverMap) {
        ApprovalTemplateNodeVo nodeVo = new ApprovalTemplateNodeVo();
        BeanUtils.copyProperties(node, nodeVo);
        List<ApprovalTemplateNodeApproverVo> approverVos = approverMap
                .getOrDefault(node.getId(), Collections.emptyList())
                .stream()
                .sorted(Comparator.comparing(
                        ApprovalTemplateNodeApprover::getSortNo,
                        Comparator.nullsLast(Integer::compareTo)
                ))
                .map(this::buildApproverVo)
                .collect(Collectors.toList());
        nodeVo.setApprovers(approverVos);
        return nodeVo;
    }
    private ApprovalTemplateNodeApproverVo buildApproverVo(ApprovalTemplateNodeApprover approver) {
        ApprovalTemplateNodeApproverVo approverVo = new ApprovalTemplateNodeApproverVo();
        BeanUtils.copyProperties(approver, approverVo);
        return approverVo;
    }
}
src/main/java/com/ruoyi/approve/service/impl/ApproveBusinessStatusService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,175 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.mapper.QualityInspectParamMapper;
import com.ruoyi.quality.mapper.QualityTestStandardMapper;
import com.ruoyi.quality.mapper.QualityTestStandardParamMapper;
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityInspectParam;
import com.ruoyi.quality.pojo.QualityTestStandard;
import com.ruoyi.quality.pojo.QualityTestStandardParam;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.SalesQuotationMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.ruoyi.sales.pojo.ShippingInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Date;
import java.util.List;
@Service
@RequiredArgsConstructor
public class ApproveBusinessStatusService {
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    private final SalesQuotationMapper salesQuotationMapper;
    private final ShippingInfoMapper shippingInfoMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final StockUtils stockUtils;
    private final QualityInspectMapper qualityInspectMapper;
    private final QualityTestStandardMapper qualityTestStandardMapper;
    private final QualityTestStandardParamMapper qualityTestStandardParamMapper;
    private final QualityInspectParamMapper qualityInspectParamMapper;
    /**
     * ç»Ÿä¸€åŒæ­¥å®¡æ‰¹ç»“果对应的业务单据状态。
     * status:1-审核中,2-审核完成,3-审核未通过。
     */
    public void syncBusinessStatus(Integer approveType, String approveReason, Integer status) {
        if (approveType == null || status == null || !StringUtils.hasText(approveReason)) {
            return;
        }
        switch (approveType) {
            case 5:
                syncPurchaseStatus(approveReason, status);
                break;
            case 6:
                syncSalesQuotationStatus(approveReason, status);
                break;
            case 7:
                syncShippingStatus(approveReason, status);
                break;
            default:
                break;
        }
    }
    // é‡‡è´­å®¡æ‰¹é€šè¿‡æ—¶ï¼ŒæŒ‰äº§å“è´¨æ£€é…ç½®å†³å®šç”Ÿæˆè´¨æ£€å•或直接入库。
    private void syncPurchaseStatus(String approveReason, Integer status) {
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                .eq(PurchaseLedger::getPurchaseContractNumber, approveReason)
                .last("limit 1"));
        if (purchaseLedger == null) {
            return;
        }
        if (status.equals(2)) {
            purchaseLedger.setApprovalStatus(3);
            List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
                    .lambda().eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId()).eq(SalesLedgerProduct::getType, 2));
            for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
                if (Boolean.TRUE.equals(salesLedgerProduct.getIsChecked())) {
                    addQualityInspect(purchaseLedger, salesLedgerProduct);
                } else {
                    stockUtils.addStockWithBatchNo(
                            salesLedgerProduct.getProductModelId(),
                            salesLedgerProduct.getQuantity(),
                            StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(),
                            purchaseLedger.getId(),
                            purchaseLedger.getPurchaseContractNumber() + "-" + salesLedgerProduct.getId());
                }
            }
        } else if (status.equals(3)) {
            purchaseLedger.setApprovalStatus(4);
        } else if (status.equals(1)) {
            purchaseLedger.setApprovalStatus(2);
        } else {
            return;
        }
        purchaseLedgerMapper.updateById(purchaseLedger);
    }
    // æŠ¥ä»·å®¡æ‰¹çŠ¶æ€å›žå†™åˆ°é”€å”®æŠ¥ä»·å•çŠ¶æ€ã€‚
    private void syncSalesQuotationStatus(String approveReason, Integer status) {
        SalesQuotation salesQuote = salesQuotationMapper.selectOne(new LambdaQueryWrapper<SalesQuotation>()
                .eq(SalesQuotation::getQuotationNo, approveReason)
                .last("limit 1"));
        if (salesQuote == null) {
            return;
        }
        if (status.equals(2)) {
            salesQuote.setStatus("通过");
        } else if (status.equals(3)) {
            salesQuote.setStatus("拒绝");
        } else if (status.equals(1)) {
            salesQuote.setStatus("审核中");
        } else {
            return;
        }
        salesQuotationMapper.updateById(salesQuote);
    }
    // å‘货审批通过时同步发货状态和出库审批状态;拒绝时删除待确认出库记录。
    private void syncShippingStatus(String approveReason, Integer status) {
        ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
                .eq(ShippingInfo::getShippingNo, approveReason)
                .orderByDesc(ShippingInfo::getCreateTime)
                .last("limit 1"));
        if (shippingInfo == null) {
            return;
        }
        if (status.equals(2)) {
            shippingInfo.setStatus("审核通过");
            shippingInfo.setShippingDate(new Date());
            stockUtils.shipmentStatus(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), shippingInfo.getId());
        } else if (status.equals(3)) {
            stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
            shippingInfo.setStatus("审核拒绝");
        } else if (status.equals(1)) {
            shippingInfo.setStatus("审核中");
        } else {
            return;
        }
        shippingInfoMapper.updateById(shippingInfo);
    }
    // ç”Ÿæˆé‡‡è´­è´¨æ£€å•,并按产品质检标准初始化质检参数。
    private void addQualityInspect(PurchaseLedger purchaseLedger, SalesLedgerProduct saleProduct) {
        QualityInspect qualityInspect = new QualityInspect();
        qualityInspect.setInspectType(0);
        qualityInspect.setSupplier(purchaseLedger.getSupplierName());
        qualityInspect.setPurchaseLedgerId(purchaseLedger.getId());
        qualityInspect.setProductId(saleProduct.getProductId());
        qualityInspect.setProductName(saleProduct.getProductCategory());
        qualityInspect.setModel(saleProduct.getSpecificationModel());
        qualityInspect.setProductModelId(saleProduct.getProductModelId());
        qualityInspect.setUnit(saleProduct.getUnit());
        qualityInspect.setQuantity(saleProduct.getQuantity());
        qualityInspectMapper.insert(qualityInspect);
        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(saleProduct.getProductId(), 0, null);
        if (qualityTestStandard.size() > 0) {
            qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
            qualityInspectMapper.updateById(qualityInspect);
            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                            .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))
                    .forEach(qualityTestStandardParam -> {
                        QualityInspectParam param = new QualityInspectParam();
                        com.ruoyi.common.utils.bean.BeanUtils.copyProperties(qualityTestStandardParam, param);
                        param.setId(null);
                        param.setInspectId(qualityInspect.getId());
                        qualityInspectParamMapper.insert(param);
                    });
        }
    }
}
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -1,7 +1,6 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -14,26 +13,12 @@
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.device.mapper.DeviceRepairMapper;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.project.system.service.ISysNoticeService;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.mapper.QualityInspectParamMapper;
import com.ruoyi.quality.mapper.QualityTestStandardMapper;
import com.ruoyi.quality.mapper.QualityTestStandardParamMapper;
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityInspectParam;
import com.ruoyi.quality.pojo.QualityTestStandard;
import com.ruoyi.quality.pojo.QualityTestStandardParam;
import com.ruoyi.sales.mapper.*;
import com.ruoyi.sales.pojo.*;
import com.ruoyi.sales.mapper.CommonFileMapper;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -55,18 +40,8 @@
    private final SysUserMapper sysUserMapper;
    private final ISysNoticeService sysNoticeService;
    private final CommonFileMapper fileMapper;
    private final DeviceRepairMapper deviceRepairMapper;
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    private final SalesQuotationMapper salesQuotationMapper;
    private final ShippingInfoMapper shippingInfoMapper;
    private final ShippingProductDetailMapper shippingProductDetailMapper;
    private final CommonFileServiceImpl commonFileService;
    private final StockUtils stockUtils;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final QualityInspectMapper qualityInspectMapper;
    private final QualityTestStandardMapper qualityTestStandardMapper;
    private final QualityTestStandardParamMapper qualityTestStandardParamMapper;
    private final QualityInspectParamMapper qualityInspectParamMapper;
    private final ApproveBusinessStatusService approveBusinessStatusService;
    private final FileUtil fileUtil;
@@ -162,74 +137,7 @@
        }
        approveProcessMapper.updateById(approveProcess);
        //采购审核
        if (approveProcess.getApproveType().equals(5)) {
            PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                    .eq(PurchaseLedger::getPurchaseContractNumber, approveProcess.getApproveReason())
                    .last("limit 1"));
            if (purchaseLedger != null) {
                if (status.equals(2)) {
                    // åŒæ„
                    purchaseLedger.setApprovalStatus(3);
                    List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
                            .lambda().eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId()).eq(SalesLedgerProduct::getType, 2));
                    for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
                        // è´¨æ£€
                        if (salesLedgerProduct.getIsChecked()) {
                            addQualityInspect(purchaseLedger, salesLedgerProduct);
                        } else {
                            //直接入库
                            stockUtils.addStockWithBatchNo(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(), purchaseLedger.getPurchaseContractNumber() + "-" + salesLedgerProduct.getId());
                        }
                    }
                } else if (status.equals(3)) {
                    // æ‹’绝
                    purchaseLedger.setApprovalStatus(4);
                } else if (status.equals(1)) {
                    // å®¡æ ¸ä¸­
                    purchaseLedger.setApprovalStatus(2);
                }
                purchaseLedgerMapper.updateById(purchaseLedger);
            }
        }
        // é”€å”®æŠ¥ä»·çŠ¶æ€ä¿®æ”¹
        if (approveProcess.getApproveType().equals(6)) {
            SalesQuotation salesQuote = salesQuotationMapper.selectOne(new LambdaQueryWrapper<SalesQuotation>()
                    .eq(SalesQuotation::getQuotationNo, approveProcess.getApproveReason())
                    .last("limit 1"));
            // åŒæ„
            if (status.equals(2) && salesQuote != null) {
                salesQuote.setStatus("通过");
            } else if (status.equals(3) && salesQuote != null) {
                salesQuote.setStatus("拒绝");
            } else if (status.equals(1) && salesQuote != null) {
                salesQuote.setStatus("审核中");
            }
            salesQuotationMapper.updateById(salesQuote);
        }
        // å‡ºåº“审批修改=发货审批
        if (approveProcess.getApproveType().equals(7)) {
            ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
                    .eq(ShippingInfo::getShippingNo, approveProcess.getApproveReason())
                    .orderByDesc(ShippingInfo::getCreateTime)
                    .last("limit 1"));
            if (shippingInfo != null) {
                if (status.equals(2)) {
                    shippingInfo.setStatus("审核通过");
                    shippingInfo.setShippingDate(new Date());
                    //更改出库审核状态(待确认改成待审核)
                    stockUtils.shipmentStatus(StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode(), shippingInfo.getId());
                } else if (status.equals(3)) {
                    //删除原本(待确认)的出库审核状态
                    stockUtils.deleteStockOutRecord(shippingInfo.getId(), StockOutQualifiedRecordTypeEnum.SALE_SHIP_STOCK_OUT.getCode());
                    shippingInfo.setStatus("审核拒绝");
                } else if (status.equals(1)) {
                    shippingInfo.setStatus("审核中");
                }
                shippingInfoMapper.updateById(shippingInfo);
            }
        }
        approveBusinessStatusService.syncBusinessStatus(approveProcess.getApproveType(), approveProcess.getApproveReason(), status);
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_NODE, approveNode.getId(), approveNode.getStorageBlobDTOS());
    }
@@ -305,34 +213,6 @@
                return "办公用品审批";
        }
        return null;
    }
    private void addQualityInspect(PurchaseLedger purchaseLedger, SalesLedgerProduct saleProduct) {
        QualityInspect qualityInspect = new QualityInspect();
        qualityInspect.setInspectType(0);
        qualityInspect.setSupplier(purchaseLedger.getSupplierName());
        qualityInspect.setPurchaseLedgerId(purchaseLedger.getId());
        qualityInspect.setProductId(saleProduct.getProductId());
        qualityInspect.setProductName(saleProduct.getProductCategory());
        qualityInspect.setModel(saleProduct.getSpecificationModel());
        qualityInspect.setProductModelId(saleProduct.getProductModelId());
        qualityInspect.setUnit(saleProduct.getUnit());
        qualityInspect.setQuantity(saleProduct.getQuantity());
        qualityInspectMapper.insert(qualityInspect);
        List<QualityTestStandard> qualityTestStandard = qualityTestStandardMapper.getQualityTestStandardByProductId(saleProduct.getProductId(), 0, null);
        if (qualityTestStandard.size() > 0) {
            qualityInspect.setTestStandardId(qualityTestStandard.get(0).getId());
            qualityInspectMapper.updateById(qualityInspect);
            qualityTestStandardParamMapper.selectList(Wrappers.<QualityTestStandardParam>lambdaQuery()
                            .eq(QualityTestStandardParam::getTestStandardId, qualityTestStandard.get(0).getId()))
                    .forEach(qualityTestStandardParam -> {
                        QualityInspectParam param = new QualityInspectParam();
                        com.ruoyi.common.utils.bean.BeanUtils.copyProperties(qualityTestStandardParam, param);
                        param.setId(null);
                        param.setInspectId(qualityInspect.getId());
                        qualityInspectParamMapper.insert(param);
                    });
        }
    }
}
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -1,8 +1,6 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -25,10 +23,8 @@
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.procurementrecord.utils.StockUtils;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysNotice;
import com.ruoyi.project.system.domain.SysUser;
@@ -38,10 +34,8 @@
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.sales.mapper.CommonFileMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
import lombok.RequiredArgsConstructor;
@@ -53,15 +47,12 @@
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class ApproveProcessServiceImpl extends ServiceImpl<ApproveProcessMapper, ApproveProcess> implements IApproveProcessService {
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
    private final SysDeptMapper sysDeptMapper;
    private final IApproveNodeService approveNodeService;
    private final SysUserMapper sysUserMapper;
@@ -70,11 +61,10 @@
    private final CommonFileServiceImpl commonFileService;
    private final ISysNoticeService sysNoticeService;
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    private final SalesLedgerProductMapper salesLedgerProductMapper;
    private final StockUtils stockUtils;
    private final ShippingInfoMapper shippingInfoMapper;
    private final ApproveNodeMapper approveNodeMapper;
    private final ApproveProcessConfigNodeService approveProcessConfigNodeService;
    private final ApproveBusinessStatusService approveBusinessStatusService;
    private final FileUtil fileUtil;
    private final ApproveProcessConfigNodeMapper approveProcessConfigNodeMapper;
@@ -82,61 +72,34 @@
    public void addApprove(ApproveProcessVO approveProcessVO) throws Exception {
        SysUser sysUser = SecurityUtils.getLoginUser().getUser();
        SysDept sysDept = sysDeptMapper.selectDeptById(SecurityUtils.getLoginUser().getCurrentDeptId());
        List<ApproveProcessConfigNodeVo> list = approveProcessConfigNodeService.listNode(approveProcessVO.getApproveType());
        if (sysDept == null) throw new RuntimeException("部门不存在");
        if (sysUser == null) throw new RuntimeException("申请人不存在");
        List<ApproveProcessConfigNodeVo> list = Optional.ofNullable(approveProcessConfigNodeService.listNode(approveProcessVO.getApproveType()))
                .orElse(Collections.emptyList());
        List<Long> nodeIds = list.stream()
                .map(ApproveProcessConfigNodeVo::getApproverId)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        // æ— å®¡æ ¸äººé€»è¾‘添加
        // å®¡æ‰¹é…ç½®æ²¡æœ‰æœ‰æ•ˆå®¡æ ¸äººæ—¶ï¼Œä¸æ–°å¢žååŒå®¡æ‰¹æµç¨‹ï¼Œç›´æŽ¥æ‰§è¡Œä¸šåŠ¡å®¡æ ¸é€šè¿‡é€»è¾‘ã€‚
        if (CollectionUtils.isEmpty(nodeIds)) {
            autoPassPurchaseApproveIfNoApprover(approveProcessVO); // é‡‡è´­å•无审核人逻辑
            approveBusinessStatusService.syncBusinessStatus(approveProcessVO.getApproveType(), approveProcessVO.getApproveReason(), 2);
            return;
        }
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(nodeIds);
        if (CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("审核用户不存在");
        if (sysDept == null) throw new RuntimeException("部门不存在");
        if (sysUser == null) throw new RuntimeException("申请人不存在");
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        ApproveProcess approveProcess = new ApproveProcess();
        String no = OrderUtils.countTodayByCreateTime(approveProcessMapper, "", "approve_id");
        approveProcess.setApproveId(no);
        approveProcess.setApproveUser(sysUser.getUserId());
        approveProcess.setApproveUserName(sysUser.getNickName());
        approveProcess.setApproveDeptId(sysDept.getDeptId());
        approveProcess.setApproveUserIds(nodeIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
        approveProcess.setApproveDeptName(sysDept.getDeptName());
        approveProcess.setApproveUserNames(sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
        approveProcess.setApproveTime(StringUtils.isEmpty(approveProcessVO.getApproveTime()) ? new Date() : dateFormat.parse(approveProcessVO.getApproveTime()));
        approveProcess.setApproveReason(approveProcessVO.getApproveReason());
        approveProcess.setDeviceRepairId(approveProcessVO.getDeviceRepairId());
        approveProcess.setMaintenancePrice(approveProcessVO.getMaintenancePrice());
        approveProcess.setPrice(approveProcessVO.getPrice());
        approveProcess.setStartDate(approveProcessVO.getStartDate());
        approveProcess.setEndDate(approveProcessVO.getEndDate());
        approveProcess.setApproveStatus(0);
        approveProcess.setApproveDelete(0);
        approveProcess.setApproveType(approveProcessVO.getApproveType());
        approveProcess.setCreateTime(LocalDateTime.now());
        approveProcess.setTenantId(approveProcessVO.getApproveDeptId());
        approveProcess.setApproveUserCurrentId(nodeIds.get(0));
        approveProcess.setApproveUserCurrentName(sysUsers
                .stream()
                .filter(SysUser -> SysUser.getUserId().equals(nodeIds.get(0)))
                .collect(Collectors.toList())
                .get(0)
                .getNickName());
        // è®¾ç½®çŠ¶æ€ä¸ºé‡æ–°æäº¤
        if (approveProcessVO.getId() != null) {
            ApproveProcess approveProcess1 = approveProcessMapper.selectById(approveProcessVO.getId());
            approveProcess1.setApproveStatus(4);
            approveProcessMapper.updateById(approveProcess1);
        }
        // æœ‰å®¡æ ¸äººæ—¶ï¼ŒæŒ‰æ­£å¸¸ååŒå®¡æ‰¹æµç¨‹åˆ›å»ºå®¡æ‰¹ä¸»è¡¨ã€å®¡æ‰¹èŠ‚ç‚¹å¹¶é€šçŸ¥é¦–ä¸ªå®¡æ ¸äººã€‚
        ApproveProcess approveProcess = buildApproveProcess(approveProcessVO, sysUser, sysDept, nodeIds, sysUsers, 0);
        markResubmitted(approveProcessVO);
        save(approveProcess);
        //初始化审批节点
        String nodeIdStr = nodeIds.stream()
                .map(String::valueOf)
                .collect(Collectors.joining(","));
        approveNodeService.initApproveNodes(nodeIdStr, no, approveProcessVO.getApproveDeptId());
        approveNodeService.initApproveNodes(nodeIdStr, approveProcess.getApproveId(), approveProcessVO.getApproveDeptId());
        // é™„件绑定
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.APPROVE_PROCESS, approveProcess.getId(), approveProcessVO.getStorageBlobDTOS());
        /*消息通知*/
@@ -154,24 +117,56 @@
        }
    }
    private void autoPassPurchaseApproveIfNoApprover(ApproveProcessVO approveProcessVO) {
        if (!Objects.equals(approveProcessVO.getApproveType(), 5)
                || !StringUtils.hasText(approveProcessVO.getApproveReason())) {
            throw new RuntimeException("审核用户不存在");
    private ApproveProcess buildApproveProcess(ApproveProcessVO approveProcessVO, SysUser sysUser, SysDept sysDept,
                                               List<Long> nodeIds, List<SysUser> sysUsers, Integer approveStatus) throws Exception {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        ApproveProcess approveProcess = new ApproveProcess();
        String no = OrderUtils.countTodayByCreateTime(approveProcessMapper, "", "approve_id", approveProcess.getCreateTime() != null ? approveProcess.getCreateTime() : LocalDateTime.now());
        approveProcess.setApproveId(no);
        approveProcess.setApproveUser(sysUser.getUserId());
        approveProcess.setApproveUserName(sysUser.getNickName());
        approveProcess.setApproveDeptId(sysDept.getDeptId());
        approveProcess.setApproveDeptName(sysDept.getDeptName());
        approveProcess.setApproveUserIds(nodeIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
        approveProcess.setApproveUserNames(sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
        approveProcess.setApproveTime(StringUtils.isEmpty(approveProcessVO.getApproveTime()) ? new Date() : dateFormat.parse(approveProcessVO.getApproveTime()));
        approveProcess.setApproveReason(approveProcessVO.getApproveReason());
        approveProcess.setDeviceRepairId(approveProcessVO.getDeviceRepairId());
        approveProcess.setMaintenancePrice(approveProcessVO.getMaintenancePrice());
        approveProcess.setPrice(approveProcessVO.getPrice());
        approveProcess.setStartDate(approveProcessVO.getStartDate());
        approveProcess.setEndDate(approveProcessVO.getEndDate());
        approveProcess.setStartDateTime(approveProcessVO.getStartDateTime());
        approveProcess.setEndDateTime(approveProcessVO.getEndDateTime());
        approveProcess.setApproveStatus(approveStatus);
        approveProcess.setApproveDelete(0);
        approveProcess.setApproveType(approveProcessVO.getApproveType());
        approveProcess.setCreateTime(LocalDateTime.now());
        approveProcess.setTenantId(approveProcessVO.getApproveDeptId());
        if (!CollectionUtils.isEmpty(nodeIds)) {
            SysUser currentUser = sysUsers.stream()
                    .filter(user -> user.getUserId().equals(nodeIds.get(0)))
                    .findFirst()
                    .orElseThrow(() -> new RuntimeException("审核用户不存在"));
            approveProcess.setApproveUserCurrentId(currentUser.getUserId());
            approveProcess.setApproveUserCurrentName(currentUser.getNickName());
        }
        if (approveStatus.equals(2) || approveStatus.equals(3) || approveStatus.equals(4)) {
            approveProcess.setApproveOverTime(new Date());
        }
        return approveProcess;
    }
        purchaseLedgerMapper.update(null, new LambdaUpdateWrapper<PurchaseLedger>()
                .eq(PurchaseLedger::getPurchaseContractNumber, approveProcessVO.getApproveReason())
                .set(PurchaseLedger::getApprovalStatus, 3));
        //采购入库
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                .eq(PurchaseLedger::getPurchaseContractNumber, approveProcessVO.getApproveReason())
                .last("limit 1"));
        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
                .lambda().eq(SalesLedgerProduct::getSalesLedgerId, purchaseLedger.getId()).eq(SalesLedgerProduct::getType, 2));
        for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
            stockUtils.addStockWithBatchNo(salesLedgerProduct.getProductModelId(), salesLedgerProduct.getQuantity(), StockInQualifiedRecordTypeEnum.PURCHASE_STOCK_IN.getCode(), purchaseLedger.getId(),purchaseLedger.getPurchaseContractNumber()+"-"+salesLedgerProduct.getId());
    private void markResubmitted(ApproveProcessVO approveProcessVO) {
        if (approveProcessVO.getId() == null) {
            return;
        }
        ApproveProcess approveProcess = approveProcessMapper.selectById(approveProcessVO.getId());
        if (approveProcess == null) {
            return;
        }
        approveProcess.setApproveStatus(4);
        approveProcessMapper.updateById(approveProcess);
    }
    @Override
src/main/java/com/ruoyi/approve/service/impl/FinReimbursementDetailServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.approve.service.impl;
import com.ruoyi.approve.pojo.FinReimbursementDetail;
import com.ruoyi.approve.mapper.FinReimbursementDetailMapper;
import com.ruoyi.approve.service.FinReimbursementDetailService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * æŠ¥é”€å•明细表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:38
 */
@Service
public class FinReimbursementDetailServiceImpl extends ServiceImpl<FinReimbursementDetailMapper, FinReimbursementDetail> implements FinReimbursementDetailService {
}
src/main/java/com/ruoyi/approve/service/impl/FinReimbursementServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,544 @@
package com.ruoyi.approve.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.bean.dto.ApprovalInstanceDto;
import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeApproverDto;
import com.ruoyi.approve.bean.dto.ApprovalTemplateNodeDto;
import com.ruoyi.approve.bean.dto.FinReimbursementDto;
import com.ruoyi.approve.bean.vo.FinReimbursementVo;
import com.ruoyi.approve.mapper.ApprovalInstanceMapper;
import com.ruoyi.approve.mapper.FinReimbursementDetailMapper;
import com.ruoyi.approve.mapper.FinReimbursementMapper;
import com.ruoyi.approve.mapper.FinReimbursementTravelMapper;
import com.ruoyi.approve.pojo.*;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.approve.service.*;
import com.ruoyi.common.enums.TypeEnums;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.project.system.service.ISysNoticeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
/**
 * <p>
 * æŠ¥é”€å•主表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:15
 */
@Service
@RequiredArgsConstructor
public class FinReimbursementServiceImpl extends ServiceImpl<FinReimbursementMapper, FinReimbursement> implements FinReimbursementService {
    private static final String BILL_STATUS_DRAFT = "DRAFT";
    private static final String BILL_STATUS_IN_APPROVAL = "IN_APPROVAL";
    private static final String NODE_STATUS_WAITING = "WAITING";
    private final ApprovalInstanceMapper approvalInstanceMapper;
    private final ApprovalInstanceService approvalInstanceService;
    private final ApprovalInstanceNodeService approvalInstanceNodeService;
    private final ApprovalTaskService approvalTaskService;
    private final ApprovalRecordService approvalRecordService;
    private final FinReimbursementMapper finReimbursementMapper;
    private final FinReimbursementTravelMapper finReimbursementTravelMapper;
    private final FinReimbursementDetailMapper finReimbursementDetailMapper;
    private final FileUtil fileUtil;
    private final ISysNoticeService sysNoticeService;
    @Override
    public IPage<FinReimbursementVo> listPage(FinReimbursementDto finReimbursementDto, Page<FinReimbursementVo> page) {
        IPage<FinReimbursementVo> finReimbursementVoIPage = finReimbursementMapper.listPage(finReimbursementDto, page);
        finReimbursementVoIPage.getRecords().forEach(vo -> {
            vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.FIN_REIMBURSEMENT,  vo.getId()));
        });
        return finReimbursementVoIPage;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean add(FinReimbursementDto finReimbursementDto) {
        String billStatus = validateAddParam(finReimbursementDto);
        // ç”ŸæˆæŠ¥é”€å•号
        String billNo = OrderUtils.countTodayByCreateTime(finReimbursementMapper, "BXD", "bill_no", finReimbursementDto.getCreateTime() != null ? finReimbursementDto.getCreateTime() : LocalDateTime.now());
        List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
        BigDecimal totalAmount = details.stream()
                .map(FinReimbursementDetail::getAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        FinReimbursement reimbursement = buildReimbursement(finReimbursementDto, billNo, totalAmount, billStatus);
        // ä¿å­˜æŠ¥é”€å•主表
        boolean saved = this.save(reimbursement);
        if (!saved || reimbursement.getId() == null) {
            throw new ServiceException("新增报销单失败");
        }
        Long reimbursementId = reimbursement.getId();
        // ä¿å­˜å·®æ—…报销扩展信息(报销类型为差旅报销时)
        FinReimbursementTravel travel = finReimbursementDto.getTravel();
        if (isTravelReimbursement(finReimbursementDto.getReimbursementType())) {
            travel.setReimbursementId(reimbursementId);
            int travelRows = finReimbursementTravelMapper.insert(travel);
            if (travelRows != 1) {
                throw new ServiceException("新增差旅报销扩展信息失败");
            }
        }
        // ä¿å­˜æŠ¥é”€å•明细
        for (int i = 0; i < details.size(); i++) {
            FinReimbursementDetail detail = details.get(i);
            detail.setId(null);
            detail.setReimbursementId(reimbursementId);
            detail.setRowNo(i + 1);
            int detailRows = finReimbursementDetailMapper.insert(detail);
            if (detailRows != 1) {
                throw new ServiceException("新增报销单明细失败");
            }
        }
        if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
            startApproval(reimbursement, finReimbursementDto);
        }
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_REIMBURSEMENT, reimbursementId, finReimbursementDto.getStorageBlobDTOs());
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(FinReimbursementDto finReimbursementDto) {
        String billStatus = validateUpdateParam(finReimbursementDto);
        Long reimbursementId = finReimbursementDto.getId();
        FinReimbursement existing = finReimbursementMapper.selectById(reimbursementId);
        if (existing == null) {
            throw new ServiceException("报销单不存在");
        }
        // è®¡ç®—明细汇总金额
        List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
        BigDecimal totalAmount = details.stream()
                .map(FinReimbursementDetail::getAmount)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        // æ›´æ–°ä¸»è¡¨
        FinReimbursement reimbursement = buildReimbursement(
                finReimbursementDto,
                existing.getBillNo(),
                totalAmount,
                billStatus
        );
        reimbursement.setId(reimbursementId);
        int mainRows = finReimbursementMapper.updateById(reimbursement);
        if (mainRows != 1) {
            throw new ServiceException("更新报销单主表失败");
        }
        // æŸ¥è¯¢æ•°æ®åº“中已有的明细
        List<FinReimbursementDetail> existingDetails = finReimbursementDetailMapper.selectList(
                new LambdaQueryWrapper<FinReimbursementDetail>()
                        .eq(FinReimbursementDetail::getReimbursementId, reimbursementId));
        Set<Long> existingDetailIds = existingDetails.stream()
                .map(FinReimbursementDetail::getId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        // æ–°æ˜Žç»†ä¸­æœ‰ID的 â†’ æ›´æ–°ï¼›æ— ID的 â†’ æ–°å¢ž
        Set<Long> submittedDetailIds = new HashSet<>();
        for (int i = 0; i < details.size(); i++) {
            FinReimbursementDetail detail = details.get(i);
            detail.setReimbursementId(reimbursementId);
            detail.setRowNo(i + 1);
            if (detail.getId() != null && existingDetailIds.contains(detail.getId())) {
                finReimbursementDetailMapper.updateById(detail);
                submittedDetailIds.add(detail.getId());
            } else {
                detail.setId(null);
                finReimbursementDetailMapper.insert(detail);
            }
        }
        // æ•°æ®åº“中已有但新明细中没有的 â†’ åˆ é™¤
        for (Long existingId : existingDetailIds) {
            if (!submittedDetailIds.contains(existingId)) {
                finReimbursementDetailMapper.deleteById(existingId);
            }
        }
        // å·®æ—…扩展:有则更新,无则新增
        FinReimbursementTravel existingTravel = finReimbursementTravelMapper.selectOne(
                new LambdaQueryWrapper<FinReimbursementTravel>()
                        .eq(FinReimbursementTravel::getReimbursementId, reimbursementId)
                        .last("LIMIT 1"));
        FinReimbursementTravel travel = finReimbursementDto.getTravel();
        if (isTravelReimbursement(finReimbursementDto.getReimbursementType()) && travel != null) {
            travel.setReimbursementId(reimbursementId);
            if (existingTravel != null) {
                travel.setId(existingTravel.getId());
                finReimbursementTravelMapper.updateById(travel);
            } else {
                travel.setId(null);
                finReimbursementTravelMapper.insert(travel);
            }
        }
        resetApprovalFlow(existing, reimbursementId);
        if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
            reimbursement.setApprovalInstanceId(null);
            startApproval(reimbursement, finReimbursementDto);
        }
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_REIMBURSEMENT, reimbursementId, finReimbursementDto.getStorageBlobDTOs());
        return true;
    }
    @Override
    public FinReimbursementVo detail(Long id) {
        if (id == null ) {
            throw new ServiceException("报销单ID不能为空");
        }
        FinReimbursement reimbursement = finReimbursementMapper.selectById(id);
        if (reimbursement == null) {
            throw new ServiceException("报销单不存在");
        }
        FinReimbursementVo vo = new FinReimbursementVo();
        vo.setId(reimbursement.getId());
        vo.setBillNo(reimbursement.getBillNo());
        vo.setReimbursementType(reimbursement.getReimbursementType());
        vo.setExpenseType(reimbursement.getExpenseType());
        vo.setApplicantId(reimbursement.getApplicantId());
        vo.setApplicantCode(reimbursement.getApplicantCode());
        vo.setApplicantName(reimbursement.getApplicantName());
        vo.setApplicantDeptId(reimbursement.getApplicantDeptId());
        vo.setApplicantDeptName(reimbursement.getApplicantDeptName());
        vo.setReason(reimbursement.getReason());
        vo.setApplyAmount(reimbursement.getApplyAmount());
        vo.setDetailTotalAmount(reimbursement.getDetailTotalAmount());
        vo.setPayeeName(reimbursement.getPayeeName());
        vo.setPayeeAccount(reimbursement.getPayeeAccount());
        vo.setPayeeBank(reimbursement.getPayeeBank());
        vo.setApprovalInstanceId(reimbursement.getApprovalInstanceId());
        vo.setApproveProcessId(reimbursement.getApproveProcessId());
        vo.setBillStatus(reimbursement.getBillStatus());
        vo.setApprovedTime(reimbursement.getApprovedTime());
        vo.setPaidTime(reimbursement.getPaidTime());
        vo.setAccountExpenseId(reimbursement.getAccountExpenseId());
        vo.setRemark(reimbursement.getRemark());
        vo.setTenantId(reimbursement.getTenantId());
        vo.setCreateUser(reimbursement.getCreateUser());
        vo.setCreateTime(reimbursement.getCreateTime());
        vo.setUpdateUser(reimbursement.getUpdateUser());
        vo.setUpdateTime(reimbursement.getUpdateTime());
        vo.setDeptId(reimbursement.getDeptId());
        vo.setDeleted(reimbursement.getDeleted());
        vo.setDetails(finReimbursementDetailMapper.selectList(
                new LambdaQueryWrapper<FinReimbursementDetail>()
                        .eq(FinReimbursementDetail::getReimbursementId, reimbursement.getId())
                        .orderByAsc(FinReimbursementDetail::getRowNo)
        ));
        if (isTravelReimbursement(reimbursement.getReimbursementType())) {
            vo.setTravel(finReimbursementTravelMapper.selectOne(
                    new LambdaQueryWrapper<FinReimbursementTravel>()
                            .eq(FinReimbursementTravel::getReimbursementId, reimbursement.getId())
                            .last("LIMIT 1")
            ));
        }
        vo.setStorageBlobVOList(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.FIN_REIMBURSEMENT, reimbursement.getId()));
        //审批记录返回
        vo.setTasks(approvalTaskService.list(new LambdaQueryWrapper<ApprovalTask>().eq(ApprovalTask::getInstanceId, reimbursement.getApprovalInstanceId())));
        vo.setRecords(approvalRecordService.list(new LambdaQueryWrapper<ApprovalRecord>().eq(ApprovalRecord::getInstanceId, reimbursement.getApprovalInstanceId())));
        return vo;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean delete(List<Long> ids) {
        fileUtil.deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum.FILE, RecordTypeEnum.FIN_REIMBURSEMENT, ids);
        //先删除明细
        finReimbursementDetailMapper.delete(new LambdaQueryWrapper<FinReimbursementDetail>().in(FinReimbursementDetail::getReimbursementId, ids));
        //删除差旅
        finReimbursementTravelMapper.delete(new LambdaQueryWrapper<FinReimbursementTravel>().in(FinReimbursementTravel::getReimbursementId, ids));
        //删除主表
        int rows = finReimbursementMapper.delete(new LambdaQueryWrapper<FinReimbursement>().in(FinReimbursement::getId, ids));
        return rows == ids.size();
    }
    private String validateUpdateParam(FinReimbursementDto finReimbursementDto) {
        if (finReimbursementDto == null || finReimbursementDto.getId() == null) {
            throw new ServiceException("报销单ID不能为空");
        }
        if (finReimbursementDto.getReimbursementType() == null) {
            throw new ServiceException("报销类型不能为空");
        }
        String billStatus = normalizeBillStatus(finReimbursementDto.getBillStatus());
        if (billStatus == null) {
            throw new ServiceException("单据状态只支持 DRAFT æˆ– IN_APPROVAL");
        }
        if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
            validateApprovalNodes(finReimbursementDto.getNodes());
        }
        List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
        if (details == null || details.isEmpty()) {
            throw new ServiceException("报销单明细不能为空");
        }
        for (FinReimbursementDetail detail : details) {
            if (detail == null) {
                throw new ServiceException("报销单明细不能为空");
            }
            if (detail.getAmount() == null || detail.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
                throw new ServiceException("报销单明细金额必须大于0");
            }
        }
        if (isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() == null) {
            throw new ServiceException("差旅报销必须填写差旅扩展信息");
        }
        if (!isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() != null) {
            throw new ServiceException("非差旅报销不允许填写差旅扩展信息");
        }
        return billStatus;
    }
    private String validateAddParam(FinReimbursementDto finReimbursementDto) {
        if (finReimbursementDto == null) {
            throw new ServiceException("报销单数据不能为空");
        }
        if (finReimbursementDto.getReimbursementType() == null) {
            throw new ServiceException("报销类型不能为空");
        }
        String billStatus = normalizeBillStatus(finReimbursementDto.getBillStatus());
        if (billStatus == null) {
            throw new ServiceException("单据状态只支持 DRAFT æˆ– IN_APPROVAL");
        }
        if (BILL_STATUS_IN_APPROVAL.equals(billStatus)) {
            validateApprovalNodes(finReimbursementDto.getNodes());
        }
        List<FinReimbursementDetail> details = finReimbursementDto.getDetails();
        if (details == null || details.isEmpty()) {
            throw new ServiceException("报销单明细不能为空");
        }
        for (FinReimbursementDetail detail : details) {
            if (detail == null) {
                throw new ServiceException("报销单明细不能为空");
            }
            if (detail.getAmount() == null || detail.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
                throw new ServiceException("报销单明细金额必须大于0");
            }
        }
        if (isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() == null) {
            throw new ServiceException("差旅报销必须填写差旅扩展信息");
        }
        if (!isTravelReimbursement(finReimbursementDto.getReimbursementType()) && finReimbursementDto.getTravel() != null) {
            throw new ServiceException("非差旅报销不允许填写差旅扩展信息");
        }
        return billStatus;
    }
    private FinReimbursement buildReimbursement(FinReimbursementDto finReimbursementDto, String billNo, BigDecimal totalAmount, String billStatus) {
        FinReimbursement reimbursement = new FinReimbursement();
        reimbursement.setId(null);
        reimbursement.setBillNo(billNo);
        reimbursement.setReimbursementType(finReimbursementDto.getReimbursementType());
        reimbursement.setExpenseType(finReimbursementDto.getExpenseType());
        reimbursement.setApplicantId(finReimbursementDto.getApplicantId());
        reimbursement.setApplicantCode(finReimbursementDto.getApplicantCode());
        reimbursement.setApplicantName(finReimbursementDto.getApplicantName());
        reimbursement.setApplicantDeptId(finReimbursementDto.getApplicantDeptId());
        reimbursement.setApplicantDeptName(finReimbursementDto.getApplicantDeptName());
        reimbursement.setReason(finReimbursementDto.getReason());
        reimbursement.setApplyAmount(finReimbursementDto.getApplyAmount());
        reimbursement.setDetailTotalAmount(totalAmount);
        reimbursement.setPayeeName(finReimbursementDto.getPayeeName());
        reimbursement.setPayeeAccount(finReimbursementDto.getPayeeAccount());
        reimbursement.setPayeeBank(finReimbursementDto.getPayeeBank());
        reimbursement.setRemark(finReimbursementDto.getRemark());
        reimbursement.setTenantId(finReimbursementDto.getTenantId());
        reimbursement.setApproveProcessId(null);
        reimbursement.setBillStatus(billStatus);
        return reimbursement;
    }
    private void startApproval(FinReimbursement reimbursement, FinReimbursementDto finReimbursementDto) {
        Long businessType = resolveBusinessType(finReimbursementDto.getReimbursementType());
        ApprovalInstanceDto approvalInstanceDto = new ApprovalInstanceDto();
        approvalInstanceDto.setInstanceNo(OrderUtils.countTodayByCreateTime(approvalInstanceMapper, "SP", "instance_no", approvalInstanceDto.getCreateTime() != null ? approvalInstanceDto.getCreateTime() : LocalDateTime.now()));
        approvalInstanceDto.setBusinessId(reimbursement.getId());
        approvalInstanceDto.setTemplateId(null);
        approvalInstanceDto.setTemplateName(TypeEnums.getLabelByValue(businessType) + "审批");
        approvalInstanceDto.setBusinessType(businessType);
        approvalInstanceDto.setTitle("报销单号:" + reimbursement.getBillNo());
        approvalInstanceDto.setApplicantId(reimbursement.getApplicantId() != null ? reimbursement.getApplicantId() : SecurityUtils.getUserId());
        approvalInstanceDto.setApplicantName(reimbursement.getApplicantName() != null ? reimbursement.getApplicantName() : SecurityUtils.getLoginUser().getNickName());
        approvalInstanceDto.setApplyTime(LocalDateTime.now());
        approvalInstanceDto.setStatus("PENDING");
        approvalInstanceDto.setCurrentLevel(1);
        boolean approvalSaved = approvalInstanceService.save(approvalInstanceDto);
        if (!approvalSaved || approvalInstanceDto.getId() == null) {
            throw new ServiceException("发起审批失败");
        }
        List<ApprovalTask> firstTasks = createApprovalNodes(approvalInstanceDto, finReimbursementDto.getNodes());
        sendApproveNotice(approvalInstanceDto, firstTasks);
        FinReimbursement update = new FinReimbursement();
        update.setId(reimbursement.getId());
        update.setApprovalInstanceId(approvalInstanceDto.getId());
        update.setBillStatus(BILL_STATUS_IN_APPROVAL);
        int rows = finReimbursementMapper.updateById(update);
        if (rows != 1) {
            throw new ServiceException("回填审批实例失败");
        }
    }
    private List<ApprovalTask> createApprovalNodes(ApprovalInstanceDto approvalInstanceDto, List<ApprovalTemplateNodeDto> nodes) {
        List<ApprovalTask> firstTasks = Collections.emptyList();
        for (int i = 0; i < nodes.size(); i++) {
            ApprovalTemplateNodeDto nodeDto = nodes.get(i);
            ApprovalInstanceNode instanceNode = new ApprovalInstanceNode();
            instanceNode.setInstanceId(approvalInstanceDto.getId());
            instanceNode.setLevelNo(nodeDto.getLevelNo());
            instanceNode.setApproveType(nodeDto.getApproveType());
            instanceNode.setStatus(i == 0 ? "PENDING" : NODE_STATUS_WAITING);
            instanceNode.setStartTime(i == 0 ? LocalDateTime.now() : null);
            instanceNode.setDeleted((byte) 0);
            approvalInstanceNodeService.save(instanceNode);
            List<ApprovalTask> tasks = nodeDto.getApprovers().stream().map(approver -> {
                ApprovalTask task = new ApprovalTask();
                task.setInstanceId(approvalInstanceDto.getId());
                task.setNodeId(instanceNode.getId());
                task.setLevelNo(instanceNode.getLevelNo());
                task.setApproverId(approver.getApproverId());
                task.setApproverName(approver.getApproverName());
                task.setTaskStatus("PENDING");
                task.setIsRead((byte) 0);
                task.setDeleted((byte) 0);
                return task;
            }).collect(Collectors.toList());
            approvalTaskService.saveBatch(tasks);
            if (i == 0) {
                firstTasks = tasks;
                ApprovalRecord record = new ApprovalRecord();
                record.setInstanceId(approvalInstanceDto.getId());
                record.setNodeId(instanceNode.getId());
                record.setOperatorId(approvalInstanceDto.getApplicantId());
                record.setOperatorName(approvalInstanceDto.getApplicantName());
                record.setAction("SUBMIT");
                record.setComment("发起审批");
                record.setDeleted((byte) 0);
                approvalRecordService.save(record);
            }
        }
        return firstTasks;
    }
    private void validateApprovalNodes(List<ApprovalTemplateNodeDto> nodes) {
        if (nodes == null || nodes.isEmpty()) {
            throw new ServiceException("提交审批时审批节点不能为空");
        }
        for (int i = 0; i < nodes.size(); i++) {
            ApprovalTemplateNodeDto node = nodes.get(i);
            if (node == null) {
                throw new ServiceException("审批节点不能为空");
            }
            if (node.getLevelNo() == null) {
                node.setLevelNo(i + 1);
            }
            if (!StringUtils.hasText(node.getApproveType())) {
                throw new ServiceException("审批节点审批方式不能为空");
            }
            List<ApprovalTemplateNodeApproverDto> approvers = node.getApprovers();
            if (approvers == null || approvers.isEmpty()) {
                throw new ServiceException("审批节点审批人不能为空");
            }
            for (ApprovalTemplateNodeApproverDto approver : approvers) {
                if (approver == null || approver.getApproverId() == null) {
                    throw new ServiceException("审批人不能为空");
                }
            }
        }
    }
    private void sendApproveNotice(ApprovalInstanceDto instance, List<ApprovalTask> tasks) {
        if (instance == null || tasks == null || tasks.isEmpty()) {
            return;
        }
        List<Long> approverIds = tasks.stream()
                .map(ApprovalTask::getApproverId)
                .filter(id -> id != null && id > 0)
                .distinct()
                .collect(Collectors.toList());
        if (approverIds.isEmpty()) {
            return;
        }
        String title = "报销审批";
        String message = "审批单号 " + instance.getInstanceNo() + " éœ€è¦æ‚¨å®¡æ‰¹";
        String jumpPath = "/approvalInstance?id=" + instance.getId();
        sysNoticeService.simpleNoticeByUser(title, message, approverIds, jumpPath);
    }
    private void resetApprovalFlow(FinReimbursement existing, Long reimbursementId) {
        if (existing == null || existing.getApprovalInstanceId() == null) {
            return;
        }
        Long approvalInstanceId = existing.getApprovalInstanceId();
        if (!"REJECTED".equals(existing.getBillStatus())) {
            approvalInstanceService.delete(Collections.singletonList(approvalInstanceId));
        }
        clearApprovalBinding(reimbursementId);
    }
    private void clearApprovalBinding(Long reimbursementId) {
        int rows = finReimbursementMapper.update(
                null,
                Wrappers.<FinReimbursement>lambdaUpdate()
                        .eq(FinReimbursement::getId, reimbursementId)
                        .set(FinReimbursement::getApprovalInstanceId, null)
        );
        if (rows != 1) {
            throw new ServiceException("重置审批流程失败");
        }
    }
    private Long resolveBusinessType(Byte reimbursementType) {
        return isTravelReimbursement(reimbursementType)
                ? TypeEnums.TRAVEL_REIMBURSEMENT_APPROVAL.getCode()
                : TypeEnums.EXPENSE_APPROVAL.getCode();
    }
    private String normalizeBillStatus(String billStatus) {
        if (billStatus == null) {
            return BILL_STATUS_DRAFT;
        }
        String normalized = billStatus.trim().toUpperCase();
        if (BILL_STATUS_DRAFT.equals(normalized) || BILL_STATUS_IN_APPROVAL.equals(normalized)) {
            return normalized;
        }
        return null;
    }
    private boolean isTravelReimbursement(Byte reimbursementType) {
        return Byte.valueOf((byte) 1).equals(reimbursementType);
    }
}
src/main/java/com/ruoyi/approve/service/impl/FinReimbursementTravelServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.approve.service.impl;
import com.ruoyi.approve.pojo.FinReimbursementTravel;
import com.ruoyi.approve.mapper.FinReimbursementTravelMapper;
import com.ruoyi.approve.service.FinReimbursementTravelService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * å·®æ—…报销扩展表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-21 09:56:47
 */
@Service
public class FinReimbursementTravelServiceImpl extends ServiceImpl<FinReimbursementTravelMapper, FinReimbursementTravel> implements FinReimbursementTravelService {
}
src/main/java/com/ruoyi/approve/utils/ApproveProcessConfigNodeUtils.java
@@ -1,9 +1,370 @@
package com.ruoyi.approve.utils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.approve.pojo.ApprovalInstance;
import com.ruoyi.approve.pojo.ApprovalInstanceNode;
import com.ruoyi.approve.pojo.ApprovalRecord;
import com.ruoyi.approve.pojo.ApprovalTask;
import com.ruoyi.approve.pojo.ApprovalTemplateNode;
import com.ruoyi.approve.pojo.ApprovalTemplateNodeApprover;
import com.ruoyi.approve.service.ApprovalInstanceNodeService;
import com.ruoyi.approve.service.ApprovalRecordService;
import com.ruoyi.approve.service.ApprovalTaskService;
import com.ruoyi.approve.service.ApprovalTemplateNodeApproverService;
import com.ruoyi.approve.service.ApprovalTemplateNodeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
 * å®¡æ‰¹æµç¨‹èŠ‚ç‚¹å·¥å…·ç±»
 */
@Component
@RequiredArgsConstructor
public class ApproveProcessConfigNodeUtils {
    private final ApprovalInstanceNodeService instanceNodeService;
    private final ApprovalTaskService approvalTaskService;
    private final ApprovalRecordService approvalRecordService;
    private final ApprovalTemplateNodeService approvalTemplateNodeService;
    private final ApprovalTemplateNodeApproverService approvalTemplateNodeApproverService;
}
    /**
     * æŒ‰å½“前层级创建审批节点和审批任务。
     * è¯¥é‡è½½ä¼šåŒæ—¶å†™å…¥ä¸€æ¡å‘起审批记录。
     */
    @Transactional(rollbackFor = Exception.class)
    public ApprovalInstanceNode createCurrentNodeAndTasks(ApprovalInstance instance) {
        return createCurrentNodeAndTasks(instance, true);
    }
    /**
     * æŒ‰å½“前层级创建审批节点和审批任务。
     *
     * @param instance å®¡æ‰¹å®žä¾‹
     * @param createSubmitRecord æ˜¯å¦åˆ›å»ºå‘起审批记录
     * @return åˆ›å»ºå‡ºçš„当前节点实例
     */
    @Transactional(rollbackFor = Exception.class)
    public ApprovalInstanceNode createCurrentNodeAndTasks(ApprovalInstance instance, boolean createSubmitRecord) {
        if (instance == null || instance.getId() == null) {
            throw new RuntimeException("审批实例不能为空");
        }
        if (instance.getTemplateId() == null) {
            throw new RuntimeException("审批模板不能为空");
        }
        Integer currentLevel = instance.getCurrentLevel() == null ? 1 : instance.getCurrentLevel();
        ApprovalInstanceNode existsNode = instanceNodeService.getOne(
                new LambdaQueryWrapper<ApprovalInstanceNode>()
                        .eq(ApprovalInstanceNode::getInstanceId, instance.getId())
                        .eq(ApprovalInstanceNode::getLevelNo, currentLevel)
                        .eq(ApprovalInstanceNode::getDeleted, 0)
                        .last("LIMIT 1")
        );
        if (existsNode != null) {
            return existsNode;
        }
        ApprovalTemplateNode templateNode = approvalTemplateNodeService.getOne(
                new LambdaQueryWrapper<ApprovalTemplateNode>()
                        .eq(ApprovalTemplateNode::getTemplateId, instance.getTemplateId())
                        .eq(ApprovalTemplateNode::getLevelNo, currentLevel)
                        .orderByAsc(ApprovalTemplateNode::getId)
                        .last("LIMIT 1")
        );
        if (templateNode == null) {
            throw new RuntimeException("未找到当前层级对应的审批模板节点");
        }
        List<ApprovalTemplateNodeApprover> approvers = approvalTemplateNodeApproverService.list(
                new LambdaQueryWrapper<ApprovalTemplateNodeApprover>()
                        .eq(ApprovalTemplateNodeApprover::getTemplateId, instance.getTemplateId())
                        .eq(ApprovalTemplateNodeApprover::getNodeId, templateNode.getId())
                        .eq(ApprovalTemplateNodeApprover::getDeleted, 0L)
                        .orderByAsc(ApprovalTemplateNodeApprover::getSortNo)
        );
        if (approvers == null || approvers.isEmpty()) {
            throw new RuntimeException("当前审批节点未配置审批人");
        }
        ApprovalInstanceNode instanceNode = new ApprovalInstanceNode();
        instanceNode.setInstanceId(instance.getId());
        instanceNode.setLevelNo(templateNode.getLevelNo());
        instanceNode.setApproveType(templateNode.getApproveType());
        instanceNode.setStatus("PENDING");
        instanceNode.setStartTime(LocalDateTime.now());
        instanceNode.setDeleted((byte) 0);
        instanceNodeService.save(instanceNode);
        List<ApprovalTask> taskList = new ArrayList<>(approvers.size());
        for (ApprovalTemplateNodeApprover approver : approvers) {
            ApprovalTask task = new ApprovalTask();
            task.setInstanceId(instance.getId());
            task.setNodeId(instanceNode.getId());
            task.setLevelNo(instanceNode.getLevelNo());
            task.setApproverId(approver.getApproverId());
            task.setApproverName(approver.getApproverName());
            task.setTaskStatus("PENDING");
            task.setIsRead((byte) 0);
            task.setDeleted((byte) 0);
            taskList.add(task);
        }
        approvalTaskService.saveBatch(taskList);
        if (createSubmitRecord) {
            ApprovalRecord record = new ApprovalRecord();
            record.setInstanceId(instance.getId());
            record.setNodeId(instanceNode.getId());
            record.setOperatorId(instance.getApplicantId());
            record.setOperatorName(instance.getApplicantName());
            record.setAction("SUBMIT");
            record.setComment("发起审批");
            record.setDeleted((byte) 0);
            approvalRecordService.save(record);
        }
        return instanceNode;
    }
    /**
     * æŸ¥è¯¢å½“前待处理节点。
     */
    public ApprovalInstanceNode getCurrentNode(Long instanceId) {
        if (instanceId == null) {
            return null;
        }
        return instanceNodeService.getOne(
                new LambdaQueryWrapper<ApprovalInstanceNode>()
                        .eq(ApprovalInstanceNode::getInstanceId, instanceId)
                        .eq(ApprovalInstanceNode::getStatus, "PENDING")
                        .eq(ApprovalInstanceNode::getDeleted, 0)
                        .orderByAsc(ApprovalInstanceNode::getLevelNo)
                        .last("LIMIT 1")
        );
    }
    /**
     * æŸ¥è¯¢å½“前审批层级。
     */
    public Integer getCurrentLevel(Long instanceId) {
        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
        return currentNode != null ? currentNode.getLevelNo() : null;
    }
    /**
     * æŸ¥è¯¢å½“前节点下的待审批任务。
     */
    public List<ApprovalTask> getCurrentPendingTasks(Long instanceId) {
        if (instanceId == null) {
            return List.of();
        }
        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
        if (currentNode == null) {
            return List.of();
        }
        return approvalTaskService.list(
                new LambdaQueryWrapper<ApprovalTask>()
                        .eq(ApprovalTask::getInstanceId, instanceId)
                        .eq(ApprovalTask::getNodeId, currentNode.getId())
                        .eq(ApprovalTask::getTaskStatus, "PENDING")
                        .eq(ApprovalTask::getDeleted, 0)
                        .orderByAsc(ApprovalTask::getLevelNo)
        );
    }
    /**
     * æŸ¥è¯¢å½“前用户在当前节点上的待审批任务。
     */
    public ApprovalTask getCurrentUserTask(Long instanceId, Long userId) {
        if (instanceId == null || userId == null) {
            return null;
        }
        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
        if (currentNode == null) {
            return null;
        }
        return approvalTaskService.getOne(
                new LambdaQueryWrapper<ApprovalTask>()
                        .eq(ApprovalTask::getInstanceId, instanceId)
                        .eq(ApprovalTask::getNodeId, currentNode.getId())
                        .eq(ApprovalTask::getApproverId, userId)
                        .eq(ApprovalTask::getTaskStatus, "PENDING")
                        .eq(ApprovalTask::getDeleted, 0)
                        .last("LIMIT 1")
        );
    }
    /**
     * åˆ¤æ–­å½“前用户是否是当前审批人。
     */
    public boolean isCurrentApprover(Long instanceId, Long userId) {
        return getCurrentUserTask(instanceId, userId) != null;
    }
    /**
     * æŸ¥è¯¢å½“前节点的审批人 ID åˆ—表。
     */
    public List<Long> getCurrentNodeApproverIds(Long instanceId) {
        return getCurrentPendingTasks(instanceId).stream()
                .map(ApprovalTask::getApproverId)
                .distinct()
                .collect(Collectors.toList());
    }
    /**
     * æŸ¥è¯¢å½“前节点剩余待审批人数。
     */
    public int getRemainingApproverCount(Long instanceId) {
        return getCurrentPendingTasks(instanceId).size();
    }
    /**
     * æŸ¥è¯¢å½“前节点已同意人数。
     */
    public int getApprovedCount(Long instanceId) {
        if (instanceId == null) {
            return 0;
        }
        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
        if (currentNode == null) {
            return 0;
        }
        return Math.toIntExact(approvalTaskService.count(
                new LambdaQueryWrapper<ApprovalTask>()
                        .eq(ApprovalTask::getInstanceId, instanceId)
                        .eq(ApprovalTask::getNodeId, currentNode.getId())
                        .eq(ApprovalTask::getTaskStatus, "APPROVED")
                        .eq(ApprovalTask::getDeleted, 0)
        ));
    }
    /**
     * æŸ¥è¯¢å½“前节点已拒绝人数。
     */
    public int getRejectedCount(Long instanceId) {
        if (instanceId == null) {
            return 0;
        }
        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
        if (currentNode == null) {
            return 0;
        }
        return Math.toIntExact(approvalTaskService.count(
                new LambdaQueryWrapper<ApprovalTask>()
                        .eq(ApprovalTask::getInstanceId, instanceId)
                        .eq(ApprovalTask::getNodeId, currentNode.getId())
                        .eq(ApprovalTask::getTaskStatus, "REJECTED")
                        .eq(ApprovalTask::getDeleted, 0)
        ));
    }
    /**
     * åˆ¤æ–­å½“前节点是否可以流转到下一层。
     */
    public boolean canProceedToNextLevel(Long instanceId, String approveType) {
        if (instanceId == null || approveType == null) {
            return false;
        }
        if (getRejectedCount(instanceId) > 0) {
            return false;
        }
        int totalApproverCount = getCurrentPendingTasks(instanceId).size() + getApprovedCount(instanceId);
        int approvedCount = getApprovedCount(instanceId);
        if ("AND".equalsIgnoreCase(approveType)) {
            return approvedCount > 0 && approvedCount == totalApproverCount;
        }
        if ("OR".equalsIgnoreCase(approveType)) {
            return approvedCount > 0;
        }
        return false;
    }
    /**
     * æŸ¥è¯¢å½“前用户在当前节点上的任务状态。
     */
    public String getUserTaskStatus(Long instanceId, Long userId) {
        if (instanceId == null || userId == null) {
            return null;
        }
        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
        if (currentNode == null) {
            return null;
        }
        ApprovalTask task = approvalTaskService.getOne(
                new LambdaQueryWrapper<ApprovalTask>()
                        .eq(ApprovalTask::getInstanceId, instanceId)
                        .eq(ApprovalTask::getNodeId, currentNode.getId())
                        .eq(ApprovalTask::getApproverId, userId)
                        .eq(ApprovalTask::getDeleted, 0)
                        .last("LIMIT 1")
        );
        return task != null ? task.getTaskStatus() : null;
    }
    /**
     * æŸ¥è¯¢æŒ‡å®šç”¨æˆ·çš„全部待审批任务。
     */
    public List<ApprovalTask> getUserAllPendingTasks(Long userId) {
        if (userId == null) {
            return List.of();
        }
        return approvalTaskService.list(
                new LambdaQueryWrapper<ApprovalTask>()
                        .eq(ApprovalTask::getApproverId, userId)
                        .eq(ApprovalTask::getTaskStatus, "PENDING")
                        .eq(ApprovalTask::getDeleted, 0)
                        .orderByDesc(ApprovalTask::getCreateTime)
        );
    }
    /**
     * æŸ¥è¯¢å®¡æ‰¹å®žä¾‹çš„进度摘要。
     */
    public String getApprovalProgress(Long instanceId) {
        if (instanceId == null) {
            return "无效的审批实例";
        }
        ApprovalInstanceNode currentNode = getCurrentNode(instanceId);
        if (currentNode == null) {
            return "审批已完成或尚未开始";
        }
        int approvedCount = getApprovedCount(instanceId);
        int rejectedCount = getRejectedCount(instanceId);
        int pendingCount = getRemainingApproverCount(instanceId);
        int totalCount = approvedCount + rejectedCount + pendingCount;
        return String.format(
                "第%d级审批:总人数=%d,已同意=%d,已拒绝=%d,待审批=%d",
                currentNode.getLevelNo(),
                totalCount,
                approvedCount,
                rejectedCount,
                pendingCount
        );
    }
}
src/main/java/com/ruoyi/basic/controller/CustomerFollowUpController.java
@@ -11,7 +11,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
@@ -55,8 +55,8 @@
     */
    @Operation(summary = "获取客户跟进详细信息")
    @GetMapping(value = "/{id}")
    public R getInfo(@PathVariable("id") Integer id) {
        return R.ok(customerFollowUpService.getFollowUpWithFiles(id));
    public AjaxResult getInfo(@PathVariable("id") Integer id) {
        return AjaxResult.success(customerFollowUpService.getFollowUpWithFiles(id));
    }
    /**
@@ -65,8 +65,8 @@
    @PostMapping("/add")
    @Operation(summary = "新增客户跟进")
    @Log(title = "客户跟进-新增", businessType = BusinessType.INSERT)
    public R<?> add(@RequestBody CustomerFollowUp customerFollowUp) {
        return R.ok();
    public AjaxResult add(@RequestBody CustomerFollowUp customerFollowUp) {
        return toAjax(customerFollowUpService.insertCustomerFollowUp(customerFollowUp));
    }
    /**
@@ -75,8 +75,8 @@
    @PutMapping("/edit")
    @Operation(summary = "修改客户跟进")
    @Log(title = "客户跟进-修改", businessType = BusinessType.UPDATE)
    public R<?> edit(@RequestBody CustomerFollowUp customerFollowUp) {
        return R.ok();
    public AjaxResult edit(@RequestBody CustomerFollowUp customerFollowUp) {
        return toAjax(customerFollowUpService.updateCustomerFollowUp(customerFollowUp));
    }
    /**
@@ -85,8 +85,8 @@
    @Operation(summary = "上传跟进附件")
    @PostMapping("/upload/{followUpId}")
    @Log(title = "客户跟进-上传附件", businessType = BusinessType.INSERT)
    public R uploadFiles(@RequestParam("files") List<MultipartFile> files, @PathVariable Integer followUpId) {
        return R.ok(customerFollowUpService.addFollowUpFiles(files, followUpId));
    public AjaxResult uploadFiles(@RequestParam("files") List<MultipartFile> files, @PathVariable Integer followUpId) {
        return AjaxResult.success(customerFollowUpService.addFollowUpFiles(files, followUpId));
    }
    /**
@@ -95,9 +95,9 @@
    @Operation(summary = "上传附件(复用)")
    @PostMapping("/upload")
    @Log(title = "上传附件(复用)", businessType = BusinessType.INSERT)
    public R uploadFiles(@RequestParam("files") List<MultipartFile> files, @RequestParam(required = false) String name) {
    public AjaxResult uploadFiles(@RequestParam("files") List<MultipartFile> files, @RequestParam(required = false) String name) {
        List<CustomerFollowUpFileDto> uploadedFiles = customerFollowUpService.addFollowUpFiles(files, null);
        return R.ok(uploadedFiles);
        return AjaxResult.success(uploadedFiles);
    }
    /**
@@ -105,8 +105,8 @@
     */
    @Operation(summary = "批量查询附件列表")
    @PostMapping("/file/list")
    public R getFileList(@RequestBody List<Long> ids) {
        return R.ok(customerFollowUpService.getFollowUpFilesByIds(ids));
    public AjaxResult getFileList(@RequestBody List<Long> ids) {
        return AjaxResult.success(customerFollowUpService.getFollowUpFilesByIds(ids));
    }
    /**
@@ -115,9 +115,9 @@
    @Operation(summary = "删除跟进附件")
    @DeleteMapping("/file/{fileId}")
    @Log(title = "客户跟进-删除附件", businessType = BusinessType.DELETE)
    public R deleteFile(@PathVariable Integer fileId) {
    public AjaxResult deleteFile(@PathVariable Integer fileId) {
        customerFollowUpService.deleteFollowUpFile(fileId);
        return R.ok();
        return AjaxResult.success();
    }
    /**
@@ -126,8 +126,8 @@
    @Operation(summary = "删除客户跟进")
    @DeleteMapping("/{id}")
    @Log(title = "客户跟进-删除", businessType = BusinessType.DELETE)
    public R remove(@PathVariable Integer id) {
        return R.ok(customerFollowUpService.deleteCustomerFollowUpById(id));
    public AjaxResult remove(@PathVariable Integer id) {
        return toAjax(customerFollowUpService.deleteCustomerFollowUpById(id));
    }
    /**
@@ -136,8 +136,8 @@
    @Operation(summary = "新增/更新回访提醒")
    @PostMapping("/return-visit")
    @Log(title = "回访提醒-新增/更新", businessType = BusinessType.UPDATE)
    public R saveReturnVisit(@RequestBody CustomerReturnVisit customerReturnVisit) {
        return  R.ok(customerReturnVisitService.saveOrUpdateReturnVisit(customerReturnVisit));
    public AjaxResult saveReturnVisit(@RequestBody CustomerReturnVisit customerReturnVisit) {
        return toAjax(customerReturnVisitService.saveOrUpdateReturnVisit(customerReturnVisit));
    }
    /**
@@ -145,8 +145,8 @@
     */
    @Operation(summary = "获取回访提醒详情")
    @GetMapping("/return-visit/{customerId}")
    public R getReturnVisit(@PathVariable Integer customerId) {
        return R.ok(customerReturnVisitService.getByCustomerId(customerId));
    public AjaxResult getReturnVisit(@PathVariable Integer customerId) {
        return AjaxResult.success(customerReturnVisitService.getByCustomerId(customerId));
    }
    /**
@@ -155,9 +155,9 @@
    @Operation(summary = "标记回访提醒已读")
    @PutMapping("/return-visit/read/{id}")
    @Log(title = "回访提醒-标记已读", businessType = BusinessType.UPDATE)
    public R markAsRead(@PathVariable Long id) {
    public AjaxResult markAsRead(@PathVariable Long id) {
        customerReturnVisitService.markAsRead(id);
        return R.ok();
        return AjaxResult.success();
    }
}
src/main/java/com/ruoyi/basic/controller/ProductController.java
@@ -15,7 +15,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.sales.service.ISalesLedgerProductService;
import io.swagger.v3.oas.annotations.Operation;
@@ -55,9 +55,8 @@
     */
    @Log(title = "产品", businessType = BusinessType.INSERT)
    @PostMapping("/addOrEditProduct")
    public R<?> addOrEditProduct(@RequestBody ProductDto productDto) {
        productService.addOrEditProduct(productDto);
        return R.ok();
    public AjaxResult addOrEditProduct(@RequestBody ProductDto productDto) {
        return toAjax(productService.addOrEditProduct(productDto));
    }
    /**
@@ -65,9 +64,8 @@
     */
    @Log(title = "产品规格型号", businessType = BusinessType.INSERT)
    @PostMapping("/addOrEditProductModel")
    public R<?> addOrEditProductModel(@RequestBody ProductModelDto productModelDto) {
        productModelService.addOrEditProductModel(productModelDto);
        return R.ok();
    public AjaxResult addOrEditProductModel(@RequestBody ProductModelDto productModelDto) {
        return toAjax(productModelService.addOrEditProductModel(productModelDto));
    }
    /**
@@ -75,19 +73,18 @@
     */
    @Log(title = "产品", businessType = BusinessType.DELETE)
    @DeleteMapping("/delProduct")
    public R<?> remove(@RequestBody Long[] ids) {
    public AjaxResult remove(@RequestBody Long[] ids) {
        if (ids == null || ids.length == 0) {
            return R.fail("请传入要删除的ID");
            return AjaxResult.error("请传入要删除的ID");
        }
        // æ£€æŸ¥æ˜¯å¦æœ‰é”€å”®å•†å“è®°å½•关联该产品
        LambdaQueryWrapper<SalesLedgerProduct> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(SalesLedgerProduct::getProductId, ids);
        List<SalesLedgerProduct> salesLedgerProductList = salesLedgerProductService.list(queryWrapper);
        if (salesLedgerProductList.size() > 0) {
            return R.fail("该产品存在销售/采购记录,不能删除");
            return AjaxResult.error("该产品存在销售/采购记录,不能删除");
        }
        productService.delProductByIds(ids);
        return R.ok();
        return toAjax(productService.delProductByIds(ids));
    }
    /**
@@ -95,19 +92,18 @@
     */
    @Log(title = "产品规格型号", businessType = BusinessType.DELETE)
    @DeleteMapping("/delProductModel")
    public R<?> delProductModel(@RequestBody Long[] ids) {
    public AjaxResult delProductModel(@RequestBody Long[] ids) {
        if (ids == null || ids.length == 0) {
            return R.fail("请传入要删除的ID");
            return AjaxResult.error("请传入要删除的ID");
        }
        // æ£€æŸ¥æ˜¯å¦æœ‰é”€å”®å•†å“è®°å½•关联该产品规格型号
        LambdaQueryWrapper<SalesLedgerProduct> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(SalesLedgerProduct::getProductModelId, ids);
        List<SalesLedgerProduct> salesLedgerProductList = salesLedgerProductService.list(queryWrapper);
        if (salesLedgerProductList.size() > 0) {
            return R.fail("该产品规格型号存在销售/采购记录,不能删除");
            return AjaxResult.error("该产品规格型号存在销售/采购记录,不能删除");
        }
        productModelService.delProductModel(ids);
        return R.ok();
        return toAjax(productModelService.delProductModel(ids));
    }
    /**
@@ -129,7 +125,7 @@
     */
    @PostMapping("/import")
    @Log(title = "导入产品", businessType = BusinessType.IMPORT)
    public R<?> importProductModel(@RequestParam("file") MultipartFile file, Integer productId) {
    public AjaxResult importProductModel(@RequestParam("file") MultipartFile file, Integer productId) {
        return productModelService.importProductModel(file, productId);
    }
src/main/java/com/ruoyi/basic/controller/SupplierManageController.java
@@ -7,7 +7,7 @@
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.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.util.CollectionUtils;
@@ -29,9 +29,9 @@
     * @return
     */
    @PostMapping("/add")
    public R add(@RequestBody SupplierManage supplierManage) {
    public AjaxResult add(@RequestBody SupplierManage supplierManage) {
        supplierService.saveSupplier(supplierManage);
        return R.ok();
        return AjaxResult.success();
    }
    /**
@@ -40,12 +40,12 @@
     * @return
     */
    @DeleteMapping("/del")
    public R delSupplier(@RequestBody List<Integer> ids) {
    public AjaxResult delSupplier(@RequestBody List<Integer> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return R.fail("请选择至少一条数据");
            return AjaxResult.error("请选择至少一条数据");
        }
        supplierService.delSupplier(ids);
        return R.ok();
        return AjaxResult.success();
    }
    /**
@@ -54,8 +54,8 @@
     * @return
     */
    @GetMapping("/{id}")
    public R supplierDetail(@PathVariable("id") Integer id) {
        return R.ok(supplierService.supplierDetail(id));
    public AjaxResult supplierDetail(@PathVariable("id") Integer id) {
        return AjaxResult.success(supplierService.supplierDetail(id));
    }
    /**
@@ -64,9 +64,9 @@
     * @return
     */
    @PostMapping("/update")
    public R update(@RequestBody SupplierManage supplierManage) {
    public AjaxResult update(@RequestBody SupplierManage supplierManage) {
        supplierService.supplierUpdate(supplierManage);
        return R.ok();
        return AjaxResult.success();
    }
    /**
@@ -76,8 +76,8 @@
     * @return
     */
    @GetMapping("/listPage")
    public R supplierListPage(Page page, SupplierManageDto supplierManageDto) {
        return R.ok(supplierService.supplierListPage(page, supplierManageDto));
    public AjaxResult supplierListPage(Page page, SupplierManageDto supplierManageDto) {
        return AjaxResult.success(supplierService.supplierListPage(page, supplierManageDto));
    }
    /**
@@ -102,8 +102,12 @@
     */
    @PostMapping("/import")
    @Log(title = "供应商导入", businessType = BusinessType.IMPORT)
    public R importData(MultipartFile file) throws Exception {
       return supplierService.importData(file);
    public AjaxResult importData(MultipartFile file) {
        Boolean b = supplierService.importData(file);
        if (b) {
            return AjaxResult.success("导入成功");
        }
        return AjaxResult.error("导入失败");
    }
@@ -112,7 +116,7 @@
     * @return
     */
    @GetMapping("/getOptions")
    public R getOptions() {
        return R.ok(supplierService.list());
    public AjaxResult getOptions() {
        return AjaxResult.success(supplierService.list());
    }
}
src/main/java/com/ruoyi/basic/controller/SupplierManageFileController.java
@@ -3,8 +3,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.pojo.SupplierManageFile;
import com.ruoyi.basic.service.SupplierManageFileService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
@@ -16,7 +15,7 @@
 */
@RestController
@RequestMapping("/basic/supplierManageFile")
public class SupplierManageFileController extends BaseController {
public class SupplierManageFileController {
    @Resource
@@ -29,8 +28,8 @@
     * @return
     */
    @PostMapping("/add")
    public R<?> add(@RequestBody SupplierManageFile supplierManageFile) {
        return R.ok(supplierManageFileService.save(supplierManageFile));
    public AjaxResult add(@RequestBody SupplierManageFile supplierManageFile) {
        return AjaxResult.success(supplierManageFileService.save(supplierManageFile));
    }
    /**
@@ -39,12 +38,12 @@
     * @return
     */
    @DeleteMapping("/del")
    public R<?> delSupplierManageFile(@RequestBody List<Integer> ids) {
    public AjaxResult delSupplierManageFile(@RequestBody List<Integer> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return R.fail("请选择至少一条数据");
            return AjaxResult.error("请选择至少一条数据");
        }
        //删除检验附件
        return R.ok(supplierManageFileService.removeBatchByIds(ids));
        return AjaxResult.success(supplierManageFileService.removeBatchByIds(ids));
    }
    /**
@@ -54,8 +53,8 @@
     * @return
     */
    @GetMapping("/listPage")
    public R<?> supplierManageFileListPage(Page page, SupplierManageFile supplierManageFile) {
        return R.ok(supplierManageFileService.supplierManageFileListPage(page, supplierManageFile));
    public AjaxResult supplierManageFileListPage(Page page, SupplierManageFile supplierManageFile) {
        return AjaxResult.success(supplierManageFileService.supplierManageFileListPage(page, supplierManageFile));
    }
src/main/java/com/ruoyi/basic/enums/RecordTypeEnum.java
@@ -57,11 +57,8 @@
    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"),
@@ -86,15 +83,11 @@
    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"),
@@ -199,13 +192,16 @@
    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"),
    FIN_REIMBURSEMENT("fin_reimbursement"),
    FIN_VOUCHER("fin_voucher"),
    ACCOUNT_FILE("account_file");
    ACCOUNT_FILE("account_file"),
    ENTERPRISE_NEWS("enterprise_news"),
    APPROVAL_INSTANCE("approval_instance"),
    ACCOUNT_INVOICE_APPLICATION("account_invoice_application"),
    ACCOUNT_PURCHASE_INVOICE("account_purchase_invoice");
    private final String type;
    RecordTypeEnum(String type) { this.type = type; }
src/main/java/com/ruoyi/basic/excel/SupplierManageExcelDto.java
@@ -15,9 +15,6 @@
    @Excel(name = "供应商名称")
    private String supplierName;
    @Excel(name = "供应商类型")
    private String supplierType;
    @Excel(name = "纳税人识别号")
    private String taxpayerIdentificationNum;
src/main/java/com/ruoyi/basic/mapper/CustomerMapper.java
@@ -6,6 +6,8 @@
import com.ruoyi.basic.dto.CustomerDto;
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.vo.CustomerVo;
import com.ruoyi.sales.vo.CustomerTransactionsDetailsVo;
import com.ruoyi.sales.vo.CustomerTransactionsVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -13,7 +15,7 @@
/**
 * å®¢æˆ·æ¡£æ¡ˆMapper接口
 *
 *
 * @author ruoyi
 * @date 2025-05-07
 */
@@ -22,7 +24,7 @@
{
    /**
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆ
     *
     *
     * @param id å®¢æˆ·æ¡£æ¡ˆä¸»é”®
     * @return å®¢æˆ·æ¡£æ¡ˆ
     */
@@ -30,7 +32,7 @@
    /**
     * æŸ¥è¯¢å®¢æˆ·æ¡£æ¡ˆåˆ—表
     *
     *
     * @param customer å®¢æˆ·æ¡£æ¡ˆ
     * @return å®¢æˆ·æ¡£æ¡ˆé›†åˆ
     */
@@ -38,7 +40,7 @@
    /**
     * æ–°å¢žå®¢æˆ·æ¡£æ¡ˆ
     *
     *
     * @param customer å®¢æˆ·æ¡£æ¡ˆ
     * @return ç»“æžœ
     */
@@ -46,7 +48,7 @@
    /**
     * ä¿®æ”¹å®¢æˆ·æ¡£æ¡ˆ
     *
     *
     * @param customer å®¢æˆ·æ¡£æ¡ˆ
     * @return ç»“æžœ
     */
@@ -54,7 +56,7 @@
    /**
     * åˆ é™¤å®¢æˆ·æ¡£æ¡ˆ
     *
     *
     * @param id å®¢æˆ·æ¡£æ¡ˆä¸»é”®
     * @return ç»“æžœ
     */
@@ -62,7 +64,7 @@
    /**
     * æ‰¹é‡åˆ é™¤å®¢æˆ·æ¡£æ¡ˆ
     *
     *
     * @param ids éœ€è¦åˆ é™¤çš„æ•°æ®ä¸»é”®é›†åˆ
     * @return ç»“æžœ
     */
@@ -71,4 +73,8 @@
    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);
}
    IPage<CustomerTransactionsVo> customewTransactions(Page page, @Param("customerName") String customerName);
    IPage<CustomerTransactionsDetailsVo> customewTransactionsDetails(Page page, @Param("customerId") Long customerId);
}
src/main/java/com/ruoyi/basic/mapper/SupplierManageMapper.java
@@ -6,6 +6,8 @@
import com.ruoyi.basic.dto.SupplierManageDto;
import com.ruoyi.basic.excel.SupplierManageExcelDto;
import com.ruoyi.basic.pojo.SupplierManage;
import com.ruoyi.purchase.vo.SupplierTransactionsDetailsVo;
import com.ruoyi.purchase.vo.SupplierTransactionsVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -17,4 +19,8 @@
    IPage<SupplierManage> supplierListPage(Page page, @Param("supplierManageDto") SupplierManageDto supplierManageDto);
    List<SupplierManageExcelDto> supplierExportList(@Param("supplierManageDto") SupplierManageDto supplierManageDto);
    IPage<SupplierTransactionsVo> supplierTransactions(Page page, @Param("supplierName") String supplierName);
    IPage<SupplierTransactionsDetailsVo> supplierTransactionsDetails(Page page, @Param("supplierId") Long supplierId);
}
src/main/java/com/ruoyi/basic/pojo/ProductModel.java
@@ -46,12 +46,6 @@
    @Excel(name = "单位")
    private String unit;
    /**
     * ç”Ÿäº§ç‚’机
     */
    @Excel(name = "生产炒机")
    private String speculativeTradingName;
    @Schema(description = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
src/main/java/com/ruoyi/basic/pojo/SupplierManage.java
@@ -20,11 +20,6 @@
    @Excel(name = "供应商名称")
    private String supplierName;
    @Schema(description = "供应商类型")
    @TableField(value = "supplier_type")
    @Excel(name = "供应商类型")
    private String supplierType;
    @Schema(description = "纳税人识别号")
    @Excel(name = "纳税人识别号")
    private String taxpayerIdentificationNum;
@@ -85,6 +80,10 @@
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @Schema(description = "供应商类型")
    @TableField(value = "supplier_type")
    private String supplierType;
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/basic/service/ICustomerService.java
@@ -7,6 +7,8 @@
import com.ruoyi.basic.pojo.Customer;
import com.ruoyi.basic.vo.CustomerVo;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.sales.vo.CustomerTransactionsDetailsVo;
import com.ruoyi.sales.vo.CustomerTransactionsVo;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -93,4 +95,20 @@
    void together(CustomerDto customerDto);
    Boolean back(Long id);
    /**
     * æŸ¥è¯¢å®¢æˆ·å¾€æ¥åˆ—表
     * @param page
     * @param customerName
     * @return
     */
    IPage<CustomerTransactionsVo> customewTransactions(Page page, String customerName);
    /**
     * æŸ¥è¯¢å®¢æˆ·å¾€æ¥æ˜Žç»†åˆ—表
     * @param page
     * @param customerId
     * @return
     */
    IPage<CustomerTransactionsDetailsVo> customewTransactionsDetails(Page page, Long customerId);
}
src/main/java/com/ruoyi/basic/service/IProductModelService.java
@@ -6,7 +6,7 @@
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -34,5 +34,5 @@
     */
    IPage<ProductModel> modelListPage(Page page , ProductDto productDto);
    R<?> importProductModel(MultipartFile file, Integer productId);
    AjaxResult importProductModel(MultipartFile file, Integer productId);
}
src/main/java/com/ruoyi/basic/service/ISupplierService.java
@@ -5,10 +5,11 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.basic.dto.SupplierManageDto;
import com.ruoyi.basic.pojo.SupplierManage;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.purchase.vo.SupplierTransactionsDetailsVo;
import com.ruoyi.purchase.vo.SupplierTransactionsVo;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
public interface ISupplierService extends IService<SupplierManage> {
@@ -56,5 +57,21 @@
     */
    void supplierExport(HttpServletResponse response, SupplierManageDto supplierManageDto);
    R importData(MultipartFile file);
    Boolean importData(MultipartFile file);
    /**
     * ä¾›åº”商往来
     * @param page
     * @param supplierName
     * @return
     */
    IPage<SupplierTransactionsVo> supplierTransactions(Page page, String supplierName);
    /**
     * ä¾›åº”商往来详情
     * @param page
     * @param supplierId
     * @return
     */
    IPage<SupplierTransactionsDetailsVo> supplierTransactionsDetails(Page page, Long supplierId);
}
src/main/java/com/ruoyi/basic/service/impl/CustomerServiceImpl.java
@@ -21,8 +21,14 @@
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
import com.ruoyi.procurementrecord.pojo.ReturnManagement;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.mapper.SalesQuotationMapper;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesQuotation;
import com.ruoyi.sales.vo.CustomerTransactionsDetailsVo;
import com.ruoyi.sales.vo.CustomerTransactionsVo;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
@@ -48,7 +54,11 @@
@Slf4j
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements ICustomerService {
    @Autowired
    private  SalesLedgerMapper salesLedgerMapper;
    private SalesLedgerMapper salesLedgerMapper;
    @Autowired
    private SalesQuotationMapper salesQuotationMapper;
    @Autowired
    private ReturnManagementMapper returnManagementMapper;
    @Autowired
    private CustomerMapper customerMapper;
@@ -216,10 +226,24 @@
    @Transactional(rollbackFor = Exception.class)
    public int deleteCustomerByIds(Long[] ids) {
        List<Long> idList = Arrays.asList(ids);
        // æ£€æŸ¥æ˜¯å¦æœ‰é”€å”®å°è´¦å…³è”
        List<SalesLedger> salesLedgers = salesLedgerMapper.selectList(new QueryWrapper<SalesLedger>().lambda().in(SalesLedger::getCustomerId, idList));
        if (!salesLedgers.isEmpty()) {
            throw new RuntimeException("客户档案下有销售合同,请先删除销售合同");
            throw new RuntimeException("客户档案下有销售台账,请先删除销售台账");
        }
        // æ£€æŸ¥æ˜¯å¦æœ‰é”€å”®é€€è´§å…³è”
        List<ReturnManagement> returnManagements = returnManagementMapper.selectList(new QueryWrapper<ReturnManagement>().lambda().in(ReturnManagement::getCustomerId, idList));
        if (!returnManagements.isEmpty()) {
            throw new RuntimeException("客户档案下有销售退货,请先删除销售退货");
        }
        // æ£€æŸ¥æ˜¯å¦æœ‰é”€å”®æŠ¥ä»·å…³è”
        List<SalesQuotation> salesQuotations = salesQuotationMapper.selectList(new QueryWrapper<SalesQuotation>().lambda().in(SalesQuotation::getCustomerId, idList));
        if (!salesQuotations.isEmpty()) {
            throw new RuntimeException("客户档案下有销售报价,请先删除销售报价");
        }
        // æŸ¥è¯¢æ˜¯å¦æœ‰å·²åˆ†é…çš„公海客户
        List<Customer> assignedPools = customerMapper.selectList(
                new QueryWrapper<Customer>().lambda()
@@ -379,6 +403,16 @@
        return this.updateById(customer);
    }
    @Override
    public IPage<CustomerTransactionsVo> customewTransactions(Page page, String customerName) {
        return customerMapper.customewTransactions(page, customerName);
    }
    @Override
    public IPage<CustomerTransactionsDetailsVo> customewTransactionsDetails(Page page, Long customerId) {
        return customerMapper.customewTransactionsDetails(page, customerId);
    }
    /**
     * ä¸‹åˆ’线命名转驼峰命名
     */
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -17,7 +17,7 @@
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.dto.LossProductModelDto;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
@@ -124,14 +124,14 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> importProductModel(MultipartFile file, Integer productId) {
    public AjaxResult importProductModel(MultipartFile file, Integer productId) {
        if (productId == null) {
            return R.fail("请先选择产品再导入规格型号");
            return AjaxResult.error("请先选择产品再导入规格型号");
        }
        Product product = productMapper.selectById(productId);
        if (product == null) {
            return R.fail("选择的产品不存在");
            return AjaxResult.error("选择的产品不存在");
        }
        try {
@@ -139,7 +139,7 @@
            List<ProductModel> productModelList = productModelExcelUtil.importExcel(file.getInputStream());
            if (CollectionUtils.isEmpty(productModelList)) {
                return R.fail("导入数据不能为空");
                return AjaxResult.error("导入数据不能为空");
            }
            //  èŽ·å–å½“å‰äº§å“ä¸‹æ‰€æœ‰çš„è§„æ ¼åž‹å·å
@@ -154,13 +154,13 @@
                int rowNum = i + 2;
                if (StringUtils.isEmpty(item.getProductCode())) {
                    return R.fail("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [产品编码] ä¸èƒ½ä¸ºç©º");
                    return AjaxResult.error("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [产品编码] ä¸èƒ½ä¸ºç©º");
                }
                if (StringUtils.isEmpty(item.getModel())) {
                    return R.fail("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [规格型号] ä¸èƒ½ä¸ºç©º");
                    return AjaxResult.error("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [规格型号] ä¸èƒ½ä¸ºç©º");
                }
                if (StringUtils.isEmpty(item.getUnit())) {
                    return R.fail("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [单位] ä¸èƒ½ä¸ºç©º");
                    return AjaxResult.error("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [单位] ä¸èƒ½ä¸ºç©º");
                }
                //  åŽ»é‡,如果已包含该型号,则跳过
@@ -180,9 +180,9 @@
            }
            if (skipCount == 0) {
                return R.ok(null, String.format("成功导入 %d æ¡æ•°æ®", waitToSaveList.size()));
                return AjaxResult.success(String.format("成功导入 %d æ¡æ•°æ®", waitToSaveList.size()));
            } else {
                return R.ok(null, String.format("成功导入 %d æ¡ï¼Œè·³è¿‡å·²å­˜åœ¨æ•°æ® %d æ¡", waitToSaveList.size(), skipCount));
                return AjaxResult.success(String.format("成功导入 %d æ¡ï¼Œè·³è¿‡å·²å­˜åœ¨æ•°æ® %d æ¡", waitToSaveList.size(), skipCount));
            }
        } catch (Exception e) {
            log.error("导入产品规格异常", e);
src/main/java/com/ruoyi/basic/service/impl/SupplierServiceImpl.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.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.SupplierManageDto;
@@ -13,10 +12,10 @@
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.R;
import com.ruoyi.project.system.mapper.SysDictDataMapper;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.purchase.vo.SupplierTransactionsDetailsVo;
import com.ruoyi.purchase.vo.SupplierTransactionsVo;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
@@ -33,7 +32,6 @@
    private final SupplierManageMapper supplierMapper;
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    private final SysDictDataMapper sysDictDataMapper;
    /**
     * ä¾›åº”商新增
@@ -111,22 +109,13 @@
    }
    @Override
    public R importData(MultipartFile file) {
    public Boolean importData(MultipartFile file) {
        try {
            ExcelUtil<SupplierManageExcelDto> util = new ExcelUtil<SupplierManageExcelDto>(SupplierManageExcelDto.class);
            List<SupplierManageExcelDto> list = util.importExcel(file.getInputStream());
            if (CollectionUtils.isEmpty(list)) {
                return R.fail("模板错误或导入数据为空");
            }
            ArrayList<SupplierManage> supplierManages = new ArrayList<>();
            list.stream().forEach(dto -> {
                // ä¾›åº”商类型是否存在 ï¼ˆç”²ä¹™ä¸™ä¸ï¼‰
                String supplierType = dto.getSupplierType();
                if (!supplierType.equals("甲") && !supplierType.equals("乙") && !supplierType.equals("丙") && !supplierType.equals("丁")) {
                    throw new RuntimeException("供应商类型 " + supplierType + " ä¸å­˜åœ¨ï¼");
                }
                SupplierManage supplierManage = new SupplierManage();
                BeanUtils.copyProperties(dto,supplierManage);
                supplierManage.setMaintainTime(LocalDate.now());
@@ -137,10 +126,20 @@
            });
            this.saveOrUpdateBatch(supplierManages);
            return R.ok("导入成功");
            return true;
        }catch (Exception e){
            e.printStackTrace();
            return R.fail(e.getMessage());
        }
        return false;
    }
    @Override
    public IPage<SupplierTransactionsVo> supplierTransactions(Page page, String supplierName) {
        return supplierMapper.supplierTransactions(page,supplierName);
    }
    @Override
    public IPage<SupplierTransactionsDetailsVo> supplierTransactionsDetails(Page page, Long supplierId) {
        return supplierMapper.supplierTransactionsDetails(page,supplierId);
    }
}
src/main/java/com/ruoyi/collaborativeApproval/controller/DutyPlanController.java
@@ -7,8 +7,7 @@
import com.ruoyi.collaborativeApproval.pojo.RulesRegulationsManagement;
import com.ruoyi.collaborativeApproval.service.DutyPlanService;
import com.ruoyi.common.utils.excel.ExcelUtils;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
@@ -21,39 +20,39 @@
@RestController
@RequestMapping("/dutyPlan")
@AllArgsConstructor
public class DutyPlanController extends BaseController {
public class DutyPlanController {
    private DutyPlanService dutyPlanService;
    @GetMapping("/getList")
    @Operation(summary = "分页查询")
    public R<?> listPage(Page page, DutyPlanDTO dutyPlanDTO){
        return R.ok(dutyPlanService.listPage(page, dutyPlanDTO));
    public AjaxResult listPage(Page page, DutyPlanDTO dutyPlanDTO){
        return AjaxResult.success(dutyPlanService.listPage(page, dutyPlanDTO));
    }
    @GetMapping("/getNum")
    @Operation(summary = "获取等级数据")
    public R<?> getNum(){
        return R.ok(dutyPlanService.getNum());
    public AjaxResult getNum(){
        return AjaxResult.success(dutyPlanService.getNum());
    }
    @PostMapping("/add")
    @Operation(summary = "新增")
    public R<?> add(@RequestBody DutyPlan dutyPlan){
        return R.ok(dutyPlanService.save(dutyPlan));
    public AjaxResult add(@RequestBody DutyPlan dutyPlan){
        return AjaxResult.success(dutyPlanService.save(dutyPlan));
    }
    @PostMapping("/update")
    @Operation(summary = "修改")
    public R<?> update(@RequestBody DutyPlan dutyPlan){
        return R.ok(dutyPlanService.updateById(dutyPlan));
    public AjaxResult update(@RequestBody DutyPlan dutyPlan){
        return AjaxResult.success(dutyPlanService.updateById(dutyPlan));
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除")
    public R<?> delete(@RequestBody List<Long> ids){
    public AjaxResult delete(@RequestBody List<Long> ids){
        if (CollectionUtils.isEmpty(ids)) {
            throw new RuntimeException("请传入要删除的ID");
        }
        return R.ok(dutyPlanService.removeBatchByIds(ids));
        return AjaxResult.success(dutyPlanService.removeBatchByIds(ids));
    }
    @PostMapping("/export")
    @Operation(summary = "导出")
src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package com.ruoyi.collaborativeApproval.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.collaborativeApproval.dto.EnterpriseNewsDto;
import com.ruoyi.collaborativeApproval.service.EnterpriseNewsService;
import com.ruoyi.collaborativeApproval.vo.EnterpriseNewsVo;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.R;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * ä¼ä¸šæ–°é—»è¡¨ å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:50:59
 */
@RestController
@RequestMapping("/enterpriseNews")
@Tag(name = "企业新闻表")
@AllArgsConstructor
public class EnterpriseNewsController {
    private final EnterpriseNewsService enterpriseNewsService;
    @Operation(summary = "分页查询")
    @GetMapping("/listPage")
    @Log(title = "企业新闻分页查询", businessType = BusinessType.OTHER)
    public R listPage(Page<EnterpriseNewsVo>  page , EnterpriseNewsDto enterpriseNewsDto) {
        return R.ok(enterpriseNewsService.listPage(page, enterpriseNewsDto));
    }
    @PostMapping("/save")
    @Operation(summary = "保存")
    @Log(title = "保存企业新闻", businessType = BusinessType.INSERT)
    public R save(@RequestBody EnterpriseNewsDto enterpriseNewsDto) {
        return R.ok(enterpriseNewsService.add(enterpriseNewsDto));
    }
    @PutMapping("/update")
    @Operation(summary = "更新")
    @Log(title = "更新企业新闻", businessType = BusinessType.UPDATE)
    public R update(@RequestBody EnterpriseNewsDto enterpriseNewsDto) {
        return R.ok(enterpriseNewsService.updateEnterpriseNewsDto(enterpriseNewsDto));
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除")
    @Log(title = "删除企业新闻", businessType = BusinessType.DELETE)
    public R delete(@RequestBody List<Long> ids) {
        return R.ok(enterpriseNewsService.delete(ids));
    }
}
src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeDeptController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.collaborativeApproval.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * ä¼ä¸šæ–°é—»é˜…读范围部门表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:51:12
 */
@RestController
@RequestMapping("/enterpriseNewsScopeDept")
public class EnterpriseNewsScopeDeptController {
}
src/main/java/com/ruoyi/collaborativeApproval/controller/EnterpriseNewsScopeUserController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.collaborativeApproval.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * ä¼ä¸šæ–°é—»é˜…读范围用户表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:51:23
 */
@RestController
@RequestMapping("/enterpriseNewsScopeUser")
public class EnterpriseNewsScopeUserController {
}
src/main/java/com/ruoyi/collaborativeApproval/controller/NoticeController.java
@@ -9,7 +9,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.service.ISysNoticeService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
@@ -32,27 +32,27 @@
    @GetMapping("/page")
    @Log(title = "分页查询", businessType = BusinessType.OTHER)
    @Operation(summary = "分页查询")
    public R<?> listPage(Page page, NoticeDTO noticeDTO){
        return R.ok(noticeService.listPage(page, noticeDTO));
    public AjaxResult listPage(Page page, NoticeDTO noticeDTO){
        return AjaxResult.success(noticeService.listPage(page, noticeDTO));
    }
    @PostMapping("/add")
    @Log(title = "新增", businessType = BusinessType.INSERT)
    @Operation(summary = "新增")
    public R<?> add(@RequestBody NoticeDTO noticeDTO){
    public AjaxResult add(@RequestBody NoticeDTO noticeDTO){
        if (noticeDTO.getStatus()==1){
            //正式发布通知所有人的消息通知
            sysNoticeService.simpleNoticeAll("通知公告",
                    noticeDTO.getTitle(),
                    "/collaborativeApproval/noticeManagement?type="+noticeDTO.getType());
        }
        return R.ok(noticeService.save(noticeDTO));
        return AjaxResult.success(noticeService.save(noticeDTO));
    }
    @PutMapping("/update")
    @Log(title = "修改", businessType = BusinessType.UPDATE)
    @Operation(summary = "修改")
    public R<?> update(@RequestBody NoticeDTO noticeDTO){
    public AjaxResult update(@RequestBody NoticeDTO noticeDTO){
        if (ObjectUtils.isNotNull(noticeDTO.getStatus()) && noticeDTO.getStatus()==1){
            Notice notice = noticeService.getById(noticeDTO.getId());
            //正式发布通知所有人的消息通知
@@ -60,23 +60,23 @@
                    notice.getTitle(),
                    "/collaborativeApproval/noticeManagement?type="+notice.getType());
        }
        return R.ok(noticeService.updateById(noticeDTO));
        return AjaxResult.success(noticeService.updateById(noticeDTO));
    }
    @DeleteMapping("/{ids}")
    @Log(title = "删除", businessType = BusinessType.DELETE)
    @Operation(summary = "删除")
    public R<?> delete(@PathVariable("ids") List<Long> ids){
    public AjaxResult delete(@PathVariable("ids") List<Long> ids){
        if (CollectionUtils.isEmpty(ids)) {
            throw new RuntimeException("请传入要删除的ID");
        }
        return R.ok(noticeService.removeBatchByIds(ids));
        return AjaxResult.success(noticeService.removeBatchByIds(ids));
    }
    @GetMapping("/count")
    @Log(title = "获取公告数量", businessType = BusinessType.OTHER)
    @Operation(summary = "获取公告数量")
    public R<?> count(){
        return R.ok(noticeService.selectCount());
    public AjaxResult count(){
        return AjaxResult.success(noticeService.selectCount());
    }
}
src/main/java/com/ruoyi/collaborativeApproval/controller/NoticeTypeController.java
@@ -5,8 +5,7 @@
import com.ruoyi.basic.pojo.SupplierManage;
import com.ruoyi.collaborativeApproval.pojo.NoticeType;
import com.ruoyi.collaborativeApproval.service.NoticeTypeService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
@@ -25,7 +24,7 @@
@RestController
@RequestMapping("/noticeType")
@AllArgsConstructor
public class NoticeTypeController extends BaseController {
public class NoticeTypeController {
    private NoticeTypeService noticeTypeService;
@@ -35,8 +34,8 @@
     * @return
     */
    @PostMapping("/add")
    public R<?> add(@RequestBody NoticeType noticeType) {
        return R.ok(noticeTypeService.saveOrUpdate(noticeType));
    public AjaxResult add(@RequestBody NoticeType noticeType) {
        return AjaxResult.success(noticeTypeService.saveOrUpdate(noticeType));
    }
    /**
@@ -45,11 +44,11 @@
     * @return
     */
    @DeleteMapping("/del")
    public R<?> delNoticeType(@RequestBody List<Integer> ids) {
    public AjaxResult delNoticeType(@RequestBody List<Integer> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return R.fail("请选择至少一条数据");
            return AjaxResult.error("请选择至少一条数据");
        }
        return R.ok(noticeTypeService.removeBatchByIds(ids));
        return AjaxResult.success(noticeTypeService.removeBatchByIds(ids));
    }
    /**
@@ -57,8 +56,8 @@
     * @return
     */
    @GetMapping("/list")
    public R<?> noticeTypeList() {
        return R.ok(noticeTypeService.list());
    public AjaxResult noticeTypeList() {
        return AjaxResult.success(noticeTypeService.list());
    }
}
src/main/java/com/ruoyi/collaborativeApproval/controller/RulesRegulationsManagementController.java
@@ -8,8 +8,7 @@
import com.ruoyi.collaborativeApproval.pojo.SealApplicationManagement;
import com.ruoyi.collaborativeApproval.service.RulesRegulationsManagementService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
@@ -24,57 +23,57 @@
@RequestMapping("/rulesRegulationsManagement")
@AllArgsConstructor
@Tag(name = "制度管理")
public class RulesRegulationsManagementController extends BaseController {
public class RulesRegulationsManagementController {
    private RulesRegulationsManagementService rulesRegulationsManagementService;
    private ReadingStatusMapper readingStatusMapper;
    @GetMapping("/getList")
    @Operation(summary = "分页查询")
    public R<?> listPage(Page page, RulesRegulationsManagement rulesRegulationsManagement){
        return R.ok(rulesRegulationsManagementService.listPage(page, rulesRegulationsManagement));
    public AjaxResult listPage(Page page, RulesRegulationsManagement rulesRegulationsManagement){
        return AjaxResult.success(rulesRegulationsManagementService.listPage(page, rulesRegulationsManagement));
    }
    @PostMapping("/add")
    @Operation(summary = "新增")
    public R<?> add(@RequestBody RulesRegulationsManagement rulesRegulationsManagement){
    public AjaxResult add(@RequestBody RulesRegulationsManagement rulesRegulationsManagement){
        rulesRegulationsManagementService.save(rulesRegulationsManagement);
        return R.ok(rulesRegulationsManagement.getId());
        return AjaxResult.success(rulesRegulationsManagement.getId());
    }
    @PostMapping("/update")
    @Operation(summary = "修改")
    public R<?> update(@RequestBody RulesRegulationsManagement rulesRegulationsManagement){
        return R.ok(rulesRegulationsManagementService.updateById(rulesRegulationsManagement));
    public AjaxResult update(@RequestBody RulesRegulationsManagement rulesRegulationsManagement){
        return AjaxResult.success(rulesRegulationsManagementService.updateById(rulesRegulationsManagement));
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除")
    public R<?> delete(@PathVariable("ids") List<Long> ids){
    public AjaxResult delete(@PathVariable("ids") List<Long> ids){
        if (CollectionUtils.isEmpty(ids)) {
            throw new RuntimeException("请传入要删除的ID");
        }
        return R.ok(rulesRegulationsManagementService.removeBatchByIds(ids));
        return AjaxResult.success(rulesRegulationsManagementService.removeBatchByIds(ids));
    }
    //规则查看时新增阅读状态
    @PostMapping("/addReadingStatus")
    @Operation(summary = "新增阅读状态")
    public R<?> addReadingStatus(@RequestBody ReadingStatus readingStatus){
        return R.ok(readingStatusMapper.insert(readingStatus));
    public AjaxResult addReadingStatus(@RequestBody ReadingStatus readingStatus){
        return AjaxResult.success(readingStatusMapper.insert(readingStatus));
    }
    @PostMapping("/updateReadingStatus")
    @Operation(summary = "修改阅读状态")
    public R<?> updateReadingStatus(@RequestBody ReadingStatus readingStatus){
        return R.ok(readingStatusMapper.updateById(readingStatus));
    public AjaxResult updateReadingStatus(@RequestBody ReadingStatus readingStatus){
        return AjaxResult.success(readingStatusMapper.updateById(readingStatus));
    }
    @GetMapping("/getReadingStatusList")
    @Operation(summary = "分页查询阅读状态")
    public R<?> listPage(Page page, ReadingStatus readingStatus){
        return R.ok(readingStatusMapper.selectPage(page,new QueryWrapper<ReadingStatus>(readingStatus)));
    public AjaxResult listPage(Page page, ReadingStatus readingStatus){
        return AjaxResult.success(readingStatusMapper.selectPage(page,new QueryWrapper<ReadingStatus>(readingStatus)));
    }
    @GetMapping("/getReadingStatusByRuleId/{ruleId}")
    @Operation(summary = "根据制度id查询阅读状态")
    public R<?> getReadingStatusByRuleId(@PathVariable Long ruleId){
        return R.ok(readingStatusMapper.selectList(new QueryWrapper<ReadingStatus>().eq("rule_id", ruleId)));
    public AjaxResult getReadingStatusByRuleId(@PathVariable Long ruleId){
        return AjaxResult.success(readingStatusMapper.selectList(new QueryWrapper<ReadingStatus>().eq("rule_id", ruleId)));
    }
    @Operation(summary = "规章制度管理导出")
src/main/java/com/ruoyi/collaborativeApproval/controller/RulesRegulationsManagementFileController.java
@@ -3,8 +3,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.collaborativeApproval.pojo.RulesRegulationsManagementFile;
import com.ruoyi.collaborativeApproval.service.RulesRegulationsManagementFileService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.quality.pojo.QualityInspectFile;
import com.ruoyi.quality.service.IQualityInspectFileService;
import org.springframework.util.CollectionUtils;
@@ -23,7 +22,7 @@
 */
@RestController
@RequestMapping("/rulesRegulationsManagementFile")
public class RulesRegulationsManagementFileController extends BaseController {
public class RulesRegulationsManagementFileController {
    @Resource
    private RulesRegulationsManagementFileService rulesRegulationsManagementFileService;
@@ -35,8 +34,8 @@
     * @return
     */
    @PostMapping("/add")
    public R<?> add(@RequestBody RulesRegulationsManagementFile rulesRegulationsManagementFile) {
        return R.ok(rulesRegulationsManagementFileService.save(rulesRegulationsManagementFile));
    public AjaxResult add(@RequestBody RulesRegulationsManagementFile rulesRegulationsManagementFile) {
        return AjaxResult.success(rulesRegulationsManagementFileService.save(rulesRegulationsManagementFile));
    }
    /**
@@ -45,12 +44,12 @@
     * @return
     */
    @DeleteMapping("/del")
    public R<?> delQualityUnqualified(@RequestBody List<Integer> ids) {
    public AjaxResult delQualityUnqualified(@RequestBody List<Integer> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return R.fail("请选择至少一条数据");
            return AjaxResult.error("请选择至少一条数据");
        }
        //删除检验附件
        return R.ok(rulesRegulationsManagementFileService.removeBatchByIds(ids));
        return AjaxResult.success(rulesRegulationsManagementFileService.removeBatchByIds(ids));
    }
    /**
@@ -60,8 +59,8 @@
     * @return
     */
    @GetMapping("/listPage")
    public R<?> listPage(Page page, RulesRegulationsManagementFile rulesRegulationsManagementFile) {
        return R.ok(rulesRegulationsManagementFileService.listPage(page, rulesRegulationsManagementFile));
    public AjaxResult listPage(Page page, RulesRegulationsManagementFile rulesRegulationsManagementFile) {
        return AjaxResult.success(rulesRegulationsManagementFileService.listPage(page, rulesRegulationsManagementFile));
    }
}
src/main/java/com/ruoyi/collaborativeApproval/controller/SealApplicationManagementController.java
@@ -11,8 +11,7 @@
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.service.ISysNoticeService;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
@@ -29,20 +28,20 @@
@RestController
@RequestMapping("/sealApplicationManagement")
@Tag(name = "用印申请管理")
public class SealApplicationManagementController extends BaseController {
public class SealApplicationManagementController {
    private SealApplicationManagementService sealApplicationManagementService;
    private ISysNoticeService sysNoticeService;
    private FileUtil fileUtil;
    @GetMapping("/getList")
    @Operation(summary = "分页查询")
    public R<?> listPage(Page page, SealApplicationManagement sealApplicationManagement){
        return R.ok(sealApplicationManagementService.listPage(page,sealApplicationManagement));
    public AjaxResult listPage(Page page, SealApplicationManagement sealApplicationManagement){
        return AjaxResult.success(sealApplicationManagementService.listPage(page,sealApplicationManagement));
    }
    @PostMapping("/add")
    @Operation(summary = "新增")
    public R<?> add(@RequestBody SealApplicationManagementDTO sealApplicationManagement){
    public AjaxResult add(@RequestBody SealApplicationManagementDTO sealApplicationManagement){
        sealApplicationManagementService.save(sealApplicationManagement);
        // 5. ä¿å­˜é”€å”®å°è´¦é™„ä»¶
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE,
@@ -55,30 +54,30 @@
                +"申请标题:"+sealApplicationManagement.getTitle(),
                Arrays.asList(sealApplicationManagement.getApproveUserId()),
                "/collaborativeApproval/sealManagement?applicationNum="+sealApplicationManagement.getApplicationNum());
        return R.ok();
        return AjaxResult.success();
    }
    @PostMapping("/update")
    @Operation(summary = "修改")
    public R<?> update(@RequestBody SealApplicationManagementDTO sealApplicationManagement){
    public AjaxResult update(@RequestBody SealApplicationManagementDTO sealApplicationManagement){
        // 5. ä¿å­˜é”€å”®å°è´¦é™„ä»¶
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE,
                RecordTypeEnum.SEAL_APPLICATION_MANAGEMENT,
                sealApplicationManagement.getId(),
                sealApplicationManagement.getStorageBlobDTOs());
        return R.ok(sealApplicationManagementService.updateById(sealApplicationManagement));
        return AjaxResult.success(sealApplicationManagementService.updateById(sealApplicationManagement));
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除")
    public R<?> delete(@PathVariable("ids") List<Long> ids){
    public AjaxResult delete(@PathVariable("ids") List<Long> ids){
        if (CollectionUtils.isEmpty(ids)) {
            throw new RuntimeException("请传入要删除的ID");
        }
        fileUtil.deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum.FILE,
                RecordTypeEnum.SEAL_APPLICATION_MANAGEMENT,
                ids);
        return R.ok(sealApplicationManagementService.removeBatchByIds(ids));
        return AjaxResult.success(sealApplicationManagementService.removeBatchByIds(ids));
    }
    @Operation(summary = "用印申请管理导出")
src/main/java/com/ruoyi/collaborativeApproval/controller/StaffContactsPersonalController.java
@@ -4,8 +4,7 @@
import com.ruoyi.collaborativeApproval.dto.StaffContactsPersonalDTO;
import com.ruoyi.collaborativeApproval.pojo.StaffContactsPersonal;
import com.ruoyi.collaborativeApproval.service.StaffContactsPersonalService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
@@ -13,27 +12,27 @@
@RestController
@RequestMapping("/staffContactsPersonal")
@AllArgsConstructor
public class StaffContactsPersonalController extends BaseController {
public class StaffContactsPersonalController {
    private StaffContactsPersonalService staffContactsPersonalService;
    @GetMapping("/getList")
    @Operation(summary = "分页查询")
    public R<?> listPage(Page page, StaffContactsPersonalDTO staffContactsPersonalDTO) {
        return R.ok(staffContactsPersonalService.listPage(page, staffContactsPersonalDTO));
    public AjaxResult listPage(Page page, StaffContactsPersonalDTO staffContactsPersonalDTO) {
        return AjaxResult.success(staffContactsPersonalService.listPage(page, staffContactsPersonalDTO));
    }
    @PostMapping("/add")
    @Operation(summary = "新增")
    public R<?> add(@RequestBody StaffContactsPersonal staffContactsPersonal) {
        return R.ok(staffContactsPersonalService.save(staffContactsPersonal));
    public AjaxResult add(@RequestBody StaffContactsPersonal staffContactsPersonal) {
        return AjaxResult.success(staffContactsPersonalService.save(staffContactsPersonal));
    }
    @DeleteMapping("/delete/{id}")
    @Operation(summary = "删除")
    public R<?> delete(@PathVariable("id") Long id) {
    public AjaxResult delete(@PathVariable("id") Long id) {
//        if (CollectionUtils.isEmpty(id)) {
//            throw new RuntimeException("请传入要删除的ID");
//        }
        return R.ok(staffContactsPersonalService.removeById(id));
        return AjaxResult.success(staffContactsPersonalService.removeById(id));
    }
}
src/main/java/com/ruoyi/collaborativeApproval/dto/EnterpriseNewsDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.ruoyi.collaborativeApproval.dto;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
import lombok.Data;
import java.util.List;
@Data
public class EnterpriseNewsDto extends EnterpriseNews {
    private String createUserName;
    private List<Long> deptIds;
    private List<Long> userIds;
    private Long templateId;
    private String templateName;
    private String createTimeStart;
    private String createTimeEnd;
    private List<StorageBlobDTO> storageBlobDTOs;
}
src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.collaborativeApproval.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.collaborativeApproval.dto.EnterpriseNewsDto;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
import com.ruoyi.collaborativeApproval.vo.EnterpriseNewsVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <p>
 * ä¼ä¸šæ–°é—»è¡¨ Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:50:59
 */
@Mapper
public interface EnterpriseNewsMapper extends BaseMapper<EnterpriseNews> {
    IPage<EnterpriseNewsVo> listPage(Page<EnterpriseNewsVo> page,@Param("enterpriseNewsDto") EnterpriseNewsDto enterpriseNewsDto);
}
src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeDeptMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.collaborativeApproval.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * ä¼ä¸šæ–°é—»é˜…读范围部门表 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:51:12
 */
@Mapper
public interface EnterpriseNewsScopeDeptMapper extends BaseMapper<EnterpriseNewsScopeDept> {
}
src/main/java/com/ruoyi/collaborativeApproval/mapper/EnterpriseNewsScopeUserMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.collaborativeApproval.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * ä¼ä¸šæ–°é—»é˜…读范围用户表 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:51:23
 */
@Mapper
public interface EnterpriseNewsScopeUserMapper extends BaseMapper<EnterpriseNewsScopeUser> {
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNews.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
package com.ruoyi.collaborativeApproval.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * ä¼ä¸šæ–°é—»è¡¨
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:50:59
 */
@Getter
@Setter
@ToString
@TableName("enterprise_news")
@ApiModel(value = "EnterpriseNews对象", description = "企业新闻表")
public class EnterpriseNews implements Serializable {
    private static final long serialVersionUID = 1L;
    @Schema(description = "编号 ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @Schema(description = "标题 Title")
    private String title;
    @Schema(description = "摘要 Summary")
    private String summary;
    @Schema(description = "正文 Content")
    private String content;
    @Schema(description = "分类 Category")
    private String category;
    @Schema(description = "阅读范围 Read scope: all å…¨å‘˜, dept éƒ¨é—¨, custom è‡ªå®šä¹‰")
    private String readScope;
    @Schema(description = "是否必读 Required flag: 0 å¦, 1 æ˜¯")
    private Byte isRequired;
    @Schema(description = "状态 Status: DRAFT è‰ç¨¿, PENDING å¾…审批, PUBLISHED å·²å‘布, REJECTED é©³å›ž, OFFLINE å·²ä¸‹çº¿")
    private String status;
    @Schema(description = "应读人数 Required read count")
    private Integer requiredReadCount;
    @Schema(description = "已读人数 Read count")
    private Integer readCount;
    @Schema(description = "创建人 Create user")
    @TableField(fill = FieldFill.INSERT)
    private Long createUser;
    @Schema(description = "创建时间 Create time")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss", iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime createTime;
    @Schema(description = "更新人 Update user")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Long updateUser;
    @Schema(description = "更新时间 Update time")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss", iso = DateTimeFormat.ISO.DATE_TIME)
    private LocalDateTime updateTime;
    @Schema(description = "部门ID Dept ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeDept.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.ruoyi.collaborativeApproval.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * ä¼ä¸šæ–°é—»é˜…读范围部门表
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:51:12
 */
@Getter
@Setter
@ToString
@TableName("enterprise_news_scope_dept")
@ApiModel(value = "EnterpriseNewsScopeDept对象", description = "企业新闻阅读范围部门表")
public class EnterpriseNewsScopeDept implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ç¼–号
     */
    @ApiModelProperty("编号")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * ä¼ä¸šæ–°é—»ID
     */
    @ApiModelProperty("企业新闻ID")
    private Long newsId;
    /**
     * éƒ¨é—¨ID
     */
    @ApiModelProperty("部门ID")
    private Long deptId;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}
src/main/java/com/ruoyi/collaborativeApproval/pojo/EnterpriseNewsScopeUser.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.ruoyi.collaborativeApproval.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
 * <p>
 * ä¼ä¸šæ–°é—»é˜…读范围用户表
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:51:23
 */
@Getter
@Setter
@ToString
@TableName("enterprise_news_scope_user")
@ApiModel(value = "EnterpriseNewsScopeUser对象", description = "企业新闻阅读范围用户表")
public class EnterpriseNewsScopeUser implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ç¼–号
     */
    @ApiModelProperty("编号")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * ä¼ä¸šæ–°é—»ID
     */
    @ApiModelProperty("企业新闻ID")
    private Long newsId;
    /**
     * ç”¨æˆ·ID
     */
    @ApiModelProperty("用户ID")
    private Long userId;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}
src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeDeptService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.collaborativeApproval.service;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * ä¼ä¸šæ–°é—»é˜…读范围部门表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:51:12
 */
public interface EnterpriseNewsScopeDeptService extends IService<EnterpriseNewsScopeDept> {
}
src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsScopeUserService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.collaborativeApproval.service;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <p>
 * ä¼ä¸šæ–°é—»é˜…读范围用户表 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:51:23
 */
public interface EnterpriseNewsScopeUserService extends IService<EnterpriseNewsScopeUser> {
}
src/main/java/com/ruoyi/collaborativeApproval/service/EnterpriseNewsService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package com.ruoyi.collaborativeApproval.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.collaborativeApproval.dto.EnterpriseNewsDto;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.collaborativeApproval.vo.EnterpriseNewsVo;
import java.util.List;
/**
 * <p>
 * ä¼ä¸šæ–°é—»è¡¨ æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:50:59
 */
public interface EnterpriseNewsService extends IService<EnterpriseNews> {
    IPage<EnterpriseNewsVo> listPage(Page<EnterpriseNewsVo> page, EnterpriseNewsDto enterpriseNewsDto);
    Boolean add(EnterpriseNewsDto enterpriseNewsDto);
    Boolean updateEnterpriseNewsDto(EnterpriseNewsDto enterpriseNewsDto);
    Boolean delete(List<Long> ids);
}
src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeDeptServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.collaborativeApproval.service.impl;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept;
import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsScopeDeptMapper;
import com.ruoyi.collaborativeApproval.service.EnterpriseNewsScopeDeptService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * ä¼ä¸šæ–°é—»é˜…读范围部门表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:51:12
 */
@Service
public class EnterpriseNewsScopeDeptServiceImpl extends ServiceImpl<EnterpriseNewsScopeDeptMapper, EnterpriseNewsScopeDept> implements EnterpriseNewsScopeDeptService {
}
src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsScopeUserServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.collaborativeApproval.service.impl;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser;
import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsScopeUserMapper;
import com.ruoyi.collaborativeApproval.service.EnterpriseNewsScopeUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
 * <p>
 * ä¼ä¸šæ–°é—»é˜…读范围用户表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:51:23
 */
@Service
public class EnterpriseNewsScopeUserServiceImpl extends ServiceImpl<EnterpriseNewsScopeUserMapper, EnterpriseNewsScopeUser> implements EnterpriseNewsScopeUserService {
}
src/main/java/com/ruoyi/collaborativeApproval/service/impl/EnterpriseNewsServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,409 @@
package com.ruoyi.collaborativeApproval.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.mapper.ApprovalInstanceMapper;
import com.ruoyi.approve.mapper.ApprovalTemplateMapper;
import com.ruoyi.approve.pojo.ApprovalInstance;
import com.ruoyi.approve.pojo.ApprovalTask;
import com.ruoyi.approve.pojo.ApprovalTemplate;
import com.ruoyi.approve.service.ApprovalInstanceService;
import com.ruoyi.approve.utils.ApproveProcessConfigNodeUtils;
import com.ruoyi.basic.enums.ApplicationTypeEnum;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.utils.FileUtil;
import com.ruoyi.collaborativeApproval.dto.EnterpriseNewsDto;
import com.ruoyi.collaborativeApproval.mapper.EnterpriseNewsMapper;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeDept;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNewsScopeUser;
import com.ruoyi.collaborativeApproval.service.EnterpriseNewsScopeDeptService;
import com.ruoyi.collaborativeApproval.service.EnterpriseNewsScopeUserService;
import com.ruoyi.collaborativeApproval.service.EnterpriseNewsService;
import com.ruoyi.collaborativeApproval.vo.EnterpriseNewsVo;
import com.ruoyi.common.enums.TypeEnums;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysUserDeptMapper;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.project.system.service.ISysNoticeService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * ä¼ä¸šæ–°é—»è¡¨æœåŠ¡å®žçŽ°ç±»
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-05-20 11:50:59
 */
@Service
@RequiredArgsConstructor
public class EnterpriseNewsServiceImpl extends ServiceImpl<EnterpriseNewsMapper, EnterpriseNews> implements EnterpriseNewsService {
    private static final String READ_SCOPE_ALL = "all";
    private static final String READ_SCOPE_DEPT = "dept";
    private static final String READ_SCOPE_CUSTOM = "custom";
    private static final String STATUS_DRAFT = "DRAFT";
    private static final String STATUS_PENDING = "PENDING";
    private static final String STATUS_PUBLISHED = "PUBLISHED";
    private static final String STATUS_REJECTED = "REJECTED";
    private static final String STATUS_OFFLINE = "OFFLINE";
    private final EnterpriseNewsMapper enterpriseNewsMapper;
    private final EnterpriseNewsScopeDeptService enterpriseNewsScopeDeptService;
    private final EnterpriseNewsScopeUserService enterpriseNewsScopeUserService;
    private final SysUserMapper sysUserMapper;
    private final SysDeptMapper sysDeptMapper;
    private final SysUserDeptMapper sysUserDeptMapper;
    private final ApprovalInstanceMapper approvalInstanceMapper;
    private final ApprovalInstanceService approvalInstanceService;
    private final ApprovalTemplateMapper approvalTemplateMapper;
    private final ApproveProcessConfigNodeUtils approveProcessConfigNodeUtils;
    private final ISysNoticeService sysNoticeService;
    private final FileUtil fileUtil;
    @Override
    public IPage<EnterpriseNewsVo> listPage(Page<EnterpriseNewsVo> page, EnterpriseNewsDto enterpriseNewsDto) {
        IPage<EnterpriseNewsVo> enterpriseNewsVoIPage = enterpriseNewsMapper.listPage(page, enterpriseNewsDto);
        enterpriseNewsVoIPage.getRecords().forEach(enterpriseNewsVo -> {
            enterpriseNewsVo.setStorageBlobDTOs(fileUtil.getStorageBlobVOsByRecordTypeAndRecordId(RecordTypeEnum.ENTERPRISE_NEWS, enterpriseNewsVo.getId()));
        });
        return enterpriseNewsVoIPage;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean add(EnterpriseNewsDto enterpriseNewsDto) {
        validateForSave(enterpriseNewsDto);
        String readScope = normalizeReadScope(enterpriseNewsDto.getReadScope());
        List<Long> deptIds = distinctIds(enterpriseNewsDto.getDeptIds());
        List<Long> userIds = distinctIds(enterpriseNewsDto.getUserIds());
        EnterpriseNews enterpriseNews = new EnterpriseNews();
        BeanUtils.copyProperties(enterpriseNewsDto, enterpriseNews);
        enterpriseNews.setReadScope(readScope);
        enterpriseNews.setIsRequired(enterpriseNewsDto.getIsRequired() == null ? (byte) 0 : enterpriseNewsDto.getIsRequired());
        enterpriseNews.setStatus(normalizeStatus(enterpriseNewsDto.getStatus(), STATUS_DRAFT));
        enterpriseNews.setReadCount(0);
        enterpriseNews.setRequiredReadCount(calculateRequiredReadCount(readScope, deptIds, userIds));
        Long[] loginDeptIds = SecurityUtils.getDeptId();
        if (StringUtils.isNotEmpty(loginDeptIds)) {
            enterpriseNews.setDeptId(loginDeptIds[0]);
        }
        if (!save(enterpriseNews) || enterpriseNews.getId() == null) {
            throw new ServiceException("新增企业新闻失败");
        }
        saveReadScopeRelations(enterpriseNews.getId(), readScope, deptIds, userIds);
        if (STATUS_PENDING.equals(enterpriseNews.getStatus())) {
            startEnterpriseNewsApproval(enterpriseNews, enterpriseNewsDto);
        }
        //添加附件
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.ENTERPRISE_NEWS, enterpriseNews.getId(), enterpriseNewsDto.getStorageBlobDTOs());
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateEnterpriseNewsDto(EnterpriseNewsDto enterpriseNewsDto) {
        if (enterpriseNewsDto == null || enterpriseNewsDto.getId() == null) {
            throw new ServiceException("企业新闻ID不能为空");
        }
        EnterpriseNews oldEnterpriseNews = getById(enterpriseNewsDto.getId());
        if (oldEnterpriseNews == null) {
            throw new ServiceException("企业新闻不存在");
        }
        if (!STATUS_DRAFT.equals(oldEnterpriseNews.getStatus())
                && !STATUS_REJECTED.equals(oldEnterpriseNews.getStatus())) {
            throw new ServiceException("待审批或已发布的企业新闻不允许修改");
        }
        validateForSave(enterpriseNewsDto);
        String readScope = normalizeReadScope(enterpriseNewsDto.getReadScope());
        List<Long> deptIds = distinctIds(enterpriseNewsDto.getDeptIds());
        List<Long> userIds = distinctIds(enterpriseNewsDto.getUserIds());
        EnterpriseNews enterpriseNews = new EnterpriseNews();
        BeanUtils.copyProperties(enterpriseNewsDto, enterpriseNews);
        enterpriseNews.setReadScope(readScope);
        enterpriseNews.setIsRequired(enterpriseNewsDto.getIsRequired() == null ? oldEnterpriseNews.getIsRequired() : enterpriseNewsDto.getIsRequired());
        enterpriseNews.setStatus(normalizeStatus(enterpriseNewsDto.getStatus(), oldEnterpriseNews.getStatus()));
        enterpriseNews.setReadCount(oldEnterpriseNews.getReadCount());
        enterpriseNews.setRequiredReadCount(calculateRequiredReadCount(readScope, deptIds, userIds));
        enterpriseNews.setCreateUser(oldEnterpriseNews.getCreateUser());
        enterpriseNews.setCreateTime(oldEnterpriseNews.getCreateTime());
        enterpriseNews.setDeptId(oldEnterpriseNews.getDeptId());
        if (!updateById(enterpriseNews)) {
            throw new ServiceException("修改企业新闻失败");
        }
        clearReadScopeRelations(enterpriseNews.getId());
        saveReadScopeRelations(enterpriseNews.getId(), readScope, deptIds, userIds);
        fileUtil.saveStorageAttachment(ApplicationTypeEnum.FILE, RecordTypeEnum.ENTERPRISE_NEWS, enterpriseNews.getId(), enterpriseNewsDto.getStorageBlobDTOs());
        if (STATUS_PENDING.equals(enterpriseNews.getStatus())) {
            resetEnterpriseNewsApprovalFlow(oldEnterpriseNews);
            startEnterpriseNewsApproval(enterpriseNews, enterpriseNewsDto);
        }
        return true;
    }
    @Override
    public Boolean delete(List<Long> ids) {
        if (ids == null || ids.isEmpty()) {
            return false;
        }
        if (!removeByIds(ids)) {
            throw new ServiceException("删除企业新闻失败");
        }
        ids.forEach(this::clearReadScopeRelations);
        return true;
    }
    private void validateForSave(EnterpriseNewsDto enterpriseNewsDto) {
        if (enterpriseNewsDto == null) {
            throw new ServiceException("企业新闻数据不能为空");
        }
        if (StringUtils.isEmpty(enterpriseNewsDto.getTitle())) {
            throw new ServiceException("标题不能为空");
        }
        if (StringUtils.isEmpty(enterpriseNewsDto.getContent())) {
            throw new ServiceException("正文不能为空");
        }
        normalizeStatus(enterpriseNewsDto.getStatus(), STATUS_DRAFT);
        String readScope = normalizeReadScope(enterpriseNewsDto.getReadScope());
        List<Long> deptIds = distinctIds(enterpriseNewsDto.getDeptIds());
        List<Long> userIds = distinctIds(enterpriseNewsDto.getUserIds());
        if (READ_SCOPE_DEPT.equals(readScope) && StringUtils.isEmpty(deptIds)) {
            throw new ServiceException("请选择阅读范围部门");
        }
        if (READ_SCOPE_CUSTOM.equals(readScope) && StringUtils.isEmpty(userIds)) {
            throw new ServiceException("请选择自定义阅读人员");
        }
        validateDeptIds(deptIds);
        validateUserIds(userIds);
    }
    private String normalizeReadScope(String readScope) {
        String normalized = StringUtils.isEmpty(readScope) ? READ_SCOPE_ALL : readScope.trim();
        if (!READ_SCOPE_ALL.equals(normalized)
                && !READ_SCOPE_DEPT.equals(normalized)
                && !READ_SCOPE_CUSTOM.equals(normalized)) {
            throw new ServiceException("阅读范围不合法");
        }
        return normalized;
    }
    private String normalizeStatus(String status, String defaultStatus) {
        String normalized = StringUtils.isEmpty(status) ? defaultStatus : status.trim().toUpperCase();
        if (!STATUS_DRAFT.equals(normalized)
                && !STATUS_PENDING.equals(normalized)
                && !STATUS_PUBLISHED.equals(normalized)
                && !STATUS_REJECTED.equals(normalized)
                && !STATUS_OFFLINE.equals(normalized)) {
            throw new ServiceException("企业新闻状态不合法");
        }
        return normalized;
    }
    private void validateDeptIds(List<Long> deptIds) {
        if (StringUtils.isEmpty(deptIds)) {
            return;
        }
        for (Long deptId : deptIds) {
            SysDept sysDept = sysDeptMapper.selectDeptById(deptId);
            if (deptId == null || sysDept == null) {
                throw new ServiceException("阅读范围部门不存在");
            }
        }
    }
    private void validateUserIds(List<Long> userIds) {
        if (StringUtils.isEmpty(userIds)) {
            return;
        }
        List<SysUser> users = sysUserMapper.selectUsersByIds(userIds);
        if (users.size() != userIds.size()) {
            throw new ServiceException("自定义阅读人员包含无效用户");
        }
    }
    private Integer calculateRequiredReadCount(String readScope, List<Long> deptIds, List<Long> userIds) {
        if (READ_SCOPE_ALL.equals(readScope)) {
            Long count = sysUserMapper.selectCount(new LambdaQueryWrapper<SysUser>()
                    .eq(SysUser::getDelFlag, "0"));
            return count == null ? 0 : count.intValue();
        }
        if (READ_SCOPE_DEPT.equals(readScope)) {
            List<Long> allDeptIds = collectDeptIdsWithChildren(deptIds);
            if (StringUtils.isEmpty(allDeptIds)) {
                return 0;
            }
            Long count = sysUserDeptMapper.countDistinctUserIdsByDeptIds(allDeptIds);
            return count == null ? 0 : count.intValue();
        }
        return userIds.size();
    }
    private List<Long> collectDeptIdsWithChildren(List<Long> deptIds) {
        Set<Long> allDeptIds = new LinkedHashSet<>();
        for (Long deptId : deptIds) {
            if (deptId == null) {
                continue;
            }
            allDeptIds.add(deptId);
            List<SysDept> children = sysDeptMapper.selectChildrenDeptById(deptId);
            if (StringUtils.isNotEmpty(children)) {
                for (SysDept child : children) {
                    allDeptIds.add(child.getDeptId());
                }
            }
        }
        return new ArrayList<>(allDeptIds);
    }
    private void saveReadScopeRelations(Long newsId, String readScope, List<Long> deptIds, List<Long> userIds) {
        if (READ_SCOPE_DEPT.equals(readScope)) {
            List<EnterpriseNewsScopeDept> scopeDeptList = new ArrayList<>();
            for (Long deptId : deptIds) {
                EnterpriseNewsScopeDept scopeDept = new EnterpriseNewsScopeDept();
                scopeDept.setNewsId(newsId);
                scopeDept.setDeptId(deptId);
                scopeDeptList.add(scopeDept);
            }
            if (StringUtils.isNotEmpty(scopeDeptList)) {
                enterpriseNewsScopeDeptService.saveBatch(scopeDeptList);
            }
            return;
        }
        if (READ_SCOPE_CUSTOM.equals(readScope)) {
            List<EnterpriseNewsScopeUser> scopeUserList = new ArrayList<>();
            for (Long userId : userIds) {
                EnterpriseNewsScopeUser scopeUser = new EnterpriseNewsScopeUser();
                scopeUser.setNewsId(newsId);
                scopeUser.setUserId(userId);
                scopeUserList.add(scopeUser);
            }
            if (StringUtils.isNotEmpty(scopeUserList)) {
                enterpriseNewsScopeUserService.saveBatch(scopeUserList);
            }
        }
    }
    private void clearReadScopeRelations(Long newsId) {
        enterpriseNewsScopeDeptService.remove(new LambdaQueryWrapper<EnterpriseNewsScopeDept>()
                .eq(EnterpriseNewsScopeDept::getNewsId, newsId));
        enterpriseNewsScopeUserService.remove(new LambdaQueryWrapper<EnterpriseNewsScopeUser>()
                .eq(EnterpriseNewsScopeUser::getNewsId, newsId));
    }
    private void resetEnterpriseNewsApprovalFlow(EnterpriseNews oldEnterpriseNews) {
        if (oldEnterpriseNews == null || !STATUS_DRAFT.equals(oldEnterpriseNews.getStatus())) {
            return;
        }
        List<Long> approvalInstanceIds = approvalInstanceMapper.selectList(new LambdaQueryWrapper<ApprovalInstance>()
                        .eq(ApprovalInstance::getBusinessId, oldEnterpriseNews.getId())
                        .eq(ApprovalInstance::getBusinessType, TypeEnums.ENTERPRISE_NEWS_APPROVAL.getCode())
                        .eq(ApprovalInstance::getDeleted, (byte) 0))
                .stream()
                .map(ApprovalInstance::getId)
                .filter(id -> id != null && id > 0)
                .collect(Collectors.toList());
        if (StringUtils.isEmpty(approvalInstanceIds)) {
            return;
        }
        approvalInstanceService.delete(approvalInstanceIds);
    }
    private List<Long> distinctIds(List<Long> ids) {
        if (StringUtils.isEmpty(ids)) {
            return new ArrayList<>();
        }
        Set<Long> distinctSet = new LinkedHashSet<>();
        for (Long id : ids) {
            if (id != null) {
                distinctSet.add(id);
            }
        }
        return new ArrayList<>(distinctSet);
    }
    private void startEnterpriseNewsApproval(EnterpriseNews enterpriseNews, EnterpriseNewsDto enterpriseNewsDto) {
        if (enterpriseNewsDto.getTemplateId() == null) {
            throw new ServiceException("审批模板不能为空");
        }
        String templateName = enterpriseNewsDto.getTemplateName();
        if (StringUtils.isEmpty(templateName)) {
            ApprovalTemplate approvalTemplate = approvalTemplateMapper.selectById(enterpriseNewsDto.getTemplateId());
            if (approvalTemplate == null) {
                throw new ServiceException("审批模板不存在");
            }
            templateName = approvalTemplate.getTemplateName();
        }
        ApprovalInstance approvalInstance = new ApprovalInstance();
        approvalInstance.setInstanceNo(OrderUtils.countTodayByCreateTime(approvalInstanceMapper, "SP", "instance_no", enterpriseNews.getCreateTime() != null ? enterpriseNews.getCreateTime() : LocalDateTime.now()));
        approvalInstance.setTemplateId(enterpriseNewsDto.getTemplateId());
        approvalInstance.setTemplateName(templateName);
        approvalInstance.setBusinessId(enterpriseNews.getId());
        approvalInstance.setBusinessType(TypeEnums.ENTERPRISE_NEWS_APPROVAL.getCode());
        approvalInstance.setTitle(enterpriseNews.getTitle());
        approvalInstance.setStatus("PENDING");
        approvalInstance.setCurrentLevel(1);
        approvalInstance.setApplicantId(SecurityUtils.getUserId());
        approvalInstance.setApplicantName(SecurityUtils.getLoginUser().getNickName());
        approvalInstance.setApplyTime(LocalDateTime.now());
        approvalInstance.setDeleted((byte) 0);
        approvalInstance.setCreateUser(SecurityUtils.getUserId());
        approvalInstance.setUpdateUser(SecurityUtils.getUserId());
        approvalInstance.setDeptId(enterpriseNews.getDeptId());
        approvalInstanceMapper.insert(approvalInstance);
        approveProcessConfigNodeUtils.createCurrentNodeAndTasks(approvalInstance);
        sendApproveNotice(approvalInstance, approveProcessConfigNodeUtils.getCurrentPendingTasks(approvalInstance.getId()));
    }
    private void sendApproveNotice(ApprovalInstance instance, List<ApprovalTask> tasks) {
        if (instance == null || tasks == null || tasks.isEmpty()) {
            return;
        }
        List<Long> approverIds = tasks.stream()
                .map(ApprovalTask::getApproverId)
                .filter(id -> id != null && id > 0)
                .distinct()
                .collect(Collectors.toList());
        if (approverIds.isEmpty()) {
            return;
        }
        String title = StringUtils.isNotEmpty(instance.getTemplateName()) ? instance.getTemplateName() : "审批提醒";
        String message = "审批单号 " + instance.getInstanceNo() + " éœ€è¦æ‚¨å®¡æ‰¹";
        String jumpPath = "/officeProcessAutomation/ApproveManage/approve-list/?id=" + instance.getId();
        sysNoticeService.simpleNoticeByUser(title, message, approverIds, jumpPath);
    }
}
src/main/java/com/ruoyi/collaborativeApproval/vo/EnterpriseNewsVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package com.ruoyi.collaborativeApproval.vo;
import com.ruoyi.basic.dto.StorageBlobVO;
import com.ruoyi.collaborativeApproval.pojo.EnterpriseNews;
import lombok.Data;
import java.util.List;
@Data
public class EnterpriseNewsVo extends EnterpriseNews {
    private String createUserName;
    private List<StorageBlobVO> storageBlobDTOs;
}
src/main/java/com/ruoyi/common/enums/ApprovalStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package com.ruoyi.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å®¡æ‰¹çŠ¶æ€æžšä¸¾
 */
@Getter
@AllArgsConstructor
public enum ApprovalStatusEnum implements BaseEnum<Integer> {
    DRAFT(0, "草稿"),
    PENDING(1, "待审批"),
    IN_PROGRESS(2, "审批中"),
    APPROVED(3, "已通过"),
    REJECTED(4, "已驳回");
    private final Integer value;
    private final String label;
    @Override
    public Integer getCode() {
        return value;
    }
    @Override
    public String getValue() {
        return label;
    }
    public static ApprovalStatusEnum fromValue(Integer value) {
        if (value == null) {
            return null;
        }
        for (ApprovalStatusEnum status : values()) {
            if (status.getCode().equals(value)) {
                return status;
            }
        }
        return null;
    }
    public static String getLabelByValue(Integer value) {
        ApprovalStatusEnum statusEnum = fromValue(value);
        return statusEnum != null ? statusEnum.getValue() : "未知状态";
    }
}
src/main/java/com/ruoyi/common/enums/EnterpriseNewsStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,143 @@
package com.ruoyi.common.enums;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
/**
 * ä¼ä¸šæ–°é—»çŠ¶æ€æžšä¸¾ç±»
 *
 * @author ruoyi
 * @date 2026-05-20
 */
@Schema(description = "企业新闻状态枚举")
public enum EnterpriseNewsStatusEnum implements BaseEnum<String> {
    /**
     * è‰ç¨¿
     */
    @Schema(description = "草稿")
    DRAFT("DRAFT", "草稿"),
    /**
     * å¾…审批
     */
    @Schema(description = "待审批")
    PENDING("PENDING", "待审批"),
    /**
     * å·²å‘布
     */
    @Schema(description = "已发布")
    PUBLISHED("PUBLISHED", "已发布"),
    /**
     * é©³å›ž
     */
    @Schema(description = "驳回")
    REJECTED("REJECTED", "驳回"),
    /**
     * å·²ä¸‹çº¿
     */
    @Schema(description = "已下线")
    OFFLINE("OFFLINE", "已下线");
    /**
     * çŠ¶æ€ç 
     */
    private final String code;
    /**
     * çŠ¶æ€æè¿°
     * -- GETTER --
     *  èŽ·å–çŠ¶æ€æè¿°
     *
     * @return çŠ¶æ€æè¿°
     */
    @Getter
    private final String description;
    EnterpriseNewsStatusEnum(String code, String description) {
        this.code = code;
        this.description = description;
    }
    /**
     * èŽ·å–çŠ¶æ€ç 
     *
     * @return çŠ¶æ€ç 
     */
    @JsonValue
    public String getCode() {
        return code;
    }
    @Override
    public String getValue() {
        return "";
    }
    /**
     * æ ¹æ®çŠ¶æ€ç èŽ·å–æžšä¸¾
     *
     * @param code çŠ¶æ€ç 
     * @return æžšä¸¾å€¼
     */
    @JsonCreator
    public static EnterpriseNewsStatusEnum getByCode(String code) {
        for (EnterpriseNewsStatusEnum status : values()) {
            if (status.code.equals(code)) {
                return status;
            }
        }
        throw new IllegalArgumentException("Invalid enterprise news status code: " + code);
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºè‰ç¨¿çŠ¶æ€
     *
     * @return æ˜¯å¦ä¸ºè‰ç¨¿çŠ¶æ€
     */
    public boolean isDraft() {
        return DRAFT.equals(this);
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºå¾…审批状态
     *
     * @return æ˜¯å¦ä¸ºå¾…审批状态
     */
    public boolean isPending() {
        return PENDING.equals(this);
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºå·²å‘布状态
     *
     * @return æ˜¯å¦ä¸ºå·²å‘布状态
     */
    public boolean isPublished() {
        return PUBLISHED.equals(this);
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºé©³å›žçŠ¶æ€
     *
     * @return æ˜¯å¦ä¸ºé©³å›žçŠ¶æ€
     */
    public boolean isRejected() {
        return REJECTED.equals(this);
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºå·²ä¸‹çº¿çŠ¶æ€
     *
     * @return æ˜¯å¦ä¸ºå·²ä¸‹çº¿çŠ¶æ€
     */
    public boolean isOffline() {
        return OFFLINE.equals(this);
    }
}
src/main/java/com/ruoyi/common/enums/SalesQuotationStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package com.ruoyi.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * é”€å”®æŠ¥ä»·çŠ¶æ€æžšä¸¾
 */
@Getter
@AllArgsConstructor
public enum SalesQuotationStatusEnum implements BaseEnum<String> {
    DRAFT("草稿", "草稿"),
    PENDING("待审批", "待审批"),
    IN_PROGRESS("审核中", "审核中"),
    APPROVED("通过", "通过"),
    REJECTED("拒绝", "拒绝");
    private final String value;
    private final String label;
    @Override
    public String getCode() {
        return value;
    }
    @Override
    public String getValue() {
        return label;
    }
    public static SalesQuotationStatusEnum fromValue(String value) {
        if (value == null) {
            return null;
        }
        for (SalesQuotationStatusEnum status : values()) {
            if (status.getCode().equals(value)) {
                return status;
            }
        }
        return null;
    }
}
src/main/java/com/ruoyi/common/enums/ShippingStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
package com.ruoyi.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * å‘货审批状态枚举
 */
@Getter
@AllArgsConstructor
public enum ShippingStatusEnum implements BaseEnum<String> {
    PENDING("待确认", "待确认"),
    IN_PROGRESS("审核中", "审核中"),
    APPROVED("审核通过", "审核通过"),
    REJECTED("审核拒绝", "审核拒绝");
    private final String value;
    private final String label;
    @Override
    public String getCode() {
        return value;
    }
    @Override
    public String getValue() {
        return label;
    }
    public static ShippingStatusEnum fromValue(String value) {
        if (value == null) {
            return null;
        }
        for (ShippingStatusEnum status : values()) {
            if (status.getCode().equals(value)) {
                return status;
            }
        }
        return null;
    }
}
src/main/java/com/ruoyi/common/enums/TypeEnums.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,70 @@
package com.ruoyi.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum TypeEnums implements BaseEnum<Long> {
    PUBLIC_OUT(1L, "公出管理"),
    LEAVE(2L, "请假管理"),
    BUSINESS_TRIP(3L, "出差管理"),
    REIMBURSEMENT(4L, "报销管理"),
    PURCHASE_APPROVAL(5L, "采购审批"),
    QUOTATION_APPROVAL(6L, "报价审批"),
    SHIPPING_APPROVAL(7L, "发货审批"),
    DANGEROUS_OPERATION(8L, "危险作业审批"),
    OFFICE_SUPPLIES(9L, "办公用品审批"),
    REGULARIZATION_APPROVAL(10L, "转正审批"),
    TRANSFER_APPROVAL(11L, "调动审批"),
    RESIGNATION_APPROVAL(12L, "离职审批"),
    WORK_HANDOVER_APPROVAL(13L, "工作交接审批"),
    LEAVE_APPROVAL(14L, "请假审批"),
    OVERTIME_APPROVAL(15L, "加班审批"),
    TRAVEL_REIMBURSEMENT_APPROVAL(16L, "出差报销审批"),
    EXPENSE_APPROVAL(17L, "费用审批"),
    ENTERPRISE_NEWS_APPROVAL(18L, "企业新闻审批");
    private final Long value;
    private final String label;
    @Override
    public Long getCode() {
        return value;
    }
    @Override
    public String getValue() {
        return label;
    }
    /**
     * æ ¹æ®å€¼èŽ·å–å¯¹åº”çš„æžšä¸¾
     * @param value ä¸šåŠ¡ç±»åž‹å€¼
     * @return å¯¹åº”的枚举,未匹配返回null
     */
    public static TypeEnums fromValue(Long value) {
        if (value == null) {
            return null;
        }
        for (TypeEnums type : values()) {
            if (type.getCode().equals(value)) {
                return type;
            }
        }
        return null;
    }
    /**
     * æ ¹æ®å€¼èŽ·å–æè¿°
     * @param value ä¸šåŠ¡ç±»åž‹å€¼
     * @return ä¸šåŠ¡ç±»åž‹æè¿°ï¼ŒæœªåŒ¹é…è¿”å›ž"自定义审批"
     */
    public static String getLabelByValue(Long value) {
        TypeEnums typeEnum = fromValue(value);
        return typeEnum != null ? typeEnum.getValue() : "自定义审批";
    }
}
src/main/java/com/ruoyi/common/utils/OrderUtils.java
@@ -61,8 +61,11 @@
     * @param <T> å®žä½“类型
     * @return è®¢å•编号
     */
    public static <T> String countTodayByCreateTime(BaseMapper<T> mapper,String preFix,String code) {
        LocalDate today = LocalDate.now();
    public static <T> String countTodayByCreateTime(BaseMapper<T> mapper,String preFix,String code, LocalDateTime createTime) {
        if (createTime == null) {
            createTime = LocalDateTime.now();
        }
        LocalDate today = createTime.toLocalDate();
        LocalDateTime todayStart = today.atStartOfDay();
        LocalDateTime tomorrowStart = today.plusDays(1).atStartOfDay();
        String dateStr = today.format(DateTimeFormatter.BASIC_ISO_DATE);
@@ -109,13 +112,17 @@
     * @param <T> å®žä½“类泛型
     * @return å½“天记录数量
     */
    public static <T> String countAfterServiceTodayByCreateTime(BaseMapper<T> mapper,String preFix) {
    public static <T> String countAfterServiceTodayByCreateTime(BaseMapper<T> mapper,String preFix, LocalDateTime createTime) {
        if (createTime == null) {
            createTime = LocalDateTime.now();
        }
        LocalDate localDate = createTime.toLocalDate();
        LocalDateTime todayStart = LocalDateTime.of(
                LocalDateTime.now().toLocalDate(),
                localDate,
                LocalTime.MIN
        );
        LocalDateTime todayEnd = LocalDateTime.of(
                LocalDateTime.now().toLocalDate(),
                localDate,
                LocalTime.MAX
        );
@@ -127,6 +134,6 @@
                .lt("create_time", endDate);
        Long aLong = mapper.selectCount(queryWrapper);
        return preFix + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE).replaceAll("-", "") + String.format("%03d", (aLong + 1));
        return preFix + localDate.format(DateTimeFormatter.ISO_LOCAL_DATE).replaceAll("-", "") + String.format("%03d", (aLong + 1));
    }
}
src/main/java/com/ruoyi/compensationperformance/controller/CompensationPerformanceController.java
@@ -8,7 +8,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.StaffOnJob;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -40,37 +40,37 @@
    @GetMapping("/listPage")
    @Log(title = "薪酬绩效-分页查询", businessType = BusinessType.OTHER)
    @Operation(summary = "薪酬绩效-分页查询")
    public R<?> listPage(Page page, String staffName, String payDateStr) {
    public AjaxResult listPage(Page page, String staffName, String payDateStr) {
        IPage<CompensationPerformance> listPage = compensationPerformanceService.listPage(page, staffName, payDateStr);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
    @PostMapping("/add")
    @Log(title = "薪酬绩效-添加", businessType = BusinessType.INSERT)
    @Operation(summary = "薪酬绩效-添加")
    @Transactional(rollbackFor = Exception.class)
    public R<?> add(@RequestBody CompensationPerformance compensationPerformance) {
    public AjaxResult add(@RequestBody CompensationPerformance compensationPerformance) {
        boolean save = compensationPerformanceService.save(compensationPerformance);
        return save ? R.ok(null, "添加成功") : R.fail("添加失败");
        return save ? AjaxResult.success("添加成功") : AjaxResult.error("添加失败");
    }
    @PostMapping("/update")
    @Log(title = "薪酬绩效-修改", businessType = BusinessType.UPDATE)
    @Operation(summary = "薪酬绩效-修改")
    @Transactional(rollbackFor = Exception.class)
    public R<?> update(@RequestBody CompensationPerformance compensationPerformance) {
    public AjaxResult update(@RequestBody CompensationPerformance compensationPerformance) {
        boolean update = compensationPerformanceService.updateById(compensationPerformance);
        return update ? R.ok(null, "修改成功") : R.fail("修改失败");
        return update ? AjaxResult.success("修改成功") : AjaxResult.error("修改失败");
    }
    @DeleteMapping("/delete")
    @Log(title = "薪酬绩效-删除", businessType = BusinessType.DELETE)
    @Operation(summary = "薪酬绩效-删除")
    @Transactional(rollbackFor = Exception.class)
    public R<?> delete(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) return R.fail("请传入要删除的ID");
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
        boolean delete = compensationPerformanceService.removeBatchByIds(ids);
        return delete ? R.ok(null, "删除成功") : R.fail("删除失败");
        return delete ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
    }
    @Log(title = "导出薪资管理列表", businessType = BusinessType.EXPORT)
@@ -91,7 +91,7 @@
    @Log(title = "导入薪资管理列表", businessType = BusinessType.IMPORT)
    @PostMapping("/importData")
    public R<?> importData(MultipartFile file) throws Exception {
    public AjaxResult importData(MultipartFile file) throws Exception {
        ExcelUtil<CompensationPerformance> util = new ExcelUtil<>(CompensationPerformance.class);
        List<CompensationPerformance> list = util.importExcel(file.getInputStream());
        list.forEach(item -> {
@@ -101,7 +101,7 @@
            }
        });
        boolean b = compensationPerformanceService.saveBatch(list);
        return R.ok(b);
        return AjaxResult.success(b);
    }
src/main/java/com/ruoyi/customervisits/controller/CustomerVisitsController.java
@@ -8,7 +8,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
@@ -31,46 +31,46 @@
    @GetMapping("/listPage")
    @Log(title = "客户拜访-分页查询", businessType = BusinessType.OTHER)
    @Operation(summary = "客户拜访-分页查询")
    public R<?> listPage(Page page, CustomerVisits customerVisits) {
    public AjaxResult listPage(Page page, CustomerVisits customerVisits) {
        IPage<CustomerVisits> listPage = customerVisitsService.listPage(page, customerVisits);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
    @Log(title = "客户拜访-添加", businessType = BusinessType.INSERT)
    @Operation(summary = "客户拜访-添加")
    @PostMapping("/add")
    @Transactional(rollbackFor = Exception.class)
    public R<?> add(@RequestBody CustomerVisits customerVisits) {
    public AjaxResult add(@RequestBody CustomerVisits customerVisits) {
        boolean save = customerVisitsService.save(customerVisits);
        if (save) {
            return R.ok(null, "添加成功");
            return AjaxResult.success("添加成功");
        }
        return R.fail("添加失败");
        return AjaxResult.error("添加失败");
    }
    @Log(title = "客户拜访-编辑", businessType = BusinessType.UPDATE)
    @Operation(summary = "客户拜访-编辑")
    @PostMapping("update")
    public R<?> updateCustomerVisit(@RequestBody CustomerVisits customerVisits) {
    public AjaxResult updateCustomerVisit(@RequestBody CustomerVisits customerVisits) {
        boolean updateResult = customerVisitsService.updateCustomerVisit(customerVisits);
        if (updateResult) {
            return R.ok(null, "编辑成功");
            return AjaxResult.success("编辑成功");
        }
        return R.fail("编辑失败");
        return AjaxResult.error("编辑失败");
    }
    @Log(title = "客户拜访-删除", businessType = BusinessType.DELETE)
    @Operation(summary = "客户拜访-删除")
    @DeleteMapping("{customerId}")
    public R<?> deleteCustomerVisit(@PathVariable Integer customerId) {
    public AjaxResult deleteCustomerVisit(@PathVariable Integer customerId) {
        if (customerId == null) {
            return R.fail("客户ID不能为空");
            return AjaxResult.error("客户ID不能为空");
        }
        boolean deleteResult = customerVisitsService.removeById(customerId);
        if (deleteResult) {
            return R.ok(null, "删除成功");
            return AjaxResult.success("删除成功");
        }
        return R.fail("删除失败");
        return AjaxResult.error("删除失败");
    }
}
src/main/java/com/ruoyi/customervisits/service/CustomerVisitsService.java
@@ -4,7 +4,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.customervisits.pojo.CustomerVisits;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
/**
 * @author :yys
src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
@@ -37,7 +37,7 @@
                wrapper.like(CustomerVisits::getVisitingPeople, customerVisits.getVisitingPeople());
            }
        }
        wrapper.orderByDesc(CustomerVisits::getId);
        return customerVisitsMapper.selectPage(page, wrapper);
    }
src/main/java/com/ruoyi/device/controller/DeviceDefectRecordController.java
@@ -4,8 +4,7 @@
import com.ruoyi.device.dto.DeviceDefectRecordDto;
import com.ruoyi.device.pojo.DeviceDefectRecord;
import com.ruoyi.device.service.DeviceDefectRecordService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
@@ -15,35 +14,35 @@
@RequestMapping("/defect")
@AllArgsConstructor
@RestController
public class DeviceDefectRecordController extends BaseController {
public class DeviceDefectRecordController {
    private DeviceDefectRecordService deviceDefectRecordService;
    @Operation(summary = "设备缺陷记录列表")
    @GetMapping("/page")
    public R<?> page(Page page , DeviceDefectRecordDto deviceDefectRecordDto) {
        return R.ok(deviceDefectRecordService.listPage(page,deviceDefectRecordDto));
    public AjaxResult page(Page page , DeviceDefectRecordDto deviceDefectRecordDto) {
        return AjaxResult.success(deviceDefectRecordService.listPage(page,deviceDefectRecordDto));
    }
    @Operation(summary = "设备id查询设备缺陷记录列表")
    @GetMapping("/find/{deviceLedgerId}")
    public R<?> find(@PathVariable Long deviceLedgerId) {
    public AjaxResult find(@PathVariable Long deviceLedgerId) {
        DeviceDefectRecordDto deviceDefectRecordDto = new DeviceDefectRecordDto();
        deviceDefectRecordDto.setDeviceLedgerId(deviceLedgerId);
        return R.ok(deviceDefectRecordService.listPage(new Page<>(1,-1),deviceDefectRecordDto));
        return AjaxResult.success(deviceDefectRecordService.listPage(new Page<>(1,-1),deviceDefectRecordDto));
    }
    @PostMapping("/add")
    @Operation(summary = "添加设备缺陷记录")
    public R<?> add(@RequestBody DeviceDefectRecord deviceDefectRecord) {
        return R.ok(deviceDefectRecordService.add(deviceDefectRecord));
    public AjaxResult add(@RequestBody DeviceDefectRecord deviceDefectRecord) {
        return AjaxResult.success(deviceDefectRecordService.add(deviceDefectRecord));
    }
    @PostMapping("/update")
    @Operation(summary = "修改设备缺陷记录")
    public R<?> update(@RequestBody DeviceDefectRecord deviceDefectRecord) {
        return R.ok(deviceDefectRecordService.updateByDDR(deviceDefectRecord));
    public AjaxResult update(@RequestBody DeviceDefectRecord deviceDefectRecord) {
        return AjaxResult.success(deviceDefectRecordService.updateByDDR(deviceDefectRecord));
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除设备缺陷记录")
    public R<?> delete(@PathVariable Long id) {
        return R.ok(deviceDefectRecordService.removeById(id));
    public AjaxResult delete(@PathVariable Long id) {
        return AjaxResult.success(deviceDefectRecordService.removeById(id));
    }
}
src/main/java/com/ruoyi/device/controller/DeviceLedgerController.java
@@ -12,8 +12,7 @@
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.service.IDeviceLedgerService;
import com.ruoyi.framework.aspectj.lang.annotation.Anonymous;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
@@ -29,53 +28,52 @@
@RequestMapping("/device/ledger")
@RestController
@AllArgsConstructor
public class DeviceLedgerController extends BaseController {
public class DeviceLedgerController {
    private IDeviceLedgerService deviceLedgerService;
    private DeviceLedgerMapper deviceLedgerMapper;
    private DeviceMaintenanceMapper deviceMaintenanceMapper;
    @Operation(summary = "设备台账列表")
    @GetMapping("/page")
    public R<?> page(Page page , DeviceLedgerDto deviceLedger) {
        return R.ok(deviceLedgerService.queryPage(page,deviceLedger));
    public AjaxResult page(Page page, DeviceLedgerDto deviceLedger) {
        return AjaxResult.success(deviceLedgerService.queryPage(page, deviceLedger));
    }
    @PostMapping()
    @Operation(summary = "添加设备台账")
    public R<?> add(@RequestBody DeviceLedger deviceLedger) {
        return deviceLedgerService.saveDeviceLedger(deviceLedger);
    public AjaxResult add(@RequestBody DeviceLedgerDto deviceLedgerDto) {
        return deviceLedgerService.saveDeviceLedger(deviceLedgerDto);
    }
    @Operation(summary = "根据id查询设备台账")
    @GetMapping("/{id}")
    public R<?> detail(@PathVariable Long id) {
        return R.ok(deviceLedgerService.getById(id));
    public AjaxResult detail(@PathVariable Long id) {
        DeviceLedgerDto deviceLedgerDto = deviceLedgerService.getDeviceLedgerDetail(id);
        return AjaxResult.success(deviceLedgerDto);
    }
    @PutMapping ()
    @PutMapping()
    @Operation(summary = "修改设备台账")
    public R<?> update(@RequestBody DeviceLedger deviceLedger) {
        return deviceLedgerService.updateDeviceLedger(deviceLedger);
    public AjaxResult update(@RequestBody DeviceLedgerDto deviceLedgerDto) {
        return deviceLedgerService.updateDeviceLedger(deviceLedgerDto);
    }
    @DeleteMapping("/{ids}")
    @Operation(summary = "删除设备台账")
    public R<?> delete(@PathVariable("ids") ArrayList<Long> ids) {
    public AjaxResult delete(@PathVariable("ids") ArrayList<Long> ids) {
        boolean b = deviceLedgerService.removeBatchByIds(ids);
        if (!b) {
            return R.fail("删除失败");
            return AjaxResult.error("删除失败");
        }
        return R.ok();
        return AjaxResult.success();
    }
    @PostMapping("export")
    @Operation(summary = "导出设备台账")
    public void export(HttpServletResponse response, Long[] ids) {
         deviceLedgerService.export(response, ids);
        deviceLedgerService.export(response, ids);
    }
    @Operation(summary = "下载模板")
@@ -87,32 +85,32 @@
    @PostMapping("/import")
    @Operation(summary = "导入设备台账")
    public R<?> importData(MultipartFile file) throws IOException {
    public AjaxResult importData(MultipartFile file) throws IOException {
        Boolean b = deviceLedgerService.importData(file);
        if (b) {
            return R.ok(null, "导入成功");
            return AjaxResult.success("导入成功");
        }
        return R.fail("导入失败");
        return AjaxResult.error("导入失败");
    }
    @GetMapping("getDeviceLedger")
    @Operation(summary = "获取设备台账")
    public R<?> getDeviceLedger( ) {
        return R.ok(deviceLedgerService.list(new QueryWrapper<DeviceLedger>().lambda()
                .select(DeviceLedger::getId, DeviceLedger::getDeviceName,DeviceLedger::getDeviceModel)));
    public AjaxResult getDeviceLedger() {
        return AjaxResult.success(deviceLedgerService.list(new QueryWrapper<DeviceLedger>().lambda()
                .select(DeviceLedger::getId, DeviceLedger::getDeviceName, DeviceLedger::getDeviceModel)));
    }
    @GetMapping("scanDevice")
    @Operation(summary = "获取设备台账")
    @Anonymous
    public R<?> scanDevice(Long id) {
    public AjaxResult scanDevice(Long id) {
        List<DeviceMaintenance> list = deviceMaintenanceMapper.list1(id);
        DeviceLedger deviceLedger = deviceLedgerMapper.selectById1(id);
        if (list.size()>0){
            deviceLedger.setUpdateTime(list.get(0).getMaintenanceActuallyTime());//最后维护时间
        if (!list.isEmpty()) {
            deviceLedger.setUpdateTime(list.getFirst().getMaintenanceActuallyTime());//最后维护时间
        }
        deviceLedger.setCreateTime(deviceLedger.getUpdateTime().plusMonths(1));//下次维护时间
        return R.ok(deviceLedger);
        return AjaxResult.success(deviceLedger);
    }
}
src/main/java/com/ruoyi/device/controller/DeviceMaintenanceController.java
@@ -7,8 +7,7 @@
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.service.IDeviceLedgerService;
import com.ruoyi.device.service.IDeviceMaintenanceService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
@@ -21,7 +20,7 @@
@RestController
@RequestMapping("/device/maintenance")
@AllArgsConstructor
public class DeviceMaintenanceController extends BaseController {
public class DeviceMaintenanceController {
    private IDeviceMaintenanceService deviceMaintenanceService;
@@ -29,13 +28,13 @@
    @Operation(summary = "设备保养列表")
    @GetMapping("/page")
    public R<?> page(Page page , DeviceMaintenanceDto deviceMaintenanceDto) {
        return R.ok(deviceMaintenanceService.queryPage(page,deviceMaintenanceDto));
    public AjaxResult page(Page page , DeviceMaintenanceDto deviceMaintenanceDto) {
        return AjaxResult.success(deviceMaintenanceService.queryPage(page,deviceMaintenanceDto));
    }
    @PostMapping()
    @Operation(summary = "添加设备保养")
    public R<?> add(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
    public AjaxResult add(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
        DeviceLedger byId = deviceLedgerService.getById(deviceMaintenance.getDeviceLedgerId());
        deviceMaintenance.setDeviceName(byId.getDeviceName());
        deviceMaintenance.setDeviceModel(byId.getDeviceModel());
@@ -44,13 +43,13 @@
    @Operation(summary = "根据id查询设备保养")
    @GetMapping("/{id}")
    public R<?> detail(@PathVariable Long id) {
        return R.ok(deviceMaintenanceService.detailById(id));
    public AjaxResult detail(@PathVariable Long id) {
        return AjaxResult.success(deviceMaintenanceService.detailById(id));
    }
    @PutMapping ()
    @Operation(summary = "修改设备保养")
    public R<?> update(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
    public AjaxResult update(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
        DeviceLedger byId = deviceLedgerService.getById(deviceMaintenance.getDeviceLedgerId());
        deviceMaintenance.setDeviceName(byId.getDeviceName());
        deviceMaintenance.setDeviceModel(byId.getDeviceModel());
@@ -59,19 +58,19 @@
    @PostMapping ("maintenance")
    @Operation(summary = "修改设备保养")
    public R<?> maintenance(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
    public AjaxResult maintenance(@RequestBody DeviceMaintenanceDto deviceMaintenance) {
        return deviceMaintenanceService.updateDeviceDeviceMaintenance(deviceMaintenance);
    }
    @DeleteMapping("/{ids}")
    @Operation(summary = "删除设备保养")
    public R<?> delete(@PathVariable("ids") Long[] ids) {
    public AjaxResult delete(@PathVariable("ids") Long[] ids) {
        boolean b = deviceMaintenanceService.removeBatchByIds(Arrays.asList(ids));
        if (!b) {
            return R.fail("删除失败");
            return AjaxResult.error("删除失败");
        }
        return R.ok();
        return AjaxResult.success();
    }
    @PostMapping("export")
src/main/java/com/ruoyi/device/controller/DeviceMaintenanceFileController.java
@@ -4,8 +4,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.device.pojo.DeviceMaintenanceFile;
import com.ruoyi.device.service.DeviceMaintenanceFileService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
@@ -24,7 +23,7 @@
@RestController
@RequestMapping("/maintenanceTaskFile")
@Tag(name = "设备保养附件")
public class DeviceMaintenanceFileController extends BaseController {
public class DeviceMaintenanceFileController {
    @Resource
    private DeviceMaintenanceFileService deviceMaintenanceFileService;
@@ -36,8 +35,8 @@
     * @return
     */
    @PostMapping("/add")
    public R<?> add(@RequestBody DeviceMaintenanceFile deviceMaintenanceFile) {
        return R.ok(deviceMaintenanceFileService.save(deviceMaintenanceFile));
    public AjaxResult add(@RequestBody DeviceMaintenanceFile deviceMaintenanceFile) {
        return AjaxResult.success(deviceMaintenanceFileService.save(deviceMaintenanceFile));
    }
    /**
@@ -46,12 +45,12 @@
     * @return
     */
    @DeleteMapping("/del")
    public R<?> delQualityUnqualified(@RequestBody List<Integer> ids) {
    public AjaxResult delQualityUnqualified(@RequestBody List<Integer> ids) {
        if(CollectionUtils.isEmpty(ids)){
            return R.fail("请选择至少一条数据");
            return AjaxResult.error("请选择至少一条数据");
        }
        //删除检验附件
        return R.ok(deviceMaintenanceFileService.removeBatchByIds(ids));
        return AjaxResult.success(deviceMaintenanceFileService.removeBatchByIds(ids));
    }
    /**
@@ -61,8 +60,8 @@
     * @return
     */
    @GetMapping("/listPage")
    public R<?> qualityInspectFileListPage(Page page, DeviceMaintenanceFile deviceMaintenanceFile) {
        return R.ok(deviceMaintenanceFileService.page(page, Wrappers.<DeviceMaintenanceFile>lambdaQuery().eq(DeviceMaintenanceFile::getDeviceMaintenanceId,deviceMaintenanceFile.getDeviceMaintenanceId())));
    public AjaxResult qualityInspectFileListPage(Page page, DeviceMaintenanceFile deviceMaintenanceFile) {
        return AjaxResult.success(deviceMaintenanceFileService.page(page, Wrappers.<DeviceMaintenanceFile>lambdaQuery().eq(DeviceMaintenanceFile::getDeviceMaintenanceId,deviceMaintenanceFile.getDeviceMaintenanceId())));
    }
src/main/java/com/ruoyi/device/controller/DeviceRepairController.java
@@ -5,8 +5,7 @@
import com.ruoyi.device.dto.DeviceRepairDto;
import com.ruoyi.device.pojo.DeviceRepair;
import com.ruoyi.device.service.IDeviceRepairService;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
@@ -19,54 +18,54 @@
@RequestMapping("/device/repair")
@RestController
@AllArgsConstructor
public class DeviceRepairController extends BaseController {
public class DeviceRepairController {
    private IDeviceRepairService deviceRepairService;
    @Operation(summary = "设备报修列表")
    @GetMapping("/page")
    public R<?> page(Page page , DeviceRepairDto deviceRepairDto) {
        return R.ok(deviceRepairService.queryPage(page,deviceRepairDto));
    public AjaxResult page(Page page , DeviceRepairDto deviceRepairDto) {
        return AjaxResult.success(deviceRepairService.queryPage(page,deviceRepairDto));
    }
    @PostMapping()
    @Operation(summary = "添加设备报修")
    public R<?> add( @RequestBody DeviceRepairDto deviceRepairDto) {
    public AjaxResult add( @RequestBody DeviceRepairDto deviceRepairDto) {
        return deviceRepairService.saveDeviceRepair(deviceRepairDto);
    }
    @Operation(summary = "根据id查询设备报修")
    @GetMapping("/{id}")
    public R<?> detail(@PathVariable Long id) {
        return R.ok(deviceRepairService.detailById(id));
    public AjaxResult detail(@PathVariable Long id) {
        return AjaxResult.success(deviceRepairService.detailById(id));
    }
    @PutMapping ()
    @Operation(summary = "修改设备报修")
    public R<?> update( @RequestBody DeviceRepairDto deviceRepairDto) {
    public AjaxResult update( @RequestBody DeviceRepairDto deviceRepairDto) {
        return deviceRepairService.updateDeviceRepair(deviceRepairDto);
    }
    @PostMapping ("/repair")
    @Operation(summary = "设备维修")
    public R<?> repair( @RequestBody DeviceRepairDto deviceRepairDto) {
    public AjaxResult repair( @RequestBody DeviceRepairDto deviceRepairDto) {
        return deviceRepairService.confirmRepair(deviceRepairDto);
    }
    @PostMapping ("/acceptance")
    @Operation(summary = "设备报修验收审批")
    public R<?> acceptance(@RequestBody DeviceRepairDto deviceRepairDto) {
    public AjaxResult acceptance(@RequestBody DeviceRepairDto deviceRepairDto) {
        return deviceRepairService.approveRepairAcceptance(deviceRepairDto);
    }
    @DeleteMapping("/{ids}")
    @Operation(summary = "删除设备报修")
    public R<?> delete(@PathVariable("ids") Long[] ids) {
    public AjaxResult delete(@PathVariable("ids") Long[] ids) {
        boolean b = deviceRepairService.removeBatchByIds(Arrays.asList(ids));
        if (!b) {
            return R.fail("删除失败");
            return AjaxResult.error("删除失败");
        }
        return R.ok();
        return AjaxResult.success();
    }
    @PostMapping("export")
src/main/java/com/ruoyi/device/controller/MaintenanceTaskController.java
@@ -6,7 +6,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
@@ -31,7 +31,7 @@
    @GetMapping("/listPage")
    @Operation(summary = "设备保养定时任务列表")
    public R<?> listPage(Page page, MaintenanceTask maintenanceTask) {
    public AjaxResult listPage(Page page, MaintenanceTask maintenanceTask) {
        return maintenanceTaskService.listPage(page,maintenanceTask);
    }
@@ -39,21 +39,21 @@
    @PostMapping("/add")
    @Operation(summary = "添加设备保养定时任务")
    @Log(title = "设备保养定时任务", businessType = BusinessType.INSERT)
    public R<?> add(@RequestBody MaintenanceTask maintenanceTask) {
    public AjaxResult add(@RequestBody MaintenanceTask maintenanceTask) {
        return maintenanceTaskService.add(maintenanceTask);
    }
    @PostMapping("/update")
    @Operation(summary = "修改设备保养定时任务")
    @Log(title = "设备保养定时任务", businessType = BusinessType.UPDATE)
    public R<?> update(@RequestBody MaintenanceTask maintenanceTask) {
    public AjaxResult update(@RequestBody MaintenanceTask maintenanceTask) {
        return maintenanceTaskService.updateByMaintenanceTaskId(maintenanceTask);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除设备保养定时任务")
    @Log(title = "设备保养定时任务", businessType = BusinessType.DELETE)
    public R<?> delete(@RequestBody List<Long> ids) {
    public AjaxResult delete(@RequestBody List<Long> ids) {
        return maintenanceTaskService.delete(ids);
    }
src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java
@@ -12,6 +12,9 @@
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.dto.StorageBlobVO;
/**
 * è®¾å¤‡å°è´¦å®žä½“ç±»
@@ -55,6 +58,20 @@
    private String supplierName;
    /**
     * è®¾å¤‡é™„ä»¶(用于接收)
     */
    @TableField(exist = false)
    @Schema(description = "设备附件接收列表")
    private List<StorageBlobDTO> storageBlobDTOs;
    /**
     * è®¾å¤‡é™„ä»¶(用于返回)
     */
    @TableField(exist = false)
    @Schema(description = "设备附件展示列表")
    private List<StorageBlobVO> storageBlobVOs;
    /**
     * å•位
     */
    private String unit;
src/main/java/com/ruoyi/device/service/IDeviceLedgerService.java
@@ -5,7 +5,7 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.device.dto.DeviceLedgerDto;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.web.multipart.MultipartFile;
import jakarta.servlet.http.HttpServletResponse;
@@ -14,9 +14,11 @@
public interface IDeviceLedgerService  extends IService<DeviceLedger> {
    IPage<DeviceLedgerDto> queryPage(Page page, DeviceLedgerDto deviceLedger);
    R<?> saveDeviceLedger(DeviceLedger deviceLedger);
    AjaxResult saveDeviceLedger(DeviceLedgerDto deviceLedgerDto);
    R<?> updateDeviceLedger(DeviceLedger deviceLedger);
    AjaxResult updateDeviceLedger(DeviceLedgerDto deviceLedgerDto);
    DeviceLedgerDto getDeviceLedgerDetail(Long id);
    void export(HttpServletResponse response, Long[] ids);
src/main/java/com/ruoyi/device/service/IDeviceMaintenanceService.java
@@ -6,7 +6,7 @@
import com.ruoyi.device.dto.DeviceMaintenanceDto;
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.vo.DeviceMaintenanceVo;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import jakarta.servlet.http.HttpServletResponse;
@@ -14,9 +14,9 @@
    IPage<DeviceMaintenanceDto> queryPage(Page page, DeviceMaintenanceDto deviceMaintenanceDto);
    R<?> saveDeviceRepair(DeviceMaintenanceDto deviceMaintenance);
    AjaxResult saveDeviceRepair(DeviceMaintenanceDto deviceMaintenance);
    R<?> updateDeviceDeviceMaintenance(DeviceMaintenanceDto deviceMaintenance);
    AjaxResult updateDeviceDeviceMaintenance(DeviceMaintenanceDto deviceMaintenance);
    void export(HttpServletResponse response, Long[] ids);
src/main/java/com/ruoyi/device/service/IDeviceRepairService.java
@@ -6,7 +6,7 @@
import com.ruoyi.device.dto.DeviceRepairDto;
import com.ruoyi.device.pojo.DeviceRepair;
import com.ruoyi.device.vo.DeviceRepairVo;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import jakarta.servlet.http.HttpServletResponse;
@@ -15,13 +15,13 @@
    IPage<DeviceRepairVo> queryPage(Page page, DeviceRepairDto deviceRepairDto);
    R<?> saveDeviceRepair(DeviceRepairDto deviceRepairDto);
    AjaxResult saveDeviceRepair(DeviceRepairDto deviceRepairDto);
    R<?> updateDeviceRepair(DeviceRepairDto deviceRepairDto);
    AjaxResult updateDeviceRepair(DeviceRepairDto deviceRepairDto);
    R<?> confirmRepair(DeviceRepairDto deviceRepairDto);
    AjaxResult confirmRepair(DeviceRepairDto deviceRepairDto);
    R<?> approveRepairAcceptance(DeviceRepairDto deviceRepairDto);
    AjaxResult approveRepairAcceptance(DeviceRepairDto deviceRepairDto);
    void export(HttpServletResponse response, Long[] ids);
src/main/java/com/ruoyi/device/service/MaintenanceTaskService.java
@@ -3,7 +3,7 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.device.pojo.MaintenanceTask;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import java.util.List;
@@ -12,11 +12,11 @@
 * @date : 2025/12/22 14:56
 */
public interface MaintenanceTaskService extends IService<MaintenanceTask> {
    R<?> listPage(Page page, MaintenanceTask maintenanceTask);
    AjaxResult listPage(Page page, MaintenanceTask maintenanceTask);
    R<?> add(MaintenanceTask maintenanceTask);
    AjaxResult add(MaintenanceTask maintenanceTask);
    R<?> updateByMaintenanceTaskId(MaintenanceTask maintenanceTask);
    AjaxResult updateByMaintenanceTaskId(MaintenanceTask maintenanceTask);
    R<?> delete(List<Long> ids);
    AjaxResult delete(List<Long> ids);
}
src/main/java/com/ruoyi/device/service/impl/DeviceLedgerServiceImpl.java
@@ -12,8 +12,11 @@
import com.ruoyi.device.execl.DeviceLedgerExeclDto;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.basic.dto.StorageAttachmentDTO;
import com.ruoyi.basic.enums.RecordTypeEnum;
import com.ruoyi.basic.service.StorageAttachmentService;
import com.ruoyi.device.service.IDeviceLedgerService;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import jakarta.servlet.http.HttpServletResponse;
@@ -36,6 +39,7 @@
    private final DeviceLedgerMapper deviceLedgerMapper;
    private final SysUserMapper sysUserMapper;
    private final StorageAttachmentService storageAttachmentService;
    @Override
    public IPage<DeviceLedgerDto> queryPage(Page page, DeviceLedgerDto deviceLedger) {
@@ -44,25 +48,61 @@
    }
    @Override
    public R<?> saveDeviceLedger(DeviceLedger deviceLedger) {
    public AjaxResult saveDeviceLedger(DeviceLedgerDto deviceLedgerDto) {
        LambdaQueryWrapper<DeviceLedger> deviceLedgerLambdaQueryWrapper = new LambdaQueryWrapper<>();
        deviceLedgerLambdaQueryWrapper.eq(DeviceLedger::getDeviceName,deviceLedger.getDeviceName());
        deviceLedgerLambdaQueryWrapper.eq(DeviceLedger::getDeviceName,deviceLedgerDto.getDeviceName());
        if (this.count(deviceLedgerLambdaQueryWrapper) > 0) {
            return R.fail("设备名称已存在");
            return AjaxResult.error("设备名称已存在");
        }
        DeviceLedger deviceLedger = new DeviceLedger();
        BeanUtils.copyProperties(deviceLedgerDto, deviceLedger);
        boolean save = this.save(deviceLedger);
        if (save){
            return R.ok();
            if (deviceLedgerDto.getStorageBlobDTOs() != null) {
                StorageAttachmentDTO attachmentDTO = new StorageAttachmentDTO();
                attachmentDTO.setApplication("image");
                attachmentDTO.setRecordType(RecordTypeEnum.DEVICE_LEDGER.getType());
                attachmentDTO.setRecordId(deviceLedger.getId());
                attachmentDTO.setStorageBlobDTOs(deviceLedgerDto.getStorageBlobDTOs());
                storageAttachmentService.saveStorageAttachment(attachmentDTO);
            }
            return AjaxResult.success();
        }
        return R.fail();
        return AjaxResult.error();
    }
    @Override
    public R<?> updateDeviceLedger(DeviceLedger deviceLedger) {
    public AjaxResult updateDeviceLedger(DeviceLedgerDto deviceLedgerDto) {
        DeviceLedger deviceLedger = new DeviceLedger();
        BeanUtils.copyProperties(deviceLedgerDto, deviceLedger);
        if (this.updateById(deviceLedger)) {
            return R.ok();
            if (deviceLedgerDto.getStorageBlobDTOs() != null) {
                StorageAttachmentDTO attachmentDTO = new StorageAttachmentDTO();
                attachmentDTO.setApplication("image");
                attachmentDTO.setRecordType(RecordTypeEnum.DEVICE_LEDGER.getType());
                attachmentDTO.setRecordId(deviceLedger.getId());
                attachmentDTO.setStorageBlobDTOs(deviceLedgerDto.getStorageBlobDTOs());
                storageAttachmentService.saveStorageAttachment(attachmentDTO);
            }
            return AjaxResult.success();
        }
        return R.fail();
        return AjaxResult.error();
    }
    @Override
    public DeviceLedgerDto getDeviceLedgerDetail(Long id) {
        DeviceLedger deviceLedger = this.getById(id);
        if (deviceLedger != null) {
            DeviceLedgerDto deviceLedgerDto = new DeviceLedgerDto();
            BeanUtils.copyProperties(deviceLedger, deviceLedgerDto);
            StorageAttachmentDTO dto = new StorageAttachmentDTO();
            dto.setRecordType(RecordTypeEnum.DEVICE_LEDGER.getType());
            dto.setRecordId(id);
            dto.setApplication("image");
            deviceLedgerDto.setStorageBlobVOs(storageAttachmentService.list(dto));
            return deviceLedgerDto;
        }
        return null;
    }
    @Override
@@ -82,9 +122,7 @@
            util.exportExcel(response, deviceLedgerExeclDtos, "设备台账导出");
        }else  {
            ArrayList<Long> arrayList = new ArrayList<>();
            Arrays.stream(ids).map(id -> {
                return arrayList.add( id);
            });
            Arrays.stream(ids).map(arrayList::add);
            List<DeviceLedger> supplierManageList = deviceLedgerMapper.selectBatchIds(arrayList);
            ArrayList<DeviceLedgerExeclDto> deviceLedgerExeclDtos = new ArrayList<>();
            supplierManageList.stream().forEach(deviceLedger -> {
@@ -116,7 +154,10 @@
            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));
            // è®¡ç®—不含税总价,处理空值情况
            if (deviceLedger.getTaxIncludingPriceTotal() != null && c.getTaxRate() != null) {
                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
@@ -15,7 +15,7 @@
import com.ruoyi.device.service.IDeviceMaintenanceService;
import com.ruoyi.device.vo.DeviceMaintenanceVo;
import com.ruoyi.device.vo.DeviceRepairVo;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.measuringinstrumentledger.mapper.SparePartsMapper;
import com.ruoyi.measuringinstrumentledger.pojo.SpareParts;
import com.ruoyi.measuringinstrumentledger.pojo.SparePartsRequisitionRecord;
@@ -48,19 +48,19 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> saveDeviceRepair(DeviceMaintenanceDto deviceMaintenance) {
    public AjaxResult saveDeviceRepair(DeviceMaintenanceDto deviceMaintenance) {
        boolean save = this.save(deviceMaintenance);
        if (save){
            // å¤„理图片上传
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_MAINTENANCE, deviceMaintenance.getId(), deviceMaintenance.getStorageBlobDTOs());
            return R.ok();
            return AjaxResult.success();
        }
        return R.fail();
        return AjaxResult.error();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> updateDeviceDeviceMaintenance(DeviceMaintenanceDto deviceMaintenance) {
    public AjaxResult updateDeviceDeviceMaintenance(DeviceMaintenanceDto deviceMaintenance) {
        DeviceMaintenance oldDeviceMaintenance = this.getById(deviceMaintenance.getId());
        // å¤„理备件使用情况
        if (com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(deviceMaintenance.getSparePartsUseList())) {
@@ -85,7 +85,7 @@
                        record.setQuantity(sparePartUse.getQuantity());
                        sparePartsRequisitionRecordService.save(record);
                    } else {
                        return R.fail("备件 " + spareParts.getName() + " æ•°é‡ä¸è¶³");
                        return AjaxResult.error("备件 " + spareParts.getName() + " æ•°é‡ä¸è¶³");
                    }
                }
            }
@@ -98,9 +98,9 @@
        if (this.updateById(deviceMaintenance)) {
            // å¤„理图片上传
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_MAINTENANCE, deviceMaintenance.getId(), deviceMaintenance.getStorageBlobDTOs());
            return R.ok();
            return AjaxResult.success();
        }
        return R.fail();
        return AjaxResult.error();
    }
    @Override
src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
@@ -18,7 +18,7 @@
import com.ruoyi.device.service.IDeviceLedgerService;
import com.ruoyi.device.service.IDeviceRepairService;
import com.ruoyi.device.vo.DeviceRepairVo;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.measuringinstrumentledger.mapper.SparePartsMapper;
import com.ruoyi.measuringinstrumentledger.pojo.SpareParts;
import com.ruoyi.measuringinstrumentledger.pojo.SparePartsRequisitionRecord;
@@ -63,7 +63,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> saveDeviceRepair(DeviceRepairDto deviceRepairDto) {
    public AjaxResult saveDeviceRepair(DeviceRepairDto deviceRepairDto) {
        DeviceLedger byId = deviceLedgerService.getById(deviceRepairDto.getDeviceLedgerId());
        deviceRepairDto.setDeviceName(byId.getDeviceName());
        deviceRepairDto.setDeviceModel(byId.getDeviceModel());
@@ -74,23 +74,23 @@
        if (save) {
            // å¤„理图片上传
            fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_REPAIR, deviceRepairDto.getId(), deviceRepairDto.getStorageBlobDTOs());
            return R.ok();
            return AjaxResult.success();
        }
        return R.fail("保存失败");
        return AjaxResult.error("保存失败");
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> updateDeviceRepair(DeviceRepairDto deviceRepairDto) {
    public AjaxResult updateDeviceRepair(DeviceRepairDto deviceRepairDto) {
        DeviceRepair oldDeviceRepair = this.getById(deviceRepairDto.getId());
        if (oldDeviceRepair == null) {
            return R.fail("报修记录不存在");
            return AjaxResult.error("报修记录不存在");
        }
        if (deviceRepairDto.getStatus() != null
                && deviceRepairDto.getStatus() == STATUS_COMPLETED
                && (oldDeviceRepair.getStatus() == null
                || oldDeviceRepair.getStatus() != STATUS_COMPLETED)) {
            return R.fail("请先提交验收审批,验收通过后才可完结");
            return AjaxResult.error("请先提交验收审批,验收通过后才可完结");
        }
        // å¤„理备件使用情况
        if (CollectionUtils.isNotEmpty(deviceRepairDto.getSparePartsUseList())) {
@@ -115,7 +115,7 @@
                        record.setQuantity(sparePartUse.getQuantity());
                        sparePartsRequisitionRecordService.save(record);
                    } else {
                        return R.fail("备件 " + spareParts.getName() + " æ•°é‡ä¸è¶³");
                        return AjaxResult.error("备件 " + spareParts.getName() + " æ•°é‡ä¸è¶³");
                    }
                }
            }
@@ -142,23 +142,23 @@
            if (deviceRepairDto.getStorageBlobDTOs() != null) {
                fileUtil.saveStorageAttachmentByRecordTypeAndRecordId("file", RecordTypeEnum.DEVICE_REPAIR, id, deviceRepairDto.getStorageBlobDTOs());
            }
            return R.ok();
            return AjaxResult.success();
        }
        return R.fail();
        return AjaxResult.error();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> confirmRepair(DeviceRepairDto deviceRepairDto) {
    public AjaxResult confirmRepair(DeviceRepairDto deviceRepairDto) {
        DeviceRepair oldDeviceRepair = this.getById(deviceRepairDto.getId());
        if (oldDeviceRepair == null) {
            return R.fail("报修记录不存在");
            return AjaxResult.error("报修记录不存在");
        }
        if (oldDeviceRepair.getStatus() != null && oldDeviceRepair.getStatus() == STATUS_COMPLETED) {
            return R.fail("该报修已完结,不能重复确认维修");
            return AjaxResult.error("该报修已完结,不能重复确认维修");
        }
        if (oldDeviceRepair.getStatus() != null && oldDeviceRepair.getStatus() == STATUS_PENDING_ACCEPTANCE) {
            return R.fail("该报修已提交验收审批");
            return AjaxResult.error("该报修已提交验收审批");
        }
        deviceRepairDto.setStatus(STATUS_PENDING_ACCEPTANCE);
        return updateDeviceRepair(deviceRepairDto);
@@ -166,25 +166,25 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R<?> approveRepairAcceptance(DeviceRepairDto deviceRepairDto) {
    public AjaxResult approveRepairAcceptance(DeviceRepairDto deviceRepairDto) {
        if (deviceRepairDto.getId() == null) {
            return R.fail("报修记录id不能为空");
            return AjaxResult.error("报修记录id不能为空");
        }
        DeviceRepair oldDeviceRepair = this.getById(deviceRepairDto.getId());
        if (oldDeviceRepair == null) {
            return R.fail("报修记录不存在");
            return AjaxResult.error("报修记录不存在");
        }
        if (oldDeviceRepair.getStatus() == null || oldDeviceRepair.getStatus() != STATUS_PENDING_ACCEPTANCE) {
            return R.fail("该报修未进入待验收状态,不能审批");
            return AjaxResult.error("该报修未进入待验收状态,不能审批");
        }
        if (StringUtils.isBlank(deviceRepairDto.getAcceptanceName())) {
            return R.fail("验收人不能为空");
            return AjaxResult.error("验收人不能为空");
        }
        if (deviceRepairDto.getAcceptanceTime() == null) {
            return R.fail("验收时间不能为空");
            return AjaxResult.error("验收时间不能为空");
        }
        if (StringUtils.isBlank(deviceRepairDto.getAcceptanceRemark())) {
            return R.fail("验收备注不能为空");
            return AjaxResult.error("验收备注不能为空");
        }
        DeviceRepair update = new DeviceRepair();
@@ -194,9 +194,9 @@
        update.setAcceptanceRemark(deviceRepairDto.getAcceptanceRemark());
        update.setStatus(STATUS_COMPLETED);
        if (this.updateById(update)) {
            return R.ok();
            return AjaxResult.success();
        }
        return R.fail("验收审批失败");
        return AjaxResult.error("验收审批失败");
    }
    @Override
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
@@ -1,13 +1,13 @@
package com.ruoyi.device.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.device.mapper.MaintenanceTaskMapper;
import com.ruoyi.device.pojo.MaintenanceTask;
import com.ruoyi.device.service.MaintenanceTaskService;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import com.ruoyi.inspectiontask.service.impl.TimingTaskServiceImpl;
import com.ruoyi.project.system.domain.SysUser;
@@ -34,11 +34,11 @@
    private final MaintenanceTaskScheduler maintenanceTaskScheduler;
    @Override
    public R<?> listPage(Page page, MaintenanceTask maintenanceTask) {
        Page<MaintenanceTask> taskPage = maintenanceTaskMapper.selectPage(page, null);
    public AjaxResult listPage(Page page, MaintenanceTask maintenanceTask) {
        Page<MaintenanceTask> taskPage = maintenanceTaskMapper.selectPage(page, new QueryWrapper<MaintenanceTask>().orderByDesc("create_time"));
        // 2. å¦‚果没有数据,直接返回空分页
        if (taskPage.getRecords().isEmpty()) {
            return R.ok(taskPage);
            return AjaxResult.success(taskPage);
        }
        // 3. æ”¶é›†æ‰€æœ‰éœ€è¦æŸ¥è¯¢çš„用户ID
@@ -63,11 +63,11 @@
                task.setRegistrant(userNickNameMap.getOrDefault(task.getRegistrantId(), "未知用户"));
            }
        });
        return R.ok(taskPage);
        return AjaxResult.success(taskPage);
    }
    @Override
    public R<?> add(MaintenanceTask maintenanceTask) {
    public AjaxResult add(MaintenanceTask maintenanceTask) {
        maintenanceTask.setActive(true);
        // è®¡ç®—首次执行时间
        TimingTask task = new TimingTask();
@@ -79,31 +79,31 @@
        if (insert > 0) {
            maintenanceTaskScheduler.scheduleMaintenanceTask(maintenanceTask);
        }
        return R.ok(null, "添加成功");
        return AjaxResult.success("添加成功");
    }
    @Override
    public R<?> updateByMaintenanceTaskId(MaintenanceTask maintenanceTask) {
    public AjaxResult updateByMaintenanceTaskId(MaintenanceTask maintenanceTask) {
        MaintenanceTask maintenanceTask1 = maintenanceTaskMapper.selectById(maintenanceTask.getId());
        if (maintenanceTask1 == null) {
            return R.fail(HttpStatus.WARN, "没有此数据");
            return AjaxResult.warn("没有此数据");
        }
        BeanUtils.copyProperties(maintenanceTask, maintenanceTask1);
        int update = maintenanceTaskMapper.updateById(maintenanceTask1);
        if (update > 0) {
            maintenanceTaskScheduler.rescheduleMaintenanceTask(maintenanceTask1);
        }
        return R.ok(null, "更新成功");
        return AjaxResult.success("更新成功");
    }
    @Override
    public R<?> delete(List<Long> ids) {
    public AjaxResult delete(List<Long> ids) {
        int delete = maintenanceTaskMapper.deleteBatchIds(ids);
        if (delete > 0) {
            ids.forEach(id -> {
                maintenanceTaskScheduler.unscheduleMaintenanceTask(id);
            });
        }
        return R.ok(null, "删除成功");
        return AjaxResult.success("删除成功");
    }
}
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/ElectricityConsumptionAreaController.java
@@ -8,7 +8,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
@@ -36,26 +36,26 @@
    @GetMapping("/listPage")
    @Operation(summary = "用电区域-分页查询")
    @Log(title = "用电区域-分页查询", businessType = BusinessType.OTHER)
    public R<?> listPage(Page page, ElectricityConsumptionArea electricityConsumptionArea) {
    public AjaxResult listPage(Page page, ElectricityConsumptionArea electricityConsumptionArea) {
        IPage<ElectricityConsumptionArea> listPage = electricityConsumptionAreaService.listPage(page, electricityConsumptionArea);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
    @PostMapping("/add")
    @Operation(summary = "用电区域-新增")
    @Log(title = "用电区域-新增", businessType = BusinessType.INSERT)
    public R<?> add(@RequestBody ElectricityConsumptionArea electricityConsumptionArea) {
    public AjaxResult add(@RequestBody ElectricityConsumptionArea electricityConsumptionArea) {
        boolean save = electricityConsumptionAreaService.saveOrUpdate(electricityConsumptionArea);
        return save ? R.ok() : R.fail();
        return save ? AjaxResult.success() : AjaxResult.error();
    }
    @DeleteMapping("/delete")
    @Operation(summary = "用电区域-删除")
    @Log(title = "用电区域-删除", businessType = BusinessType.DELETE)
    public R<?> delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)) return R.fail("请选择至少一条数据");
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请选择至少一条数据");
        boolean remove = electricityConsumptionAreaService.removeBatchByIds(ids);
        return remove ? R.ok() : R.fail();
        return remove ? AjaxResult.success() : AjaxResult.error();
    }
}
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/EnergyPeriodController.java
@@ -8,7 +8,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletResponse;
@@ -28,42 +28,42 @@
    @GetMapping("/listPage")
    @Operation(summary = "用电时段-分页查询")
    @Log(title = "用电时段-分页查询", businessType = BusinessType.OTHER)
    public R<?> listPage(Page page, EnergyPeriod energyPeriod) {
    public AjaxResult listPage(Page page, EnergyPeriod energyPeriod) {
        IPage<EnergyPeriod> listPage = energyPeriodService.listPage(page, energyPeriod);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
    @PostMapping("/add")
    @Operation(summary = "用电时段-新增")
    @Log(title = "用电时段-新增", businessType = BusinessType.INSERT)
    public R<?> add(@RequestBody EnergyPeriod energyPeriod) {
    public AjaxResult add(@RequestBody EnergyPeriod energyPeriod) {
        boolean save = energyPeriodService.save(energyPeriod);
        return save ? R.ok() : R.fail();
        return save ? AjaxResult.success() : AjaxResult.error();
    }
    @PostMapping("/addBatch")
    @Operation(summary = "用电时段-批量新增")
    @Log(title = "用电时段-批量新增", businessType = BusinessType.INSERT)
    public R<?> addBatch(@RequestBody List<EnergyPeriod> energyPeriods) {
    public AjaxResult addBatch(@RequestBody List<EnergyPeriod> energyPeriods) {
        boolean save = energyPeriodService.saveBatch(energyPeriods);
        return save ? R.ok() : R.fail();
        return save ? AjaxResult.success() : AjaxResult.error();
    }
    @PostMapping("/update")
    @Operation(summary = "用电时段-修改")
    @Log(title = "用电时段-修改", businessType = BusinessType.UPDATE)
    public R<?> update(@RequestBody EnergyPeriod energyPeriod) {
    public AjaxResult update(@RequestBody EnergyPeriod energyPeriod) {
        boolean update = energyPeriodService.updateById(energyPeriod);
        return update ? R.ok() : R.fail();
        return update ? AjaxResult.success() : AjaxResult.error();
    }
    @DeleteMapping("/delete")
    @Operation(summary = "用电时段-删除")
    @Log(title = "用电时段-删除", businessType = BusinessType.DELETE)
    public R<?> delete(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) return R.fail("请选择至少一条数据");
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) return AjaxResult.error("请选择至少一条数据");
        boolean remove = energyPeriodService.removeBatchByIds(ids);
        return remove ? R.ok() : R.fail("删除失败");
        return remove ? AjaxResult.success() : AjaxResult.error("删除失败");
    }
src/main/java/com/ruoyi/equipmentenergyconsumption/controller/EquipmentEnergyConsumptionController.java
@@ -12,7 +12,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.AllArgsConstructor;
@@ -41,50 +41,50 @@
    @GetMapping("/listPage")
    @Operation(summary = "设备能耗-分页查询")
    @Log(title = "设备能耗-分页查询", businessType = BusinessType.OTHER)
    public R<?> listPage(Page page, EquipmentEnergyConsumption equipmentEnergyConsumption) {
    public AjaxResult listPage(Page page, EquipmentEnergyConsumption equipmentEnergyConsumption) {
        IPage<EquipmentEnergyConsumption> listPage = equipmentEnergyConsumptionService.listPage(page, equipmentEnergyConsumption);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
    @GetMapping("/deviceList")
    @Operation(summary = "设备台账-查询")
    @Log(title = "设备台账-查询", businessType = BusinessType.OTHER)
    public R<?> deviceList(DeviceLedger deviceLedger) {
    public AjaxResult deviceList(DeviceLedger deviceLedger) {
        List<DeviceLedger> listPage = equipmentEnergyConsumptionService.deviceList(deviceLedger);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
    @PostMapping("/add")
    @Operation(summary = "设备能耗-新增")
    @Log(title = "设备能耗-新增", businessType = BusinessType.INSERT)
    public R<?> add(@RequestBody EquipmentEnergyConsumption equipmentEnergyConsumption) {
    public AjaxResult add(@RequestBody EquipmentEnergyConsumption equipmentEnergyConsumption) {
        boolean save = equipmentEnergyConsumptionService.save(equipmentEnergyConsumption);
        return save ? R.ok() : R.fail();
        return save ? AjaxResult.success() : AjaxResult.error();
    }
    @PostMapping("/addBatch")
    @Operation(summary = "设备能耗-批量新增")
    @Log(title = "设备能耗-批量新增", businessType = BusinessType.INSERT)
    public R<?> addBatch(@RequestBody List<EquipmentEnergyConsumption> list) {
    public AjaxResult addBatch(@RequestBody List<EquipmentEnergyConsumption> list) {
        boolean save = equipmentEnergyConsumptionService.saveBatch(list);
        return save ? R.ok() : R.fail();
        return save ? AjaxResult.success() : AjaxResult.error();
    }
    @PostMapping("/update")
    @Operation(summary = "设备能耗-修改")
    @Log(title = "设备能耗-修改", businessType = BusinessType.UPDATE)
    public R<?> update(@RequestBody EquipmentEnergyConsumption equipmentEnergyConsumption) {
    public AjaxResult update(@RequestBody EquipmentEnergyConsumption equipmentEnergyConsumption) {
        boolean update = equipmentEnergyConsumptionService.updateById(equipmentEnergyConsumption);
        return update ? R.ok() : R.fail();
        return update ? AjaxResult.success() : AjaxResult.error();
    }
    @DeleteMapping("/delete")
    @Operation(summary = "设备能耗-删除")
    @Log(title = "设备能耗-删除", businessType = BusinessType.DELETE)
    public R<?> delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)) return R.fail("请选择至少一条数据");
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请选择至少一条数据");
        boolean remove = equipmentEnergyConsumptionService.removeBatchByIds(ids);
        return remove ? R.ok() : R.fail();
        return remove ? AjaxResult.success() : AjaxResult.error();
    }
    /**
@@ -93,7 +93,7 @@
    @Log(title = "导入设备能耗", businessType = BusinessType.IMPORT)
    @PostMapping("/importData")
    @Operation(summary = "导入设备能耗")
    public R<?> importData(MultipartFile file) throws Exception {
    public AjaxResult importData(MultipartFile file) throws Exception {
        return equipmentEnergyConsumptionService.importData(file);
    }
@@ -114,9 +114,9 @@
    @GetMapping("/listPageByTrend")
    @Operation(summary = "设备能耗-能源趋势-分页查询")
    @Log(title = "设备能耗-能源趋势-分页查询", businessType = BusinessType.OTHER)
    public R<?> listPageByTrend(Page page, EquipmentEnergyConsumption equipmentEnergyConsumption) {
    public AjaxResult listPageByTrend(Page page, EquipmentEnergyConsumption equipmentEnergyConsumption) {
        IPage<EquipmentEnergyConsumption> listPage = equipmentEnergyConsumptionService.listPageByTrend(page, equipmentEnergyConsumption);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
    /**
src/main/java/com/ruoyi/equipmentenergyconsumption/service/EquipmentEnergyConsumptionService.java
@@ -5,7 +5,7 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.equipmentenergyconsumption.pojo.EquipmentEnergyConsumption;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -19,7 +19,7 @@
    IPage<EquipmentEnergyConsumption> listPage(Page page, EquipmentEnergyConsumption equipmentEnergyConsumption);
    R<?> importData(MultipartFile file);
    AjaxResult importData(MultipartFile file);
    IPage<EquipmentEnergyConsumption> listPageByTrend(Page page, EquipmentEnergyConsumption equipmentEnergyConsumption);
src/main/java/com/ruoyi/equipmentenergyconsumption/service/impl/EquipmentEnergyConsumptionServiceImpl.java
@@ -10,8 +10,7 @@
import com.ruoyi.equipmentenergyconsumption.mapper.EquipmentEnergyConsumptionMapper;
import com.ruoyi.equipmentenergyconsumption.pojo.EquipmentEnergyConsumption;
import com.ruoyi.equipmentenergyconsumption.service.EquipmentEnergyConsumptionService;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -42,18 +41,18 @@
    }
    @Override
    public R<?> importData(MultipartFile file) {
    public AjaxResult importData(MultipartFile file) {
        try {
            ExcelUtil<EquipmentEnergyConsumption> util = new ExcelUtil<EquipmentEnergyConsumption>(EquipmentEnergyConsumption.class);
            List<EquipmentEnergyConsumption> userList = util.importExcel(file.getInputStream());
            if(CollectionUtils.isEmpty(userList)){
                return R.fail(HttpStatus.WARN, "模板错误或导入数据为空");
                return AjaxResult.warn("模板错误或导入数据为空");
            }
            this.saveOrUpdateBatch(userList);
            return R.ok(true);
            return AjaxResult.success(true);
        }catch (Exception e){
            e.printStackTrace();
            return R.fail("导入失败");
            return AjaxResult.error("导入失败");
        }
    }
src/main/java/com/ruoyi/framework/web/controller/BaseController.java
@@ -16,7 +16,7 @@
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.sql.SqlUtil;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.page.PageDomain;
import com.ruoyi.framework.web.page.TableDataInfo;
import com.ruoyi.framework.web.page.TableSupport;
@@ -93,69 +93,69 @@
    /**
     * è¿”回成功
     */
    public R<?> success()
    public AjaxResult success()
    {
        return R.ok();
        return AjaxResult.success();
    }
    /**
     * è¿”回成功消息
     */
    public R<?> success(String message)
    public AjaxResult success(String message)
    {
        return R.ok(null, message);
        return AjaxResult.success(message);
    }
    /**
     * è¿”回成功消息
     */
    public R<?> success(Object data)
    public AjaxResult success(Object data)
    {
        return R.ok(data);
        return AjaxResult.success(data);
    }
    /**
     * è¿”回失败消息
     */
    public R<?> error()
    public AjaxResult error()
    {
        return R.fail();
        return AjaxResult.error();
    }
    /**
     * è¿”回失败消息
     */
    public R<?> error(String message)
    public AjaxResult error(String message)
    {
        return R.fail(message);
        return AjaxResult.error(message);
    }
    /**
     * è¿”回警告消息
     */
    public R<?> warn(String message)
    public AjaxResult warn(String message)
    {
        return R.fail(HttpStatus.WARN, message);
        return AjaxResult.warn(message);
    }
    /**
     * å“åº”返回结果
     *
     *
     * @param rows å½±å“è¡Œæ•°
     * @return æ“ä½œç»“æžœ
     */
    protected R<?> toAjax(int rows)
    protected AjaxResult toAjax(int rows)
    {
        return rows > 0 ? R.ok() : R.fail();
        return rows > 0 ? AjaxResult.success() : AjaxResult.error();
    }
    /**
     * å“åº”返回结果
     *
     *
     * @param result ç»“æžœ
     * @return æ“ä½œç»“æžœ
     */
    protected R<?> toAjax(boolean result)
    protected AjaxResult toAjax(boolean result)
    {
        return result ? success() : error();
    }
src/main/java/com/ruoyi/home/controller/HomeController.java
@@ -38,6 +38,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
/**
 * @author :yys
src/main/java/com/ruoyi/home/dto/StatisticsReceivablePayableDto.java
@@ -19,10 +19,10 @@
    @Schema(description = "应付金额")
    private BigDecimal payableMoney;
    @Schema(description = "预收金额")
    @Schema(description = "收款金额")
    private BigDecimal advanceMoney;
    @Schema(description = "预付金额")
    @Schema(description = "付款金额")
    private BigDecimal prepayMoney;
}
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
@@ -3,9 +3,20 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.ruoyi.account.mapper.AccountExpenseMapper;
import com.ruoyi.account.mapper.AccountIncomeMapper;
import com.ruoyi.account.pojo.AccountExpense;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.bean.dto.purchase.PurchaseInboundDto;
import com.ruoyi.account.bean.dto.purchase.PurchaseReturnDto;
import com.ruoyi.account.bean.dto.sales.SalesOutboundDto;
import com.ruoyi.account.bean.dto.sales.SalesReturnDto;
import com.ruoyi.account.bean.vo.purchase.PurchaseInboundVo;
import com.ruoyi.account.bean.vo.purchase.PurchaseReturnVo;
import com.ruoyi.account.bean.vo.sales.SalesOutboundVo;
import com.ruoyi.account.bean.vo.sales.SalesReturnVo;
import com.ruoyi.account.mapper.AccountStatementMapper;
import com.ruoyi.account.mapper.purchase.AccountPurchasePaymentMapper;
import com.ruoyi.account.mapper.sales.AccountSalesCollectionMapper;
import com.ruoyi.account.pojo.purchase.AccountPurchasePayment;
import com.ruoyi.account.pojo.sales.AccountSalesCollection;
import com.ruoyi.approve.mapper.ApproveProcessMapper;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.basic.mapper.CustomerMapper;
@@ -26,27 +37,27 @@
import com.ruoyi.home.dto.*;
import com.ruoyi.home.mapper.HomeMapper;
import com.ruoyi.home.service.HomeService;
import com.ruoyi.procurementrecord.mapper.ReturnManagementMapper;
import com.ruoyi.production.bean.dto.ProductionProductOutputDto;
import com.ruoyi.production.mapper.*;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.purchase.mapper.PaymentRegistrationMapper;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PaymentRegistration;
import com.ruoyi.purchase.mapper.PurchaseReturnOrdersMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.quality.mapper.QualityInspectMapper;
import com.ruoyi.quality.mapper.QualityUnqualifiedMapper;
import com.ruoyi.quality.pojo.QualityInspect;
import com.ruoyi.quality.pojo.QualityUnqualified;
import com.ruoyi.sales.mapper.ReceiptPaymentMapper;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.ReceiptPayment;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.stock.mapper.StockInRecordMapper;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.mapper.StockOutRecordMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -72,6 +83,10 @@
public class HomeServiceImpl implements HomeService {
    private final SalesLedgerMapper salesLedgerMapper;
    private final StockOutRecordMapper stockOutRecordMapper;
    private final ReturnManagementMapper returnManagementMapper;
    private final StockInRecordMapper stockInRecordMapper;
    private final PurchaseReturnOrdersMapper purchaseReturnOrdersMapper;
    private final PurchaseLedgerMapper purchaseLedgerMapper;
@@ -82,10 +97,6 @@
    private final QualityInspectMapper qualityStatisticsMapper;
    private final ApproveProcessMapper approveProcessMapper;
    private final ReceiptPaymentMapper receiptPaymentMapper;
    private final PaymentRegistrationMapper paymentRegistrationMapper;
    private final SysDeptMapper sysDeptMapper;
@@ -111,9 +122,9 @@
    private final ProductionOperationTaskMapper productionOperationTaskMapper;
    private final AccountExpenseMapper accountExpenseMapper;
    private final AccountIncomeMapper accountIncomeMapper;
    private final AccountPurchasePaymentMapper accountPurchasePaymentMapper;
    private final AccountSalesCollectionMapper accountSalesCollectionMapper;
    private final AccountStatementMapper accountStatementMapper;
    private final ProductionAccountMapper productionAccountMapper;
@@ -141,12 +152,8 @@
                            salesLedgers.stream().map(SalesLedger::getId).collect(Collectors.toList()));
            List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper
                    .selectList(salesLedgerProductMapperLambdaQueryWrapper);
            // æœªå¼€ç¥¨é‡‘额
            BigDecimal noInvoiceAmountTotal = salesLedgerProducts.stream().map(SalesLedgerProduct::getNoInvoiceAmount)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            homeBusinessDto.setMonthSaleMoney(contractAmount.setScale(2, RoundingMode.HALF_UP).toString());
            homeBusinessDto.setMonthSaleHaveMoney(noInvoiceAmountTotal.setScale(2, RoundingMode.HALF_UP).toString());
            homeBusinessDto.setMonthSaleHaveMoney(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP).toString());
        }
        // åˆ›å»ºLambdaQueryWrapper
        LambdaQueryWrapper<PurchaseLedger> queryWrapper = new LambdaQueryWrapper<>();
@@ -169,14 +176,8 @@
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            //  å¾…付款总金额
            BigDecimal unReceiptPaymentAmount = salesLedgerProductsCopy.stream()
                    .map(SalesLedgerProduct::getPendingTicketsTotal)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            homeBusinessDto.setMonthPurchaseMoney(receiveAmount.setScale(2, RoundingMode.HALF_UP).toString());
            homeBusinessDto.setMonthPurchaseHaveMoney(unReceiptPaymentAmount.setScale(2, RoundingMode.HALF_UP).toString());
            homeBusinessDto.setMonthPurchaseHaveMoney(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP).toString());
        }
        // ç»Ÿè®¡åº“å­˜
        BigDecimal stockQuantityTotal = stockInventoryMapper.selectTotal();
@@ -312,19 +313,35 @@
            queryWrapper.ge(QualityInspect::getCheckTime, monthStart.toString())
                    .le(QualityInspect::getCheckTime, monthEnd.toString());
            List<QualityInspect> monthInspects = qualityStatisticsMapper.selectList(queryWrapper);
            // ç»Ÿè®¡æ€»æ•°é‡ï¼ˆåˆæ ¼æ•°é‡ + ä¸åˆæ ¼æ•°é‡ï¼‰
            BigDecimal reduce = monthInspects.stream()
                    .filter(inspect -> inspect.getInspectType().equals(0))
                    .map(QualityInspect::getQuantity)
                    .map(inspect -> {
                        BigDecimal qualified = inspect.getQualifiedQuantity() != null ? inspect.getQualifiedQuantity() : BigDecimal.ZERO;
                        BigDecimal unqualified = inspect.getUnqualifiedQuantity() != null ? inspect.getUnqualifiedQuantity() : BigDecimal.ZERO;
                        return qualified.add(unqualified);
                    })
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            supplierNum = supplierNum.add(reduce);
            BigDecimal reduce1 = monthInspects.stream()
                    .filter(inspect -> inspect.getInspectType().equals(1))
                    .map(QualityInspect::getQuantity)
                    .map(inspect -> {
                        BigDecimal qualified = inspect.getQualifiedQuantity() != null ? inspect.getQualifiedQuantity() : BigDecimal.ZERO;
                        BigDecimal unqualified = inspect.getUnqualifiedQuantity() != null ? inspect.getUnqualifiedQuantity() : BigDecimal.ZERO;
                        return qualified.add(unqualified);
                    })
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            processNum = processNum.add(reduce1);
            BigDecimal reduce2 = monthInspects.stream()
                    .filter(inspect -> inspect.getInspectType().equals(2))
                    .map(QualityInspect::getQuantity)
                    .map(inspect -> {
                        BigDecimal qualified = inspect.getQualifiedQuantity() != null ? inspect.getQualifiedQuantity() : BigDecimal.ZERO;
                        BigDecimal unqualified = inspect.getUnqualifiedQuantity() != null ? inspect.getUnqualifiedQuantity() : BigDecimal.ZERO;
                        return qualified.add(unqualified);
                    })
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            factoryNum = factoryNum.add(reduce2);
@@ -334,25 +351,22 @@
            // 1. ä¾›åº”商检验(类型0)- åˆæ ¼æ•°é‡
            BigDecimal supplierQualified = monthInspects.stream()
                    .filter(inspect -> inspect.getInspectType().equals(0)
                            && "合格".equals(inspect.getCheckResult()))
                    .map(QualityInspect::getQuantity)
                    .filter(inspect -> inspect.getInspectType().equals(0))
                    .map(inspect -> inspect.getQualifiedQuantity() != null ? inspect.getQualifiedQuantity() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            item.setSupplierNum(supplierQualified);
            // 2. å·¥åºæ£€éªŒï¼ˆç±»åž‹1)- åˆæ ¼æ•°é‡
            BigDecimal processQualified = monthInspects.stream()
                    .filter(inspect -> inspect.getInspectType().equals(1)
                            && "合格".equals(inspect.getCheckResult()))
                    .map(QualityInspect::getQuantity)
                    .filter(inspect -> inspect.getInspectType().equals(1))
                    .map(inspect -> inspect.getQualifiedQuantity() != null ? inspect.getQualifiedQuantity() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            item.setProcessNum(processQualified);
            // 3. å·¥åŽ‚æ£€éªŒï¼ˆç±»åž‹2)- åˆæ ¼æ•°é‡
            BigDecimal factoryQualified = monthInspects.stream()
                    .filter(inspect -> inspect.getInspectType().equals(2)
                            && "合格".equals(inspect.getCheckResult()))
                    .map(QualityInspect::getQuantity)
                    .filter(inspect -> inspect.getInspectType().equals(2))
                    .map(inspect -> inspect.getQualifiedQuantity() != null ? inspect.getQualifiedQuantity() : BigDecimal.ZERO)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            item.setFactoryNum(factoryQualified);
@@ -414,76 +428,33 @@
     */
    @Override
    public StatisticsReceivablePayableDto statisticsReceivablePayable(Integer type) {
        LocalDate today = LocalDate.now();
        LocalDate startDate = null;
        LocalDate endDate = null;
        switch (type) {
            case 1:
                // èŽ·å–æœ¬å‘¨å‘¨ä¸€
                startDate = today.with(DayOfWeek.MONDAY);
                // èŽ·å–æœ¬å‘¨å‘¨æ—¥
                endDate = today.with(DayOfWeek.SUNDAY);
                break;
            case 2:
                startDate = today.with(TemporalAdjusters.firstDayOfMonth());
                endDate = today.with(TemporalAdjusters.lastDayOfMonth());
                break;
            case 3:
                Month currentMonth = today.getMonth();
                Month firstMonthOfQuarter = currentMonth.firstMonthOfQuarter();
                Month lastMonthOfQuarter = Month.of(firstMonthOfQuarter.getValue() + 2);
        StatisticsReceivablePayableDto dto = new StatisticsReceivablePayableDto();
        LocalDate[] range = resolveFinanceRange(type);
        LocalDate startDate = range[0];
        LocalDate endDate = range[1];
                startDate = today.withMonth(firstMonthOfQuarter.getValue())
                        .with(TemporalAdjusters.firstDayOfMonth());
                endDate = today.withMonth(lastMonthOfQuarter.getValue())
                        .with(TemporalAdjusters.lastDayOfMonth());
                break;
        }
        // åº”æ”¶
        List<SalesLedger> salesLedgers = salesLedgerMapper.selectList(new LambdaQueryWrapper<SalesLedger>()
                // .ge(SalesLedger::getEntryDate, startDate)
                // .lt(SalesLedger::getEntryDate, endDate)
        );
        // BigDecimal receivableMoney =
        // salesLedgers.stream().map(SalesLedger::getContractAmount).reduce(BigDecimal.ZERO,
        // BigDecimal::add);
        BigDecimal receivableMoney = sumAmount(salesLedgers, SalesLedger::getContractAmount);
        // åº”付
        List<PurchaseLedger> procurementRecords = purchaseLedgerMapper
                .selectList(new LambdaQueryWrapper<PurchaseLedger>()
                        // .ge(PurchaseLedger::getEntryDate, startDate)
                        // .lt(PurchaseLedger::getEntryDate, endDate)
                );
        // BigDecimal payableMoney =
        // procurementRecords.stream().map(PurchaseLedger::getContractAmount).reduce(BigDecimal.ZERO,
        // BigDecimal::add);
        BigDecimal payableMoney = sumAmount(procurementRecords, PurchaseLedger::getContractAmount);
        // é¢„æ”¶
        List<ReceiptPayment> receiptPayments = receiptPaymentMapper.selectList(new LambdaQueryWrapper<ReceiptPayment>()
                // .ge(ReceiptPayment::getReceiptPaymentDate, startDate)
                // .lt(ReceiptPayment::getReceiptPaymentDate, endDate)
        );
        // BigDecimal advanceMoney =
        // receiptPayments.stream().map(ReceiptPayment::getReceiptPaymentAmount).reduce(BigDecimal.ZERO,
        // BigDecimal::add);
        BigDecimal advanceMoney = sumAmount(receiptPayments, ReceiptPayment::getReceiptPaymentAmount);
        // é¢„付
        List<PaymentRegistration> paymentRegistrations = paymentRegistrationMapper
                .selectList(new LambdaQueryWrapper<PaymentRegistration>()
                        // .ge(PaymentRegistration::getPaymentDate, startDate)
                        // .lt(PaymentRegistration::getPaymentDate, endDate)
                );
        // BigDecimal prepayMoney =
        // paymentRegistrations.stream().map(PaymentRegistration::getCurrentPaymentAmount).reduce(BigDecimal.ZERO,
        // BigDecimal::add);
        BigDecimal prepayMoney = sumAmount(paymentRegistrations, PaymentRegistration::getCurrentPaymentAmount);
        StatisticsReceivablePayableDto statisticsReceivablePayableDto = new StatisticsReceivablePayableDto();
        statisticsReceivablePayableDto.setPayableMoney(payableMoney.subtract(prepayMoney));
        statisticsReceivablePayableDto.setReceivableMoney(receivableMoney.subtract(advanceMoney));
        statisticsReceivablePayableDto.setAdvanceMoney(advanceMoney);
        statisticsReceivablePayableDto.setPrepayMoney(prepayMoney);
        //销售出库
        BigDecimal receivableBase = sumSalesContractAmount(startDate, endDate);
        //销售退货
        BigDecimal salesReturnAmount = sumSalesReturnAmount(startDate, endDate);
        //采购入库
        BigDecimal payableBase = sumPurchaseContractAmount(startDate, endDate);
        //采购退货
        BigDecimal purchaseReturnAmount = sumPurchaseReturnAmount(startDate, endDate);
        //收款
        BigDecimal advanceMoney = sumCollectionAmount(startDate, endDate);
        //付款
        BigDecimal prepayMoney = sumPaymentAmount(startDate, endDate);
        return statisticsReceivablePayableDto;
        //应收金额=销售出库-销售退货
        dto.setReceivableMoney(scaleMoney(maxZero(receivableBase.subtract(salesReturnAmount))));
        //应付金额=采购入库-采购退货
        dto.setPayableMoney(scaleMoney(maxZero(payableBase.subtract(purchaseReturnAmount))));
        //收款金额=收款单
        dto.setAdvanceMoney(scaleMoney(advanceMoney));
        //付款金额=付款单
        dto.setPrepayMoney(scaleMoney(prepayMoney));
        return dto;
    }
    public static <T> BigDecimal sumAmount(List<T> list, java.util.function.Function<T, BigDecimal> amountExtractor) {
@@ -1164,13 +1135,9 @@
        String endStr = endDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        // 2. æŸ¥è¯¢æ•°æ®
        List<IncomeExpenseAnalysisDto> incomeList = accountIncomeMapper.selectIncomeStats(startStr, endStr, dateFormat);
        List<IncomeExpenseAnalysisDto> incomeList = accountSalesCollectionMapper.selectIncomeStats(startStr, endStr, dateFormat);
        List<IncomeExpenseAnalysisDto> expenseList = accountPurchasePaymentMapper.selectPayment(startStr, endStr, dateFormat);
        // List<IncomeExpenseAnalysisDto> purchaseList =
        // purchaseLedgerMapper.selectPurchaseStats(startStr, endStr, dateFormat);
        List<IncomeExpenseAnalysisDto> expenseList = accountExpenseMapper.selectAccountExpenseStats(startStr, endStr,
                dateFormat);
        // 3. è½¬ Map(自动合并)
        Map<String, BigDecimal> incomeMap = incomeList.stream()
@@ -1179,11 +1146,7 @@
                        IncomeExpenseAnalysisDto::getAmount,
                        BigDecimal::add));
        // Map<String, BigDecimal> purchaseMap = purchaseList.stream()
        // .collect(Collectors.toMap(
        // IncomeExpenseAnalysisDto::getDateStr,
        // IncomeExpenseAnalysisDto::getAmount,
        // BigDecimal::add));
        Map<String, BigDecimal> expenseMap = expenseList.stream()
                .collect(Collectors.toMap(
@@ -1197,18 +1160,12 @@
        for (String dateStr : xAxis) {
            Map<String, Object> item = new HashMap<>();
            item.put("date", dateStr);
            // æ”¶å…¥
            BigDecimal income = incomeMap.getOrDefault(dateStr, BigDecimal.ZERO);
            item.put("income", income.setScale(2, RoundingMode.HALF_UP));
            // æ”¯å‡º = é‡‡è´­ + è´¢åŠ¡æ”¯å‡º
            // BigDecimal purchase = purchaseMap.getOrDefault(dateStr, BigDecimal.ZERO);
            // æ”¯å‡º
            BigDecimal expense = expenseMap.getOrDefault(dateStr, BigDecimal.ZERO);
            // BigDecimal totalExpense = purchase.add(expense);
            BigDecimal totalExpense = expense;
            item.put("expense", totalExpense.setScale(2, RoundingMode.HALF_UP));
            item.put("expense", expense.setScale(2, RoundingMode.HALF_UP));
            result.add(item);
        }
@@ -1235,8 +1192,8 @@
        String startStr = startDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String endStr = endDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        List<IncomeExpenseAnalysisDto> incomeList = accountIncomeMapper.selectIncomeStats(startStr, endStr, dateFormat);
        List<IncomeExpenseAnalysisDto> expenseList = accountExpenseMapper.selectAccountExpenseStats(startStr, endStr, dateFormat);
        List<IncomeExpenseAnalysisDto> incomeList = accountSalesCollectionMapper.selectIncomeStats(startStr, endStr, dateFormat);
        List<IncomeExpenseAnalysisDto> expenseList = accountPurchasePaymentMapper.selectPayment(startStr, endStr, dateFormat);
        Map<String, BigDecimal> incomeMap = incomeList.stream().collect(Collectors
                .toMap(IncomeExpenseAnalysisDto::getDateStr, IncomeExpenseAnalysisDto::getAmount, BigDecimal::add));
@@ -1272,11 +1229,6 @@
            rawMaterialDto.setName("原材料");
            rawMaterialDto.setValue(rawMaterialAmount != null ? rawMaterialAmount.toString() : "0");
            result.add(rawMaterialDto);
            List<MapDto> expenseList = accountExpenseMapper.selectExpenseComposition(null, null);
            if (expenseList != null) {
                result.addAll(expenseList);
            }
        }
        BigDecimal total = BigDecimal.ZERO;
@@ -1311,161 +1263,55 @@
    @Override
    public MonthlyIncomeDto monthlyIncome() {
        MonthlyIncomeDto dto = new MonthlyIncomeDto();
        LocalDate now = LocalDate.now();
        YearMonth currentMonth = YearMonth.from(now);
        LocalDateTime startOfMonth = currentMonth.atDay(1).atStartOfDay();
        LocalDateTime endOfMonth = currentMonth.atEndOfMonth().atTime(23, 59, 59);
        LambdaQueryWrapper<SalesLedgerProduct> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SalesLedgerProduct::getType, 1);
        wrapper.ge(SalesLedgerProduct::getRegisterDate, startOfMonth);
        wrapper.le(SalesLedgerProduct::getRegisterDate, endOfMonth);
        List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(wrapper);
        if (CollectionUtils.isEmpty(products)) {
            return dto;
        }
        BigDecimal collected = products.stream()
                .map(SalesLedgerProduct::getInvoiceTotal)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        dto.setMonthlyIncome(collected);
        BigDecimal overdue = products.stream()
                .map(SalesLedgerProduct::getPendingInvoiceTotal)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        dto.setOverdueNum(overdue);
        BigDecimal total = collected.add(overdue);
        if (total.compareTo(BigDecimal.ZERO) > 0) {
            String collectionRate = collected.divide(total, 4, RoundingMode.HALF_UP)
                    .multiply(new BigDecimal("100"))
                    .setScale(2, RoundingMode.HALF_UP)
                    .toString();
            dto.setCollectionRate(collectionRate);
            String overdueRate = overdue.divide(total, 4, RoundingMode.HALF_UP)
                    .multiply(new BigDecimal("100"))
                    .setScale(2, RoundingMode.HALF_UP)
                    .toString();
            dto.setOverdueRate(overdueRate);
        }
        LocalDate today = LocalDate.now();
        YearMonth currentMonth = YearMonth.from(today);
        LocalDate startDate = currentMonth.atDay(1);
        LocalDate endDate = currentMonth.atEndOfMonth();
        //收款
        BigDecimal monthlyIncome = sumCollectionAmount(startDate, endDate);
        //销售出库
        BigDecimal receivableBase = sumSalesContractAmount(startDate, endDate);
        //销售退货
        BigDecimal salesReturnAmount = sumSalesReturnAmount(startDate, endDate);
        //回款率=收款/(销售出库-销售退货)应收
        String collectionRate = toRateString(monthlyIncome, receivableBase.subtract(salesReturnAmount));
        //逾期数=(销售出库金额-销售退货金额)应收金额-收款金额
        BigDecimal overdueAmount = receivableBase.subtract(salesReturnAmount).subtract(monthlyIncome);
        //逾期率=逾期数/应收金额
        String overdueRate = toRateString(overdueAmount, receivableBase);
        dto.setMonthlyIncome(scaleMoney(monthlyIncome));
        dto.setCollectionRate(collectionRate);
        dto.setOverdueNum(overdueAmount);
        dto.setOverdueRate(overdueRate);
        return dto;
    }
    @Override
    public MonthlyExpenditureDto monthlyExpenditure() {
        MonthlyExpenditureDto dto = new MonthlyExpenditureDto();
        LocalDate today = LocalDate.now();
        YearMonth currentMonth = YearMonth.from(today);
        LocalDate startDate = currentMonth.atDay(1);
        LocalDate endDate = currentMonth.atEndOfMonth();
        //支出
        BigDecimal monthlyExpenditure = sumPaymentAmount(startDate, endDate);
        //采购入库
        BigDecimal payableBase = sumPurchaseContractAmount(startDate, endDate);
        //采购退货
        BigDecimal purchaseReturnAmount = sumPurchaseReturnAmount(startDate, endDate);
        //付款率=付款/(采购入库-采购退货)应付
        String paymentRate = toRateString(monthlyExpenditure, payableBase.subtract(purchaseReturnAmount));
        //收款
        BigDecimal monthlyIncome = sumCollectionAmount(startDate, endDate);
        //毛利润= æ”¶æ¬¾-支出
        BigDecimal grossProfit = monthlyIncome.subtract(monthlyExpenditure);
        //利润率=毛利润/收款
        String profitMarginRate = toRateString(grossProfit, monthlyIncome);
        // å½“月时间范围
        LocalDate now = LocalDate.now();
        YearMonth currentMonth = YearMonth.from(now);
        LocalDateTime startOfMonth = currentMonth.atDay(1).atStartOfDay();
        LocalDateTime endOfMonth = currentMonth.atEndOfMonth().atTime(23, 59, 59);
        // é‡‡è´­å°è´¦ï¼ˆtype = 2)
        LambdaQueryWrapper<SalesLedgerProduct> purchaseWrapper = new LambdaQueryWrapper<>();
        purchaseWrapper.eq(SalesLedgerProduct::getType, 2);
        purchaseWrapper.ge(SalesLedgerProduct::getRegisterDate, startOfMonth);
        purchaseWrapper.le(SalesLedgerProduct::getRegisterDate, endOfMonth);
        List<SalesLedgerProduct> purchaseProducts = salesLedgerProductMapper.selectList(purchaseWrapper);
        BigDecimal rawMaterialCost = BigDecimal.ZERO; // åŽŸææ–™æˆæœ¬
        BigDecimal paidAmount = BigDecimal.ZERO; // å·²ä»˜æ¬¾é‡‘额
        BigDecimal pendingAmount = BigDecimal.ZERO; // å¾…付款金额
        if (!CollectionUtils.isEmpty(purchaseProducts)) {
            for (SalesLedgerProduct p : purchaseProducts) {
                if (p.getTaxInclusiveTotalPrice() != null) {
                    rawMaterialCost = rawMaterialCost.add(p.getTaxInclusiveTotalPrice());
                }
                if (p.getTicketsTotal() != null) {
                    paidAmount = paidAmount.add(p.getTicketsTotal());
                }
                if (p.getPendingTicketsTotal() != null) {
                    pendingAmount = pendingAmount.add(p.getPendingTicketsTotal());
                }
            }
        }
        // å…¶ä»–费用
        LambdaQueryWrapper<AccountExpense> expenseWrapper = new LambdaQueryWrapper<>();
        expenseWrapper.ge(AccountExpense::getExpenseDate,
                java.sql.Date.valueOf(currentMonth.atDay(1)));
        expenseWrapper.le(AccountExpense::getExpenseDate,
                java.sql.Date.valueOf(currentMonth.atEndOfMonth()));
        List<AccountExpense> expenses = accountExpenseMapper.selectList(expenseWrapper);
        BigDecimal otherExpense = BigDecimal.ZERO;
        if (!CollectionUtils.isEmpty(expenses)) {
            for (AccountExpense e : expenses) {
                if (e.getExpenseMoney() != null) {
                    otherExpense = otherExpense.add(e.getExpenseMoney());
                }
            }
        }
        // æœˆåº¦æ€»æ”¯å‡º
        // BigDecimal monthlyExpenditure = rawMaterialCost.add(otherExpense);
        BigDecimal monthlyExpenditure = otherExpense;
        dto.setMonthlyExpenditure(monthlyExpenditure);
        // å·²ä»˜æ¬¾ Ã·ï¼ˆå·²ä»˜æ¬¾ + å¾…付款)
        BigDecimal totalPayable = paidAmount.add(pendingAmount);
        if (totalPayable.compareTo(BigDecimal.ZERO) > 0) {
            String paymentRate = paidAmount
                    .divide(totalPayable, 4, RoundingMode.HALF_UP)
                    .multiply(BigDecimal.valueOf(100))
                    .setScale(2, RoundingMode.HALF_UP)
                    .toString();
            dto.setPaymentRate(paymentRate);
        }
        // å”®å°è´¦ï¼ˆtype = 1)
        LambdaQueryWrapper<SalesLedgerProduct> salesWrapper = new LambdaQueryWrapper<>();
        salesWrapper.eq(SalesLedgerProduct::getType, 1);
        salesWrapper.ge(SalesLedgerProduct::getRegisterDate, startOfMonth);
        salesWrapper.le(SalesLedgerProduct::getRegisterDate, endOfMonth);
        List<SalesLedgerProduct> salesProducts = salesLedgerProductMapper.selectList(salesWrapper);
        BigDecimal revenue = BigDecimal.ZERO;
        if (!CollectionUtils.isEmpty(salesProducts)) {
            for (SalesLedgerProduct s : salesProducts) {
                if (s.getInvoiceAmount() != null) {
                    revenue = revenue.add(s.getInvoiceAmount());
                }
            }
        }
        // æ¯›åˆ©æ¶¦ & åˆ©æ¶¦çއ
        if (revenue.compareTo(BigDecimal.ZERO) > 0) {
            // æ¯›åˆ©æ¶¦ = é”€å”®æ”¶å…¥ - åŽŸææ–™æˆæœ¬
            BigDecimal grossProfit = revenue.subtract(rawMaterialCost);
            dto.setGrossProfit(grossProfit);
            // åˆ©æ¶¦çއ = (销售收入 - æœˆåº¦æ€»æ”¯å‡º) / é”€å”®æ”¶å…¥
            BigDecimal profit = revenue.subtract(monthlyExpenditure);
            String profitMarginRate = profit
                    .divide(revenue, 4, RoundingMode.HALF_UP)
                    .multiply(BigDecimal.valueOf(100))
                    .setScale(2, RoundingMode.HALF_UP)
                    .toString();
            dto.setProfitMarginRate(profitMarginRate);
        }
        dto.setMonthlyExpenditure(scaleMoney(monthlyExpenditure));
        dto.setPaymentRate(paymentRate);
        dto.setGrossProfit(scaleMoney(grossProfit));
        dto.setProfitMarginRate(profitMarginRate);
        return dto;
    }
@@ -1904,11 +1750,8 @@
        BigDecimal unqualifiedCount = BigDecimal.ZERO;
        for (QualityInspect item : list) {
            if ("合格".equals(item.getCheckResult())) {
                qualifiedCount = qualifiedCount.add(item.getQuantity());
            } else {
                unqualifiedCount = unqualifiedCount.add(item.getQuantity());
            }
            qualifiedCount = qualifiedCount.add(item.getQualifiedQuantity() != null ? item.getQualifiedQuantity() : BigDecimal.ZERO);
            unqualifiedCount = unqualifiedCount.add(item.getUnqualifiedQuantity() != null ? item.getUnqualifiedQuantity() : BigDecimal.ZERO);
        }
        BigDecimal totalCount = qualifiedCount.add(unqualifiedCount);
@@ -2177,13 +2020,11 @@
                continue;
            }
            BigDecimal quantity = item.getQuantity();
            BigDecimal qualifiedQty = item.getQualifiedQuantity() != null ? item.getQualifiedQuantity() : BigDecimal.ZERO;
            BigDecimal unqualifiedQty = item.getUnqualifiedQuantity() != null ? item.getUnqualifiedQuantity() : BigDecimal.ZERO;
            if ("合格".equals(item.getCheckResult())) {
                dto.setQualifiedCount(dto.getQualifiedCount().add(quantity));
            } else {
                dto.setUnqualifiedCount(dto.getUnqualifiedCount().add(quantity));
            }
            dto.setQualifiedCount(dto.getQualifiedCount().add(qualifiedQty));
            dto.setUnqualifiedCount(dto.getUnqualifiedCount().add(unqualifiedQty));
        }
        // è®¡ç®—合格率
@@ -2224,14 +2065,12 @@
            BigDecimal unqualifiedCount = BigDecimal.ZERO;
            for (QualityInspect item : items) {
                BigDecimal qty = item.getQuantity();
                totalCount = totalCount.add(qty);
                BigDecimal qualifiedQty = item.getQualifiedQuantity() != null ? item.getQualifiedQuantity() : BigDecimal.ZERO;
                BigDecimal unqualifiedQty = item.getUnqualifiedQuantity() != null ? item.getUnqualifiedQuantity() : BigDecimal.ZERO;
                if ("合格".equals(item.getCheckResult())) {
                    qualifiedCount = qualifiedCount.add(qty);
                } else {
                    unqualifiedCount = unqualifiedCount.add(qty);
                }
                totalCount = totalCount.add(qualifiedQty.add(unqualifiedQty));
                qualifiedCount = qualifiedCount.add(qualifiedQty);
                unqualifiedCount = unqualifiedCount.add(unqualifiedQty);
            }
            if (totalCount.compareTo(BigDecimal.ZERO) == 0) {
@@ -2360,13 +2199,17 @@
        dto.setProcessNum(sumQuantity(qualityInspectList, 1)); // è¿‡ç¨‹
        dto.setFactoryNum(sumQuantity(qualityInspectList, 2)); // å‡ºåŽ‚
        // å‡è®¾ qualityInspectList æ˜¯ä¸€ä¸ª List<QualityInspect> ç±»åž‹çš„集合
        Map<String, List<QualityInspect>> groupedByCheckResult = qualityInspectList.stream()
                .collect(Collectors.groupingBy(QualityInspect::getCheckResult));
        List<QualityInspect> qualityInspects = groupedByCheckResult.get("不合格");
        if (ObjectUtils.isNull(qualityInspects) || qualityInspects.size() == 0) {
            return null;
        // æ ¹æ® unqualifiedQuantity > 0 ç­›é€‰ä¸åˆæ ¼è®°å½•
        List<QualityInspect> qualityInspects = qualityInspectList.stream()
                .filter(i -> i.getUnqualifiedQuantity() != null && i.getUnqualifiedQuantity().compareTo(BigDecimal.ZERO) > 0)
                .collect(Collectors.toList());
        if (ObjectUtils.isEmpty(qualityInspects)) {
            // å³ä½¿æ²¡æœ‰ä¸åˆæ ¼è®°å½•,也应该返回统计数据,只是图表项为空
            dto.setItem(new ArrayList<>());
            return dto;
        }
        // 4. å¤„理图表项 (Item)
        List<QualityStatisticsItem> itemList = new ArrayList<>();
@@ -2409,8 +2252,11 @@
    private BigDecimal sumQuantity(List<QualityInspect> list, Integer type) {
        return list.stream()
                .filter(i -> i.getInspectType().equals(type))
                .map(QualityInspect::getQuantity)
                .filter(Objects::nonNull)
                .map(i -> {
                    BigDecimal qualified = i.getQualifiedQuantity() != null ? i.getQualifiedQuantity() : BigDecimal.ZERO;
                    BigDecimal unqualified = i.getUnqualifiedQuantity() != null ? i.getUnqualifiedQuantity() : BigDecimal.ZERO;
                    return qualified.add(unqualified);
                })
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
@@ -2418,11 +2264,18 @@
        QualityStatisticsItem item = new QualityStatisticsItem();
        item.setDate(dateLabel);
        item.setSupplierNum(list.stream().filter(i -> i.getInspectType() == 0).map(QualityInspect::getQuantity)
        // ç»Ÿè®¡æ¯ç§æ£€éªŒç±»åž‹çš„不合格数量
        item.setSupplierNum(list.stream()
                .filter(i -> i.getInspectType() == 0)
                .map(i -> i.getUnqualifiedQuantity() != null ? i.getUnqualifiedQuantity() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add));
        item.setProcessNum(list.stream().filter(i -> i.getInspectType() == 1).map(QualityInspect::getQuantity)
        item.setProcessNum(list.stream()
                .filter(i -> i.getInspectType() == 1)
                .map(i -> i.getUnqualifiedQuantity() != null ? i.getUnqualifiedQuantity() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add));
        item.setFactoryNum(list.stream().filter(i -> i.getInspectType() == 2).map(QualityInspect::getQuantity)
        item.setFactoryNum(list.stream()
                .filter(i -> i.getInspectType() == 2)
                .map(i -> i.getUnqualifiedQuantity() != null ? i.getUnqualifiedQuantity() : BigDecimal.ZERO)
                .reduce(BigDecimal.ZERO, BigDecimal::add));
        return item;
@@ -2462,6 +2315,121 @@
        return productionOperationTaskMapper.calculateProductionStatistics(startDateTime, endDateTime, userId, processIds);
    }
    private LocalDate[] resolveFinanceRange(Integer type) {
        LocalDate today = LocalDate.now();
        int safeType = type == null ? 1 : type;
        LocalDate startDate;
        LocalDate endDate;
        switch (safeType) {
            case 1:
                startDate = today.with(DayOfWeek.MONDAY);
                endDate = today.with(DayOfWeek.SUNDAY);
                break;
            case 2:
                startDate = today.with(TemporalAdjusters.firstDayOfMonth());
                endDate = today.with(TemporalAdjusters.lastDayOfMonth());
                break;
            case 3:
                Month firstMonthOfQuarter = today.getMonth().firstMonthOfQuarter();
                startDate = LocalDate.of(today.getYear(), firstMonthOfQuarter, 1);
                endDate = startDate.plusMonths(2).with(TemporalAdjusters.lastDayOfMonth());
                break;
            default:
                startDate = today.with(DayOfWeek.MONDAY);
                endDate = today.with(DayOfWeek.SUNDAY);
                break;
        }
        return new LocalDate[]{startDate, endDate};
    }
    //计算日期内的销售出库金额
    private BigDecimal sumSalesContractAmount(LocalDate startDate, LocalDate endDate) {
        SalesOutboundDto salesOutboundDto = new SalesOutboundDto();
        salesOutboundDto.setStartDate(startDate);
        salesOutboundDto.setEndDate(endDate);
        List<SalesOutboundVo> salesOutboundVos = stockOutRecordMapper.listPageAccountSales(new Page(1, -1), salesOutboundDto).getRecords();
        return sumAmount(salesOutboundVos, SalesOutboundVo::getOutboundAmount);
    }
    //计算日期内的销售退货金额
    private BigDecimal sumSalesReturnAmount(LocalDate startDate, LocalDate endDate) {
        SalesReturnDto salesReturnDto = new SalesReturnDto();
        salesReturnDto.setStartDate(startDate);
        salesReturnDto.setEndDate(endDate);
        List<SalesReturnVo> salesReturnVos = returnManagementMapper.listPageAccountSalesReturn(new Page(1, -1), salesReturnDto).getRecords();
        return sumAmount(salesReturnVos, SalesReturnVo::getRefundAmount);
    }
    //计算日期内的采购入库金额
    private BigDecimal sumPurchaseContractAmount(LocalDate startDate, LocalDate endDate) {
        PurchaseInboundDto purchaseInboundDto = new PurchaseInboundDto();
        purchaseInboundDto.setStartDate(startDate);
        purchaseInboundDto.setEndDate(endDate);
        List<PurchaseInboundVo> purchaseInboundVos = stockInRecordMapper.listPageAccountPurchase(new Page(1, -1), purchaseInboundDto).getRecords();
        return sumAmount(purchaseInboundVos, PurchaseInboundVo::getInboundAmount);
    }
    //计算日期内的采购退货金额
    private BigDecimal sumPurchaseReturnAmount(LocalDate startDate, LocalDate endDate) {
        PurchaseReturnDto purchaseReturnDto = new PurchaseReturnDto();
        purchaseReturnDto.setStartDate(startDate);
        purchaseReturnDto.setEndDate(endDate);
        List<PurchaseReturnVo> purchaseReturnVos = purchaseReturnOrdersMapper.listPageAccountPurchaseReturn(new Page(1, -1), purchaseReturnDto).getRecords();
        return sumAmount(purchaseReturnVos, PurchaseReturnVo::getTotalAmount);
    }
    //计算日期内的总收款金额
    private BigDecimal sumCollectionAmount(LocalDate startDate, LocalDate endDate) {
        List<AccountSalesCollection> collections = defaultList(accountSalesCollectionMapper.selectList(
                new LambdaQueryWrapper<AccountSalesCollection>()
                        .ge(AccountSalesCollection::getCollectionDate, startDate)
                        .le(AccountSalesCollection::getCollectionDate, endDate)));
        return sumAmount(collections, AccountSalesCollection::getCollectionAmount);
    }
    //计算日期内的总付款金额
    private BigDecimal sumPaymentAmount(LocalDate startDate, LocalDate endDate) {
        List<AccountPurchasePayment> payments = defaultList(accountPurchasePaymentMapper.selectList(
                new LambdaQueryWrapper<AccountPurchasePayment>()
                        .ge(AccountPurchasePayment::getPaymentDate, startDate)
                        .le(AccountPurchasePayment::getPaymentDate, endDate)));
        return sumAmount(payments, AccountPurchasePayment::getPaymentAmount);
    }
    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 toRateString(BigDecimal numerator, BigDecimal denominator) {
        if (denominator == null || denominator.compareTo(BigDecimal.ZERO) <= 0) {
            return "0.00";
        }
        return defaultDecimal(numerator)
                .divide(denominator, 4, RoundingMode.HALF_UP)
                .multiply(BigDecimal.valueOf(100))
                .setScale(2, RoundingMode.HALF_UP)
                .toString();
    }
    private BigDecimal maxZero(BigDecimal value) {
        if (value == null) {
            return BigDecimal.ZERO;
        }
        return value.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : value;
    }
    private BigDecimal scaleMoney(BigDecimal value) {
        return defaultDecimal(value).setScale(2, RoundingMode.HALF_UP);
    }
    private <T> List<T> defaultList(List<T> list) {
        return list == null ? List.of() : list;
    }
    private BigDecimal defaultDecimal(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
@@ -67,6 +67,7 @@
        if (StringUtils.isNotBlank(inspectionTaskDto.getInspectionProject())) {
            queryWrapper.like(InspectionTask::getInspectionProject, inspectionTaskDto.getInspectionProject());
        }
        queryWrapper.orderByDesc(InspectionTask::getCreateTime);
        IPage<InspectionTask> entityPage = inspectionTaskMapper.selectPage(page, queryWrapper);
        //  æ— æ•°æ®æå‰è¿”回
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -53,6 +53,7 @@
        if (timingTask.getIsEnabled() != null) {
            queryWrapper.eq(TimingTask::getIsEnabled, timingTask.getIsEnabled());
        }
        queryWrapper.orderByDesc(TimingTask::getCreateTime);
        IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, queryWrapper);
        // 2. å¦‚果没有数据,直接返回空分页
src/main/java/com/ruoyi/lavorissue/controller/LavorIssueController.java
@@ -11,7 +11,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.lavorissue.dto.StatisticsLaborIssue;
import com.ruoyi.lavorissue.mapper.LavorIssueMapper;
import com.ruoyi.lavorissue.pojo.LaborIssue;
@@ -52,24 +52,24 @@
    @GetMapping("/listPage")
    @Log(title = "劳保发放-分页查询", businessType = BusinessType.OTHER)
    @Operation(summary = "劳保发放-分页查询")
    public R<?> listPage(Page page, LaborIssue laborIssue){
    public AjaxResult listPage(Page page, LaborIssue laborIssue){
        IPage<LaborIssue> listPage = laborIssueService.listPage(page, laborIssue);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
    @GetMapping("/statisticsList")
    @Log(title = "劳保发放-统计查询", businessType = BusinessType.OTHER)
    @Operation(summary = "劳保发放-统计查询")
    public R<?> statisticsList(LaborIssue laborIssue){
    public AjaxResult statisticsList(LaborIssue laborIssue){
        List<Map<String, Object>> listPage = laborIssueService.statisticsList(laborIssue);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
    @PostMapping("/add")
    @Log(title = "劳保发放-添加", businessType = BusinessType.INSERT)
    @Operation(summary = "劳保发放-添加")
    @Transactional(rollbackFor = Exception.class)
    public R<?> add(@RequestBody LaborIssue laborIssue){
    public AjaxResult add(@RequestBody LaborIssue laborIssue){
        String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        StartAndEndDateDto dateTime = DailyRedisCounter.getDateTime();
        Long approveId = lavorIssueMapper.selectCount(new LambdaQueryWrapper<LaborIssue>()
@@ -84,32 +84,32 @@
            laborIssue.setOrderNo(String.format("%03d", l + 1));
        }
        boolean save = laborIssueService.save(laborIssue);
        return save ? R.ok() : R.fail();
        return save ? AjaxResult.success() : AjaxResult.error();
    }
    @PostMapping("/update")
    @Log(title = "劳保发放-修改", businessType = BusinessType.UPDATE)
    @Operation(summary = "劳保发放-修改")
    @Transactional(rollbackFor = Exception.class)
    public R<?> update(@RequestBody LaborIssue laborIssue){
    public AjaxResult update(@RequestBody LaborIssue laborIssue){
        boolean update = laborIssueService.updateById(laborIssue);
        return update ? R.ok() : R.fail();
        return update ? AjaxResult.success() : AjaxResult.error();
    }
    @DeleteMapping("/delete")
    @Log(title = "劳保发放-删除", businessType = BusinessType.DELETE)
    @Operation(summary = "劳保发放-删除")
    @Transactional(rollbackFor = Exception.class)
    public R<?> delete(@RequestBody List<Long> ids){
    public AjaxResult delete(@RequestBody List<Long> ids){
        boolean delete = laborIssueService.removeBatchByIds(ids);
        return delete ? R.ok() : R.fail();
        return delete ? AjaxResult.success() : AjaxResult.error();
    }
    @GetMapping("/statistics")
    @Operation(summary = "劳保发放-统计")
    public R<?> statistics(StatisticsLaborIssue req) throws Exception {
    public AjaxResult statistics(StatisticsLaborIssue req) throws Exception {
        StatisticsLaborIssue statisticsLaborIssue = laborIssueService.statistics(req);
        return R.ok(statisticsLaborIssue);
        return AjaxResult.success(statisticsLaborIssue);
    }
src/main/java/com/ruoyi/measuringinstrumentledger/controller/MeasuringInstrumentLedgerController.java
@@ -6,7 +6,7 @@
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.measuringinstrumentledger.dto.MeasuringInstrumentLedgerDto;
import com.ruoyi.measuringinstrumentledger.mapper.MeasuringInstrumentLedgerRecordMapper;
import com.ruoyi.measuringinstrumentledger.pojo.MeasuringInstrumentLedger;
@@ -43,9 +43,9 @@
    @GetMapping("/listPage")
    @Operation(summary = "计量器具台账-分页查询")
    @Log(title = "计量器具台账-分页查询", businessType = BusinessType.OTHER)
    public R<?> listPage(Page page, MeasuringInstrumentLedger measuringInstrumentLedger) {
    public AjaxResult listPage(Page page, MeasuringInstrumentLedger measuringInstrumentLedger) {
        IPage<MeasuringInstrumentLedger> listPage = measuringInstrumentLedgerService.listPage(page, measuringInstrumentLedger);
        return R.ok(listPage);
        return AjaxResult.success(listPage);
    }
@@ -53,59 +53,59 @@
    @Operation(summary = "计量器具台账-新增")
    @Log(title = "计量器具台账-新增", businessType = BusinessType.INSERT)
    @Transactional(rollbackFor = Exception.class)
    public R<?> add(@RequestBody MeasuringInstrumentLedger measuringInstrumentLedger) throws IOException {
    public AjaxResult add(@RequestBody MeasuringInstrumentLedger measuringInstrumentLedger) throws IOException {
        boolean save = measuringInstrumentLedgerService.add(measuringInstrumentLedger);
        if (save) {
            return R.ok();
            return AjaxResult.success();
        }
        return R.fail();
        return AjaxResult.error();
    }
    @PostMapping("/update")
    @Operation(summary = "计量器具台账-修改")
    @Log(title = "计量器具台账-修改", businessType = BusinessType.UPDATE)
    @Transactional(rollbackFor = Exception.class)
    public R<?> update(@RequestBody MeasuringInstrumentLedger measuringInstrumentLedger) {
    public AjaxResult update(@RequestBody MeasuringInstrumentLedger measuringInstrumentLedger) {
        SysUser sysUser = sysUserMapper.selectUserById(measuringInstrumentLedger.getUserId());
        if (sysUser == null) {
            return R.fail("用户不存在");
            return AjaxResult.error("用户不存在");
        }
        measuringInstrumentLedger.setUserName(sysUser.getUserName());
        boolean update = measuringInstrumentLedgerService.updateById(measuringInstrumentLedger);
        if (update) {
            return R.ok();
            return AjaxResult.success();
        }
        return R.fail();
        return AjaxResult.error();
    }
    @DeleteMapping("/delete")
    @Operation(summary = "计量器具台账-删除")
    @Log(title = "计量器具台账-删除", businessType = BusinessType.DELETE)
    @Transactional(rollbackFor = Exception.class)
    public R<?> delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)) return R.fail("请选择至少一条数据");
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请选择至少一条数据");
        for (Long id : ids) {
            LambdaQueryWrapper<MeasuringInstrumentLedgerRecord> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(MeasuringInstrumentLedgerRecord::getMeasuringInstrumentLedgerId,id);
            List<MeasuringInstrumentLedgerRecord> measuringInstrumentLedgerRecords = measuringInstrumentLedgerRecordMapper.selectList(queryWrapper);
            if(!CollectionUtils.isEmpty(measuringInstrumentLedgerRecords)){
                return R.fail("请先删除选中计量器具台账下的所有检定记录");
                return AjaxResult.error("请先删除选中计量器具台账下的所有检定记录");
            }
        }
        boolean delete = measuringInstrumentLedgerService.removeBatchByIds(ids);
        if (delete) {
            return R.ok();
            return AjaxResult.success();
        }
        return R.fail();
        return AjaxResult.error();
    }
    @PostMapping("/verifying")
    @Operation(summary = "计量器具台账-检定")
    @Log(title = "计量器具台账-检定", businessType = BusinessType.UPDATE)
    @Transactional(rollbackFor = Exception.class)
    public R<?> verifying(@RequestBody MeasuringInstrumentLedgerDto measuringInstrumentLedger) throws IOException {
    public AjaxResult verifying(@RequestBody MeasuringInstrumentLedgerDto measuringInstrumentLedger) throws IOException {
        boolean update = measuringInstrumentLedgerService.verifying(measuringInstrumentLedger);
        return update ? R.ok(null, "检定成功") : R.fail("检定失败");
        return update ? AjaxResult.success("检定成功") : AjaxResult.error("检定失败");
    }
    /**
在上述文件截断后对比
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/officesupplies/controller/OfficeSuppliesController.java src/main/java/com/ruoyi/officesupplies/service/OfficeSuppliesService.java src/main/java/com/ruoyi/officesupplies/service/impl/OfficeSuppliesServiceImpl.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/pojo/CustomStorage.java src/main/java/com/ruoyi/procurementrecord/pojo/ReturnManagement.java src/main/java/com/ruoyi/procurementrecord/service/ProcurementRecordService.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/production/controller/ProductionOrderRoutingController.java src/main/java/com/ruoyi/production/pojo/ProductionOrder.java src/main/java/com/ruoyi/production/service/ProductionOrderRoutingOperationService.java src/main/java/com/ruoyi/production/service/impl/ProductionBomStructureServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionOrderServiceImpl.java src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java src/main/java/com/ruoyi/project/common/CaptchaController.java src/main/java/com/ruoyi/project/monitor/controller/CacheController.java src/main/java/com/ruoyi/project/monitor/controller/ServerController.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/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/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/mapper/SysUserDeptMapper.java src/main/java/com/ruoyi/project/system/service/ISysUserService.java src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java src/main/java/com/ruoyi/project/system/service/impl/SysUserServiceImpl.java src/main/java/com/ruoyi/project/tool/gen/controller/GenController.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/pojo/Roles.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/PurchaseReturnOrdersController.java src/main/java/com/ruoyi/purchase/controller/TicketRegistrationController.java (已删除) src/main/java/com/ruoyi/purchase/dto/InvoicePurchaseDto.java (已删除) src/main/java/com/ruoyi/purchase/dto/InvoicePurchaseReportDto.java (已删除) src/main/java/com/ruoyi/purchase/dto/PaymentRegistrationDto.java (已删除) src/main/java/com/ruoyi/purchase/dto/ProductRecordDto.java (已删除) src/main/java/com/ruoyi/purchase/dto/PurchaseLedgerDto.java src/main/java/com/ruoyi/purchase/dto/TicketRegistrationDto.java (已删除) src/main/java/com/ruoyi/purchase/dto/VatDto.java src/main/java/com/ruoyi/purchase/mapper/InvoicePurchaseMapper.java (已删除) src/main/java/com/ruoyi/purchase/mapper/PaymentRegistrationMapper.java (已删除) src/main/java/com/ruoyi/purchase/mapper/ProductRecordMapper.java (已删除) src/main/java/com/ruoyi/purchase/mapper/PurchaseLedgerMapper.java src/main/java/com/ruoyi/purchase/mapper/TicketRegistrationMapper.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/PurchaseReturnOrders.java src/main/java/com/ruoyi/purchase/pojo/TicketRegistration.java (已删除) src/main/java/com/ruoyi/purchase/service/IInvoicePurchaseService.java (已删除) src/main/java/com/ruoyi/purchase/service/IPaymentRegistrationService.java (已删除) src/main/java/com/ruoyi/purchase/service/IProductRecordService.java (已删除) src/main/java/com/ruoyi/purchase/service/IPurchaseLedgerService.java src/main/java/com/ruoyi/purchase/service/ITicketRegistrationService.java (已删除) src/main/java/com/ruoyi/purchase/service/PurchaseReportService.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/ProductRecordServiceImpl.java (已删除) src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java src/main/java/com/ruoyi/purchase/service/impl/PurchaseReportServiceImpl.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/PurchaseReportVo.java src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsDetailsVo.java src/main/java/com/ruoyi/purchase/vo/SupplierTransactionsVo.java src/main/java/com/ruoyi/quality/controller/QualityInspectFileController.java src/main/java/com/ruoyi/quality/controller/QualityTestStandardParamController.java src/main/java/com/ruoyi/quality/pojo/QualityInspect.java src/main/java/com/ruoyi/quality/service/impl/QualityInspectServiceImpl.java src/main/java/com/ruoyi/quality/utils/QualityInspectHelper.java src/main/java/com/ruoyi/sales/controller/CommonFileController.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/ShippingInfoController.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/SalesQuotationDto.java src/main/java/com/ruoyi/sales/dto/ShippingInfoDto.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/InvoiceLedgerFileMapper.java (已删除) src/main/java/com/ruoyi/sales/mapper/InvoiceLedgerMapper.java (已删除) src/main/java/com/ruoyi/sales/mapper/InvoiceRegistrationMapper.java (已删除) src/main/java/com/ruoyi/sales/mapper/InvoiceRegistrationProductMapper.java (已删除) src/main/java/com/ruoyi/sales/mapper/ReceiptPaymentMapper.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/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/PaymentShipping.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/ShippingInfo.java src/main/java/com/ruoyi/sales/service/ISalesLedgerProductService.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/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/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/ShippingInfoServiceImpl.java src/main/java/com/ruoyi/sales/vo/CustomerTransactionsDetailsVo.java src/main/java/com/ruoyi/sales/vo/CustomerTransactionsVo.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/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/StaffOnJobDto.java src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java src/main/java/com/ruoyi/staff/service/IStaffOnJobService.java src/main/java/com/ruoyi/staff/service/SchemeApplicableStaffService.java src/main/java/com/ruoyi/staff/service/StaffSalaryMainService.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/stock/controller/StockInRecordController.java src/main/java/com/ruoyi/stock/controller/StockOutRecordController.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/pojo/StockInRecord.java src/main/java/com/ruoyi/stock/pojo/StockOutRecord.java src/main/java/com/ruoyi/stock/service/StockInRecordService.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/controller/TechnologyOperationController.java src/main/java/com/ruoyi/technology/controller/TechnologyOperationParamController.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/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/mapper/DocumentationFileMapper.java src/main/java/com/ruoyi/warehouse/service/DocumentationFileService.java src/main/java/com/ruoyi/waterrecord/controller/WaterRecordController.java src/main/java/com/ruoyi/waterrecord/service/WaterRecordService.java src/main/java/com/ruoyi/waterrecord/service/impl/WaterRecordServiceImpl.java src/main/resources/application-ckgm.yml src/main/resources/application-dev.yml src/main/resources/application-hqjc.yml src/main/resources/financial-agent-prompt.txt src/main/resources/mapper/account/AccountExpenseMapper.xml (已删除) src/main/resources/mapper/account/AccountFileMapper.xml (已删除) src/main/resources/mapper/account/AccountIncomeMapper.xml (已删除) src/main/resources/mapper/account/AccountStatementMapper.xml src/main/resources/mapper/account/BorrowInfoMapper.xml (已删除) src/main/resources/mapper/account/purchase/AccountPaymentApplicationMapper.xml src/main/resources/mapper/account/purchase/AccountPurchaseInvoiceMapper.xml src/main/resources/mapper/account/purchase/AccountPurchasePaymentMapper.xml src/main/resources/mapper/account/sales/AccountInvoiceApplicationMapper.xml src/main/resources/mapper/account/sales/AccountSalesCollectionMapper.xml src/main/resources/mapper/account/sales/AccountSalesInvoiceMapper.xml src/main/resources/mapper/aftersalesservice/AfterSalesNearExpiryMapper.xml src/main/resources/mapper/aftersalesservice/AfterSalesServiceMapper.xml src/main/resources/mapper/approve/ApprovalInstanceMapper.xml src/main/resources/mapper/approve/ApprovalInstanceNodeMapper.xml src/main/resources/mapper/approve/ApprovalRecordMapper.xml src/main/resources/mapper/approve/ApprovalTaskMapper.xml src/main/resources/mapper/approve/ApprovalTemplateMapper.xml src/main/resources/mapper/approve/ApprovalTemplateNodeApproverMapper.xml src/main/resources/mapper/approve/ApprovalTemplateNodeMapper.xml src/main/resources/mapper/approve/ApproveProcessMapper.xml src/main/resources/mapper/approve/FinReimbursementDetailMapper.xml src/main/resources/mapper/approve/FinReimbursementMapper.xml src/main/resources/mapper/approve/FinReimbursementTravelMapper.xml src/main/resources/mapper/approve/KnowledgeBaseMapper.xml src/main/resources/mapper/basic/CustomerMapper.xml src/main/resources/mapper/basic/ProductModelMapper.xml src/main/resources/mapper/basic/SupplierManageMapper.xml src/main/resources/mapper/collaborativeApproval/EnterpriseNewsMapper.xml src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeDeptMapper.xml src/main/resources/mapper/collaborativeApproval/EnterpriseNewsScopeUserMapper.xml src/main/resources/mapper/collaborativeApproval/NoticeMapper.xml src/main/resources/mapper/collaborativeApproval/RulesRegulationsManagementMapper.xml src/main/resources/mapper/collaborativeApproval/SealApplicationManagementMapper.xml src/main/resources/mapper/device/DeviceMaintenanceMapper.xml src/main/resources/mapper/device/DeviceRepairMapper.xml src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerRecordMapper.xml src/main/resources/mapper/measuringinstrumentledger/SparePartsMapper.xml src/main/resources/mapper/measuringinstrumentledger/SparePartsRequisitionRecordMapper.xml src/main/resources/mapper/procurementrecord/ReturnManagementMapper.xml src/main/resources/mapper/production/ProductionOperationTaskMapper.xml src/main/resources/mapper/purchase/InvoicePurchaseMapper.xml (已删除) src/main/resources/mapper/purchase/PaymentRegistrationMapper.xml (已删除) src/main/resources/mapper/purchase/ProductRecordMapper.xml (已删除) src/main/resources/mapper/purchase/PurchaseLedgerMapper.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/quality/QualityUnqualifiedMapper.xml src/main/resources/mapper/sales/InvoiceLedgerMapper.xml (已删除) src/main/resources/mapper/sales/InvoiceRegistrationMapper.xml (已删除) src/main/resources/mapper/sales/InvoiceRegistrationProductMapper.xml (已删除) src/main/resources/mapper/sales/ReceiptPaymentMapper.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/staff/PersonalAttendanceRecordsMapper.xml src/main/resources/mapper/staff/PersonalShiftMapper.xml src/main/resources/mapper/staff/StaffLeaveMapper.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/system/SysDeptMapper.xml src/main/resources/mapper/system/SysDictTypeMapper.xml src/main/resources/mapper/system/SysNoticeMapper.xml src/main/resources/mapper/system/SysPostMapper.xml src/main/resources/mapper/system/SysUserMapper.xml src/main/resources/mapper/technology/TechnologyOperationMapper.xml src/main/resources/mapper/technology/TechnologyOperationParamMapper.xml src/main/resources/mapper/warehouse/DocumentationBorrowManagementMapper.xml src/main/resources/mapper/warehouse/DocumentationMapper.xml src/main/resources/mapper/warehouse/DocumentationReturnManagementMapper.xml src/main/resources/mapper/warehouse/WarehouseGoodsShelvesRowcolMapper.xml