From 8d8a8cc30ce54ebd5ac59d1ee5339ec9ec3dd226 Mon Sep 17 00:00:00 2001 From: wuzhen <52326163@qq.com> Date: Mon, 4 Aug 2025 13:38:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mybatis/config/MybatisPlusConfig.java | 4 +- .../CwApprovalRecordController.java | 74 +- .../CwProjectApplicationController.java | 57 +- .../cw/domain/vo/CwProjectApplicationVo.java | 2 +- .../cw/mapper/CwApprovalRecordMapper.java | 24 + .../cw/service/ICwApprovalRecordService.java | 32 + .../service/ICwProjectApplicationService.java | 22 +- .../impl/CwApprovalRecordServiceImpl.java | 408 +- .../impl/CwProjectApplicationServiceImpl.java | 273 + .../mapper/CwApprovalRecordMapper.xml | 23 + .../code-adjust-record-2025-01-18-wuzhen.md | 1 + ...rd-2025-01-27-cursor-approval-logic-fix.md | 263 + ...01-27-cursor-approval-record-import-fix.md | 192 + ...-01-27-cursor-approval-status-final-fix.md | 155 + ...-27-cursor-approval-workflow-adjustment.md | 172 + ...01-27-cursor-approval-workflow-redesign.md | 303 + ...025-01-27-cursor-budget-calculation-fix.md | 201 + ...compilation-error-fix-approval-nickname.md | 129 + ...7-cursor-compilation-error-tenantid-fix.md | 132 + ...cord-2025-01-27-cursor-complete-rewrite.md | 302 + ...ord-2025-01-27-cursor-dept-approval-fix.md | 229 + ...-27-cursor-dept-approval-simplification.md | 296 + ...ord-2025-01-27-cursor-detail-dialog-fix.md | 270 + ...ord-2025-01-27-cursor-detail-fix-revert.md | 182 + ...ord-2025-01-27-cursor-field-mapping-fix.md | 150 + ...25-01-27-cursor-finance-approval-module.md | 169 + ...ecord-2025-01-27-cursor-simple-redirect.md | 194 + .../code-adjust-record-2025-01-27-cursor.md | 5007 ++++++++++++++++- ...25-01-27-jwc001-approval-permission-fix.md | 166 + ...ode-adjust-record-2025-01-27-statistics.md | 123 + .../code-adjust-record-2025-01-27-wuzhen.md | 77 + .../code-adjust-record-2025-01-28-wuzhen.md | 42 + .../code-adjust-record-2025-07-26-wuzhen.md | 74 + ...cord-2025-08-02-cursor-approval-api-fix.md | 207 +- ...t-record-2025-08-02-cursor-approval-fix.md | 189 +- ...025-08-02-cursor-approval-record-ui-fix.md | 265 +- ...-02-cursor-approval-records-cleanup-fix.md | 249 +- ...02-cursor-backend-status-validation-fix.md | 176 +- ...25-08-02-cursor-compilation-error-fix-2.md | 187 +- ...25-08-02-cursor-leader-approval-feature.md | 294 +- ...d-2025-08-02-cursor-returned-status-fix.md | 159 +- ...2025-08-02-cursor-sql-deleted-field-fix.md | 290 +- .../code-adjust-record-2025-08-04-wuzhen.md | 202 + script/fix-jwc001-approval-permissions.sql | 181 + script/sql/cw-finance-approval-menu.sql | 34 + script/sql/cw-statistics-menu.sql | 60 + web/src/api/cw/approvalRecord.ts | 47 +- web/src/api/cw/financeApproval.ts | 46 + web/src/api/cw/statistics.ts | 41 + web/src/views/cw/application/form.vue | 106 +- web/src/views/cw/application/index.vue | 114 +- web/src/views/cw/approvalRecord/index.vue | 989 +--- web/src/views/cw/financeApproval/index.vue | 685 +++ web/src/views/cw/leaderApproval/index.vue | 23 +- web/src/views/cw/statistics/index.vue | 674 +++ 工程本地启动指导.md | 2 +- 56 files changed, 13727 insertions(+), 1241 deletions(-) create mode 100644 docs/变更管理/code-adjust-record-2025-01-18-wuzhen.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-approval-logic-fix.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-approval-record-import-fix.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-approval-status-final-fix.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-approval-workflow-adjustment.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-approval-workflow-redesign.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-budget-calculation-fix.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-compilation-error-fix-approval-nickname.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-compilation-error-tenantid-fix.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-complete-rewrite.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-dept-approval-fix.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-dept-approval-simplification.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-detail-dialog-fix.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-detail-fix-revert.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-field-mapping-fix.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-finance-approval-module.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-cursor-simple-redirect.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-jwc001-approval-permission-fix.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-statistics.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-27-wuzhen.md create mode 100644 docs/变更管理/code-adjust-record-2025-01-28-wuzhen.md create mode 100644 docs/变更管理/code-adjust-record-2025-07-26-wuzhen.md create mode 100644 docs/变更管理/code-adjust-record-2025-08-04-wuzhen.md create mode 100644 script/fix-jwc001-approval-permissions.sql create mode 100644 script/sql/cw-finance-approval-menu.sql create mode 100644 script/sql/cw-statistics-menu.sql create mode 100644 web/src/api/cw/financeApproval.ts create mode 100644 web/src/api/cw/statistics.ts create mode 100644 web/src/views/cw/financeApproval/index.vue create mode 100644 web/src/views/cw/statistics/index.vue diff --git a/backend/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java b/backend/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java index 00c26912..41034412 100644 --- a/backend/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java +++ b/backend/ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/config/MybatisPlusConfig.java @@ -28,7 +28,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement; * @author Lion Li */ @EnableTransactionManagement(proxyTargetClass = true) -@MapperScan("${mybatis-plus.mapperPackage}") +@MapperScan("org.dromara.**.mapper") @PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class) public class MybatisPlusConfig { @@ -54,7 +54,7 @@ public class MybatisPlusConfig { * 数据权限拦截器 */ public PlusDataPermissionInterceptor dataPermissionInterceptor() { - return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage")); + return new PlusDataPermissionInterceptor("org.dromara.**.mapper"); } /** diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwApprovalRecordController.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwApprovalRecordController.java index e88053f7..c38ae1c7 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwApprovalRecordController.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwApprovalRecordController.java @@ -111,6 +111,46 @@ public class CwApprovalRecordController extends BaseController { return R.ok(list); } + /** + * 查询财务待审批申报列表 + */ + @SaCheckPermission("cw:approval:finance") + @GetMapping("/finance/pending") + public R> getFinancePendingList() { + List list = approvalRecordService.queryFinancePendingApprovals(); + return R.ok(list); + } + + /** + * 查询财务已审批申报列表 + */ + @SaCheckPermission("cw:approval:finance") + @GetMapping("/finance/approved") + public R> getFinanceApprovedList() { + List list = approvalRecordService.queryFinanceApprovedRecords(); + return R.ok(list); + } + + /** + * 查询学院领导待审批申报列表 + */ + @SaCheckPermission("cw:approval:leader") + @GetMapping("/leader/pending") + public R> getLeaderPendingList() { + List list = approvalRecordService.queryLeaderPendingApprovals(); + return R.ok(list); + } + + /** + * 查询学院领导已审批申报列表 + */ + @SaCheckPermission("cw:approval:leader") + @GetMapping("/leader/approved") + public R> getLeaderApprovedList() { + List list = approvalRecordService.queryLeaderApprovedApprovals(); + return R.ok(list); + } + /** * 查询所有待审批申报列表(超级管理员专用) */ @@ -232,7 +272,7 @@ public class CwApprovalRecordController extends BaseController { /** * 获取可选审批人列表(院领导) */ - @SaCheckPermission("cw:approval:process") + @SaCheckPermission("cw:approval:finance") @GetMapping("/approvers/{applicationId}") public R>> getAvailableApprovers(@PathVariable Long applicationId) { List> approvers = approvalRecordService.getAvailableApprovers(applicationId); @@ -264,4 +304,36 @@ public class CwApprovalRecordController extends BaseController { } } + /** + * 财务审批 + */ + @SaCheckPermission("cw:approval:finance") + @PostMapping("/finance") + public R financeApproval(@RequestParam Long applicationId, + @RequestParam String approveAction, + @RequestParam String approveOpinion, + @RequestParam(required = false) Long selectedApproverId) { + log.info("🔍 财务审批:applicationId={}, approveAction={}, approveOpinion={}, selectedApproverId={}", + applicationId, approveAction, approveOpinion, selectedApproverId); + + try { + // 参数验证:审批通过时必须选择下一环节审批人 + if ("approve".equals(approveAction) && selectedApproverId == null) { + log.error("❌ 财务审批参数验证失败:审批通过时必须指定下一环节审批人"); + return R.fail("审批通过时必须选择下一环节的审批人"); + } + + // 调用财务审批服务 + approvalRecordService.financeApproval(applicationId, approveAction, approveOpinion, selectedApproverId); + + String actionText = "approve".equals(approveAction) ? "通过" : "退回"; + log.info("✅ 财务审批成功:applicationId={}, action={}", applicationId, actionText); + + return R.ok(); + } catch (Exception e) { + log.error("❌ 财务审批失败:applicationId={}, error={}", applicationId, e.getMessage(), e); + return R.fail("审批失败:" + e.getMessage()); + } + } + } diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectApplicationController.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectApplicationController.java index c04cde38..880317c0 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectApplicationController.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectApplicationController.java @@ -1,6 +1,7 @@ package org.dromara.cw.controller; import java.util.List; +import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -81,14 +82,62 @@ public class CwProjectApplicationController extends BaseController { } /** - * 导出项目预算申报列表 + * 导出项目申报信息 */ @SaCheckPermission("cw:application:export") - @Log(title = "项目预算申报", businessType = BusinessType.EXPORT) + @Log(title = "项目申报", businessType = BusinessType.EXPORT) @PostMapping("/export") public void export(CwProjectApplicationBo bo, HttpServletResponse response) { - List list = projectApplicationService.exportList(bo); - ExcelUtil.exportExcel(list, "项目预算申报", CwProjectApplicationVo.class, response); + List list = projectApplicationService.queryList(bo); + ExcelUtil.exportExcel(list, "项目申报", CwProjectApplicationVo.class, response); + } + + /** + * 项目预算统计 - 获取项目清单 + */ + @SaCheckPermission("cw:statistics:list") + @GetMapping("/statistics/list") + public TableDataInfo getStatisticsList(CwProjectApplicationBo bo, PageQuery pageQuery) { + log.info("🚀 [统计页面-项目清单] Controller接收到统计查询请求"); + log.info("📋 [统计页面-项目清单] 请求参数: {}", bo); + log.info("📄 [统计页面-项目清单] 分页参数: pageNum={}, pageSize={}", pageQuery.getPageNum(), pageQuery.getPageSize()); + + TableDataInfo result = projectApplicationService.queryStatisticsPageList(bo, pageQuery); + + log.info("✅ [统计页面-项目清单] Controller返回结果: 总数={}, 当前页记录数={}", + result.getTotal(), result.getRows() != null ? result.getRows().size() : 0); + + return result; + } + + /** + * 项目预算统计 - 状态统计 + */ + @SaCheckPermission("cw:statistics:list") + @GetMapping("/statistics/status") + public R> getStatusStatistics(@RequestParam(required = false) String budgetYear) { + Map statistics = projectApplicationService.getStatusStatistics(budgetYear); + return R.ok(statistics); + } + + /** + * 项目预算统计 - 部门预算汇总 + */ + @SaCheckPermission("cw:statistics:list") + @GetMapping("/statistics/department") + public R>> getDepartmentStatistics(@RequestParam(required = false) String budgetYear) { + List> statistics = projectApplicationService.getDepartmentStatistics(budgetYear); + return R.ok(statistics); + } + + /** + * 项目预算统计 - 年度汇总 + */ + @SaCheckPermission("cw:statistics:list") + @GetMapping("/statistics/yearly") + public R>> getYearlyStatistics() { + List> statistics = projectApplicationService.getYearlyStatistics(); + return R.ok(statistics); } /** diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectApplicationVo.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectApplicationVo.java index 86f25a33..88ef6fea 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectApplicationVo.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectApplicationVo.java @@ -296,4 +296,4 @@ public class CwProjectApplicationVo implements Serializable { * 绩效指标列表 */ private java.util.List performanceIndicators; -} \ No newline at end of file +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwApprovalRecordMapper.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwApprovalRecordMapper.java index 44d5f3a1..38fbd29e 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwApprovalRecordMapper.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwApprovalRecordMapper.java @@ -55,6 +55,30 @@ public interface CwApprovalRecordMapper extends BaseMapperPlus selectByNode(@Param("nodeCode") String nodeCode, + @Param("nodeStatus") String nodeStatus); + + /** + * 查询财务审批节点的待审批记录(包含waiting和pending状态) + * + * @return 财务审批待审批记录列表 + */ + List selectFinancePendingApprovals(); + + /** + * 查询财务审批节点的已审批记录(completed和rejected状态) + * + * @return 财务审批已审批记录列表 + */ + List selectFinanceApprovedRecords(); + /** * 查询所有待审批记录(超级管理员专用) * diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwApprovalRecordService.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwApprovalRecordService.java index 9339a505..5064c532 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwApprovalRecordService.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwApprovalRecordService.java @@ -60,6 +60,15 @@ public interface ICwApprovalRecordService { */ void leaderApproval(Long applicationId, String approveAction, String approveOpinion); + /** + * 财务审批 + * @param applicationId 申请ID + * @param approveAction 审批动作 (approve/reject) + * @param approveOpinion 审批意见 + * @param selectedApproverId 选择的院领导ID(审批通过时必须指定) + */ + void financeApproval(Long applicationId, String approveAction, String approveOpinion, Long selectedApproverId); + /** * 查询指定审批人的待审批记录列表 */ @@ -71,6 +80,29 @@ public interface ICwApprovalRecordService { */ List queryDeptPendingApprovals(); + /** + * 查询财务待审批申报列表 + * 财务审批:部门审批通过且财务审批未通过的数据,所有部门满足条件的数据都可以看到 + */ + List queryFinancePendingApprovals(); + + /** + * 查询财务已审批申报列表 + */ + List queryFinanceApprovedRecords(); + + /** + * 查询学院领导待审批申报列表 + * 学院领导审批:财务审批通过但是领导审批未通过的数据,是流程的最后一个环节 + */ + List queryLeaderPendingApprovals(); + + /** + * 查询学院领导已审批申报列表 + * 学院领导已审批:领导审批已完成的数据 + */ + List queryLeaderApprovedApprovals(); + /** * 查询所有待审批申报列表(超级管理员专用) * 超级管理员可以查看所有待审批的申报 diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApplicationService.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApplicationService.java index 779ade0b..743be450 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApplicationService.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApplicationService.java @@ -27,6 +27,11 @@ public interface ICwProjectApplicationService { */ TableDataInfo queryPageList(CwProjectApplicationBo bo, PageQuery pageQuery); + /** + * 统计页面 - 查询项目清单(不应用数据权限过滤) + */ + TableDataInfo queryStatisticsPageList(CwProjectApplicationBo bo, PageQuery pageQuery); + /** * 查询项目预算申报列表 */ @@ -122,4 +127,19 @@ public interface ICwProjectApplicationService { */ Boolean updateApprovalStatus(Long id, String approvalStatus); -} \ No newline at end of file + /** + * 获取状态统计信息 + */ + Map getStatusStatistics(String budgetYear); + + /** + * 获取部门预算汇总统计 + */ + List> getDepartmentStatistics(String budgetYear); + + /** + * 获取年度汇总统计 + */ + List> getYearlyStatistics(); + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwApprovalRecordServiceImpl.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwApprovalRecordServiceImpl.java index 93a85aa2..b5606037 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwApprovalRecordServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwApprovalRecordServiceImpl.java @@ -4,6 +4,7 @@ import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.helper.DataPermissionHelper; import org.dromara.common.redis.utils.SequenceUtils; import org.dromara.common.satoken.utils.LoginHelper; @@ -200,17 +201,13 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { if ("current".equals(bo.getDeptId())) { // 查询当前用户部门的申报,且处于部门审批节点 Long currentDeptId = LoginHelper.getDeptId(); - log.info("部门审批查询:当前用户部门ID={}, 用户ID={}", currentDeptId, LoginHelper.getUserId()); + Long currentUserId = LoginHelper.getUserId(); + log.info("部门审批查询:当前用户ID={}, 部门ID={}", currentUserId, currentDeptId); if (currentDeptId != null) { - Long currentUserId = LoginHelper.getUserId(); - log.info("部门审批查询:当前用户ID={}, 部门ID={}", currentUserId, currentDeptId); - - // 先查询该部门的项目申报ID列表 - String subQuery = "SELECT id FROM cw_project_application WHERE department_id = " + currentDeptId + " AND deleted = 0"; - log.info("部门审批子查询SQL:{}", subQuery); - - lqw.in(CwApprovalRecord::getApplicationId, subQuery); + // 查询该部门的项目申报ID列表 + lqw.inSql(CwApprovalRecord::getApplicationId, + "SELECT id FROM cw_project_application WHERE department_id = " + currentDeptId + " AND deleted = 0"); // 只显示部门审批节点的记录 lqw.eq(CwApprovalRecord::getNodeCode, "dept_approve"); @@ -218,10 +215,10 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { // 只显示待审批的记录(waiting) lqw.eq(CwApprovalRecord::getNodeStatus, "waiting"); - // 关键修复:只显示当前用户作为审批人的记录 - lqw.eq(CwApprovalRecord::getApproverId, currentUserId); + // 🔧 修复:不限制审批人,显示本部门所有待审批的申请 + // 部门审批人可以看到本部门下所有用户提交的申请 - log.info("部门审批查询条件:nodeCode=dept_approve, nodeStatus=waiting, approverId={}, deptId={}", currentUserId, currentDeptId); + log.info("部门审批查询条件:nodeCode=dept_approve, nodeStatus=waiting, deptId={}", currentDeptId); } else { log.warn("当前用户部门ID为null,无法进行部门审批查询"); // 添加一个不可能匹配的条件,确保返回空结果 @@ -375,13 +372,18 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { return new ArrayList<>(); } - // 查询当前用户已审批的记录(状态不是waiting和pending) - LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); - queryWrapper.eq(CwApprovalRecord::getApproverId, currentUserId) - .notIn(CwApprovalRecord::getNodeStatus, Arrays.asList("waiting", "pending")) - .orderByDesc(CwApprovalRecord::getEndTime); + // 🔧 使用DataPermissionHelper.ignore()绕过数据权限过滤 + // 原因:我已审批功能应该显示当前用户的所有审批记录,不受数据权限限制 + List records = DataPermissionHelper.ignore(() -> { + // 查询当前用户已审批的记录(精确匹配已完成的状态) + LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + queryWrapper.eq(CwApprovalRecord::getApproverId, currentUserId) + .in(CwApprovalRecord::getNodeStatus, Arrays.asList("approved", "rejected", "returned")) + .orderByDesc(CwApprovalRecord::getEndTime); + + return baseMapper.selectList(queryWrapper); + }); - List records = baseMapper.selectList(queryWrapper); log.info("✅ 查询到已审批记录数量:{}", records.size()); // 转换为VO并补充申报信息 @@ -423,6 +425,48 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { return baseMapper.selectByApproverAndNode(currentUserId, "dept_approve", "waiting"); } + /** + * 查询财务待审批申报列表 + * 财务审批:部门审批通过但是财务审批未通过的数据 + */ + @Override + public List queryFinancePendingApprovals() { + log.info("🔍 查询财务待审批申报列表"); + return baseMapper.selectFinancePendingApprovals(); + } + + /** + * 查询财务已审批申报列表 + * 财务审批:已完成或已拒绝的财务审批记录 + */ + @Override + public List queryFinanceApprovedRecords() { + log.info("🔍 查询财务已审批申报列表"); + return baseMapper.selectFinanceApprovedRecords(); + } + + /** + * 查询学院领导待审批申报列表 + * 学院领导审批:财务审批通过但是领导审批未通过的数据,是流程的最后一个环节 + */ + @Override + public List queryLeaderPendingApprovals() { + Long currentUserId = LoginHelper.getUserId(); + log.info("🔍 查询学院领导待审批申报列表,当前用户ID={}", currentUserId); + return baseMapper.selectByApproverAndNode(currentUserId, "leader_approve", "waiting"); + } + + /** + * 查询学院领导已审批申报列表 + * 学院领导已审批:领导审批已完成的数据 + */ + @Override + public List queryLeaderApprovedApprovals() { + Long currentUserId = LoginHelper.getUserId(); + log.info("🔍 查询学院领导已审批申报列表,当前用户ID={}", currentUserId); + return baseMapper.selectByApproverAndNode(currentUserId, "leader_approve", "complete"); + } + /** * 查询所有待审批申报列表(超级管理员专用) */ @@ -531,30 +575,21 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { // 清理失败不应该阻止新记录创建,记录错误后继续 } - // 创建审批流程记录 + // 🔧 修复:渐进式创建审批记录,避免冗余数据 + // 只创建当前需要的审批记录(第一步:部门审批) List records = new ArrayList<>(); - log.info("📝 开始创建新的审批记录..."); + log.info("📝 开始创建部门审批记录(渐进式创建)..."); - // 第1节点:部门审批 + // 第1节点:部门审批(提交时只创建这一个记录) CwApprovalRecord deptRecord = createApprovalRecord(applicationId, applicationNo, "dept_approve", "部门审批", 1, "waiting", currentUserId, currentUserName, currentDeptId, currentTenantId); records.add(deptRecord); log.info("✅ 创建部门审批记录:ID={}, 审批人={}", deptRecord.getId(), deptRecord.getApproverName()); - // 第2节点:财务审批(初始状态为未开始) - CwApprovalRecord financeRecord = createApprovalRecord(applicationId, applicationNo, - "finance_approve", "财务审批", 2, "pending", - currentUserId, currentUserName, currentDeptId, currentTenantId); - records.add(financeRecord); - log.info("✅ 创建财务审批记录:ID={}, 审批人={}", financeRecord.getId(), financeRecord.getApproverName()); - - // 第3节点:领导审批(初始状态为未开始) - CwApprovalRecord leaderRecord = createApprovalRecord(applicationId, applicationNo, - "leader_approve", "领导审批", 3, "pending", - currentUserId, currentUserName, currentDeptId, currentTenantId); - records.add(leaderRecord); - log.info("✅ 创建领导审批记录:ID={}, 审批人={}", leaderRecord.getId(), leaderRecord.getApproverName()); + // 💡 后续审批记录将在前一步骤完成时动态创建: + // - 部门审批通过后,创建财务审批记录 + // - 财务审批通过后,创建学院领导审批记录 log.info("📊 准备批量插入审批记录,共{}条", records.size()); @@ -964,18 +999,90 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { } private Boolean startNextApprovalNode(Long applicationId, String nextNodeCode) { - // 更新下一个节点为等待状态 - CwApprovalRecordVo nextRecord = queryByApplicationAndNode(applicationId, nextNodeCode); - if (nextRecord != null) { - CwApprovalRecordBo updateBo = new CwApprovalRecordBo(); - updateBo.setId(nextRecord.getId()); - updateBo.setNodeStatus("waiting"); - updateBo.setStartTime(new Date()); - updateByBo(updateBo); - } + try { + // 🔧 修复:渐进式创建审批记录,检查下一个节点是否存在,不存在则创建 + CwApprovalRecordVo nextRecord = queryByApplicationAndNode(applicationId, nextNodeCode); - // 更新申报状态为审批中 - return projectApplicationService.updateApprovalStatus(applicationId, "approving"); + if (nextRecord == null) { + // 下一个审批记录不存在,需要动态创建 + log.info("📝 下一个审批节点不存在,开始创建:applicationId={}, nodeCode={}", applicationId, nextNodeCode); + + // 获取申报信息 + CwProjectApplicationVo application = projectApplicationService.queryById(applicationId); + if (application == null) { + throw new RuntimeException("申报记录不存在"); + } + + // 根据节点类型创建对应的审批记录 + CwApprovalRecord newRecord = createApprovalRecordForNode(applicationId, application.getApplicationNo(), + nextNodeCode, application.getCreateBy(), application.getApplicantName(), + application.getDepartmentId(), LoginHelper.getTenantId()); + + if (newRecord != null) { + // 插入新的审批记录 + baseMapper.insert(newRecord); + log.info("✅ 成功创建审批记录:nodeCode={}, approverId={}", nextNodeCode, newRecord.getApproverId()); + + // 更新申报状态为审批中 + return projectApplicationService.updateApprovalStatus(applicationId, "approving"); + } + } else { + // 审批记录已存在,直接启动 + CwApprovalRecordBo updateBo = new CwApprovalRecordBo(); + updateBo.setId(nextRecord.getId()); + updateBo.setNodeStatus("waiting"); + updateBo.setStartTime(new Date()); + updateByBo(updateBo); + + // 更新申报状态为审批中 + return projectApplicationService.updateApprovalStatus(applicationId, "approving"); + } + + return true; + } catch (Exception e) { + log.error("启动下一个审批节点失败:applicationId={}, nodeCode={}, error={}", + applicationId, nextNodeCode, e.getMessage(), e); + throw new RuntimeException("启动下一个审批节点失败:" + e.getMessage()); + } + } + + /** + * 动态创建指定节点的审批记录 + */ + private CwApprovalRecord createApprovalRecordForNode(Long applicationId, String applicationNo, + String nodeCode, Long createBy, String createByName, + Long deptId, String tenantId) { + try { + String nodeName; + Integer nodeOrder; + + // 根据节点代码确定节点名称和顺序 + switch (nodeCode) { + case "finance_approve": + nodeName = "财务审批"; + nodeOrder = 2; + break; + case "leader_approve": + nodeName = "学院领导审批"; + nodeOrder = 3; + break; + default: + throw new RuntimeException("不支持的审批节点:" + nodeCode); + } + + // 创建审批记录 + CwApprovalRecord record = createApprovalRecord(applicationId, applicationNo, + nodeCode, nodeName, nodeOrder, "waiting", + createBy, createByName, deptId, tenantId); + + log.info("✅ 动态创建审批记录:nodeCode={}, nodeName={}, approverId={}", + nodeCode, nodeName, record.getApproverId()); + + return record; + } catch (Exception e) { + log.error("创建审批记录失败:nodeCode={}, error={}", nodeCode, e.getMessage(), e); + throw new RuntimeException("创建审批记录失败:" + e.getMessage()); + } } /** @@ -1092,13 +1199,20 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { Date now = new Date(); CwApprovalRecord updateRecord = new CwApprovalRecord(); updateRecord.setId(currentRecord.getId()); - updateRecord.setNodeStatus("approve".equals(approveAction) ? "completed" : "rejected"); + updateRecord.setNodeStatus("approve".equals(approveAction) ? "approved" : "rejected"); updateRecord.setApproveAction(approveAction); updateRecord.setApproveOpinion(approveOpinion); updateRecord.setEndTime(now); updateRecord.setUpdateBy(currentUserId); updateRecord.setUpdateTime(now); + // 更新实际审批人信息 + updateRecord.setApproverId(currentUserId); + SysUserVo currentUser = sysUserService.selectUserById(currentUserId); + if (currentUser != null) { + updateRecord.setApproverName(currentUser.getNickName()); + } + int updated = baseMapper.updateById(updateRecord); if (updated == 0) { throw new RuntimeException("更新审批记录失败"); @@ -1107,12 +1221,9 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { // 3. 更新项目申请状态 String newStatus; if ("approve".equals(approveAction)) { - // 审批通过,状态改为财务审批中 - newStatus = "finance_approving"; - log.info("✅ 分管领导审批通过,提交财务审批:applicationId={}", applicationId); - - // TODO: 这里可以添加创建财务审批记录的逻辑 - // createFinanceApprovalRecord(applicationId); + // 分管领导审批通过,整个审批流程完成 + newStatus = "approved"; + log.info("✅ 分管领导审批通过,整个审批流程完成:applicationId={}", applicationId); } else { // 审批退回,状态改为已退回 @@ -1130,4 +1241,197 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { log.info("✅ 分管领导审批完成:applicationId={}, status={}", applicationId, newStatus); } + + /** + * 财务审批 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void financeApproval(Long applicationId, String approveAction, String approveOpinion, Long selectedApproverId) { + Long currentUserId = LoginHelper.getUserId(); + log.info("🔍 财务审批开始:applicationId={}, userId={}, action={}, selectedApproverId={}", + applicationId, currentUserId, approveAction, selectedApproverId); + + // 1. 参数验证 + if ("approve".equals(approveAction) && selectedApproverId == null) { + throw new RuntimeException("财务审批通过时必须选择院领导"); + } + + // 2. 查找财务审批节点的待审批记录(不限制特定审批人) + log.info("🔍 查询财务审批记录:applicationId={}, nodeCode=finance_approve, nodeStatus=waiting", applicationId); + + List records = baseMapper.selectList( + Wrappers.lambdaQuery(CwApprovalRecord.class) + .eq(CwApprovalRecord::getApplicationId, applicationId) + .eq(CwApprovalRecord::getNodeCode, "finance_approve") + .eq(CwApprovalRecord::getNodeStatus, "waiting") + .orderByDesc(CwApprovalRecord::getCreateTime) + ); + + if (records.isEmpty()) { + log.error("❌ 未找到财务审批记录:applicationId={}", applicationId); + // 查询所有相关记录用于调试 + List allRecords = baseMapper.selectList( + Wrappers.lambdaQuery(CwApprovalRecord.class) + .eq(CwApprovalRecord::getApplicationId, applicationId) + ); + log.error("📋 该申请的所有审批记录:{}", allRecords.size()); + for (CwApprovalRecord record : allRecords) { + log.error("📋 记录详情:nodeCode={}, nodeStatus={}, approverId={}", + record.getNodeCode(), record.getNodeStatus(), record.getApproverId()); + } + throw new RuntimeException("未找到待审批记录或该申请已被处理"); + } + + // 如果有多条记录,选择最新的一条 + CwApprovalRecord currentRecord = records.get(0); + if (records.size() > 1) { + log.warn("⚠️ 发现多条财务审批记录,选择最新的一条:applicationId={}, 记录数={}", applicationId, records.size()); + } + + log.info("✅ 找到财务审批记录:recordId={}, nodeCode={}, nodeStatus={}, approverId={}", + currentRecord.getId(), currentRecord.getNodeCode(), currentRecord.getNodeStatus(), currentRecord.getApproverId()); + + // 3. 更新当前审批记录 + Date now = new Date(); + CwApprovalRecord updateRecord = new CwApprovalRecord(); + updateRecord.setId(currentRecord.getId()); + updateRecord.setNodeStatus("approve".equals(approveAction) ? "approved" : "rejected"); + updateRecord.setApproveAction(approveAction); + updateRecord.setApproveOpinion(approveOpinion); + updateRecord.setEndTime(now); + updateRecord.setUpdateBy(currentUserId); + updateRecord.setUpdateTime(now); + + // 🔧 修复:更新实际审批人信息(解决"我已审批"查询不到的问题) + updateRecord.setApproverId(currentUserId); + + // 更新审批人姓名(保持数据一致性) + SysUserVo currentUser = sysUserService.selectUserById(currentUserId); + if (currentUser != null) { + updateRecord.setApproverName(currentUser.getNickName()); + } + + int updated = baseMapper.updateById(updateRecord); + if (updated == 0) { + throw new RuntimeException("更新审批记录失败"); + } + + // 4. 根据审批结果处理后续流程 + String newStatus; + if ("approve".equals(approveAction)) { + // 财务审批通过,启动分管领导审批 + updateLeaderApproverAndStart(applicationId, selectedApproverId); + newStatus = "approving"; + log.info("✅ 财务审批通过,启动分管领导审批:applicationId={}, leaderId={}", applicationId, selectedApproverId); + } else { + // 财务审批退回,状态改为已退回 + newStatus = "returned"; + log.info("🔄 财务审批退回:applicationId={}", applicationId); + } + + // 5. 更新项目申请状态 + ICwProjectApplicationService applicationService = SpringUtils.getBean(ICwProjectApplicationService.class); + boolean result = applicationService.updateApprovalStatus(applicationId, newStatus); + if (!result) { + throw new RuntimeException("更新项目申请状态失败"); + } + + log.info("✅ 财务审批完成:applicationId={}, status={}", + applicationId, newStatus); + } + + /** + * 更新分管领导审批人并启动审批 + */ + private void updateLeaderApproverAndStart(Long applicationId, Long selectedApproverId) { + // 1. 查找分管领导审批记录(先按waiting状态查找) + CwApprovalRecord leaderRecord = baseMapper.selectOne( + Wrappers.lambdaQuery(CwApprovalRecord.class) + .eq(CwApprovalRecord::getApplicationId, applicationId) + .eq(CwApprovalRecord::getNodeCode, "leader_approve") + .eq(CwApprovalRecord::getNodeStatus, "waiting") + ); + + // 2. 如果waiting状态未找到,尝试查找pending状态(兼容处理) + if (leaderRecord == null) { + leaderRecord = baseMapper.selectOne( + Wrappers.lambdaQuery(CwApprovalRecord.class) + .eq(CwApprovalRecord::getApplicationId, applicationId) + .eq(CwApprovalRecord::getNodeCode, "leader_approve") + .eq(CwApprovalRecord::getNodeStatus, "pending") + ); + } + + // 3. 如果仍未找到,动态创建分管领导审批记录 + if (leaderRecord == null) { + log.warn("未找到分管领导审批记录,开始动态创建:applicationId={}", applicationId); + + // 获取申请信息 + CwProjectApplicationVo applicationVo = projectApplicationService.queryById(applicationId); + if (applicationVo == null) { + throw new RuntimeException("申请信息不存在:" + applicationId); + } + + // 获取当前用户信息和租户信息 + Long currentUserId = LoginHelper.getUserId(); + String currentUserName = LoginHelper.getUsername(); + Long currentDeptId = LoginHelper.getDeptId(); + String currentTenantId = LoginHelper.getTenantId(); + + // 创建分管领导审批记录 + leaderRecord = createApprovalRecord( + applicationId, + applicationVo.getApplicationNo(), + "leader_approve", + "分管领导审批", + 3, // leader_approve 节点顺序 + "waiting", + currentUserId, + currentUserName, + currentDeptId, + currentTenantId + ); + + // 保存到数据库 + int result = baseMapper.insert(leaderRecord); + if (result <= 0) { + throw new RuntimeException("创建分管领导审批记录失败"); + } + + log.info("✅ 动态创建分管领导审批记录成功:recordId={}", leaderRecord.getId()); + } + + // 2. 获取选择的院领导信息 + try { + SysUserVo selectedUser = sysUserService.selectUserById(selectedApproverId); + if (selectedUser == null) { + throw new RuntimeException("选择的院领导不存在:" + selectedApproverId); + } + + // 3. 更新分管领导审批记录 + Date now = new Date(); + CwApprovalRecord updateRecord = new CwApprovalRecord(); + updateRecord.setId(leaderRecord.getId()); + updateRecord.setApproverId(selectedApproverId); + updateRecord.setApproverName(selectedUser.getUserName()); + updateRecord.setNodeStatus("waiting"); // 启动审批 + updateRecord.setStartTime(now); + updateRecord.setUpdateBy(LoginHelper.getUserId()); + updateRecord.setUpdateTime(now); + + int updated = baseMapper.updateById(updateRecord); + if (updated == 0) { + throw new RuntimeException("更新分管领导审批记录失败"); + } + + log.info("✅ 成功指定院领导审批人:userId={}, userName={}", + selectedApproverId, selectedUser.getUserName()); + + } catch (Exception e) { + log.error("❌ 更新分管领导审批人失败:applicationId={}, selectedApproverId={}, error={}", + applicationId, selectedApproverId, e.getMessage(), e); + throw new RuntimeException("指定院领导审批人失败:" + e.getMessage()); + } + } } diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApplicationServiceImpl.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApplicationServiceImpl.java index df9d512f..9264426f 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApplicationServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApplicationServiceImpl.java @@ -25,6 +25,8 @@ import org.springframework.context.ApplicationEventPublisher; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ThreadLocalRandom; +import java.math.BigDecimal; +import java.util.ArrayList; /** * 项目预算申报Service业务层处理 @@ -65,6 +67,40 @@ public class CwProjectApplicationServiceImpl implements ICwProjectApplicationSer return TableDataInfo.build(result); } + /** + * 统计页面 - 查询项目清单(不应用数据权限过滤) + */ + @Override + public TableDataInfo queryStatisticsPageList(CwProjectApplicationBo bo, PageQuery pageQuery) { + log.info("🚀 [统计页面-项目清单] 开始查询项目清单数据"); + log.info("📋 [统计页面-项目清单] 查询参数: {}", bo); + log.info("📄 [统计页面-项目清单] 分页参数: pageNum={}, pageSize={}", pageQuery.getPageNum(), pageQuery.getPageSize()); + + LambdaQueryWrapper lqw = buildStatisticsQueryWrapper(bo); + + // 打印最终的查询条件 + log.info("🔍 [统计页面-项目清单] 构建的查询条件: {}", lqw.getTargetSql()); + + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + + log.info("✅ [统计页面-项目清单] 查询完成"); + log.info("📊 [统计页面-项目清单] 查询结果: 总数={}, 当前页记录数={}", result.getTotal(), result.getRecords().size()); + + if (!result.getRecords().isEmpty()) { + log.info("📝 [统计页面-项目清单] 前3条数据示例:"); + for (int i = 0; i < Math.min(result.getRecords().size(), 3); i++) { + CwProjectApplicationVo record = result.getRecords().get(i); + log.info(" {}. ID: {}, 申报编号: {}, 项目名称: {}, 状态: {}, 申报人: {}, 部门: {}", + i + 1, record.getId(), record.getApplicationNo(), record.getProjectName(), + record.getApprovalStatus(), record.getApplicantName(), record.getDepartmentName()); + } + } else { + log.warn("⚠️ [统计页面-项目清单] 查询结果为空!"); + } + + return TableDataInfo.build(result); + } + /** * 查询项目预算申报列表 */ @@ -136,6 +172,71 @@ public class CwProjectApplicationServiceImpl implements ICwProjectApplicationSer return lqw; } + /** + * 构建统计查询条件(不应用数据权限过滤) + */ + private LambdaQueryWrapper buildStatisticsQueryWrapper(CwProjectApplicationBo bo) { + log.info("🔧 [统计页面-项目清单] 开始构建统计查询条件"); + log.info("📋 [统计页面-项目清单] 输入参数: {}", bo); + + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + + // 统计查询不应用数据权限过滤,允许查看所有项目数据 + log.info("🔍 [统计页面-项目清单] 统计查询 - 不应用数据权限过滤,显示所有项目数据"); + + // 记录每个查询条件的应用情况 + if (StringUtils.isNotBlank(bo.getApplicationNo())) { + log.info("📝 [统计页面-项目清单] 添加申报编号模糊查询: {}", bo.getApplicationNo()); + } + if (StringUtils.isNotBlank(bo.getProjectName())) { + log.info("📝 [统计页面-项目清单] 添加项目名称模糊查询: {}", bo.getProjectName()); + } + if (StringUtils.isNotBlank(bo.getApprovalStatus())) { + log.info("📝 [统计页面-项目清单] 添加审批状态查询: {}", bo.getApprovalStatus()); + } + if (bo.getYear() != null) { + log.info("📝 [统计页面-项目清单] 添加年度查询: {}", bo.getYear()); + } + + // 处理budgetYear参数(从前端传过来的) + String budgetYear = null; + if (params != null && params.containsKey("budgetYear")) { + budgetYear = (String) params.get("budgetYear"); + log.info("📝 [统计页面-项目清单] 检测到budgetYear参数: {}", budgetYear); + } + + lqw.like(StringUtils.isNotBlank(bo.getApplicationNo()), CwProjectApplication::getApplicationNo, bo.getApplicationNo()); + lqw.like(StringUtils.isNotBlank(bo.getProjectName()), CwProjectApplication::getProjectName, bo.getProjectName()); + lqw.eq(StringUtils.isNotBlank(bo.getPrincipal()), CwProjectApplication::getPrincipal, bo.getPrincipal()); + lqw.eq(StringUtils.isNotBlank(bo.getContactPhone()), CwProjectApplication::getContactPhone, bo.getContactPhone()); + lqw.eq(bo.getStartDate() != null, CwProjectApplication::getStartDate, bo.getStartDate()); + lqw.eq(bo.getEndDate() != null, CwProjectApplication::getEndDate, bo.getEndDate()); + lqw.eq(bo.getTargetSme() != null, CwProjectApplication::getTargetSme, bo.getTargetSme()); + lqw.eq(bo.getTargetMicroSme() != null, CwProjectApplication::getTargetMicroSme, bo.getTargetMicroSme()); + lqw.eq(bo.getImportEquipment() != null, CwProjectApplication::getImportEquipment, bo.getImportEquipment()); + lqw.eq(bo.getApplicantId() != null, CwProjectApplication::getApplicantId, bo.getApplicantId()); + lqw.like(StringUtils.isNotBlank(bo.getApplicantName()), CwProjectApplication::getApplicantName, bo.getApplicantName()); + lqw.eq(bo.getDepartmentId() != null, CwProjectApplication::getDepartmentId, bo.getDepartmentId()); + lqw.like(StringUtils.isNotBlank(bo.getDepartmentName()), CwProjectApplication::getDepartmentName, bo.getDepartmentName()); + lqw.eq(StringUtils.isNotBlank(bo.getApprovalStatus()), CwProjectApplication::getApprovalStatus, bo.getApprovalStatus()); + lqw.eq(bo.getYear() != null, CwProjectApplication::getYear, bo.getYear()); + + // 🔧 修复:处理前端传递的budgetYear参数,映射到数据库的year字段 + if (StringUtils.isNotBlank(budgetYear)) { + log.info("📝 [统计页面-项目清单] 应用budgetYear查询条件: year = {}", budgetYear); + lqw.eq(CwProjectApplication::getYear, Integer.parseInt(budgetYear)); + } + lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null, + CwProjectApplication::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime")); + lqw.between(params.get("beginSubmitTime") != null && params.get("endSubmitTime") != null, + CwProjectApplication::getSubmitTime, params.get("beginSubmitTime"), params.get("endSubmitTime")); + lqw.orderByDesc(CwProjectApplication::getCreateTime); + + log.info("✅ [统计页面-项目清单] 统计查询条件构建完成"); + return lqw; + } + /** * 添加数据权限过滤 * 超级管理员:查看所有数据 @@ -614,4 +715,176 @@ public class CwProjectApplicationServiceImpl implements ICwProjectApplicationSer return queryList(bo); } + /** + * 获取状态统计信息 + */ + @Override + public Map getStatusStatistics(String budgetYear) { + log.info("🔍 获取状态统计信息,预算年度:{}", budgetYear); + + Map result = new HashMap<>(); + + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + if (StringUtils.isNotBlank(budgetYear)) { + wrapper.eq(CwProjectApplication::getYear, Integer.parseInt(budgetYear)); + } + + // 总项目数 + Long totalCount = baseMapper.selectCount(wrapper); + result.put("totalCount", totalCount); + + // 各状态统计 + List> statusStats = new ArrayList<>(); + String[] statuses = {"draft", "submitted", "approving", "approved", "rejected", "returned", "cancelled"}; + String[] statusLabels = {"草稿", "已提交", "审批中", "已通过", "已拒绝", "已退回", "已取消"}; + + for (int i = 0; i < statuses.length; i++) { + LambdaQueryWrapper statusWrapper = Wrappers.lambdaQuery(); + statusWrapper.eq(CwProjectApplication::getApprovalStatus, statuses[i]); + if (StringUtils.isNotBlank(budgetYear)) { + statusWrapper.eq(CwProjectApplication::getYear, Integer.parseInt(budgetYear)); + } + + Long count = baseMapper.selectCount(statusWrapper); + Map statusStat = new HashMap<>(); + statusStat.put("status", statuses[i]); + statusStat.put("statusLabel", statusLabels[i]); + statusStat.put("count", count); + statusStats.add(statusStat); + } + + result.put("statusStatistics", statusStats); + + // 预算总金额统计 + LambdaQueryWrapper amountWrapper = Wrappers.lambdaQuery(); + if (StringUtils.isNotBlank(budgetYear)) { + amountWrapper.eq(CwProjectApplication::getYear, Integer.parseInt(budgetYear)); + } + + List applications = baseMapper.selectList(amountWrapper); + BigDecimal totalAmount = applications.stream() + .map(app -> app.getTotalInvestment() != null ? app.getTotalInvestment() : BigDecimal.ZERO) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + result.put("totalAmount", totalAmount); + + log.info("✅ 状态统计完成:总数={}, 总金额={}", totalCount, totalAmount); + return result; + } + + /** + * 获取部门预算汇总统计 + */ + @Override + public List> getDepartmentStatistics(String budgetYear) { + log.info("🔍 获取部门预算汇总统计,预算年度:{}", budgetYear); + + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + if (StringUtils.isNotBlank(budgetYear)) { + wrapper.eq(CwProjectApplication::getYear, Integer.parseInt(budgetYear)); + } + + List applications = baseMapper.selectList(wrapper); + + // 按部门分组统计 + Map> deptMap = new HashMap<>(); + + for (CwProjectApplication app : applications) { + String deptName = app.getDepartmentName(); + if (StringUtils.isBlank(deptName)) { + deptName = "未分配部门"; + } + + Map deptStat = deptMap.computeIfAbsent(deptName, k -> { + Map stat = new HashMap<>(); + stat.put("departmentName", k); + stat.put("projectCount", 0); + stat.put("totalAmount", BigDecimal.ZERO); + stat.put("approvedCount", 0); + stat.put("approvedAmount", BigDecimal.ZERO); + return stat; + }); + + // 项目总数和总金额 + deptStat.put("projectCount", (Integer) deptStat.get("projectCount") + 1); + BigDecimal currentTotal = (BigDecimal) deptStat.get("totalAmount"); + BigDecimal appAmount = app.getTotalInvestment() != null ? app.getTotalInvestment() : BigDecimal.ZERO; + deptStat.put("totalAmount", currentTotal.add(appAmount)); + + // 已通过项目统计 + if ("approved".equals(app.getApprovalStatus())) { + deptStat.put("approvedCount", (Integer) deptStat.get("approvedCount") + 1); + BigDecimal currentApproved = (BigDecimal) deptStat.get("approvedAmount"); + deptStat.put("approvedAmount", currentApproved.add(appAmount)); + } + } + + List> result = new ArrayList<>(deptMap.values()); + + // 按总金额降序排序 + result.sort((a, b) -> { + BigDecimal amountA = (BigDecimal) a.get("totalAmount"); + BigDecimal amountB = (BigDecimal) b.get("totalAmount"); + return amountB.compareTo(amountA); + }); + + log.info("✅ 部门统计完成:{} 个部门", result.size()); + return result; + } + + /** + * 获取年度汇总统计 + */ + @Override + public List> getYearlyStatistics() { + log.info("🔍 获取年度汇总统计"); + + List applications = baseMapper.selectList(Wrappers.emptyWrapper()); + + // 按年度分组统计 + Map> yearMap = new HashMap<>(); + + for (CwProjectApplication app : applications) { + String year = app.getYear() != null ? app.getYear().toString() : "未指定年度"; + + Map yearStat = yearMap.computeIfAbsent(year, k -> { + Map stat = new HashMap<>(); + stat.put("budgetYear", k); + stat.put("projectCount", 0); + stat.put("totalAmount", BigDecimal.ZERO); + stat.put("approvedCount", 0); + stat.put("approvedAmount", BigDecimal.ZERO); + return stat; + }); + + // 项目总数和总金额 + yearStat.put("projectCount", (Integer) yearStat.get("projectCount") + 1); + BigDecimal currentTotal = (BigDecimal) yearStat.get("totalAmount"); + BigDecimal appAmount = app.getTotalInvestment() != null ? app.getTotalInvestment() : BigDecimal.ZERO; + yearStat.put("totalAmount", currentTotal.add(appAmount)); + + // 已通过项目统计 + if ("approved".equals(app.getApprovalStatus())) { + yearStat.put("approvedCount", (Integer) yearStat.get("approvedCount") + 1); + BigDecimal currentApproved = (BigDecimal) yearStat.get("approvedAmount"); + yearStat.put("approvedAmount", currentApproved.add(appAmount)); + } + } + + List> result = new ArrayList<>(yearMap.values()); + + // 按年度降序排序 + result.sort((a, b) -> { + String yearA = (String) a.get("budgetYear"); + String yearB = (String) b.get("budgetYear"); + // 处理"未指定年度"的情况,让它排在最后 + if ("未指定年度".equals(yearA)) return 1; + if ("未指定年度".equals(yearB)) return -1; + return yearB.compareTo(yearA); + }); + + log.info("✅ 年度统计完成:{} 个年度", result.size()); + return result; + } + } diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwApprovalRecordMapper.xml b/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwApprovalRecordMapper.xml index ec3b3cfd..825815dd 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwApprovalRecordMapper.xml +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwApprovalRecordMapper.xml @@ -124,6 +124,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by ar.create_time desc + + + + + + + +