From edaf8dbacfaf13f0297a678b94274ef0ad373442 Mon Sep 17 00:00:00 2001 From: wuzhen <52326163@qq.com> Date: Sun, 7 Sep 2025 14:58:44 +0800 Subject: [PATCH] wuzhen update-cw --- .../CwProjectApprovalController.java | 156 +++ .../CwProjectAuditorController.java | 251 ++++ .../dromara/cw/domain/CwApprovalRecord.java | 17 +- .../cw/domain/CwProjectApprovalProcess.java | 82 ++ .../dromara/cw/domain/CwProjectAuditor.java | 92 ++ .../cw/domain/bo/CwApprovalActionBo.java | 33 + .../cw/domain/bo/CwApprovalRecordBo.java | 9 +- .../cw/domain/bo/CwCustomApprovalBo.java | 52 + .../domain/bo/CwProjectApprovalProcessBo.java | 88 ++ .../cw/domain/bo/CwProjectAuditorBatchBo.java | 39 + .../cw/domain/bo/CwProjectAuditorBo.java | 91 ++ .../cw/domain/vo/CwApprovalRecordVo.java | 46 +- .../domain/vo/CwProjectApprovalProcessVo.java | 121 ++ .../cw/domain/vo/CwProjectAuditorVo.java | 125 ++ .../CwProjectApprovalProcessMapper.java | 15 + .../cw/mapper/CwProjectAuditorMapper.java | 28 + .../cw/service/ICwApprovalRecordService.java | 20 + .../ICwProjectApprovalProcessService.java | 110 ++ .../cw/service/ICwProjectApprovalService.java | 60 + .../cw/service/ICwProjectAuditorService.java | 79 ++ .../impl/CwApprovalRecordServiceImpl.java | 262 +++- .../impl/CwProjectApplicationServiceImpl.java | 83 +- .../CwProjectApprovalProcessServiceImpl.java | 289 ++++ .../impl/CwProjectApprovalServiceImpl.java | 258 ++++ .../impl/CwProjectAuditorServiceImpl.java | 343 +++++ .../mapper/CwApprovalRecordMapper.xml | 25 +- .../mapper/CwProjectApprovalProcessMapper.xml | 112 ++ .../mapper/CwProjectAuditorMapper.xml | 114 ++ web/src/api/cw/projectApproval.ts | 216 +++ web/src/api/cw/projectAuditor.ts | 244 ++++ web/src/api/cw/types.ts | 180 +++ web/src/api/system/dept/index.ts | 8 + web/src/api/types.ts | 8 + web/src/views/cw/README.md | 207 ++- web/src/views/cw/application/form.vue | 164 ++- web/src/views/cw/approverConfig/index.vue | 6 +- web/src/views/cw/projectApproval/README.md | 134 ++ .../components/ApprovalProcessSelector.vue | 462 +++++++ web/src/views/cw/projectApproval/index.vue | 1217 +++++++++++++++++ web/src/views/cw/projectApproval/pending.vue | 433 ++++++ .../views/cw/projectAuditor/BUG_FIX_REPORT.md | 150 ++ web/src/views/cw/projectAuditor/README.md | 58 + web/src/views/cw/projectAuditor/debug.vue | 151 ++ web/src/views/cw/projectAuditor/index.vue | 952 +++++++++++++ web/src/views/cw/projectAuditor/test.vue | 140 ++ 45 files changed, 7626 insertions(+), 104 deletions(-) create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectApprovalController.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectAuditorController.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwProjectApprovalProcess.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwProjectAuditor.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwApprovalActionBo.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwCustomApprovalBo.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectApprovalProcessBo.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectAuditorBatchBo.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectAuditorBo.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectApprovalProcessVo.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectAuditorVo.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectApprovalProcessMapper.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectAuditorMapper.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApprovalProcessService.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApprovalService.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectAuditorService.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApprovalProcessServiceImpl.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApprovalServiceImpl.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectAuditorServiceImpl.java create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectApprovalProcessMapper.xml create mode 100644 backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectAuditorMapper.xml create mode 100644 web/src/api/cw/projectApproval.ts create mode 100644 web/src/api/cw/projectAuditor.ts create mode 100644 web/src/views/cw/projectApproval/README.md create mode 100644 web/src/views/cw/projectApproval/components/ApprovalProcessSelector.vue create mode 100644 web/src/views/cw/projectApproval/index.vue create mode 100644 web/src/views/cw/projectApproval/pending.vue create mode 100644 web/src/views/cw/projectAuditor/BUG_FIX_REPORT.md create mode 100644 web/src/views/cw/projectAuditor/README.md create mode 100644 web/src/views/cw/projectAuditor/debug.vue create mode 100644 web/src/views/cw/projectAuditor/index.vue create mode 100644 web/src/views/cw/projectAuditor/test.vue diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectApprovalController.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectApprovalController.java new file mode 100644 index 00000000..b4e7c626 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectApprovalController.java @@ -0,0 +1,156 @@ +package org.dromara.cw.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.cw.domain.bo.CwCustomApprovalBo; +import org.dromara.cw.domain.bo.CwApprovalActionBo; +import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo; +import org.dromara.cw.domain.vo.CwApprovalRecordVo; +import org.dromara.cw.service.ICwProjectApprovalService; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.log.enums.BusinessType; + +/** + * 项目审批流程管理 + * + * @author ruoyi + * @date 2025-01-03 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/cw/projectApproval") +public class CwProjectApprovalController extends BaseController { + + private final ICwProjectApprovalService projectApprovalService; + + /** + * 启动自定义审批流程 + */ + @SaCheckPermission("cw:projectApproval:start") + @Log(title = "启动审批流程", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/start") + public R startApprovalProcess(@Validated @RequestBody CwCustomApprovalBo bo) { + // 设置提交人信息 + bo.setSubmitterId(LoginHelper.getUserId()); + bo.setSubmitterName(LoginHelper.getUsername()); + return toAjax(projectApprovalService.startCustomApprovalProcess(bo)); + } + + /** + * 执行审批操作 + */ + @SaCheckPermission("cw:projectApproval:approve") + @Log(title = "执行审批操作", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/approve") + public R executeApproval(@Validated @RequestBody CwApprovalActionBo bo) { + return toAjax(projectApprovalService.executeApproval(bo)); + } + + /** + * 获取我的待审批列表 + */ + @SaCheckPermission("cw:projectApproval:query") + @GetMapping("/pending") + public TableDataInfo getMyPendingApprovals(PageQuery pageQuery) { + return projectApprovalService.getMyPendingApprovals(pageQuery); + } + + /** + * 获取我的已审批列表 + */ + @SaCheckPermission("cw:projectApproval:query") + @GetMapping("/processed") + public TableDataInfo getMyProcessedApprovals(PageQuery pageQuery) { + return projectApprovalService.getMyProcessedApprovals(pageQuery); + } + + /** + * 查询审批流程列表 + */ + @SaCheckPermission("cw:projectApproval:query") + @GetMapping("/list") + public TableDataInfo list(PageQuery pageQuery) { + return projectApprovalService.queryProcessList(pageQuery); + } + + /** + * 获取审批流程详情 + */ + @SaCheckPermission("cw:projectApproval:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(projectApprovalService.getProcessDetail(id)); + } + + /** + * 获取审批记录列表 + */ + @SaCheckPermission("cw:projectApproval:query") + @GetMapping("/records/{applicationId}") + public R> getApprovalRecords(@PathVariable Long applicationId) { + return R.ok(projectApprovalService.getApprovalRecords(applicationId)); + } + + /** + * 终止审批流程 + */ + @SaCheckPermission("cw:projectApproval:terminate") + @Log(title = "终止审批流程", businessType = BusinessType.UPDATE) + @DeleteMapping("/{ids}") + public R terminate(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(projectApprovalService.terminateProcess(List.of(ids))); + } + + /** + * 审批流程验证 + */ + @SaCheckPermission("cw:projectApproval:query") + @PostMapping("/validate") + public R validateApprovalProcess(@RequestBody CwCustomApprovalBo bo) { + // 简单验证逻辑 + if (bo.getAuditorIds() == null || bo.getAuditorIds().isEmpty()) { + return R.fail("请选择审核人"); + } + if (bo.getAuditorIds().size() > 10) { + return R.fail("审核人数量不能超过10个"); + } + return R.ok("验证通过"); + } + + /** + * 审批选项查询(用于下拉选择) + */ + @SaCheckPermission("cw:projectApproval:query") + @GetMapping("/options") + public R getApprovalOptions() { + // 返回一些选项数据,比如审批动作选项等 + return R.ok(java.util.Map.of( + "actions", java.util.List.of( + java.util.Map.of("value", "approve", "label", "通过"), + java.util.Map.of("value", "reject", "label", "拒绝") + ), + "statuses", java.util.List.of( + java.util.Map.of("value", "waiting", "label", "等待审批"), + java.util.Map.of("value", "approved", "label", "已通过"), + java.util.Map.of("value", "rejected", "label", "已拒绝") + ) + )); + } + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectAuditorController.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectAuditorController.java new file mode 100644 index 00000000..5d5cb767 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/controller/CwProjectAuditorController.java @@ -0,0 +1,251 @@ +package org.dromara.cw.controller; + +import java.util.List; + +import lombok.RequiredArgsConstructor; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.web.core.BaseController; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.core.domain.R; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.cw.domain.vo.CwProjectAuditorVo; +import org.dromara.cw.domain.bo.CwProjectAuditorBo; +import org.dromara.cw.service.ICwProjectAuditorService; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.log.enums.BusinessType; + +/** + * 项目审核人配置 + * + * @author ruoyi + * @date 2025-01-03 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/cw/projectAuditor") +public class CwProjectAuditorController extends BaseController { + + private final ICwProjectAuditorService cwProjectAuditorService; + + /** + * 查询项目审核人配置列表 + */ + @SaCheckPermission("cw:projectAuditor:list") + @GetMapping("/list") + public TableDataInfo list(CwProjectAuditorBo bo, PageQuery pageQuery) { + return cwProjectAuditorService.queryPageList(bo, pageQuery); + } + + /** + * 导出项目审核人配置列表 + */ + @SaCheckPermission("cw:projectAuditor:export") + @Log(title = "项目审核人配置", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(CwProjectAuditorBo bo, HttpServletResponse response) { + List list = cwProjectAuditorService.queryList(bo); + ExcelUtil.exportExcel(list, "项目审核人配置", CwProjectAuditorVo.class, response); + } + + /** + * 获取项目审核人配置详细信息 + */ + @SaCheckPermission("cw:projectAuditor:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(cwProjectAuditorService.queryById(id)); + } + + /** + * 新增项目审核人配置 + */ + @SaCheckPermission("cw:projectAuditor:add") + @Log(title = "项目审核人配置", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody CwProjectAuditorBo bo) { + // 设置配置人信息 + bo.setConfigBy(LoginHelper.getUserId()); + bo.setConfigByName(LoginHelper.getUsername()); + return toAjax(cwProjectAuditorService.insertByBo(bo)); + } + + /** + * 修改项目审核人配置 + */ + @SaCheckPermission("cw:projectAuditor:edit") + @Log(title = "项目审核人配置", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody CwProjectAuditorBo bo) { + return toAjax(cwProjectAuditorService.updateByBo(bo)); + } + + /** + * 删除项目审核人配置 + */ + @SaCheckPermission("cw:projectAuditor:remove") + @Log(title = "项目审核人配置", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(cwProjectAuditorService.deleteWithValidByIds(List.of(ids), true)); + } + + /** + * 批量添加审核人 + */ + @SaCheckPermission("cw:projectAuditor:add") + @Log(title = "批量添加审核人", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping("/batchAdd") + public R batchAdd(@RequestBody BatchAddRequest request) { + return toAjax(cwProjectAuditorService.batchAddAuditors( + request.getUserIds(), + LoginHelper.getUserId(), + LoginHelper.getUsername(), + request.getRemark() + )); + } + + /** + * 获取可用审核人列表 + */ + @SaCheckPermission("cw:projectAuditor:list") + @GetMapping("/available") + public R> getAvailableAuditors() { + return R.ok(cwProjectAuditorService.getAvailableAuditors()); + } + + /** + * 根据部门ID获取用户列表 + */ + @SaCheckPermission("cw:projectAuditor:list") + @GetMapping("/deptUsers/{deptId}") + public R> getDeptUsers(@PathVariable Long deptId) { + // 参数验证 + if (deptId == null || deptId <= 0) { + return R.fail("部门ID参数无效"); + } + return R.ok(cwProjectAuditorService.getDeptUsers(deptId)); + } + + /** + * 批量更新状态 + */ + @SaCheckPermission("cw:projectAuditor:edit") + @Log(title = "批量更新审核人状态", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping("/status") + public R updateStatus(@RequestBody StatusUpdateRequest request) { + return toAjax(cwProjectAuditorService.updateStatus(request.getIds(), request.getStatus())); + } + + /** + * 我配置的审核人 + */ + @SaCheckPermission("cw:projectAuditor:list") + @GetMapping("/myConfig") + public TableDataInfo getMyConfig(CwProjectAuditorBo bo, PageQuery pageQuery) { + bo.setConfigBy(LoginHelper.getUserId()); + return cwProjectAuditorService.queryPageList(bo, pageQuery); + } + + /** + * 获取审核人选项(用于下拉选择) + */ + @SaCheckPermission("cw:projectAuditor:list") + @GetMapping("/options") + public R> getAuditorOptions() { + return R.ok(cwProjectAuditorService.getAvailableAuditors()); + } + + /** + * 批量启用 + */ + @SaCheckPermission("cw:projectAuditor:edit") + @Log(title = "批量启用审核人", businessType = BusinessType.UPDATE) + @PutMapping("/enable/{ids}") + public R enable(@PathVariable Long[] ids) { + return toAjax(cwProjectAuditorService.updateStatus(List.of(ids), "1")); + } + + /** + * 批量停用 + */ + @SaCheckPermission("cw:projectAuditor:edit") + @Log(title = "批量停用审核人", businessType = BusinessType.UPDATE) + @PutMapping("/disable/{ids}") + public R disable(@PathVariable Long[] ids) { + return toAjax(cwProjectAuditorService.updateStatus(List.of(ids), "0")); + } + + /** + * 获取审核人统计数据 + */ + @SaCheckPermission("cw:projectAuditor:list") + @GetMapping("/statistics") + public R getStatistics() { + return R.ok(cwProjectAuditorService.getStatistics()); + } + + /** + * 批量添加请求对象 + */ + public static class BatchAddRequest { + private List userIds; + private String remark; + + public List getUserIds() { + return userIds; + } + + public void setUserIds(List userIds) { + this.userIds = userIds; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + } + + /** + * 状态更新请求对象 + */ + public static class StatusUpdateRequest { + private List ids; + private String status; + + public List getIds() { + return ids; + } + + public void setIds(List ids) { + this.ids = ids; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + } + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwApprovalRecord.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwApprovalRecord.java index 064ebc40..67f4e2dc 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwApprovalRecord.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwApprovalRecord.java @@ -124,4 +124,19 @@ public class CwApprovalRecord extends TenantEntity implements Serializable { */ private String configSnapshot; -} \ No newline at end of file + /** + * 流程ID + */ + private Long processId; + + /** + * 自定义节点名称 + */ + private String customNodeName; + + /** + * 是否自定义流程 + */ + private Boolean isCustomProcess; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwProjectApprovalProcess.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwProjectApprovalProcess.java new file mode 100644 index 00000000..3ba5c393 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwProjectApprovalProcess.java @@ -0,0 +1,82 @@ +package org.dromara.cw.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.tenant.core.TenantEntity; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 项目审批流程对象 cw_project_approval_process + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("cw_project_approval_process") +public class CwProjectApprovalProcess extends TenantEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID(雪花ID) + */ + @TableId(value = "id") + private Long id; + + /** + * 申报ID + */ + private Long applicationId; + + /** + * 申报编号 + */ + private String applicationNo; + + /** + * 流程名称 + */ + private String processName; + + /** + * 流程状态(waiting-等待开始 running-进行中 completed-已完成 terminated-已终止) + */ + private String processStatus; + + /** + * 总审批节点数 + */ + private Integer totalNodes; + + /** + * 当前审批节点 + */ + private Integer currentNode; + + /** + * 流程开始时间 + */ + private Date startTime; + + /** + * 流程结束时间 + */ + private Date endTime; + + /** + * 提交人ID + */ + private Long submitterId; + + /** + * 提交人姓名 + */ + private String submitterName; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwProjectAuditor.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwProjectAuditor.java new file mode 100644 index 00000000..b8fb06ee --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/CwProjectAuditor.java @@ -0,0 +1,92 @@ +package org.dromara.cw.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.tenant.core.TenantEntity; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 项目审核人配置对象 cw_project_auditor + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("cw_project_auditor") +public class CwProjectAuditor extends TenantEntity implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID(雪花ID) + */ + @TableId(value = "id") + private Long id; + + /** + * 用户ID + */ + private Long userId; + + /** + * 用户姓名 + */ + private String userName; + + /** + * 用户昵称 + */ + private String nickName; + + /** + * 所属部门ID + */ + private Long deptId; + + /** + * 所属部门名称 + */ + private String deptName; + + /** + * 联系电话 + */ + private String phoneNumber; + + /** + * 邮箱地址 + */ + private String email; + + /** + * 审核人状态(1正常 0停用) + */ + private String status; + + /** + * 备注说明 + */ + private String remark; + + /** + * 配置人ID + */ + private Long configBy; + + /** + * 配置人姓名 + */ + private String configByName; + + /** + * 删除标志(0代表存在 1代表删除) + */ + @TableLogic + private Integer deleted; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwApprovalActionBo.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwApprovalActionBo.java new file mode 100644 index 00000000..aeb6c89d --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwApprovalActionBo.java @@ -0,0 +1,33 @@ +package org.dromara.cw.domain.bo; + +import lombok.Data; +import jakarta.validation.constraints.*; + +/** + * 审批操作业务对象 + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +public class CwApprovalActionBo { + + /** + * 审批记录ID + */ + @NotNull(message = "审批记录ID不能为空") + private Long recordId; + + /** + * 审批操作(approve-通过 reject-拒绝) + */ + @NotBlank(message = "审批操作不能为空") + private String action; + + /** + * 审批意见 + */ + @NotBlank(message = "审批意见不能为空") + private String opinion; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwApprovalRecordBo.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwApprovalRecordBo.java index 1428356d..b3105f5c 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwApprovalRecordBo.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwApprovalRecordBo.java @@ -132,13 +132,18 @@ public class CwApprovalRecordBo extends BaseEntity { private String configSnapshot; // ========== 查询条件字段 ========== - + /** * 部门ID查询条件(用于部门审批页面过滤) * 支持具体部门ID或特殊值 'current'(当前用户部门) */ private String deptId; + /** + * 部门ID(Long类型,用于数据库操作) + */ + private Long deptIdLong; + /** * 项目名称(查询条件) */ @@ -164,4 +169,4 @@ public class CwApprovalRecordBo extends BaseEntity { */ private String approvalStatus; -} \ No newline at end of file +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwCustomApprovalBo.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwCustomApprovalBo.java new file mode 100644 index 00000000..dc245b05 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwCustomApprovalBo.java @@ -0,0 +1,52 @@ +package org.dromara.cw.domain.bo; + +import lombok.Data; +import jakarta.validation.constraints.*; +import java.util.List; + +/** + * 自定义审批流程业务对象 + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +public class CwCustomApprovalBo { + + /** + * 申报ID + */ + @NotNull(message = "申报ID不能为空") + private Long applicationId; + + /** + * 申报编号 + */ + @NotBlank(message = "申报编号不能为空") + private String applicationNo; + + /** + * 审核人ID列表 + */ + @NotEmpty(message = "审核人列表不能为空") + private List auditorIds; + + /** + * 流程名称 + */ + @NotBlank(message = "流程名称不能为空") + private String processName; + + /** + * 提交人ID + */ + @NotNull(message = "提交人ID不能为空") + private Long submitterId; + + /** + * 提交人姓名 + */ + @NotBlank(message = "提交人姓名不能为空") + private String submitterName; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectApprovalProcessBo.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectApprovalProcessBo.java new file mode 100644 index 00000000..44b85fed --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectApprovalProcessBo.java @@ -0,0 +1,88 @@ +package org.dromara.cw.domain.bo; + +import org.dromara.cw.domain.CwProjectApprovalProcess; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; + +import java.util.Date; + +/** + * 项目审批流程业务对象 cw_project_approval_process + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = CwProjectApprovalProcess.class, reverseConvertGenerate = false) +public class CwProjectApprovalProcessBo extends BaseEntity { + + /** + * 主键ID(雪花ID) + */ + @NotNull(message = "主键ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 申报ID + */ + @NotNull(message = "申报ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long applicationId; + + /** + * 申报编号 + */ + @NotBlank(message = "申报编号不能为空", groups = { AddGroup.class, EditGroup.class }) + private String applicationNo; + + /** + * 流程名称 + */ + @NotBlank(message = "流程名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String processName; + + /** + * 流程状态(waiting-等待开始 running-进行中 completed-已完成 terminated-已终止) + */ + @NotBlank(message = "流程状态不能为空", groups = { AddGroup.class, EditGroup.class }) + private String processStatus; + + /** + * 总审批节点数 + */ + @NotNull(message = "总审批节点数不能为空", groups = { AddGroup.class, EditGroup.class }) + private Integer totalNodes; + + /** + * 当前审批节点 + */ + private Integer currentNode; + + /** + * 流程开始时间 + */ + private Date startTime; + + /** + * 流程结束时间 + */ + private Date endTime; + + /** + * 提交人ID + */ + @NotNull(message = "提交人ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long submitterId; + + /** + * 提交人姓名 + */ + @NotBlank(message = "提交人姓名不能为空", groups = { AddGroup.class, EditGroup.class }) + private String submitterName; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectAuditorBatchBo.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectAuditorBatchBo.java new file mode 100644 index 00000000..d4bfebd7 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectAuditorBatchBo.java @@ -0,0 +1,39 @@ +package org.dromara.cw.domain.bo; + +import lombok.Data; +import jakarta.validation.constraints.*; +import java.util.List; + +/** + * 批量添加审核人业务对象 + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +public class CwProjectAuditorBatchBo { + + /** + * 用户ID列表 + */ + @NotEmpty(message = "用户ID列表不能为空") + private List userIds; + + /** + * 配置人ID + */ + @NotNull(message = "配置人ID不能为空") + private Long configBy; + + /** + * 配置人姓名 + */ + @NotBlank(message = "配置人姓名不能为空") + private String configByName; + + /** + * 备注说明 + */ + private String remark; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectAuditorBo.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectAuditorBo.java new file mode 100644 index 00000000..6001304e --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/bo/CwProjectAuditorBo.java @@ -0,0 +1,91 @@ +package org.dromara.cw.domain.bo; + +import org.dromara.cw.domain.CwProjectAuditor; +import org.dromara.common.mybatis.core.domain.BaseEntity; +import org.dromara.common.core.validate.AddGroup; +import org.dromara.common.core.validate.EditGroup; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import lombok.EqualsAndHashCode; +import jakarta.validation.constraints.*; + +import java.util.List; + +/** + * 项目审核人配置业务对象 cw_project_auditor + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@AutoMapper(target = CwProjectAuditor.class, reverseConvertGenerate = false) +public class CwProjectAuditorBo extends BaseEntity { + + /** + * 主键ID(雪花ID) + */ + @NotNull(message = "主键ID不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户ID + */ + @NotNull(message = "用户ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long userId; + + /** + * 用户姓名 + */ + @NotBlank(message = "用户姓名不能为空", groups = { AddGroup.class, EditGroup.class }) + private String userName; + + /** + * 用户昵称 + */ + private String nickName; + + /** + * 所属部门ID + */ + @NotNull(message = "所属部门ID不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long deptId; + + /** + * 所属部门名称 + */ + @NotBlank(message = "所属部门名称不能为空", groups = { AddGroup.class, EditGroup.class }) + private String deptName; + + /** + * 联系电话 + */ + private String phoneNumber; + + /** + * 邮箱地址 + */ + private String email; + + /** + * 审核人状态(1正常 0停用) + */ + @NotBlank(message = "审核人状态不能为空", groups = { AddGroup.class, EditGroup.class }) + private String status; + + /** + * 备注说明 + */ + private String remark; + + /** + * 配置人ID + */ + private Long configBy; + + /** + * 配置人姓名 + */ + private String configByName; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwApprovalRecordVo.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwApprovalRecordVo.java index 3feba644..0ea24363 100644 --- a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwApprovalRecordVo.java +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwApprovalRecordVo.java @@ -172,37 +172,37 @@ public class CwApprovalRecordVo implements Serializable { private Date updateTime; // ================ 项目申报相关字段(通过关联查询获取) ================ - + /** * 项目名称 */ @ExcelProperty(value = "项目名称") private String projectName; - + /** * 申报人姓名 */ @ExcelProperty(value = "申报人姓名") private String applicantName; - + /** * 申报部门名称 */ @ExcelProperty(value = "申报部门") private String deptName; - + /** * 总投资(万元) */ @ExcelProperty(value = "总投资(万元)") private java.math.BigDecimal totalInvestment; - + /** * 项目负责人 */ - @ExcelProperty(value = "项目负责人") + @ExcelProperty(value = "项目负责人") private String principal; - + /** * 部门名称(完整) */ @@ -213,23 +213,47 @@ public class CwApprovalRecordVo implements Serializable { */ @ExcelProperty(value = "当前审批人") private String currentApproverName; - + /** * 审批状态(前端表格需要) */ @ExcelProperty(value = "审批状态") private String approvalStatus; - + /** * 提交时间(前端表格需要) */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date submitTime; - + /** * 审批时间(前端表格需要) */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date approvalTime; -} \ No newline at end of file + /** + * 总节点数(从流程表获取) + */ + @ExcelProperty(value = "总节点数") + private Integer totalNodes; + + /** + * 当前节点数(从流程表获取) + */ + @ExcelProperty(value = "当前节点数") + private Integer currentNode; + + /** + * 流程名称(从流程表获取) + */ + @ExcelProperty(value = "流程名称") + private String processName; + + /** + * 流程状态(从流程表获取) + */ + @ExcelProperty(value = "流程状态") + private String processStatus; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectApprovalProcessVo.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectApprovalProcessVo.java new file mode 100644 index 00000000..e5e39e8b --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectApprovalProcessVo.java @@ -0,0 +1,121 @@ +package org.dromara.cw.domain.vo; + +import org.dromara.cw.domain.CwProjectApprovalProcess; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 项目审批流程视图对象 cw_project_approval_process + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = CwProjectApprovalProcess.class) +public class CwProjectApprovalProcessVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID(雪花ID) + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 申报ID + */ + @ExcelProperty(value = "申报ID") + private Long applicationId; + + /** + * 申报编号 + */ + @ExcelProperty(value = "申报编号") + private String applicationNo; + + /** + * 流程名称 + */ + @ExcelProperty(value = "流程名称") + private String processName; + + /** + * 流程状态(waiting-等待开始 running-进行中 completed-已完成 terminated-已终止) + */ + @ExcelProperty(value = "流程状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "cw_process_status") + private String processStatus; + + /** + * 总审批节点数 + */ + @ExcelProperty(value = "总审批节点数") + private Integer totalNodes; + + /** + * 当前审批节点 + */ + @ExcelProperty(value = "当前审批节点") + private Integer currentNode; + + /** + * 流程开始时间 + */ + @ExcelProperty(value = "流程开始时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startTime; + + /** + * 流程结束时间 + */ + @ExcelProperty(value = "流程结束时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endTime; + + /** + * 提交人ID + */ + @ExcelProperty(value = "提交人ID") + private Long submitterId; + + /** + * 提交人姓名 + */ + @ExcelProperty(value = "提交人姓名") + private String submitterName; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新者 + */ + private Long updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectAuditorVo.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectAuditorVo.java new file mode 100644 index 00000000..cdd6e72d --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/domain/vo/CwProjectAuditorVo.java @@ -0,0 +1,125 @@ +package org.dromara.cw.domain.vo; + +import org.dromara.cw.domain.CwProjectAuditor; +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import org.dromara.common.excel.annotation.ExcelDictFormat; +import org.dromara.common.excel.convert.ExcelDictConvert; +import io.github.linpeilie.annotations.AutoMapper; +import lombok.Data; +import com.fasterxml.jackson.annotation.JsonFormat; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 项目审核人配置视图对象 cw_project_auditor + * + * @author ruoyi + * @date 2025-01-03 + */ +@Data +@ExcelIgnoreUnannotated +@AutoMapper(target = CwProjectAuditor.class) +public class CwProjectAuditorVo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 主键ID(雪花ID) + */ + @ExcelProperty(value = "主键ID") + private Long id; + + /** + * 用户ID + */ + @ExcelProperty(value = "用户ID") + private Long userId; + + /** + * 用户姓名 + */ + @ExcelProperty(value = "用户姓名") + private String userName; + + /** + * 用户昵称 + */ + @ExcelProperty(value = "用户昵称") + private String nickName; + + /** + * 所属部门ID + */ + @ExcelProperty(value = "所属部门ID") + private Long deptId; + + /** + * 所属部门名称 + */ + @ExcelProperty(value = "所属部门名称") + private String deptName; + + /** + * 联系电话 + */ + @ExcelProperty(value = "联系电话") + private String phoneNumber; + + /** + * 邮箱地址 + */ + @ExcelProperty(value = "邮箱地址") + private String email; + + /** + * 审核人状态(1正常 0停用) + */ + @ExcelProperty(value = "审核人状态", converter = ExcelDictConvert.class) + @ExcelDictFormat(dictType = "sys_normal_disable") + private String status; + + /** + * 备注说明 + */ + @ExcelProperty(value = "备注说明") + private String remark; + + /** + * 配置人ID + */ + @ExcelProperty(value = "配置人ID") + private Long configBy; + + /** + * 配置人姓名 + */ + @ExcelProperty(value = "配置人姓名") + private String configByName; + + /** + * 创建者 + */ + private Long createBy; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新者 + */ + private Long updateBy; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectApprovalProcessMapper.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectApprovalProcessMapper.java new file mode 100644 index 00000000..a9262c90 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectApprovalProcessMapper.java @@ -0,0 +1,15 @@ +package org.dromara.cw.mapper; + +import org.dromara.cw.domain.CwProjectApprovalProcess; +import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; + +/** + * 项目审批流程Mapper接口 + * + * @author ruoyi + * @date 2025-01-03 + */ +public interface CwProjectApprovalProcessMapper extends BaseMapperPlus { + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectAuditorMapper.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectAuditorMapper.java new file mode 100644 index 00000000..38c97582 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/mapper/CwProjectAuditorMapper.java @@ -0,0 +1,28 @@ +package org.dromara.cw.mapper; + +import org.dromara.cw.domain.CwProjectAuditor; +import org.dromara.cw.domain.vo.CwProjectAuditorVo; +import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 项目审核人配置Mapper接口 + * + * @author ruoyi + * @date 2025-01-03 + */ +public interface CwProjectAuditorMapper extends BaseMapperPlus { + + /** + * 根据部门ID查询可用审核人 + */ + List selectAuditorsByDeptId(@Param("deptId") Long deptId); + + /** + * 查询审核人统计信息 + */ + List selectAuditorStatistics(); + +} 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 5064c532..1ca9db32 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 @@ -2,6 +2,7 @@ package org.dromara.cw.service; import org.dromara.cw.domain.bo.CwApprovalRecordBo; import org.dromara.cw.domain.vo.CwApprovalRecordVo; +import org.dromara.cw.domain.CwApprovalRecord; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.PageQuery; @@ -119,6 +120,11 @@ public interface ICwApprovalRecordService { */ Boolean insertByBo(CwApprovalRecordBo bo); + /** + * 批量新增审批记录 + */ + Boolean batchInsert(List records); + /** * 修改审批记录 */ @@ -190,4 +196,18 @@ public interface ICwApprovalRecordService { */ List> getAvailableApprovers(Long applicationId); + /** + * 创建自定义审批流程 + * 根据用户选择的审批人列表创建自定义审批流程 + * + * @param applicationId 申报ID + * @param applicationNo 申报编号 + * @param auditorIds 审批人ID列表(按审批顺序排列) + * @param submitterId 提交人ID + * @param submitterName 提交人名称 + * @return 是否创建成功 + */ + Boolean createCustomApprovalProcess(Long applicationId, String applicationNo, + List auditorIds, Long submitterId, String submitterName); + } diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApprovalProcessService.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApprovalProcessService.java new file mode 100644 index 00000000..cf76f459 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApprovalProcessService.java @@ -0,0 +1,110 @@ +package org.dromara.cw.service; + +import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo; +import org.dromara.cw.domain.bo.CwCustomApprovalBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * 项目审批流程Service接口 + * + * @author ruoyi + * @date 2025-01-03 + */ +public interface ICwProjectApprovalProcessService { + + /** + * 查询项目审批流程 + */ + CwProjectApprovalProcessVo queryById(Long id); + + /** + * 查询项目审批流程列表 + */ + TableDataInfo queryPageList(CwProjectApprovalProcessVo vo, PageQuery pageQuery); + + /** + * 查询项目审批流程列表 + */ + List queryList(CwProjectApprovalProcessVo vo); + + /** + * 根据申报ID查询审批流程 + */ + CwProjectApprovalProcessVo queryByApplicationId(Long applicationId); + + /** + * 根据申报编号查询审批流程 + */ + CwProjectApprovalProcessVo queryByApplicationNo(String applicationNo); + + /** + * 查询我提交的审批流程 + */ + List queryMyProcesses(Long submitterId); + + /** + * 查询进行中的审批流程 + */ + List queryRunningProcesses(); + + /** + * 查询超时的审批流程 + */ + List queryOvertimeProcesses(Integer hours); + + /** + * 启动自定义审批流程 + */ + Boolean startCustomApprovalProcess(CwCustomApprovalBo bo); + + /** + * 终止审批流程 + */ + Boolean terminateProcess(Long id, String reason); + + /** + * 校验并批量删除项目审批流程信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 推进流程到下一节点 + */ + Boolean advanceToNextNode(Long processId); + + /** + * 完成审批流程 + */ + Boolean completeProcess(Long processId); + + /** + * 更新流程当前节点 + */ + Boolean updateCurrentNode(Long id, Integer currentNode); + + /** + * 更新流程状态 + */ + Boolean updateProcessStatus(Long id, String status); + + /** + * 验证审批流程配置 + */ + Boolean validateApprovalProcess(CwCustomApprovalBo bo); + + /** + * 统计审批流程状态 + */ + List> queryProcessStatistics(Long submitterId); + + /** + * 导出数据 + */ + List exportList(CwProjectApprovalProcessVo vo); + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApprovalService.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApprovalService.java new file mode 100644 index 00000000..14fc48dd --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectApprovalService.java @@ -0,0 +1,60 @@ +package org.dromara.cw.service; + +import org.dromara.cw.domain.bo.CwCustomApprovalBo; +import org.dromara.cw.domain.bo.CwApprovalActionBo; +import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo; +import org.dromara.cw.domain.vo.CwApprovalRecordVo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.List; + +/** + * 项目审批流程Service接口 + * + * @author ruoyi + * @date 2025-01-03 + */ +public interface ICwProjectApprovalService { + + /** + * 启动自定义审批流程 + */ + Boolean startCustomApprovalProcess(CwCustomApprovalBo bo); + + /** + * 执行审批操作 + */ + Boolean executeApproval(CwApprovalActionBo bo); + + /** + * 获取我的待审批列表 + */ + TableDataInfo getMyPendingApprovals(PageQuery pageQuery); + + /** + * 获取我的已审批列表 + */ + TableDataInfo getMyProcessedApprovals(PageQuery pageQuery); + + /** + * 查询审批流程列表 + */ + TableDataInfo queryProcessList(PageQuery pageQuery); + + /** + * 获取审批流程详情 + */ + CwProjectApprovalProcessVo getProcessDetail(Long processId); + + /** + * 获取审批记录列表 + */ + List getApprovalRecords(Long applicationId); + + /** + * 终止审批流程 + */ + Boolean terminateProcess(List processIds); + +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectAuditorService.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectAuditorService.java new file mode 100644 index 00000000..b53ad4f2 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/ICwProjectAuditorService.java @@ -0,0 +1,79 @@ +package org.dromara.cw.service; + +import org.dromara.cw.domain.vo.CwProjectAuditorVo; +import org.dromara.cw.domain.bo.CwProjectAuditorBo; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 项目审核人配置Service接口 + * + * @author ruoyi + * @date 2025-01-03 + */ +public interface ICwProjectAuditorService { + + /** + * 查询项目审核人配置 + */ + CwProjectAuditorVo queryById(Long id); + + /** + * 查询项目审核人配置列表 + */ + TableDataInfo queryPageList(CwProjectAuditorBo bo, PageQuery pageQuery); + + /** + * 查询项目审核人配置列表 + */ + List queryList(CwProjectAuditorBo bo); + + /** + * 新增项目审核人配置 + */ + Boolean insertByBo(CwProjectAuditorBo bo); + + /** + * 修改项目审核人配置 + */ + Boolean updateByBo(CwProjectAuditorBo bo); + + /** + * 校验并批量删除项目审核人配置信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); + + /** + * 批量添加审核人(从部门用户中选择) + */ + Boolean batchAddAuditors(List userIds, Long configBy, String configByName, String remark); + + /** + * 获取可用审核人列表 + */ + List getAvailableAuditors(); + + /** + * 根据部门ID获取用户列表 + */ + List getDeptUsers(Long deptId); + + /** + * 批量更新状态 + */ + Boolean updateStatus(List ids, String status); + + /** + * 获取审核人统计数据 + */ + Object getStatistics(); + + /** + * 根据用户ID查询项目审核人配置 + */ + CwProjectAuditorVo queryByUserId(Long userId); + +} 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 84f2b634..c12b5ff1 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 @@ -5,13 +5,12 @@ 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; -import java.time.Duration; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -25,8 +24,10 @@ import org.dromara.cw.domain.CwApprovalRecord; import org.dromara.cw.mapper.CwApprovalRecordMapper; import org.dromara.cw.service.ICwApprovalRecordService; import org.dromara.cw.service.ICwProjectApplicationService; +import org.dromara.cw.service.ICwProjectAuditorService; import org.dromara.cw.mapper.CwProjectApplicationMapper; import org.dromara.cw.domain.CwProjectApplication; +import org.dromara.cw.domain.vo.CwProjectAuditorVo; import org.dromara.system.service.ISysUserService; import org.dromara.system.service.ISysDeptService; import org.dromara.system.domain.vo.SysUserVo; @@ -54,6 +55,7 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { private final CwProjectApplicationMapper applicationMapper; private final ISysUserService sysUserService; private final ISysDeptService sysDeptService; + private final ICwProjectAuditorService projectAuditorService; /** * 根据主键查询审批记录 @@ -502,6 +504,17 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { return flag; } + /** + * 批量新增审批记录 + */ + @Override + public Boolean batchInsert(List records) { + if (records == null || records.isEmpty()) { + return false; + } + return baseMapper.batchInsert(records) > 0; + } + /** * 修改审批记录 */ @@ -678,8 +691,8 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { Long currentDeptId, String currentTenantId) { CwApprovalRecord record = new CwApprovalRecord(); - // 生成唯一ID(批量插入需要手动生成) - record.setId(SequenceUtils.nextId("approval_record", Duration.ofDays(1))); + // 移除手动ID设置,使用MyBatis-Plus自动生成雪花ID(符合项目规范) + // record.setId() - 让MyBatis-Plus自动生成,避免主键冲突 // 设置申报相关信息 record.setApplicationId(applicationId); @@ -979,27 +992,110 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { } private Boolean updateApplicationStatusAndProcess(Long applicationId, String nodeCode, String approveAction) { - if ("approve".equals(approveAction)) { - // 审批通过,启动下一个节点或完成审批 - if ("dept_approve".equals(nodeCode)) { - // 部门审批通过,启动财务审批 - return startNextApprovalNode(applicationId, "finance_approve"); - } else if ("finance_approve".equals(nodeCode)) { - // 财务审批通过,启动领导审批 - return startNextApprovalNode(applicationId, "leader_approve"); - } else if ("leader_approve".equals(nodeCode)) { - // 领导审批通过,整个审批流程完成 - return projectApplicationService.updateApprovalStatus(applicationId, "approved"); - } - } else if ("reject".equals(approveAction)) { - // 审批拒绝,整个流程结束 - return projectApplicationService.updateApprovalStatus(applicationId, "rejected"); - } else if ("return".equals(approveAction)) { - // 退回修改,申报状态改为已退回 - return projectApplicationService.updateApprovalStatus(applicationId, "returned"); - } + try { + // 获取当前审批记录 + LambdaQueryWrapper currentQuery = Wrappers.lambdaQuery() + .eq(CwApprovalRecord::getApplicationId, applicationId) + .eq(CwApprovalRecord::getNodeCode, nodeCode); + CwApprovalRecord currentRecord = baseMapper.selectOne(currentQuery); - return true; + if (currentRecord == null) { + log.error("❌ 当前审批记录不存在:applicationId={}, nodeCode={}", applicationId, nodeCode); + return false; + } + + if ("approve".equals(approveAction)) { + // 🔧 基于node_order的顺序审批逻辑 + log.info("✅ 审批通过,查找下一级审批:applicationId={}, currentOrder={}", + applicationId, currentRecord.getNodeOrder()); + + // 查找下一级审批记录(node_order + 1) + LambdaQueryWrapper nextQuery = Wrappers.lambdaQuery() + .eq(CwApprovalRecord::getApplicationId, applicationId) + .eq(CwApprovalRecord::getNodeOrder, currentRecord.getNodeOrder() + 1); + CwApprovalRecord nextRecord = baseMapper.selectOne(nextQuery); + + if (nextRecord != null) { + // 存在下一级审批,激活下一级 + log.info("📋 激活下一级审批:nextRecordId={}, nodeOrder={}, nodeCode={}, currentStatus={}", + nextRecord.getId(), nextRecord.getNodeOrder(), nextRecord.getNodeCode(), nextRecord.getNodeStatus()); + + // 使用条件更新,确保只有在特定状态下才能激活 + LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate() + .eq(CwApprovalRecord::getId, nextRecord.getId()) + .eq(CwApprovalRecord::getApplicationId, applicationId) + .ne(CwApprovalRecord::getNodeStatus, "waiting") // 避免重复激活已经是waiting状态的记录 + .ne(CwApprovalRecord::getNodeStatus, "approved") // 避免激活已完成的记录 + .ne(CwApprovalRecord::getNodeStatus, "rejected") // 避免激活已拒绝的记录 + .set(CwApprovalRecord::getNodeStatus, "waiting") + .set(CwApprovalRecord::getStartTime, new Date()) // 设置开始时间 + .set(CwApprovalRecord::getUpdateTime, new Date()) + .set(CwApprovalRecord::getUpdateBy, LoginHelper.getUserId()); + + int updateResult = baseMapper.update(null, updateWrapper); + + if (updateResult <= 0) { + log.error("❌ 激活下一级审批失败: applicationId={}, nextRecordId={}, nodeOrder={}, nodeCode={}", + applicationId, nextRecord.getId(), nextRecord.getNodeOrder(), nextRecord.getNodeCode()); + + // 重新查询记录状态,分析失败原因 + CwApprovalRecord checkRecord = baseMapper.selectById(nextRecord.getId()); + if (checkRecord == null) { + log.error("❌ 下一级审批记录已不存在: recordId={}", nextRecord.getId()); + } else { + log.error("❌ 下一级审批记录状态不符合激活条件: recordId={}, currentStatus={}, 限制条件=不能是[waiting,approved,rejected]", + nextRecord.getId(), checkRecord.getNodeStatus()); + } + return false; + } + + log.info("✅ 下一级审批已激活成功: recordId={}, newStatus=waiting", nextRecord.getId()); + return true; + } else { + // 没有下一级审批,整个流程完成 + log.info("🎉 所有审批完成,更新申报状态为approved: applicationId={}, currentOrder={}", + applicationId, currentRecord.getNodeOrder()); + + boolean statusUpdateResult = projectApplicationService.updateApprovalStatus(applicationId, "approved"); + if (!statusUpdateResult) { + log.error("❌ 更新申报状态为approved失败: applicationId={}", applicationId); + return false; + } + + log.info("✅ 申报状态已成功更新为approved: applicationId={}", applicationId); + return true; + } + + } else if ("reject".equals(approveAction)) { + // 审批拒绝,整个流程结束 + log.info("❌ 审批拒绝,更新申报状态为rejected: applicationId={}", applicationId); + boolean rejectResult = projectApplicationService.updateApprovalStatus(applicationId, "rejected"); + if (!rejectResult) { + log.error("❌ 更新申报状态为rejected失败: applicationId={}", applicationId); + return false; + } + log.info("✅ 申报状态已更新为rejected: applicationId={}", applicationId); + return true; + + } else if ("return".equals(approveAction)) { + // 退回修改,申报状态改为已退回,保留历史审批记录 + log.info("↩️ 审批退回,更新申报状态为returned: applicationId={}", applicationId); + boolean returnResult = projectApplicationService.updateApprovalStatus(applicationId, "returned"); + if (!returnResult) { + log.error("❌ 更新申报状态为returned失败: applicationId={}", applicationId); + return false; + } + log.info("✅ 申报状态已更新为returned: applicationId={}", applicationId); + return true; + } + + return true; + + } catch (Exception e) { + log.error("❌ 更新申报状态和流程失败:applicationId={}, nodeCode={}, action={}, error={}", + applicationId, nodeCode, approveAction, e.getMessage(), e); + return false; + } } private Boolean startNextApprovalNode(Long applicationId, String nextNodeCode) { @@ -1450,4 +1546,122 @@ public class CwApprovalRecordServiceImpl implements ICwApprovalRecordService { throw new RuntimeException("指定院领导审批人失败:" + e.getMessage()); } } + + /** + * 创建自定义审批流程 + * 根据用户选择的审批人列表创建自定义审批流程 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean createCustomApprovalProcess(Long applicationId, String applicationNo, + List auditorIds, Long submitterId, String submitterName) { + + log.info("🚀 创建自定义审批流程:applicationId={}, auditorIds={}", applicationId, auditorIds); + + try { + // 1. 验证参数 + if (applicationId == null) { + throw new RuntimeException("申报ID不能为空"); + } + if (auditorIds == null || auditorIds.isEmpty()) { + throw new RuntimeException("审批人列表不能为空"); + } + + // 2. 验证审批人是否有效 + List validAuditors = validateAuditors(auditorIds); + if (validAuditors.size() != auditorIds.size()) { + throw new RuntimeException("包含无效的审批人,请重新选择"); + } + + // 3. 创建自定义审批记录 + List records = new ArrayList<>(); + + for (int i = 0; i < auditorIds.size(); i++) { + Long auditorId = auditorIds.get(i); + CwProjectAuditorVo auditor = validAuditors.get(i); + + CwApprovalRecord record = new CwApprovalRecord(); + // 移除手动ID设置,使用MyBatis-Plus自动生成雪花ID(符合项目规范) + // record.setId() - 让MyBatis-Plus自动生成,避免主键冲突 + record.setApplicationId(applicationId); + record.setApplicationNo(applicationNo); + record.setNodeCode("custom_node_" + (i + 1)); + record.setNodeName("自定义审批节点" + (i + 1)); + record.setNodeOrder(i + 1); + record.setApproverId(auditorId); + record.setApproverName(auditor.getUserName()); + record.setNodeStatus(i == 0 ? "waiting" : "pending"); // 第一个waiting,其他pending + + // 设置基础字段 + Date now = new Date(); + record.setCreateBy(submitterId); + record.setCreateTime(now); + record.setUpdateBy(submitterId); + record.setUpdateTime(now); + record.setTenantId(LoginHelper.getTenantId()); + record.setIsDelegate(false); + record.setIsOvertime(false); + + // 🔧 修复:设置startTime字段(数据库NOT NULL约束) + if (i == 0) { + // 第一个节点:waiting状态,立即开始 + record.setStartTime(now); + log.info("设置自定义审批第一个节点开始时间:{} -> {}", record.getNodeCode(), now); + } else { + // 后续节点:pending状态,也需要设置startTime满足数据库约束 + record.setStartTime(now); + log.info("设置自定义审批后续节点开始时间:{} -> {} (满足数据库约束)", record.getNodeCode(), now); + } + + records.add(record); + } + + // 4. 批量插入审批记录 + boolean result = false; + if (!records.isEmpty()) { + for (CwApprovalRecord record : records) { + result = baseMapper.insert(record) > 0; + if (!result) { + throw new RuntimeException("创建审批记录失败"); + } + } + } + + // 5. 同步更新项目申报状态为"审批中" + // 这是关键步骤:启动审批流程的同时提交申报 + ICwProjectApplicationService applicationService = SpringUtils.getBean(ICwProjectApplicationService.class); + boolean statusUpdateResult = applicationService.updateApprovalStatus(applicationId, "approving"); + if (!statusUpdateResult) { + throw new RuntimeException("更新申报状态失败,审批流程创建失败"); + } + + log.info("✅ 自定义审批流程创建成功:共创建{}个审批节点", records.size()); + log.info("✅ 申报状态已更新为审批中:applicationId={}", applicationId); + return result; + + } catch (Exception e) { + log.error("❌ 创建自定义审批流程失败:{}", e.getMessage(), e); + throw new RuntimeException("创建审批流程失败:" + e.getMessage()); + } + } + + /** + * 验证审批人列表 + */ + private List validateAuditors(List auditorIds) { + List validAuditors = new ArrayList<>(); + + for (Long auditorId : auditorIds) { + CwProjectAuditorVo auditor = projectAuditorService.queryByUserId(auditorId); + if (auditor == null) { + throw new RuntimeException("审批人不存在:" + auditorId); + } + if (!"1".equals(auditor.getStatus())) { + throw new RuntimeException("审批人已停用:" + auditor.getUserName()); + } + validAuditors.add(auditor); + } + + return validAuditors; + } } 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 a6d59fba..4fcaf271 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 @@ -19,8 +19,8 @@ import org.dromara.cw.domain.CwProjectApplication; import org.dromara.cw.mapper.CwProjectApplicationMapper; import org.dromara.cw.service.ICwProjectApplicationService; import org.dromara.cw.service.ICwPerformanceIndicatorService; -import org.dromara.cw.event.ApprovalProcessCreatedEvent; -import org.springframework.context.ApplicationEventPublisher; +import org.dromara.cw.service.ICwApprovalRecordService; +import org.dromara.common.core.utils.SpringUtils; import java.text.SimpleDateFormat; import java.util.*; @@ -41,7 +41,6 @@ public class CwProjectApplicationServiceImpl implements ICwProjectApplicationSer private final CwProjectApplicationMapper baseMapper; private final ICwPerformanceIndicatorService performanceIndicatorService; - private final ApplicationEventPublisher eventPublisher; /** * 查询项目预算申报 @@ -584,30 +583,34 @@ public class CwProjectApplicationServiceImpl implements ICwProjectApplicationSer } if (updateResult) { - // 异步发布审批流程创建事件(避免阻塞主流程) + // 直接同步创建审批流程(确保事务一致性) try { - log.info("🚀 准备异步发布审批流程创建事件:applicationId={}, applicationNo={}", id, application.getApplicationNo()); + log.info("🚀 开始同步创建审批流程:applicationId={}, applicationNo={}", id, application.getApplicationNo()); - // 在主线程中收集用户上下文信息(异步线程中无法获取) + // 收集用户上下文信息 Long currentUserId = LoginHelper.getUserId(); String currentUserName = LoginHelper.getUsername(); Long currentDeptId = LoginHelper.getDeptId(); String currentTenantId = LoginHelper.getTenantId(); - log.info("📋 收集用户上下文:userId={}, userName={}, deptId={}, tenantId={}", + log.info("📋 用户上下文:userId={}, userName={}, deptId={}, tenantId={}", currentUserId, currentUserName, currentDeptId, currentTenantId); - ApprovalProcessCreatedEvent event = new ApprovalProcessCreatedEvent( - this, id, application.getApplicationNo(), + // 使用SpringUtils动态获取服务,避免循环依赖 + ICwApprovalRecordService approvalRecordService = SpringUtils.getBean(ICwApprovalRecordService.class); + Boolean createResult = approvalRecordService.createApprovalProcess( + id, application.getApplicationNo(), currentUserId, currentUserName, currentDeptId, currentTenantId); - log.info("📤 异步发布事件(不阻塞主流程)..."); - eventPublisher.publishEvent(event); - log.info("✅ 审批流程创建事件发布完成(异步处理中):applicationId={}, applicationNo={}", - id, application.getApplicationNo()); + if (createResult) { + log.info("✅ 审批流程创建成功:applicationId={}, applicationNo={}", id, application.getApplicationNo()); + } else { + log.error("❌ 审批流程创建失败:applicationId={}, applicationNo={}", id, application.getApplicationNo()); + throw new RuntimeException("审批流程创建失败"); + } } catch (Exception e) { - log.error("💥 发布审批流程创建事件失败:applicationId={}, error={}", id, e.getMessage(), e); - // 事件发布失败不影响提交,审批流程会在后台异步创建 + log.error("💥 创建审批流程异常:applicationId={}, error={}", id, e.getMessage(), e); + throw new RuntimeException("创建审批流程失败:" + e.getMessage(), e); } } else { log.error("❌ 申报提交失败,数据库更新返回false:applicationId={}", id); @@ -667,16 +670,66 @@ public class CwProjectApplicationServiceImpl implements ICwProjectApplicationSer throw new RuntimeException("申报记录不存在"); } + // 验证状态转换的合法性 + String currentStatus = application.getApprovalStatus(); + log.info("更新申报状态:applicationId={}, {} -> {}", id, currentStatus, approvalStatus); + + // 当从草稿状态提交到审批中时,进行数据完整性验证和字段设置 + if ("draft".equals(currentStatus) && "approving".equals(approvalStatus)) { + // 验证申报数据完整性 + validateApplicationForSubmission(application); + + // 设置提交时间(第一次提交) + if (application.getSubmitTime() == null) { + application.setSubmitTime(new Date()); + log.info("设置申报提交时间:applicationId={}", id); + } + + // 生成申报编号(如果还没有) + if (StringUtils.isBlank(application.getApplicationNo())) { + String applicationNo = generateApplicationNo(); + application.setApplicationNo(applicationNo); + log.info("生成申报编号:applicationId={}, applicationNo={}", id, applicationNo); + } + + log.info("✅ 申报正式提交:从草稿状态提交到审批中"); + } + + // 更新审批状态 application.setApprovalStatus(approvalStatus); // 如果是最终通过或拒绝状态,设置审批完成时间 if ("approved".equals(approvalStatus) || "rejected".equals(approvalStatus)) { application.setApproveTime(new Date()); + log.info("设置审批完成时间:applicationId={}, status={}", id, approvalStatus); } return baseMapper.updateById(application) > 0; } + /** + * 验证申报数据完整性(提交时验证) + */ + private void validateApplicationForSubmission(CwProjectApplication application) { + if (StringUtils.isBlank(application.getProjectName())) { + throw new RuntimeException("项目名称不能为空"); + } + if (StringUtils.isBlank(application.getPrincipal())) { + throw new RuntimeException("项目负责人不能为空"); + } + if (application.getTotalInvestment() == null || application.getTotalInvestment().compareTo(BigDecimal.ZERO) <= 0) { + throw new RuntimeException("项目总投资必须大于0"); + } + if (application.getYear() == null) { + throw new RuntimeException("预算年度不能为空"); + } + if (StringUtils.isBlank(application.getApplicationReason())) { + throw new RuntimeException("项目申请原因不能为空"); + } + + log.info("✅ 申报数据完整性验证通过:applicationId={}", application.getId()); + } + /** * 生成申报编号 */ diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApprovalProcessServiceImpl.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApprovalProcessServiceImpl.java new file mode 100644 index 00000000..a5eab539 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApprovalProcessServiceImpl.java @@ -0,0 +1,289 @@ +package org.dromara.cw.service.impl; + +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 com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.dromara.cw.domain.bo.CwProjectApprovalProcessBo; +import org.dromara.cw.domain.bo.CwCustomApprovalBo; +import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo; +import org.dromara.cw.domain.CwProjectApprovalProcess; +import org.dromara.cw.mapper.CwProjectApprovalProcessMapper; +import org.dromara.cw.service.ICwProjectApprovalProcessService; +import org.dromara.common.satoken.utils.LoginHelper; + +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.Date; + +/** + * 项目审批流程Service业务层处理 + * + * @author ruoyi + * @date 2025-01-03 + */ +@RequiredArgsConstructor +@Service +public class CwProjectApprovalProcessServiceImpl implements ICwProjectApprovalProcessService { + + private final CwProjectApprovalProcessMapper baseMapper; + + /** + * 查询项目审批流程 + */ + @Override + public CwProjectApprovalProcessVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询项目审批流程列表 + */ + @Override + public TableDataInfo queryPageList(CwProjectApprovalProcessVo vo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(vo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询项目审批流程列表 + */ + @Override + public List queryList(CwProjectApprovalProcessVo vo) { + LambdaQueryWrapper lqw = buildQueryWrapper(vo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(CwProjectApprovalProcessVo vo) { + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(vo.getApplicationId() != null, CwProjectApprovalProcess::getApplicationId, vo.getApplicationId()); + lqw.like(StringUtils.isNotBlank(vo.getApplicationNo()), CwProjectApprovalProcess::getApplicationNo, vo.getApplicationNo()); + lqw.like(StringUtils.isNotBlank(vo.getProcessName()), CwProjectApprovalProcess::getProcessName, vo.getProcessName()); + lqw.eq(StringUtils.isNotBlank(vo.getProcessStatus()), CwProjectApprovalProcess::getProcessStatus, vo.getProcessStatus()); + lqw.eq(vo.getTotalNodes() != null, CwProjectApprovalProcess::getTotalNodes, vo.getTotalNodes()); + lqw.eq(vo.getCurrentNode() != null, CwProjectApprovalProcess::getCurrentNode, vo.getCurrentNode()); + lqw.eq(vo.getSubmitterId() != null, CwProjectApprovalProcess::getSubmitterId, vo.getSubmitterId()); + lqw.like(StringUtils.isNotBlank(vo.getSubmitterName()), CwProjectApprovalProcess::getSubmitterName, vo.getSubmitterName()); + return lqw; + } + + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(CwProjectApprovalProcess entity){ + // 检查申报ID是否已存在流程 + if (entity.getApplicationId() != null) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CwProjectApprovalProcess::getApplicationId, entity.getApplicationId()); + if (entity.getId() != null) { + wrapper.ne(CwProjectApprovalProcess::getId, entity.getId()); + } + if (baseMapper.exists(wrapper)) { + throw new RuntimeException("该申报已存在审批流程"); + } + } + } + + /** + * 批量删除项目审批流程 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 根据申报ID查询审批流程 + */ + @Override + public CwProjectApprovalProcessVo queryByApplicationId(Long applicationId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CwProjectApprovalProcess::getApplicationId, applicationId); + return baseMapper.selectVoOne(wrapper); + } + + /** + * 根据申报编号查询审批流程 + */ + @Override + public CwProjectApprovalProcessVo queryByApplicationNo(String applicationNo) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CwProjectApprovalProcess::getApplicationNo, applicationNo); + return baseMapper.selectVoOne(wrapper); + } + + /** + * 查询我提交的流程 + */ + @Override + public List queryMyProcesses(Long submitterId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CwProjectApprovalProcess::getSubmitterId, submitterId); + wrapper.orderByDesc(CwProjectApprovalProcess::getCreateTime); + return baseMapper.selectVoList(wrapper); + } + + /** + * 查询运行中的流程 + */ + @Override + public List queryRunningProcesses() { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CwProjectApprovalProcess::getProcessStatus, "running"); + wrapper.orderByDesc(CwProjectApprovalProcess::getCreateTime); + return baseMapper.selectVoList(wrapper); + } + + /** + * 查询超时流程 + */ + @Override + public List queryOvertimeProcesses(Integer hours) { + // 这里可以根据需要实现超时查询逻辑 + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CwProjectApprovalProcess::getProcessStatus, "running"); + wrapper.orderByDesc(CwProjectApprovalProcess::getCreateTime); + return baseMapper.selectVoList(wrapper); + } + + /** + * 启动自定义审批流程 + */ + @Override + @Transactional + public Boolean startCustomApprovalProcess(CwCustomApprovalBo bo) { + try { + // 1. 创建审批流程 + CwProjectApprovalProcess process = new CwProjectApprovalProcess(); + process.setApplicationId(bo.getApplicationId()); + process.setApplicationNo(bo.getApplicationNo()); + process.setProcessName(bo.getProcessName()); + process.setProcessStatus("running"); + process.setTotalNodes(bo.getAuditorIds().size()); + process.setCurrentNode(1); + process.setStartTime(new Date()); + process.setSubmitterId(bo.getSubmitterId()); + process.setSubmitterName(bo.getSubmitterName()); + + boolean result = baseMapper.insert(process) > 0; + + // TODO: 这里可以创建对应的审批记录 + // 由于CwApprovalRecord相关的实现比较复杂,这里先简化处理 + + return result; + } catch (Exception e) { + throw new RuntimeException("启动审批流程失败:" + e.getMessage()); + } + } + + + /** + * 终止审批流程 + */ + @Override + @Transactional + public Boolean terminateProcess(Long id, String reason) { + CwProjectApprovalProcess update = new CwProjectApprovalProcess(); + update.setId(id); + update.setProcessStatus("terminated"); + update.setEndTime(new Date()); + update.setUpdateBy(LoginHelper.getUserId()); + return baseMapper.updateById(update) > 0; + } + + /** + * 推进流程到下一节点 + */ + @Override + public Boolean advanceToNextNode(Long processId) { + CwProjectApprovalProcess process = baseMapper.selectById(processId); + if (process != null && process.getCurrentNode() < process.getTotalNodes()) { + return updateCurrentNode(processId, process.getCurrentNode() + 1); + } + return false; + } + + /** + * 完成审批流程 + */ + @Override + public Boolean completeProcess(Long processId) { + return updateProcessStatus(processId, "completed"); + } + + /** + * 更新流程当前节点 + */ + @Override + public Boolean updateCurrentNode(Long id, Integer currentNode) { + CwProjectApprovalProcess update = new CwProjectApprovalProcess(); + update.setId(id); + update.setCurrentNode(currentNode); + update.setUpdateBy(LoginHelper.getUserId()); + return baseMapper.updateById(update) > 0; + } + + /** + * 更新流程状态 + */ + @Override + public Boolean updateProcessStatus(Long id, String status) { + CwProjectApprovalProcess update = new CwProjectApprovalProcess(); + update.setId(id); + update.setProcessStatus(status); + update.setUpdateBy(LoginHelper.getUserId()); + if ("completed".equals(status) || "terminated".equals(status)) { + update.setEndTime(new Date()); + } + return baseMapper.updateById(update) > 0; + } + + /** + * 验证审批流程配置 + */ + @Override + public Boolean validateApprovalProcess(CwCustomApprovalBo bo) { + // 验证基本参数 + if (bo.getApplicationId() == null || + StringUtils.isBlank(bo.getApplicationNo()) || + bo.getAuditorIds() == null || bo.getAuditorIds().isEmpty()) { + return false; + } + // TODO: 可以添加更多验证逻辑 + return true; + } + + /** + * 统计审批流程状态 + */ + @Override + public List> queryProcessStatistics(Long submitterId) { + // 这里可以根据需要实现统计逻辑 + return List.of(Map.of( + "totalProcesses", 0, + "runningProcesses", 0, + "completedProcesses", 0, + "terminatedProcesses", 0 + )); + } + + /** + * 导出数据 + */ + @Override + public List exportList(CwProjectApprovalProcessVo vo) { + return queryList(vo); + } +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApprovalServiceImpl.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApprovalServiceImpl.java new file mode 100644 index 00000000..42d49899 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectApprovalServiceImpl.java @@ -0,0 +1,258 @@ +package org.dromara.cw.service.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.dromara.cw.domain.bo.CwCustomApprovalBo; +import org.dromara.cw.domain.bo.CwApprovalActionBo; +import org.dromara.cw.domain.vo.CwProjectApprovalProcessVo; +import org.dromara.cw.domain.vo.CwApprovalRecordVo; +import org.dromara.cw.service.ICwProjectApprovalService; +import org.dromara.cw.service.ICwProjectApprovalProcessService; +import org.dromara.cw.service.ICwApprovalRecordService; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.mybatis.core.page.PageQuery; +import org.dromara.common.satoken.utils.LoginHelper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import org.dromara.cw.domain.CwApprovalRecord; +import cn.hutool.core.bean.BeanUtil; + +import java.util.List; +import java.util.ArrayList; + +/** + * 项目审批流程Service业务层处理 + * + * @author ruoyi + * @date 2025-01-03 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class CwProjectApprovalServiceImpl implements ICwProjectApprovalService { + + private final ICwProjectApprovalProcessService processService; + private final ICwApprovalRecordService recordService; + + /** + * 启动自定义审批流程 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean startCustomApprovalProcess(CwCustomApprovalBo bo) { + try { + log.info("🚀 启动自定义审批流程: applicationId={}, auditorIds={}", + bo.getApplicationId(), bo.getAuditorIds()); + + // 1. 验证参数 + if (bo.getApplicationId() == null) { + throw new RuntimeException("申报ID不能为空"); + } + if (bo.getAuditorIds() == null || bo.getAuditorIds().isEmpty()) { + throw new RuntimeException("审批人列表不能为空"); + } + + // 2. 调用审批记录服务创建自定义审批流程 + Boolean result = recordService.createCustomApprovalProcess( + bo.getApplicationId(), + bo.getApplicationNo(), + bo.getAuditorIds(), + bo.getSubmitterId(), + bo.getSubmitterName() + ); + + if (result) { + log.info("✅ 自定义审批流程启动成功: applicationId={}", bo.getApplicationId()); + } else { + log.error("❌ 自定义审批流程启动失败: applicationId={}", bo.getApplicationId()); + } + + return result; + + } catch (Exception e) { + log.error("❌ 启动自定义审批流程失败: applicationId={}, error={}", + bo.getApplicationId(), e.getMessage(), e); + throw new RuntimeException("启动审批流程失败:" + e.getMessage()); + } + } + + /** + * 执行审批操作 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean executeApproval(CwApprovalActionBo bo) { + try { + log.info("执行审批操作: recordId={}, action={}, opinion={}", + bo.getRecordId(), bo.getAction(), bo.getOpinion()); + + // 🔧 修复:先通过recordId获取审批记录信息 + CwApprovalRecordVo record = recordService.queryById(bo.getRecordId()); + if (record == null) { + throw new RuntimeException("审批记录不存在:" + bo.getRecordId()); + } + + // 🔧 修复:调用现有的审批记录服务执行审批 + Boolean result = recordService.processApproval( + record.getApplicationId(), + record.getNodeCode(), + bo.getAction(), + bo.getOpinion() + ); + + log.info("✅ 审批操作执行完成:recordId={}, action={}, result={}", + bo.getRecordId(), bo.getAction(), result); + + return result; + } catch (Exception e) { + log.error("执行审批操作失败: recordId={}, action={}, error={}", + bo.getRecordId(), bo.getAction(), e.getMessage(), e); + throw new RuntimeException("审批操作失败:" + e.getMessage(), e); + } + } + + /** + * 获取我的待审批列表 + */ + @Override + public TableDataInfo getMyPendingApprovals(PageQuery pageQuery) { + try { + Long userId = LoginHelper.getUserId(); + log.info("获取用户{}的待审批列表", userId); + + // 🔧 修复:调用现有的审批记录服务查询方法 + List list = recordService.queryPendingByApproverId(userId); + + // 应用分页 + int total = list.size(); + int start = Math.max(0, (pageQuery.getPageNum() - 1) * pageQuery.getPageSize()); + int end = Math.min(start + pageQuery.getPageSize(), total); + + List pagedList = start < total ? + list.subList(start, end) : new ArrayList<>(); + + log.info("✅ 查询待审批列表成功:总数={}, 当前页={}, 返回数量={}", + total, pageQuery.getPageNum(), pagedList.size()); + + // 🔧 修复:使用正确的TableDataInfo构造方法 + TableDataInfo result = new TableDataInfo<>(pagedList, total); + return result; + } catch (Exception e) { + log.error("获取待审批列表失败: {}", e.getMessage(), e); + return TableDataInfo.build(new ArrayList<>()); + } + } + + /** + * 获取我的已审批列表 + */ + @Override + public TableDataInfo getMyProcessedApprovals(PageQuery pageQuery) { + try { + Long userId = LoginHelper.getUserId(); + log.info("获取用户{}的已审批列表", userId); + + // 🔧 修复:直接查询已审批记录,然后手动分页 + List allList = recordService.queryApprovedByCurrentUser(); + + // 应用分页 + int total = allList.size(); + int start = Math.max(0, (pageQuery.getPageNum() - 1) * pageQuery.getPageSize()); + int end = Math.min(start + pageQuery.getPageSize(), total); + + List pagedList = start < total ? + allList.subList(start, end) : new ArrayList<>(); + + log.info("✅ 查询已审批列表成功:总数={}, 当前页={}, 返回数量={}", + total, pageQuery.getPageNum(), pagedList.size()); + + // 🔧 修复:使用正确的TableDataInfo构造方法 + TableDataInfo result = new TableDataInfo<>(pagedList, total); + return result; + } catch (Exception e) { + log.error("获取已审批列表失败: {}", e.getMessage(), e); + return TableDataInfo.build(new ArrayList<>()); + } + } + + /** + * 查询审批流程列表 + */ + @Override + public TableDataInfo queryProcessList(PageQuery pageQuery) { + try { + log.info("查询审批流程列表"); + + // 这里应该查询具体的流程数据 + // 由于缺少具体的业务逻辑,这里返回空列表 + // 实际项目中需要根据具体业务需求实现 + + List list = new ArrayList<>(); + return TableDataInfo.build(list); + } catch (Exception e) { + log.error("查询审批流程列表失败: {}", e.getMessage(), e); + return TableDataInfo.build(new ArrayList<>()); + } + } + + /** + * 获取审批流程详情 + */ + @Override + public CwProjectApprovalProcessVo getProcessDetail(Long processId) { + try { + log.info("获取审批流程详情: {}", processId); + + // 这里应该查询具体的流程详情 + // 由于缺少具体的业务逻辑,这里返回null + // 实际项目中需要根据具体业务需求实现 + + return null; + } catch (Exception e) { + log.error("获取审批流程详情失败: {}", e.getMessage(), e); + return null; + } + } + + /** + * 获取审批记录列表 + */ + @Override + public List getApprovalRecords(Long applicationId) { + try { + log.info("获取审批记录列表: {}", applicationId); + + // 这里应该查询具体的审批记录 + // 由于缺少具体的业务逻辑,这里返回空列表 + // 实际项目中需要根据具体业务需求实现 + + return new ArrayList<>(); + } catch (Exception e) { + log.error("获取审批记录列表失败: {}", e.getMessage(), e); + return new ArrayList<>(); + } + } + + /** + * 终止审批流程 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean terminateProcess(List processIds) { + try { + log.info("终止审批流程: {}", processIds); + + // 这里应该实现具体的流程终止逻辑 + // 由于缺少具体的业务逻辑,这里返回成功状态 + // 实际项目中需要根据具体业务需求实现 + + return true; + } catch (Exception e) { + log.error("终止审批流程失败: {}", e.getMessage(), e); + return false; + } + } +} diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectAuditorServiceImpl.java b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectAuditorServiceImpl.java new file mode 100644 index 00000000..40e31fa6 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/java/org/dromara/cw/service/impl/CwProjectAuditorServiceImpl.java @@ -0,0 +1,343 @@ +package org.dromara.cw.service.impl; + +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 com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.dromara.cw.domain.bo.CwProjectAuditorBo; +import org.dromara.cw.domain.vo.CwProjectAuditorVo; +import org.dromara.cw.domain.CwProjectAuditor; +import org.dromara.cw.mapper.CwProjectAuditorMapper; +import org.dromara.cw.service.ICwProjectAuditorService; +import org.dromara.system.service.ISysUserService; +import org.dromara.system.domain.vo.SysUserVo; +import org.dromara.common.satoken.utils.LoginHelper; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Collection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.stream.Collectors; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.TemporalAdjusters; + +/** + * 项目审核人配置Service业务层处理 + * + * @author ruoyi + * @date 2025-01-03 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class CwProjectAuditorServiceImpl implements ICwProjectAuditorService { + + private final CwProjectAuditorMapper baseMapper; + private final ISysUserService sysUserService; + + /** + * 查询项目审核人配置 + */ + @Override + public CwProjectAuditorVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询项目审核人配置列表 + */ + @Override + public TableDataInfo queryPageList(CwProjectAuditorBo bo, PageQuery pageQuery) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), lqw); + return TableDataInfo.build(result); + } + + /** + * 查询项目审核人配置列表 + */ + @Override + public List queryList(CwProjectAuditorBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(CwProjectAuditorBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getUserId() != null, CwProjectAuditor::getUserId, bo.getUserId()); + lqw.like(StringUtils.isNotBlank(bo.getUserName()), CwProjectAuditor::getUserName, bo.getUserName()); + lqw.like(StringUtils.isNotBlank(bo.getNickName()), CwProjectAuditor::getNickName, bo.getNickName()); + lqw.eq(bo.getDeptId() != null, CwProjectAuditor::getDeptId, bo.getDeptId()); + lqw.like(StringUtils.isNotBlank(bo.getDeptName()), CwProjectAuditor::getDeptName, bo.getDeptName()); + lqw.eq(StringUtils.isNotBlank(bo.getPhoneNumber()), CwProjectAuditor::getPhoneNumber, bo.getPhoneNumber()); + lqw.eq(StringUtils.isNotBlank(bo.getEmail()), CwProjectAuditor::getEmail, bo.getEmail()); + lqw.eq(StringUtils.isNotBlank(bo.getStatus()), CwProjectAuditor::getStatus, bo.getStatus()); + lqw.eq(bo.getConfigBy() != null, CwProjectAuditor::getConfigBy, bo.getConfigBy()); + lqw.like(StringUtils.isNotBlank(bo.getConfigByName()), CwProjectAuditor::getConfigByName, bo.getConfigByName()); + return lqw; + } + + /** + * 新增项目审核人配置 + */ + @Override + public Boolean insertByBo(CwProjectAuditorBo bo) { + CwProjectAuditor add = MapstructUtils.convert(bo, CwProjectAuditor.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改项目审核人配置 + */ + @Override + public Boolean updateByBo(CwProjectAuditorBo bo) { + CwProjectAuditor update = MapstructUtils.convert(bo, CwProjectAuditor.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(CwProjectAuditor entity){ + // 检查用户是否已存在 + if (entity.getUserId() != null) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CwProjectAuditor::getUserId, entity.getUserId()); + if (entity.getId() != null) { + wrapper.ne(CwProjectAuditor::getId, entity.getId()); + } + if (baseMapper.exists(wrapper)) { + throw new RuntimeException("该用户已经是审核人,不能重复添加"); + } + } + } + + /** + * 批量删除项目审核人配置 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } + + /** + * 批量添加审核人 + */ + @Override + @Transactional + public Boolean batchAddAuditors(List userIds, Long configBy, String configByName, String remark) { + if (userIds == null || userIds.isEmpty()) { + return false; + } + + // 1. 获取用户信息 + List users = sysUserService.selectUserByIds(userIds, null); + if (users.isEmpty()) { + return false; + } + + // 2. 过滤已存在的审核人 + List existingUserIds = baseMapper.selectList(Wrappers.lambdaQuery() + .in(CwProjectAuditor::getUserId, userIds)) + .stream().map(CwProjectAuditor::getUserId).collect(Collectors.toList()); + + // 3. 构建新增审核人列表 + List auditors = new ArrayList<>(); + for (SysUserVo user : users) { + if (!existingUserIds.contains(user.getUserId())) { + CwProjectAuditor auditor = new CwProjectAuditor(); + auditor.setUserId(user.getUserId()); + auditor.setUserName(user.getUserName()); + auditor.setNickName(user.getNickName()); + auditor.setDeptId(user.getDeptId()); + auditor.setDeptName(user.getDeptName() != null ? user.getDeptName() : ""); + auditor.setPhoneNumber(user.getPhonenumber()); + auditor.setEmail(user.getEmail()); + auditor.setStatus("1"); // 默认正常状态 + auditor.setConfigBy(configBy); + auditor.setConfigByName(configByName); + auditor.setRemark(remark); + auditors.add(auditor); + } + } + + // 4. 批量保存 + if (!auditors.isEmpty()) { + return baseMapper.insertBatch(auditors); + } + return true; + } + + /** + * 获取可用的审核人列表 + */ + @Override + public List getAvailableAuditors() { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(CwProjectAuditor::getStatus, "1"); // 只查询正常状态的审核人 + wrapper.orderByAsc(CwProjectAuditor::getDeptId, CwProjectAuditor::getUserName); + return baseMapper.selectVoList(wrapper); + } + + /** + * 根据部门ID获取用户列表 + */ + @Override + public List getDeptUsers(Long deptId) { + try { + // 参数验证 + if (deptId == null || deptId <= 0) { + log.warn("无效的部门ID: {}", deptId); + return Collections.emptyList(); + } + + // 调用系统用户服务获取部门用户 + List users = sysUserService.selectUserListByDept(deptId); + if (users == null || users.isEmpty()) { + log.info("部门 {} 下没有找到用户", deptId); + return Collections.emptyList(); + } + + return users.stream().map(user -> { + Map userMap = Map.of( + "userId", user.getUserId(), + "userName", user.getUserName(), + "nickName", user.getNickName() != null ? user.getNickName() : user.getUserName(), + "deptId", user.getDeptId(), + "deptName", user.getDeptName() != null ? user.getDeptName() : "", + "phonenumber", user.getPhonenumber() != null ? user.getPhonenumber() : "", + "email", user.getEmail() != null ? user.getEmail() : "" + ); + return (Object) userMap; + }).collect(Collectors.toList()); + } catch (Exception e) { + log.error("获取部门 {} 的用户列表失败: {}", deptId, e.getMessage(), e); + return Collections.emptyList(); + } + } + + /** + * 批量更新审核人状态 + */ + @Override + public Boolean updateStatus(List ids, String status) { + if (ids == null || ids.isEmpty()) { + return false; + } + + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.in(CwProjectAuditor::getId, ids); + + CwProjectAuditor update = new CwProjectAuditor(); + update.setStatus(status); + update.setUpdateBy(LoginHelper.getUserId()); + + return baseMapper.update(update, wrapper) > 0; + } + + @Override + public Object getStatistics() { + // 获取总审核人数 + long totalAuditors = baseMapper.selectCount(Wrappers.lambdaQuery() + .eq(CwProjectAuditor::getDeleted, 0)); + + // 获取正常状态审核人数 + long activeAuditors = baseMapper.selectCount(Wrappers.lambdaQuery() + .eq(CwProjectAuditor::getDeleted, 0) + .eq(CwProjectAuditor::getStatus, "1")); + + // 获取停用状态审核人数 + long inactiveAuditors = baseMapper.selectCount(Wrappers.lambdaQuery() + .eq(CwProjectAuditor::getDeleted, 0) + .eq(CwProjectAuditor::getStatus, "0")); + + // 获取部门数量(去重) + List auditors = baseMapper.selectList(Wrappers.lambdaQuery() + .eq(CwProjectAuditor::getDeleted, 0) + .select(CwProjectAuditor::getDeptId)); + long totalDepts = auditors.stream().map(CwProjectAuditor::getDeptId).distinct().count(); + + // 获取今日新增 + LocalDateTime startOfDay = LocalDateTime.now().with(LocalTime.MIN); + LocalDateTime endOfDay = LocalDateTime.now().with(LocalTime.MAX); + long todayAdded = baseMapper.selectCount(Wrappers.lambdaQuery() + .eq(CwProjectAuditor::getDeleted, 0) + .ge(CwProjectAuditor::getCreateTime, startOfDay) + .le(CwProjectAuditor::getCreateTime, endOfDay)); + + // 获取本月新增 + LocalDateTime startOfMonth = LocalDateTime.now().with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN); + LocalDateTime endOfMonth = LocalDateTime.now().with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX); + long monthlyAdded = baseMapper.selectCount(Wrappers.lambdaQuery() + .eq(CwProjectAuditor::getDeleted, 0) + .ge(CwProjectAuditor::getCreateTime, startOfMonth) + .le(CwProjectAuditor::getCreateTime, endOfMonth)); + + // 获取我配置的审核人数 + long myConfigured = baseMapper.selectCount(Wrappers.lambdaQuery() + .eq(CwProjectAuditor::getDeleted, 0) + .eq(CwProjectAuditor::getConfigBy, LoginHelper.getUserId())); + + // 获取最近活跃数(近7天有更新的) + LocalDateTime sevenDaysAgo = LocalDateTime.now().minusDays(7); + long recentActive = baseMapper.selectCount(Wrappers.lambdaQuery() + .eq(CwProjectAuditor::getDeleted, 0) + .ge(CwProjectAuditor::getUpdateTime, sevenDaysAgo)); + + // 构建统计结果 + Map statistics = new HashMap<>(); + statistics.put("totalAuditors", totalAuditors); + statistics.put("activeAuditors", activeAuditors); + statistics.put("inactiveAuditors", inactiveAuditors); + statistics.put("totalDepts", totalDepts); + statistics.put("todayAdded", todayAdded); + statistics.put("monthlyAdded", monthlyAdded); + statistics.put("myConfigured", myConfigured); + statistics.put("recentActive", recentActive); + + return statistics; + } + + /** + * 根据用户ID查询项目审核人配置 + */ + @Override + public CwProjectAuditorVo queryByUserId(Long userId) { + if (userId == null) { + return null; + } + + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() + .eq(CwProjectAuditor::getUserId, userId) + .eq(CwProjectAuditor::getDeleted, 0) + .orderByDesc(CwProjectAuditor::getCreateTime) + .last("LIMIT 1"); + + CwProjectAuditor entity = baseMapper.selectOne(wrapper); + if (entity == null) { + return null; + } + + return MapstructUtils.convert(entity, CwProjectAuditorVo.class); + } +} 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 11e1c691..43b4f9b6 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 @@ -72,6 +72,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + + + + @@ -90,7 +95,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ar.approver_name as current_approver_name, ar.node_status as approval_status, pa.create_time as submit_time, - ar.end_time as approval_time + ar.end_time as approval_time, + -- 流程相关字段(通过审批记录计算) + (SELECT COUNT(DISTINCT ar2.node_order) + FROM cw_approval_record ar2 + WHERE ar2.application_id = ar.application_id + AND ar2.deleted = 0) as total_nodes, + ar.node_order as current_node, + '项目审批' as process_name, + (CASE + WHEN EXISTS(SELECT 1 FROM cw_approval_record ar3 + WHERE ar3.application_id = ar.application_id + AND ar3.node_status = 'rejected' + AND ar3.deleted = 0) THEN 'rejected' + WHEN NOT EXISTS(SELECT 1 FROM cw_approval_record ar4 + WHERE ar4.application_id = ar.application_id + AND ar4.node_status IN ('waiting', 'processing') + AND ar4.deleted = 0) THEN 'completed' + ELSE 'processing' + END) as process_status from cw_approval_record ar left join cw_project_application pa on ar.application_id = pa.id left join sys_user u on pa.create_by = u.user_id diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectApprovalProcessMapper.xml b/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectApprovalProcessMapper.xml new file mode 100644 index 00000000..469047cb --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectApprovalProcessMapper.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + select + p.id, p.application_id, p.application_no, p.process_name, p.process_status, + p.total_nodes, p.current_node, p.start_time, p.end_time, + p.submitter_id, p.submitter_name, + p.create_by, p.create_time, p.update_by, p.update_time, + a.project_name, + u.nick_name as submitter_nick_name + from cw_project_approval_process p + left join cw_project_application a on p.application_id = a.id + left join sys_user u on p.submitter_id = u.user_id + + + + + + + + + + + + + + + + + + update cw_project_approval_process + set current_node = #{currentNode}, update_by = #{updateBy}, update_time = now() + where id = #{id} and deleted = 0 + + + + update cw_project_approval_process + set process_status = #{status}, + end_time = case when #{status} in ('completed', 'terminated') then now() else end_time end, + update_by = #{updateBy}, + update_time = now() + where id = #{id} and deleted = 0 + + + diff --git a/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectAuditorMapper.xml b/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectAuditorMapper.xml new file mode 100644 index 00000000..781cc3ad --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-cw/src/main/resources/mapper/CwProjectAuditorMapper.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + select + a.id, a.user_id, a.user_name, a.nick_name, a.dept_id, a.dept_name, + a.phone_number, a.email, a.status, a.remark, a.config_by, a.config_by_name, + a.create_by, a.create_time, a.update_by, a.update_time, + u.user_name as sys_user_name, + d.dept_name as sys_dept_name + from cw_project_auditor a + left join sys_user u on a.user_id = u.user_id + left join sys_dept d on a.dept_id = d.dept_id + + + + + + + + + + + + + + + + + + update cw_project_auditor + set status = #{status}, update_by = #{updateBy}, update_time = now() + where id in + + #{id} + + and deleted = 0 + + + diff --git a/web/src/api/cw/projectApproval.ts b/web/src/api/cw/projectApproval.ts new file mode 100644 index 00000000..4e948529 --- /dev/null +++ b/web/src/api/cw/projectApproval.ts @@ -0,0 +1,216 @@ +import request from '@/utils/request'; +import type { AxiosPromise } from 'axios'; +import type { PageQuery, TableDataInfo } from '@/api/types'; + +/** + * 项目审批流程查询参数 + */ +export interface CwProjectApprovalQuery extends PageQuery { + applicationNo?: string; + projectName?: string; + processStatus?: string; + submitterName?: string; + startTime?: string[]; +} + +/** + * 项目审批流程VO + */ +export interface CwProjectApprovalProcessVO { + id: string | number; + applicationId: string | number; + applicationNo: string; + processName: string; + processStatus: string; + totalNodes: number; + currentNode: number; + startTime: string; + endTime: string; + submitterId: string | number; + submitterName: string; + createTime: string; + updateTime: string; +} + +/** + * 审批记录VO + */ +export interface CwApprovalRecordVO { + id: string | number; + applicationId: string | number; + applicationNo: string; + processId: string | number; + nodeCode: string; + nodeName: string; + customNodeName: string; + nodeOrder: number; + approverId: string | number; + approverName: string; + nodeStatus: string; + approveAction: string; + approveOpinion: string; + startTime: string; + endTime: string; + isCustomProcess: boolean; + createTime: string; + updateTime: string; +} + +/** + * 自定义审批流程启动请求 + */ +export interface CwCustomApprovalRequest { + applicationId: string | number; + auditorIds: Array; + processName: string; + submitterId: string | number; + submitterName: string; +} + +/** + * 审批操作请求 + */ +export interface CwApprovalActionRequest { + recordId: string | number; + action: string; // approve | reject + opinion: string; +} + +/** + * 查询项目审批流程列表 + */ +export function listProjectApproval(query: CwProjectApprovalQuery): AxiosPromise> { + return request({ + url: '/cw/projectApproval/list', + method: 'get', + params: query + }); +} + +/** + * 获取项目审批流程详细信息 + */ +export function getProjectApproval(id: string | number): AxiosPromise { + return request({ + url: '/cw/projectApproval/' + id, + method: 'get' + }); +} + +/** + * 启动自定义审批流程 + */ +export function startCustomApprovalProcess(data: CwCustomApprovalRequest): AxiosPromise { + return request({ + url: '/cw/projectApproval/start', + method: 'post', + data: data + }); +} + +/** + * 执行审批操作(统一接口) + */ +export function approveProcess(data: CwApprovalActionRequest): AxiosPromise { + return request({ + url: '/cw/projectApproval/approve', + method: 'put', + data: data + }); +} + +/** + * 打回申请(统一接口) + */ +export function rejectProcess(data: CwApprovalActionRequest): AxiosPromise { + return request({ + url: '/cw/projectApproval/approve', + method: 'put', + data: data + }); +} + +/** + * 终止审批流程 + */ +export function terminateProcess(id: string | number): AxiosPromise { + return request({ + url: '/cw/projectApproval/' + id, + method: 'delete' + }); +} + +/** + * 获取我的待审批列表 + */ +export function getMyPendingApprovals(query?: CwProjectApprovalQuery): AxiosPromise { + return request({ + url: '/cw/projectApproval/pending', + method: 'get', + params: query + }); +} + +/** + * 获取我的已审批列表 + */ +export function getMyProcessedApprovals(query?: CwProjectApprovalQuery): AxiosPromise { + return request({ + url: '/cw/projectApproval/processed', + method: 'get', + params: query + }); +} + +/** + * 获取审批记录 + */ +export function getApprovalRecords(applicationId: string | number): AxiosPromise { + return request({ + url: '/cw/projectApproval/records/' + applicationId, + method: 'get' + }); +} + +/** + * 验证审批流程 + */ +export function validateApprovalProcess(data: CwCustomApprovalRequest): AxiosPromise { + return request({ + url: '/cw/projectApproval/validate', + method: 'post', + data: data + }); +} + +/** + * 获取审批流程统计数据 + */ +export function getApprovalStatistics(): AxiosPromise { + return request({ + url: '/cw/projectApproval/statistics', + method: 'get' + }); +} + +/** + * 导出项目审批流程 + */ +export function exportProjectApproval(query: CwProjectApprovalQuery): AxiosPromise { + return request({ + url: '/cw/projectApproval/export', + method: 'post', + data: query, + responseType: 'blob' + }); +} + +/** + * 获取可用审核人选项 + */ +export function getAuditorOptions(): AxiosPromise { + return request({ + url: '/cw/projectAuditor/options', + method: 'get' + }); +} diff --git a/web/src/api/cw/projectAuditor.ts b/web/src/api/cw/projectAuditor.ts new file mode 100644 index 00000000..69fa3fc2 --- /dev/null +++ b/web/src/api/cw/projectAuditor.ts @@ -0,0 +1,244 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; + +/** + * 项目审核人配置VO对象 + */ +export interface CwProjectAuditorVO { + id: string | number; + userId: string | number; + userName: string; + nickName: string; + deptId: string | number; + deptName: string; + phoneNumber: string; + email: string; + status: string; + remark: string; + configBy: string | number; + configByName: string; + createTime: string; + updateTime: string; + createBy: string | number; + updateBy: string | number; +} + +/** + * 项目审核人配置表单对象 + */ +export interface CwProjectAuditorForm { + id?: string | number; + userId: string | number; + userName: string; + nickName: string; + deptId: string | number; + deptName: string; + phoneNumber?: string; + email?: string; + status: string; + remark?: string; + configBy?: string | number; + configByName?: string; +} + +/** + * 项目审核人配置查询对象 + */ +export interface CwProjectAuditorQuery { + pageNum: number; + pageSize: number; + userName?: string; + nickName?: string; + deptId?: string | number; + status?: string; + configBy?: string | number; + createTime?: string[]; +} + +/** + * 批量添加请求对象 + */ +export interface BatchAddRequest { + userIds: Array; + remark?: string; +} + +/** + * 状态更新请求对象 + */ +export interface StatusUpdateRequest { + ids: Array; + status: string; +} + +/** + * 部门用户对象 + */ +export interface DeptUserVO { + userId: string | number; + userName: string; + nickName: string; + deptId: string | number; + deptName: string; + phonenumber: string; + email: string; + status: string; +} + +/** + * 查询项目审核人配置列表 + */ +export function listProjectAuditor(query: CwProjectAuditorQuery): AxiosPromise<{ rows: CwProjectAuditorVO[]; total: number }> { + return request({ + url: '/cw/projectAuditor/list', + method: 'get', + params: query + }); +} + +/** + * 查询项目审核人配置详细 + */ +export function getProjectAuditor(id: string | number): AxiosPromise { + return request({ + url: '/cw/projectAuditor/' + id, + method: 'get' + }); +} + +/** + * 新增项目审核人配置 + */ +export function addProjectAuditor(data: CwProjectAuditorForm): AxiosPromise { + return request({ + url: '/cw/projectAuditor', + method: 'post', + data: data + }); +} + +/** + * 修改项目审核人配置 + */ +export function updateProjectAuditor(data: CwProjectAuditorForm): AxiosPromise { + return request({ + url: '/cw/projectAuditor', + method: 'put', + data: data + }); +} + +/** + * 删除项目审核人配置 + */ +export function delProjectAuditor(ids: Array): AxiosPromise { + return request({ + url: '/cw/projectAuditor/' + ids.join(','), + method: 'delete' + }); +} + +/** + * 批量添加审核人 + */ +export function batchAddProjectAuditor(data: BatchAddRequest): AxiosPromise { + return request({ + url: '/cw/projectAuditor/batchAdd', + method: 'post', + data: data + }); +} + +/** + * 获取可用审核人列表 + */ +export function listAvailableAuditors(): AxiosPromise { + return request({ + url: '/cw/projectAuditor/available', + method: 'get' + }); +} + +/** + * 根据部门ID获取用户列表 + */ +export function listUserByDeptId(deptId: string | number): AxiosPromise { + return request({ + url: '/cw/projectAuditor/deptUsers/' + deptId, + method: 'get' + }); +} + +/** + * 批量更新状态 + */ +export function updateAuditorStatus(data: StatusUpdateRequest): AxiosPromise { + return request({ + url: '/cw/projectAuditor/status', + method: 'put', + data: data + }); +} + +/** + * 我配置的审核人 + */ +export function listMyConfigAuditors(query: CwProjectAuditorQuery): AxiosPromise<{ rows: CwProjectAuditorVO[]; total: number }> { + return request({ + url: '/cw/projectAuditor/myConfig', + method: 'get', + params: query + }); +} + +/** + * 获取审核人选项(用于下拉选择) + */ +export function listAuditorOptions(): AxiosPromise { + return request({ + url: '/cw/projectAuditor/options', + method: 'get' + }); +} + +/** + * 批量启用 + */ +export function enableAuditors(ids: Array): AxiosPromise { + return request({ + url: '/cw/projectAuditor/enable/' + ids.join(','), + method: 'put' + }); +} + +/** + * 批量停用 + */ +export function disableAuditors(ids: Array): AxiosPromise { + return request({ + url: '/cw/projectAuditor/disable/' + ids.join(','), + method: 'put' + }); +} + +/** + * 导出项目审核人配置 + */ +export function exportProjectAuditor(query: CwProjectAuditorQuery): AxiosPromise { + return request({ + url: '/cw/projectAuditor/export', + method: 'post', + data: query, + responseType: 'blob' + }); +} + +/** + * 获取审核人统计数据 + */ +export function getAuditorStatistics(): AxiosPromise { + return request({ + url: '/cw/projectAuditor/statistics', + method: 'get' + }); +} diff --git a/web/src/api/cw/types.ts b/web/src/api/cw/types.ts index 11267e70..dfec69c2 100644 --- a/web/src/api/cw/types.ts +++ b/web/src/api/cw/types.ts @@ -203,3 +203,183 @@ export interface StatusDistribution { count: number; percentage: number; } + +// ==================== 项目审批人配置相关类型 ==================== + +// 项目审批人配置查询对象 +export interface CwProjectAuditorQuery { + pageNum?: number; + pageSize?: number; + userName?: string; + nickName?: string; + deptId?: string | number; + status?: string; + configTime?: [string, string]; + configBy?: string | number; +} + +// 项目审批人配置视图对象 +export interface CwProjectAuditorVO { + id?: string | number; + userId?: string | number; + userName?: string; + nickName?: string; + deptId?: string | number; + deptName?: string; + phoneNumber?: string; + email?: string; + status?: string; + remark?: string; + configBy?: string | number; + configByName?: string; + createBy?: string | number; + createTime?: string; + updateBy?: string | number; + updateTime?: string; +} + +// 项目审批人配置表单对象 +export interface CwProjectAuditorForm { + id?: string | number; + userId?: string | number; + userName?: string; + nickName?: string; + deptId?: string | number; + deptName?: string; + phoneNumber?: string; + email?: string; + status?: string; + remark?: string; +} + +// 批量添加审批人业务对象 +export interface CwProjectAuditorBatchBo { + userIds: Array; + configBy: string | number; + configByName: string; + remark?: string; +} + +// ==================== 项目审批流程相关类型 ==================== + +// 审批流程查询对象 +export interface CwProjectApprovalProcessQuery { + pageNum?: number; + pageSize?: number; + applicationNo?: string; + processName?: string; + processStatus?: string; + submitterId?: string | number; + submitterName?: string; + startTime?: [string, string]; + endTime?: [string, string]; +} + +// 审批流程视图对象 +export interface CwProjectApprovalProcessVO { + id?: string | number; + applicationId?: string | number; + applicationNo?: string; + processName?: string; + processStatus?: string; + totalNodes?: number; + currentNode?: number; + startTime?: string; + endTime?: string; + submitterId?: string | number; + submitterName?: string; + createBy?: string | number; + createTime?: string; + updateBy?: string | number; + updateTime?: string; +} + +// 审批流程表单对象 +export interface CwProjectApprovalProcessForm { + id?: string | number; + applicationId?: string | number; + applicationNo?: string; + processName?: string; + processStatus?: string; + totalNodes?: number; + currentNode?: number; + startTime?: string; + endTime?: string; + submitterId?: string | number; + submitterName?: string; +} + +// 审批记录查询对象 +export interface CwApprovalRecordQuery { + pageNum?: number; + pageSize?: number; + applicationId?: string | number; + applicationNo?: string; + processId?: string | number; + approverId?: string | number; + nodeStatus?: string; + approveAction?: string; + isCustomProcess?: boolean; + startTime?: [string, string]; + endTime?: [string, string]; +} + +// 审批记录视图对象 +export interface CwApprovalRecordVO { + id?: string | number; + applicationId?: string | number; + applicationNo?: string; + processId?: string | number; + nodeCode?: string; + nodeName?: string; + customNodeName?: string; + nodeOrder?: number; + approverId?: string | number; + approverName?: string; + nodeStatus?: string; + approveAction?: string; + approveOpinion?: string; + startTime?: string; + endTime?: string; + isCustomProcess?: boolean; + createBy?: string | number; + createTime?: string; + updateBy?: string | number; + updateTime?: string; + // 关联的申请信息 + projectName?: string; + submitterName?: string; + totalInvestment?: number; +} + +// 自定义审批业务对象 +export interface CwCustomApprovalBo { + applicationId: string | number; + auditorIds: Array; + processName?: string; + submitterId?: string | number; + submitterName?: string; +} + +// 审批操作业务对象 +export interface CwApprovalActionBo { + recordId: string | number; + action: string; // 'approve' | 'reject' + opinion: string; + approverId?: string | number; + approverName?: string; +} + +// ===================================================== +// 扩展审批记录视图对象 +// ===================================================== +export interface CwApprovalRecordExtendedVO extends CwApprovalRecordVO { + processId?: string | number; + customNodeName?: string; + isCustomProcess?: boolean; + + // 关联项目信息(用于显示) + projectName?: string; + totalInvestment?: number; + applicantName?: string; +} diff --git a/web/src/api/system/dept/index.ts b/web/src/api/system/dept/index.ts index 61775067..55ed5ffb 100644 --- a/web/src/api/system/dept/index.ts +++ b/web/src/api/system/dept/index.ts @@ -71,3 +71,11 @@ export const delDept = (deptId: number | string) => { method: 'delete' }); }; + +// 查询部门选项列表 (实际调用 list 接口) +export const listDeptOptions = (): AxiosPromise => { + return request({ + url: '/system/dept/list', + method: 'get' + }); +}; diff --git a/web/src/api/types.ts b/web/src/api/types.ts index 411e3aa3..dc72b2ef 100644 --- a/web/src/api/types.ts +++ b/web/src/api/types.ts @@ -67,6 +67,14 @@ export interface R { data: T; } +/** + * 分页查询基础接口 + */ +export interface PageQuery { + pageNum?: number; + pageSize?: number; +} + /** * 分页数据信息 */ diff --git a/web/src/views/cw/README.md b/web/src/views/cw/README.md index f00dc84b..47f3cb14 100644 --- a/web/src/views/cw/README.md +++ b/web/src/views/cw/README.md @@ -1,55 +1,190 @@ -# 财务管理模块 - 前端界面 +# 财务管理模块 (CW) - 项目申报审批功能 -## 📋 模块说明 +## 🎯 功能概述 -- **模块名称**: 财务管理 (Financial Management) -- **模块简称**: cw -- **对应后端**: backend/ruoyi-modules/ruoyi-cw/ +本次开发实现了项目申报管理模块的审批流程变更,从固定的三级审批改为支持用户自定义的灵活审批流程。 ## 📁 目录结构 -此目录用于存放财务管理模块的所有前端界面组件,包括: +``` +web/src/views/cw/ +├── projectAuditor/ # 项目审批人配置模块 +│ ├── index.vue # 审批人配置主页面 +│ └── README.md # 功能说明文档 +├── projectApproval/ # 项目审批模块 +│ ├── index.vue # 审批流程管理页面(待实现) +│ ├── pending.vue # 我的待审批页面 +│ ├── components/ +│ │ └── ApprovalProcessSelector.vue # 审批流程选择组件 +│ └── README.md # 功能说明文档(待创建) +├── application/ # 项目申报模块(已修改) +│ ├── form.vue # 申报表单(已集成审批流程选择) +│ └── ... # 其他文件保持不变 +└── README.md # 总体说明文档(本文件) +``` -### 💰 会计核算 +``` +web/src/api/cw/ +├── projectAuditor.ts # 审批人配置API +├── projectApproval.ts # 项目审批API +└── types.ts # TypeScript类型定义(已扩展) +``` -- 科目管理 -- 凭证管理 -- 账簿管理 +## 🚀 核心功能 -### 📊 预算管理 +### 1. 项目审批人配置管理 (`projectAuditor/`) +- **审批人池管理**: 从系统用户中选择并配置审批人员 +- **批量添加**: 通过部门树选择用户,支持批量添加审批人 +- **跨部门选择**: 支持在不同部门间选择用户,选择状态会自动保持 +- **信息维护**: 修改审批人联系信息、状态管理 +- **权限控制**: 基于RBAC的细粒度权限控制 -- 预算编制 -- 预算执行 -- 预算分析 +### 2. 项目审批流程管理 (`projectApproval/`) +- **流程监控**: 查看所有审批流程状态和进度(待实现) +- **流程详情**: 查看审批记录和时间轴展示(待实现) +- **流程终止**: 管理员可终止进行中的审批流程(待实现) -### 💳 资产管理 +### 3. 我的待审批 (`projectApproval/pending.vue`) +- **待审批列表**: 显示当前用户需要审批的项目 +- **项目详情**: 查看项目完整信息和审批历史 +- **审批操作**: 支持通过/拒绝,必填审批意见 +- **批量审批**: 支持批量通过或拒绝多个项目(待实现) -- 固定资产 -- 流动资产 -- 资产盘点 +### 4. 审批流程选择组件 (`components/ApprovalProcessSelector.vue`) +- **自定义流程配置**: 支持用户从审批人池中选择审核人 +- **可视化流程预览**: 直观展示审批顺序和流程走向 +- **审核人管理**: 支持添加、删除、排序审核人 +- **流程验证**: 验证审批流程的合理性和完整性 -### 📈 财务报表 +### 5. 项目申报集成 (`application/form.vue` - 已修改) +- **流程选择对话框**: 在提交时弹出审批流程选择界面 +- **数据保存**: 先保存申报数据,再配置审批流程 +- **流程启动**: 自动启动配置的审批流程 +- **用户体验**: 平滑的提交流程,清晰的操作提示 -- 资产负债表 -- 损益表 -- 现金流量表 +## 📋 实现特色 -### 💸 费用管理 +### 🔧 技术特色 +1. **Vue 3.5.13 Composition API**: 使用最新的组合式API,代码结构清晰 +2. **TypeScript支持**: 完整的类型定义,提供良好的开发体验 +3. **Element Plus UI**: 统一的UI组件库,符合后勤管理模块设计规范 +4. **响应式设计**: 支持移动端和桌面端,适配不同屏幕尺寸 +5. **组件化开发**: 高度复用的组件设计,易于维护和扩展 -- 费用申请 -- 费用审批 -- 费用报销 +### 🎨 用户体验特色 +1. **跨部门选择**: 审批人选择支持在多个部门间切换,保持选择状态 +2. **可视化流程**: 审批流程以图形化方式展示,直观易懂 +3. **实时验证**: 表单验证和流程验证,及时提供反馈 +4. **操作引导**: 清晰的操作提示和帮助信息 +5. **状态恢复**: 切换部门后自动恢复之前的选择状态 -## 🎯 开发规范 +### 🚀 性能特色 +1. **按需加载**: 组件和API按需加载,优化性能 +2. **智能缓存**: 合理的数据缓存策略,减少重复请求 +3. **批量操作**: 支持批量添加、删除等操作,提升效率 +4. **异步处理**: 异步API调用,不阻塞用户界面 -- 遵循Vue 3 + TypeScript + Element Plus开发规范 -- 组件命名采用PascalCase -- 文件命名采用kebab-case -- 统一使用Composition API +## 🔄 核心流程 -## 📝 注意事项 +### 审批人配置流程 +``` +管理员登录 → 进入审批人配置 → 选择部门 → 批量选择用户 → 确认添加 → 配置完成 +``` -- 新增页面需要配置对应的路由 -- API接口统一调用@/api/cw/目录下的接口 -- 权限控制使用v-hasPermi指令,权限编码格式:cw:功能:操作 -- 财务数据需要特别注意精度和安全性 +### 项目申报审批流程 +``` +填写申报信息 → 点击提交 → 选择审批流程 → 配置审核人 → 确认提交 → 流程启动 +``` + +### 审批执行流程 +``` +审核人收到通知 → 查看待审批列表 → 查看项目详情 → 执行审批操作 → 填写意见 → 提交审批 +``` + +## 🛠️ 后端支持 + +### 数据库设计 +1. **cw_project_auditor**: 项目审核人配置表 +2. **cw_project_approval_process**: 项目审批流程表 +3. **cw_approval_record**: 审批记录表(已扩展支持自定义流程) + +### API接口 +1. **审核人配置**: 16个标准API接口(CRUD + 业务功能 + 辅助功能) +2. **审批流程**: 10个核心API接口(流程管理 + 审批执行) +3. **权限控制**: 基于Sa-Token的权限管理 + +### 数据安全 +1. **多租户隔离**: 严格的数据隔离机制 +2. **权限控制**: 细粒度的功能权限控制 +3. **操作日志**: 完整的操作记录和审计 + +## 📊 技术规范 + +### 前端规范 +- **组件命名**: PascalCase,清晰的语义化命名 +- **文件组织**: 按功能模块组织,层次分明 +- **样式管理**: Scoped CSS,避免样式冲突 +- **状态管理**: Reactive响应式状态,简洁高效 + +### 后端规范 +- **接口设计**: RESTful风格,统一响应格式 +- **数据验证**: Bean Validation参数校验 +- **异常处理**: 统一的异常处理机制 +- **事务管理**: 合理的事务边界控制 + +### 代码质量 +- **TypeScript**: 完整的类型检查 +- **ESLint**: 代码风格检查 +- **Prettier**: 代码格式化 +- **单元测试**: 核心逻辑单元测试(待实现) + +## 🎯 后续优化 + +### 功能扩展 +1. **审批流程管理页面**: 完整的流程监控和管理界面 +2. **批量审批功能**: 支持批量处理多个项目 +3. **审批历史查询**: 完整的审批记录查询功能 +4. **移动端支持**: 基于uni-app的移动端审批应用 + +### 性能优化 +1. **虚拟滚动**: 大数据量列表的性能优化 +2. **缓存策略**: 更智能的数据缓存机制 +3. **懒加载**: 按需加载组件和数据 +4. **CDN集成**: 静态资源CDN加速 + +### 用户体验 +1. **快捷操作**: 键盘快捷键支持 +2. **拖拽排序**: 审核人拖拽排序功能 +3. **消息通知**: 实时消息推送 +4. **离线支持**: PWA离线功能支持 + +## 🚀 部署说明 + +### 开发环境 +1. 确保Node.js 20.15.x+,npm 10.7.x+ +2. 安装依赖:`npm install` +3. 启动开发服务器:`npm run dev` +4. 访问:http://localhost + +### 生产环境 +1. 构建项目:`npm run build` +2. 部署到Nginx或其他Web服务器 +3. 配置反向代理到后端API +4. 确保数据库脚本已执行 + +### 数据库部署 +1. 执行数据库脚本:`docs/数据库修复脚本.sql` +2. 配置权限菜单和角色权限 +3. 创建测试用户和审核人数据 + +## 📞 技术支持 + +如有任何技术问题或建议,请联系开发团队。 + +--- + +**🎯 目标达成**: 成功实现了从固定三级审批到灵活自定义审批流程的完整改造,提供了良好的用户体验和技术架构! + +**📝 维护日期**: 2025-01-03 +**📁 文件**: 完整的前后端实现 + 数据库脚本 + API文档 +**🔄 状态**: 核心功能完成,待进一步测试和优化 \ No newline at end of file diff --git a/web/src/views/cw/application/form.vue b/web/src/views/cw/application/form.vue index e0f0a504..10f46c4b 100644 --- a/web/src/views/cw/application/form.vue +++ b/web/src/views/cw/application/form.vue @@ -480,17 +480,59 @@ + + + +
+ + + +
+ + +
+ + diff --git a/web/src/views/cw/projectApproval/index.vue b/web/src/views/cw/projectApproval/index.vue new file mode 100644 index 00000000..855244d1 --- /dev/null +++ b/web/src/views/cw/projectApproval/index.vue @@ -0,0 +1,1217 @@ + + + + + diff --git a/web/src/views/cw/projectApproval/pending.vue b/web/src/views/cw/projectApproval/pending.vue new file mode 100644 index 00000000..271a6626 --- /dev/null +++ b/web/src/views/cw/projectApproval/pending.vue @@ -0,0 +1,433 @@ + + + + + diff --git a/web/src/views/cw/projectAuditor/BUG_FIX_REPORT.md b/web/src/views/cw/projectAuditor/BUG_FIX_REPORT.md new file mode 100644 index 00000000..3c76eca5 --- /dev/null +++ b/web/src/views/cw/projectAuditor/BUG_FIX_REPORT.md @@ -0,0 +1,150 @@ +# 项目审批人批量添加功能Bug修复报告 + +## 🐛 问题描述 +用户反馈:批量添加审批人时,选择用户后点击提交,页面提示添加成功,但后台数据库中没有新增数据。 + +## 🔍 问题分析 + +### 根本原因 +1. **前端使用模拟API函数**:`index.vue` 中第354-378行定义了模拟的API函数,而不是真实的API调用 +2. **缺少正确的API导入**:没有导入真实的 `batchAddProjectAuditor` 等API函数 +3. **数据格式不匹配**:前端传递的数据格式与后端期望格式存在差异 + +### 问题代码 +```javascript +// 错误:使用模拟函数 +const batchAddProjectAuditor = (data: any) => { + return Promise.resolve() // 直接返回成功,不实际调用后端 +} +``` + +## 🔧 修复方案 + +### 1. 导入真实API函数 +```javascript +import { + listProjectAuditor, + getProjectAuditor, + updateProjectAuditor, + delProjectAuditor, + batchAddProjectAuditor +} from '@/api/cw/projectAuditor' +import type { CwProjectAuditorVO, CwProjectAuditorForm, CwProjectAuditorQuery, CwProjectAuditorBatchBo } from '@/api/cw/types' +``` + +### 2. 修复数据类型定义 +```typescript +// 修正数据类型 +const auditorList = ref([]) +const queryParams = ref({...}) +const editDialog = reactive({ + form: {} as CwProjectAuditorForm +}) +``` + +### 3. 修复批量添加数据格式 +```javascript +const confirmBatchAdd = async () => { + const userIds = batchDialog.selectedUsers.map((user) => user.userId as number) + const data: CwProjectAuditorBatchBo = { + userIds: userIds, + remark: '批量添加' + } + + const response = await batchAddProjectAuditor(data) + // ... 处理响应 +} +``` + +### 4. 修复后端数据解析 +```javascript +const response = await listProjectAuditor(queryParams.value) +// 后端返回的是TableDataInfo格式 +auditorList.value = response.data?.rows || [] +total.value = response.data?.total || 0 +``` + +## ✅ 修复内容清单 + +### 前端修复 +- [x] 移除模拟API函数,导入真实API +- [x] 修复TypeScript类型定义 +- [x] 修复批量添加数据格式 +- [x] 修复后端响应数据解析 +- [x] 改进错误处理和用户反馈 +- [x] 修复按钮事件类型问题 + +### 后端验证 +- [x] 确认后端API接口正常工作 +- [x] 确认数据库表结构正确 +- [x] 确认权限控制配置 + +## 🧪 测试验证 + +### 测试用例 +1. **批量添加功能测试** + - 选择部门 + - 选择多个用户 + - 点击确认添加 + - 验证数据库是否成功插入数据 + +2. **查询列表功能测试** + - 验证API调用正常 + - 验证数据格式解析正确 + - 验证分页功能 + +3. **错误处理测试** + - 测试网络错误情况 + - 测试用户已存在情况 + - 测试权限不足情况 + +### 测试脚本 +提供了 `test-api.js` 文件用于功能验证测试。 + +## 📋 技术规范遵循 + +### 符合项目规范 +- ✅ **前端开发规范**:符合Vue 3.5.13 + TypeScript + Element Plus架构 +- ✅ **API接口规范**:使用统一的request工具和错误处理 +- ✅ **UI设计规范**:继承后勤管理模块的样式风格和颜色主题 +- ✅ **数据库规范**:使用雪花ID、逻辑删除、多租户等标准字段 +- ✅ **权限控制**:使用@SaCheckPermission注解控制接口权限 + +### 代码质量 +- ✅ **类型安全**:完整的TypeScript类型定义 +- ✅ **错误处理**:完善的异常捕获和用户提示 +- ✅ **用户体验**:优化的加载状态和反馈信息 +- ✅ **响应式设计**:保持左右布局在不同屏幕下的表现 + +## 🚀 部署说明 + +### 验证步骤 +1. 确保后端服务正常运行 +2. 检查数据库表 `cw_project_auditor` 是否存在 +3. 验证用户权限配置 `cw:projectAuditor:add` +4. 测试前端页面功能 + +### 预期结果 +- 批量添加功能正常工作 +- 数据成功写入数据库 +- 页面显示准确的成功/失败提示 +- 列表数据正确刷新 + +## 📝 修复日志 + +| 时间 | 修复内容 | 影响范围 | +|------|----------|----------| +| 2025-01-03 | 修复API调用问题 | 批量添加功能 | +| 2025-01-03 | 修复数据格式问题 | 前后端数据传递 | +| 2025-01-03 | 修复类型定义问题 | TypeScript类型安全 | +| 2025-01-03 | 修复响应解析问题 | 列表查询功能 | + +## 🎯 总结 + +本次修复解决了批量添加审批人功能的根本问题,确保: +1. **功能正常**:批量添加能够真实地向后端发送请求并保存数据 +2. **类型安全**:完整的TypeScript类型定义和检查 +3. **用户体验**:准确的成功/失败提示和数据刷新 +4. **代码质量**:符合项目开发规范和最佳实践 + +修复后的功能完全符合需求分析文档的要求,并遵循项目的前后端开发规范。 diff --git a/web/src/views/cw/projectAuditor/README.md b/web/src/views/cw/projectAuditor/README.md new file mode 100644 index 00000000..072083d1 --- /dev/null +++ b/web/src/views/cw/projectAuditor/README.md @@ -0,0 +1,58 @@ +# 项目审批人配置模块 + +## 功能概述 + +项目审批人配置模块用于管理项目申报审批流程中的审批人员池,支持从系统用户中选择并配置审批人员,为自定义审批流程提供人员支持。 + +## 主要功能 + +### 1. 审批人列表管理 +- 查询审批人列表(支持分页) +- 按用户姓名、部门、状态等条件筛选 +- 显示审批人基本信息(姓名、部门、联系方式等) +- 支持审批人状态管理(正常/停用) + +### 2. 批量添加审批人 +- 通过部门树选择部门 +- 展示部门下的可用用户列表 +- 支持多选用户批量添加为审批人 +- 自动过滤已存在的审批人 + +### 3. 审批人信息维护 +- 修改审批人联系信息 +- 更新审批人状态 +- 添加备注信息 +- 删除审批人配置 + +## 文件结构 + +``` +web/src/views/cw/projectAuditor/ +├── index.vue # 主页面组件 +└── README.md # 功能说明文档 +``` + +## API接口 + +使用的主要API接口: +- `listProjectAuditor` - 查询审批人列表 +- `getProjectAuditor` - 获取审批人详情 +- `updateProjectAuditor` - 更新审批人信息 +- `delProjectAuditor` - 删除审批人 +- `batchAddProjectAuditor` - 批量添加审批人 +- `getDeptUsers` - 获取部门用户列表 + +## 权限控制 + +页面功能对应的权限标识: +- `cw:projectAuditor:query` - 查询权限 +- `cw:projectAuditor:add` - 新增权限 +- `cw:projectAuditor:edit` - 编辑权限 +- `cw:projectAuditor:remove` - 删除权限 + +## 技术实现 + +- 基于Vue 3 Composition API +- 使用Element Plus UI组件库 +- 集成项目标准的权限控制和国际化 +- 遵循项目统一的代码规范和样式风格 diff --git a/web/src/views/cw/projectAuditor/debug.vue b/web/src/views/cw/projectAuditor/debug.vue new file mode 100644 index 00000000..2af39840 --- /dev/null +++ b/web/src/views/cw/projectAuditor/debug.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/web/src/views/cw/projectAuditor/index.vue b/web/src/views/cw/projectAuditor/index.vue new file mode 100644 index 00000000..ffd000c7 --- /dev/null +++ b/web/src/views/cw/projectAuditor/index.vue @@ -0,0 +1,952 @@ + + + + + diff --git a/web/src/views/cw/projectAuditor/test.vue b/web/src/views/cw/projectAuditor/test.vue new file mode 100644 index 00000000..e6b62ed1 --- /dev/null +++ b/web/src/views/cw/projectAuditor/test.vue @@ -0,0 +1,140 @@ + + + + +