Compare commits

27 Commits

Author SHA1 Message Date
6f33ab9c05 feat(project): 补充项目、执行、任务相关能力 2026-05-09 10:37:27 +08:00
dk
36752d1d15 fix(产品需求): 修复产品需求使用状态和终止态字典的问题 2026-05-07 17:07:52 +08:00
dk
73360d70ce fix(产品需求): 修复产品需求查询的问题、修复产品需求树返回数据的问题 2026-05-07 11:10:21 +08:00
dk
7913c210cd feat(产品需求): 产品需求相关代码 2026-05-06 17:49:30 +08:00
dk
06d29210ba Merge branch 'main' of http://192.168.1.22:3000/Microservice/cn-rdms
# Conflicts:
#	rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/product/ProductServiceImpl.java
2026-04-28 16:58:35 +08:00
dk
b4e1aae062 feat(产品需求): 产品和产品需求相关的测试类 2026-04-28 16:53:33 +08:00
dk
9ad7e063c0 feat(产品需求): 产品和产品需求相关的测试类 2026-04-28 16:50:04 +08:00
dk
846348e1aa feat(user): 支持前端用用户昵称字段进行模糊搜索
fix(post): 使岗位排序能按照sort字段来排序。
2026-04-28 16:43:38 +08:00
ae90dcec24 feat(project): 为项目活动时间线添加成员角色名称显示功能
- 在 ObjectActivityConstants 中添加 MEMBER_ACTION_UPDATE 类型支持
- 为 ProductActivityQueryService 和 ProductActivityTimelineQueryService
  添加角色名称加载和缓存功能
- 实现角色名称解析和 JSON 数据结构扩展
- 添加相关单元测试验证角色名称显示逻辑
- 集成 ObjectPermissionApi 获取角色信息并实现缓存机制
2026-04-24 16:22:23 +08:00
ee732b97bf feat(project): 新增产品动态时间线接口并重构活动查询逻辑
- 新增 GET /project/product/{id}/activities/page 接口用于产品动态时间线分页查询
- 添加 ProductActivityTimelinePageReqVO 和 ProductActivityTimelineRespVO 数据传输对象
- 实现 ProductActivityTimelineQueryService 服务处理动态时间线查询逻辑
- 在 BizAuditLogMapper 中新增按业务类型和动作类型查询的方法
- 在 ProductStatusLogMapper 中新增按产品ID和动作类型查询的方法
- 将硬编码的活动类型常量抽取到 ObjectActivityConstants 统一管理
- 重构 ProductActivityQueryService 使用统一的常量和查询方法
- 更新 ProductMemberServiceImpl 和 ProductServiceImpl 使用新的活动常量
- 添加相应的单元测试验证新接口和查询逻辑的正确性
- 新增产品对象首页改版设计文档和产品动态时间线接口需求说明文档
2026-04-24 15:43:38 +08:00
0a6d70f7cf feat(permission): 新增对象权限API接口及实现
- 定义ObjectPermissionApi接口提供对象作用域权限查询功能
- 实现ObjectPermissionApiImpl提供角色权限查询和转换逻辑
- 添加ObjectMenuRespDTO、ObjectRoleRespDTO和ObjectRolePermissionRespDTO数据传输对象
- 实现按角色ID、角色编码查询对象作用域角色及权限的功能
- 提供获取对象作用域角色菜单与权限聚合结果的方法
- 添加完整单元测试覆盖对象权限API的主要业务场景
2026-04-23 09:23:33 +08:00
156728b1b9 feat(permission): 重构权限系统实现对象级别权限控制
- 在PermissionService中新增getScopedMenusByRoleId和getScopedPermissionsByRoleId方法
- 实现getScopedMenusByRoleId方法用于获取角色的对象范围菜单列表
- 实现getScopedPermissionsByRoleId方法用于获取角色的对象范围权限集合
- 添加getEnabledScopedRole私有方法确保只处理启用状态的角色对象
- 在ProductMemberServiceImpl中替换SystemRoleMapper为ObjectPermissionApi调用
- 将验证产品角色的方法改为调用远程权限接口验证
- 更新ProductObjectPermissionService使用远程权限接口替代本地查询
- 修改ProductServiceImpl中权限获取逻辑使用新的对象权限API
- 移除原有的系统菜单和角色相关的数据对象依赖
- 在测试类中更新模拟对象和断言逻辑适配新的权限接口调用
2026-04-23 09:22:43 +08:00
2943a6255b docs(product): 删除产品管理SQL口径和业务设计文档
- 移除02-产品管理SQL已确认口径文档
- 移除02-产品管理业务设计文档
- 清理产品管理模块的详细设计说明
- 删除产品需求状态字段口径定义
- 移除来源承接与需求拆分口径说明
- 清理需求终态原因承接口径内容
- 删除产品生命周期管理设计
- 移除产品团队权限管理规范
- 清理产品与项目关系约束说明
- 删除轻量需求管理业务规则
- 移除产品状态机与流程设计
- 清理权限与动作矩阵定义
2026-04-22 18:18:38 +08:00
dk
f8231c2d51 feat(user): 支持前端用用户昵称字段进行模糊搜索
fix(post): 使岗位排序能按照sort字段来排序。
2026-04-22 14:38:41 +08:00
a1f5936d20 Merge remote-tracking branch 'origin/main' 2026-04-18 14:20:06 +08:00
38c69c748c feat(other): 产品基础功能提交 2026-04-18 14:19:45 +08:00
dk
5815f49a79 fix(system-boot_user): 增加用户昵称不能为空的后端校验。 2026-04-16 20:55:29 +08:00
dk
0c91f5deaa fix(system-api、boot): 给用户管理功能相关的各种需要company字段的类,新增company字段。 2026-04-16 20:29:36 +08:00
dk
67040aaf5d fix(UserManagementRelationxxx.java): 优化了一些细节,主要是汇报关系 -> 管理链路。 2026-04-15 20:56:58 +08:00
dk
8af6842809 fix(UserManagementRelationxxx.java): 优化了一些细节,主要是代码注释,带人关系 -> 汇报关系。 2026-04-15 20:48:17 +08:00
9384b2f502 feat(system): 取消角色superadmin能看到所有菜单的约定,改为实际配置实际显示 2026-04-14 18:58:26 +08:00
dk
07d07c8f5f feat(UserManagementRelationxxx.java): 改造带人关系树的构造代码。
feat(UserController.java): 新增/list-by-dept-id接口,根据部门ID获取该部门和下属部门的用户精简信息列表。
fix(AdminUserServiceImpl.java): 修复删除某用户(含批量删除)后,带人关系树构造错乱、加载不出来的问题。
2026-04-14 16:32:06 +08:00
dk
c3dd0c9802 fix(package-info.java): 增加包声明。 2026-04-13 13:44:02 +08:00
dk
21ca027f3b feat(user-management-relation): 完成带人关系后端接口(即直接管理) 2026-04-10 16:26:59 +08:00
dk
017beb1d5f 1.提交到本地 2026-04-07 11:21:18 +08:00
dk
09cba49a7d Merge remote-tracking branch 'origin/main' 2026-04-07 11:14:54 +08:00
dk
7e22f79b5f 1.修复当有用户使用某个角色时,该角色也可以被禁用的BUG
2.引入热部署依赖,配置开启热部署(热更新快捷键:Ctrl+F9)
2026-04-07 11:14:28 +08:00
298 changed files with 24766 additions and 1130 deletions

View File

@@ -0,0 +1,31 @@
{
"permissions": {
"allow": [
"Bash(git *)",
"Bash(cmd *)",
"PowerShell('--rdms-project--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project' | Sort-Object; '--rdms-system--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-system' | Sort-Object; '--rdms-framework--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-framework' | Sort-Object; '--rdms-gateway--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-gateway' | Sort-Object)",
"PowerShell('--docs--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\docs' -Recurse -Depth 2 | Sort-Object)",
"PowerShell('--project-boot src--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project\\\\rdms-project-boot\\\\src\\\\main\\\\java\\\\com\\\\njcn\\\\rdms\\\\module\\\\project' -Recurse -Depth 2 | Sort-Object)",
"PowerShell('--project-api src--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project\\\\rdms-project-api\\\\src\\\\main\\\\java' -Recurse -Depth 5 | Sort-Object)",
"PowerShell('--system-boot src--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-system\\\\rdms-system-boot\\\\src\\\\main\\\\java\\\\com\\\\njcn\\\\rdms\\\\module\\\\system' -Recurse -Depth 2 | Sort-Object)",
"PowerShell('--project-boot service/project--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project\\\\rdms-project-boot\\\\src\\\\main\\\\java\\\\com\\\\njcn\\\\rdms\\\\module\\\\project\\\\service\\\\project' -Recurse -Depth 3 | Sort-Object)",
"PowerShell('--project-boot controller--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project\\\\rdms-project-boot\\\\src\\\\main\\\\java\\\\com\\\\njcn\\\\rdms\\\\module\\\\project\\\\controller' -Recurse -Depth 4 | Sort-Object)",
"PowerShell('--project-api full--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project\\\\rdms-project-api\\\\src\\\\main\\\\java\\\\com\\\\njcn\\\\rdms\\\\module\\\\project' -Recurse | Sort-Object)",
"PowerShell('--project-boot dal--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project\\\\rdms-project-boot\\\\src\\\\main\\\\java\\\\com\\\\njcn\\\\rdms\\\\module\\\\project\\\\dal' -Recurse | Sort-Object)",
"PowerShell('--project-api enums--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project\\\\rdms-project-api\\\\src\\\\main\\\\java\\\\com\\\\njcn\\\\rdms\\\\module\\\\project\\\\enums' -Recurse | Sort-Object; '--system-api full--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-system\\\\rdms-system-api\\\\src\\\\main\\\\java' -Recurse -Depth 6 | Sort-Object)",
"PowerShell('--project-boot framework--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project\\\\rdms-project-boot\\\\src\\\\main\\\\java\\\\com\\\\njcn\\\\rdms\\\\module\\\\project\\\\framework' -Recurse | Sort-Object)",
"PowerShell('--sql resources--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project\\\\rdms-project-boot\\\\src\\\\main\\\\resources' -Recurse | Sort-Object; '--system sql--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-system\\\\rdms-system-boot\\\\src\\\\main\\\\resources\\\\sql' -Recurse | Sort-Object)",
"PowerShell('--project-boot resources sql--'; Get-ChildItem -Force -Name 'C:\\\\code\\\\gitea\\\\rdms\\\\cn-rdms\\\\rdms-project\\\\rdms-project-boot\\\\src\\\\main\\\\resources\\\\sql' -Recurse | Sort-Object)",
"WebSearch",
"PowerShell($env:JAVA_HOME = \"C:\\\\Program Files\\\\Java\\\\jdk-17\"; & \"C:\\\\software\\\\apache-maven-3.8.9\\\\bin\\\\mvn.cmd\" -pl rdms-project/rdms-project-boot -Dtest=TaskAssigneeServiceImplTest test 2>&1 | Select-Object -Last 60)",
"PowerShell($env:JAVA_HOME = \"C:\\\\Program Files\\\\Java\\\\jdk-17\"; & \"C:\\\\software\\\\apache-maven-3.8.9\\\\bin\\\\mvn.cmd\" -pl rdms-project/rdms-project-boot -am -Dtest=TaskAssigneeServiceImplTest test 2>&1 | Select-Object -Last 80)",
"PowerShell($env:JAVA_HOME = \"C:\\\\Program Files\\\\Java\\\\jdk-17\"; & \"C:\\\\software\\\\apache-maven-3.8.9\\\\bin\\\\mvn.cmd\" -pl rdms-project/rdms-project-boot -am -Dtest=TaskAssigneeServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test 2>&1 | Select-Object -Last 80)",
"PowerShell($env:JAVA_HOME = \"C:\\\\Program Files\\\\Java\\\\jdk-17\"; & \"C:\\\\software\\\\apache-maven-3.8.9\\\\bin\\\\mvn.cmd\" -pl rdms-project/rdms-project-boot -am \"-Dtest=TaskAssigneeServiceImplTest\" \"-Dsurefire.failIfNoSpecifiedTests=false\" test 2>&1 | Select-Object -Last 80)",
"PowerShell($env:JAVA_HOME = \"C:\\\\Program Files\\\\Java\\\\jdk-17\"; & \"C:\\\\software\\\\apache-maven-3.8.9\\\\bin\\\\mvn.cmd\" -pl rdms-project/rdms-project-boot -am \"-Dtest=TaskAssigneeServiceImplTest\" \"-Dsurefire.failIfNoSpecifiedTests=false\" test 2>&1 | Select-Object -Last 50)",
"PowerShell($env:JAVA_HOME = \"C:\\\\Program Files\\\\Java\\\\jdk-17\"; & \"C:\\\\software\\\\apache-maven-3.8.9\\\\bin\\\\mvn.cmd\" -pl rdms-project/rdms-project-boot -am test 2>&1 | Select-Object -Last 80)",
"PowerShell($env:JAVA_HOME = \"C:\\\\Program Files\\\\Java\\\\jdk-17\"; & \"C:\\\\software\\\\apache-maven-3.8.9\\\\bin\\\\mvn.cmd\" -pl rdms-project/rdms-project-boot \"-Dtest=TaskAssigneeServiceImplTest,ProjectTaskServiceImplTest,ProjectTaskStatusViewServiceTest,ProjectExecutionServiceImplTest\" \"-Dsurefire.failIfNoSpecifiedTests=false\" test 2>&1 | Select-Object -Last 30)",
"PowerShell($env:JAVA_HOME = \"C:\\\\Program Files\\\\Java\\\\jdk-17\"; & \"C:\\\\software\\\\apache-maven-3.8.9\\\\bin\\\\mvn.cmd\" -pl rdms-project/rdms-project-boot -am \"-Dtest=TaskAssigneeServiceImplTest,ProjectTaskServiceImplTest,ProjectTaskStatusViewServiceTest,ProjectExecutionServiceImplTest\" \"-Dsurefire.failIfNoSpecifiedTests=false\" test 2>&1 | Select-Object -Last 30)",
"Bash(grep -rn \"INSERT INTO \\\\`system_menu\\\\`\\\\|INSERT INTO system_menu\" --include=\"*.sql\" rdms-project rdms-system)"
]
}
}

View File

@@ -0,0 +1,4 @@
#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice.
#Tue Apr 21 08:50:58 CST 2026
https\://maven.aliyun.com/repository/public/.lastUpdated=1776732658408
https\://maven.aliyun.com/repository/public/.error=

243
AGENTS.md Normal file
View File

@@ -0,0 +1,243 @@
# AGENTS.md
## 适用范围
本说明适用于以 `C:\code\gitea\rdms\cn-rdms` 为根目录的整个仓库。
描述仓库现状时,以当前代码、当前配置、当前文档中可直接验证的事实为准;除非用户明确要求,不引入历史实现、过渡方案或已废弃模型来解释当前状态。
默认回答保持精简,优先给结论、改动点和必要风险,不做过多展开;如果存在你关心但未展开的细节,由你继续追问后再补充。
## 交互原则
- 默认先给执行方案,说明目标、涉及模块、预计改动点和验证方式。
- 在用户评审并明确同意前,不直接开始实际修改、编译、测试、打包或其他执行动作。
- 是否执行由用户决定;如果用户只要求分析、审阅或出方案,就停留在分析和方案层。
## 项目概览
这是一个面向 RDMS 服务的多模块 Maven 单仓库项目。
- Java 版本17
- 构建工具Maven
- 根模块打包类型:`pom`
- Spring Boot 版本:`3.5.9`
## 本地环境约定
- 本仓库要求使用 `JDK 17`;不要使用 `JDK 8``JDK 11` 或其他版本执行编译、测试、启动、打包。
- 本机 JDK 17 路径:`C:\Program Files\Java\jdk-17`
- 如需执行 Maven、Java、测试、启动等命令应先确认当前 `JAVA_HOME` 指向 `C:\Program Files\Java\jdk-17`,并确保 `java -version` 实际输出为 `17`
- 本机 Maven 安装路径:`C:\software\apache-maven-3.8.9`
- 如需执行 Maven 命令,优先使用完整路径:`C:\software\apache-maven-3.8.9\bin\mvn.cmd`
- 不要假设 `mvn` 已加入 PATH
- 不要假设系统默认 `JAVA_HOME` 已正确指向 JDK 17如果当前 shell 不是 JDK 17先在当前命令上下文显式切换后再执行 Maven
- 只有在用户已明确同意执行编译、测试、打包等 Maven 命令时,才使用上述路径执行
顶层模块:
1. `rdms-system`
2. `rdms-project`
3. `rdms-framework`
4. `rdms-gateway`
当前系统域能力主要集中在 `rdms-system`RDMS 核心交付域能力主要集中在 `rdms-project`,但这只是现阶段结构,不应被理解为长期只保留这两个业务模块。
后续如果新增独立业务服务,例如项目/产品管理模块、工作流模块,应继续沿用当前仓库的模块拆分方式,而不是把所有后续业务长期堆进 `rdms-system`
## 模块说明
### `rdms-system`
当前已存在的系统业务聚合模块。
- `rdms-system/rdms-system-boot`
- 主应用模块
- 启动入口:`rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/SystemServerApplication.java`
- 主包路径:`com.njcn.rdms.module.system`
- 常见子包:`api``controller``convert``dal``framework``job``service``util``websocket`
- `rdms-system/rdms-system-api`
- 供其他服务依赖的共享 API 模块
- 包含对外 API 契约与枚举定义
说明:
- 当前权限、用户、组织、岗位、菜单、角色等系统核心能力主要落在这里。
- 如果后续只是给系统域补充新的系统子能力,可以继续在 `rdms-system` 内按现有结构扩展。
- 如果后续形成独立业务域,例如 `rdms-project``rdms-workflow`,应优先建设为新的独立业务模块,而不是默认继续塞进 `rdms-system`
### `rdms-project`
当前已存在的 RDMS 核心交付业务聚合模块。
- `rdms-project/rdms-project-boot`
- 主应用模块
- 启动入口:`rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/ProjectServerApplication.java`
- 主包路径:`com.njcn.rdms.module.project`
- 常见子包:`api``controller``convert``dal``framework``service`
- `rdms-project/rdms-project-api`
- 供其他服务依赖的共享 API 模块
- 包含对外 API 契约与枚举定义
说明:
- 当前项目集、项目、产品、需求、任务、工单、执行等 RDMS 核心业务能力应优先落在这里。
- 需要复用用户、组织、岗位、权限等系统能力时,应通过 `rdms-system-api` 调用,不要反向依赖 `rdms-system-boot`
### `rdms-framework`
共享框架与内部 starter 模块。
- `rdms-framework/rdms-common`
- 核心通用工具与公共抽象
- 其他 `rdms-spring-boot-starter-*` 模块
- 内部 starter覆盖 `env``web``rpc``security``mybatis``redis``mq``websocket``excel``protection``test``biz-ip`
### `rdms-gateway`
Spring Cloud Gateway 网关服务。
- 启动入口:`rdms-gateway/src/main/java/com/njcn/rdms/gateway/GatewayServerApplication.java`
- 主包路径:`com.njcn.rdms.gateway`
- 常见子包:`filter``handler``jackson``route``util`
## 模块演进约束
后续新增业务能力时,先区分下面两种情况,不要混用:
1. 新增独立微服务模块,例如 `rdms-project``rdms-workflow`
2. 只是在现有 `rdms-system` 中新增一个业务子域
### 新增独立微服务模块
如果后续能力已经具备独立服务边界,应优先按下面结构建设:
```text
rdms-xxx
├─ rdms-xxx-api
└─ rdms-xxx-boot
```
约束:
-`pom.xml` 增加新的聚合模块
- `api` 模块承载对外 RPC/Feign 接口、DTO、错误码、枚举、常量
- `boot` 模块承载启动类、controller、service、dal、convert、api 实现、模块级 framework 配置和资源文件
- 包路径、`spring.application.name``ApiConstants``RpcConstants``rdms.info.base-package` 必须保持一致
- 新服务不是简单复制 `rdms-system` 的名字,而是复用它的工程骨架和分层习惯
### 在 `rdms-system` 中新增业务子域
如果只是给系统服务补一个当前阶段仍适合放在 `rdms-system` 内的子域,则继续沿用现有结构:
- `controller/admin/...``controller/app/...`
- `service/...`
- `dal/dataobject/...`
- `dal/mysql/...`
- `convert/...`
- 需要跨模块暴露时,在 `rdms-system-api` 中补 API、DTO、错误码、枚举
约束:
- 不要为了新增子域引入一套平行的 `application/domain/infrastructure/adapter` 分层语言
- 不要让外部模块直接依赖 `rdms-system-boot` 的 service 或 mapper
- 如果某项能力未来明显会演进成独立微服务,文档和实现上都要避免把它写死成只能存在于 `rdms-system`
## 代码目录
- Java 源码:`*/src/main/java`
- 资源文件:`*/src/main/resources`
- 测试代码:`*/src/test/java`
- 本地辅助脚本:`scripts/`
## 分层职责约束
### `rdms-framework`
- `rdms-framework` 承担基础能力,不承载具体业务语义。
- 除非出现框架级缺陷,或该能力明确属于全局可复用基础设施,否则不要把业务判断硬塞进 framework。
### `rdms-gateway`
- `rdms-gateway` 只负责统一入口、令牌校验、登录用户透传、路由和网关层横切逻辑。
- 不要在 gateway 层承载组织、成员、负责人、项目、产品、工作流状态流转或数据可见性这类业务语义。
### Controller 层
- Controller 负责 HTTP 暴露、参数校验、权限注解、结果封装。
- 不要在 controller 中直接编排复杂业务流程,也不要直接操作多个 mapper 拼装业务规则。
- 请求和响应对象优先沿用 `ReqVO``RespVO` 风格,不要直接把 DO 暴露给前端。
### Service 层
- 核心业务规则、事务、缓存、领域编排应落在 service 层。
- 如果是已有领域增强,优先在现有 service 下扩展,不要为了“看起来更整齐”平移整套代码。
- 不要把复杂规则散落到 controller、mapper 或 `util` 中。
### DAL 层
- 新表应有对应的 DO 和 Mapper。
- Mapper 优先继承 `BaseMapperX<T>`,不要重复写样板 CRUD。
- 查询条件优先沿用 `LambdaQueryWrapperX`、默认方法封装和现有 MyBatis Plus 风格,不要无必要回退到 XML。
- Mapper 以查询封装为主,不承担领域校验职责。
### Convert 层
- 如果某个领域已经有 `convert` 风格,则继续沿用。
- 简单场景允许直接使用 `BeanUtils`
- 不要为了统一而强推所有地方都改成 MapStruct也不要反过来把已有 convert 全部删掉。
## 认证与共享调用约束
- 默认沿用现有 OAuth2 / Token / `LoginUser` / `login-user` 透传主链,不要另造一套认证上下文体系。
- 不要额外发明 ThreadLocal、Session 或自定义 header 体系替代当前登录态恢复方式。
- 接口级权限判断默认沿用 `@PreAuthorize("@ss.hasPermission(...)")` 这条链路,不要绕开现有权限框架另起一套实现。
- 跨模块、跨服务访问能力时,优先通过对应的 `*-api` 模块定义 API、DTO、常量和枚举。
- 不要让外部模块直接依赖某个 `*-boot` 模块的 service 或 mapper。
## 数据与 SQL 约束
- 新增业务表的 DO 优先复用当前 `BaseDO` / 审计字段风格;除非表本身明确不需要逻辑删除,不要再引入另一套审计基类。
- 不要假设运行时存在自动数据库迁移;如果代码依赖新表、新字段或新索引,必须同步补齐对应 SQL 与文档说明。
- SQL 脚本应放在目标模块的 `src/main/resources/sql/...` 下,并保持可审阅、可单独执行、语义清晰。
- 变更缓存、日志、审计相关逻辑时,优先沿用现有机制,不要绕开现有登录上下文、缓存约定和审计字段填充方式。
## 注释与编码
- 新增或修改代码时,关键字段、关键分支、关键约束和非直观实现应补充简洁中文注释。
- 不要为了省事删除原有有效注释,也不要添加无信息量的注释。
- 写入中文内容时必须保持 UTF-8 编码,并自行检查中文显示是否正常;不要用“改成英文”规避乱码问题。
- 使用 superpowers 产出的功能文档时例如设计文档、实施计划、联调说明除非用户明确要求否则默认用中文落地代码标识、文件路径、接口路径、SQL、命令保持原始技术标识不做意译。
## 工作规则
1. 除非任务明确要求修改共享契约或 starter否则优先进行有边界的模块内改动避免跨模块扩散。
2. 业务逻辑应放在对应业务模块的 `*-boot` 实现模块;可复用契约放在对应的 `*-api` 模块;可复用框架能力放在 `rdms-framework`
3. 除非任务本身就是环境配置调整,否则避免修改 `application-local.yaml``application-dev.yaml`
4. 将本地资源 YAML 视为可能带有机器环境差异的文件;修改前先检查 git 状态。
5. 保持既有包结构约定不变:
- 控制器放在 `controller`
- 服务层放在 `service`
- 持久层放在 `dal`
- DTO/VO 转换放在 `convert`
6. 当前系统域代码主要在 `rdms-system`RDMS 核心交付域代码主要在 `rdms-project`,但这不是永久约束;新增业务能力时,先判断应该落在现有系统域内、现有项目交付域内,还是应建设为新的 `rdms-xxx` 业务模块。
7. 新增共享能力时,优先扩展现有 `rdms-spring-boot-starter-*` 模块,不要在业务服务里重复堆配置。
8. 修改跨模块使用的 API 时,需要同时更新提供方实现和对应的 `rdms-system-api` 或对应 `rdms-xxx-api` 契约。
9. 除非用户明确要求,否则不执行任何编译、构建、测试、打包或其他会实际运行项目的命令,包括但不限于 `mvn`、启动命令和脚本。
## 测试指引
先定义验证方式,再实施修改。默认通过以下方式验证:
- 代码路径是否闭环,调用链是否与模块边界一致
- 配置项、接口契约、权限标识、路由或资源注册是否前后一致
- 改动范围是否控制在当前任务所需的最小集合内
- 受影响的文档、SQL、配置或接口说明是否需要同步更新
如果任务影响了 Spring 配置、序列化、安全、路由、RPC 契约、MyBatis 行为或跨模块 API一律明确说明哪些部分已静态检查、哪些部分尚未实际运行验证。
## 给后续 Agent 的说明
- 仓库中可能存在未提交的本地配置改动,不要覆盖与当前任务无关的编辑。
- `docs/` 目录属于当前工作上下文的一部分,不是归档材料;做架构级修改前先查阅。
- 根目录 `pom.xml` 负责统一版本和依赖对齐;涉及版本调整时,优先修改根 `pom.xml`,不要散落到子模块中。

110
CLAUDE.md Normal file
View File

@@ -0,0 +1,110 @@
# CLAUDE.md
本文件为 Claude Code 在 `C:\code\gitea\rdms\cn-rdms` 仓库工作时的常驻指引,等价并补充 `AGENTS.md`。两份文件冲突时以本文件为准;本文件未覆盖的细节回退到 `AGENTS.md`
## 工作方式
- 默认先给执行方案:目标、涉及模块、改动点、验证方式。用户明确同意前不要直接动手修改、编译、测试、打包。
- 用户只要分析或评审时,停在分析层;不要顺手开工。
- 描述仓库现状以**当前**代码、配置、文档可验证的事实为准;不要拿历史实现、过渡方案或已废弃模型解释当前状态。
- 回答保持精简,先给结论、改动点、必要风险;细节等用户追问。
- **不要废话**:默认极简输出,不展开背景、不复述需求、不堆叠章节标题;能用一两句讲清就别写成清单;用户主动追问再展开。
## 本机环境
- JDK必须使用 `JDK 17`,路径 `C:\Program Files\Java\jdk-17`。不要使用 JDK 8 / 11 / 其他版本。
- Maven`C:\software\apache-maven-3.8.9`,命令优先用完整路径 `C:\software\apache-maven-3.8.9\bin\mvn.cmd`。不要假设 `mvn` 在 PATH。
- 执行任何 Maven / java 命令前,先确认当前 shell 的 `JAVA_HOME` 指向 JDK 17`java -version` 输出 17否则在该命令上下文中显式切换。
- **只有在用户已明确同意执行编译/测试/打包等命令时**,才使用上述路径执行。日常默认不跑任何会实际运行项目的命令。
## 仓库结构
多模块 Maven 单仓库Java 17Spring Boot 3.5.9,根模块打包 `pom`
顶层模块:
1. `rdms-system` — 系统域(用户/组织/岗位/菜单/角色/权限)
2. `rdms-project` — RDMS 核心交付域(项目集/项目/产品/需求/任务/工单/执行)
3. `rdms-framework` — 共享框架与内部 starter
4. `rdms-gateway` — Spring Cloud Gateway 网关
每个业务模块按 `xxx-api` + `xxx-boot` 拆分:
- `*-api`:对外 RPC/Feign 接口、DTO、错误码、枚举、常量
- `*-boot`启动类、controller、service、dal、convert、api 实现、模块级 framework 配置
主包/启动类:
- `rdms-system-boot``com.njcn.rdms.module.system.SystemServerApplication`
- `rdms-project-boot``com.njcn.rdms.module.project.ProjectServerApplication`
- `rdms-gateway``com.njcn.rdms.gateway.GatewayServerApplication`
`rdms-framework` 子模块:`rdms-common` 及一组 `rdms-spring-boot-starter-*``env``web``rpc``security``mybatis``redis``mq``websocket``excel``protection``test``biz-ip`)。
## 模块演进判断
新增能力时**先判断落点**
- 落在现有 `rdms-system` 子域 → 沿用 `controller/admin|app``service``dal/dataobject``dal/mysql``convert` 的现有结构,跨模块暴露时在 `rdms-system-api` 补 API/DTO/错误码/枚举。
- 落在现有 `rdms-project` 子域 → 同理,跨模块走 `rdms-project-api`
- 已具备独立服务边界(如未来的 `rdms-workflow`)→ 新建 `rdms-xxx` + `rdms-xxx-api` + `rdms-xxx-boot`,根 `pom.xml` 加聚合,包路径 / `spring.application.name` / `ApiConstants` / `RpcConstants` / `rdms.info.base-package` 保持一致。
不要:
- 把后续业务长期堆进 `rdms-system`
- 为新增子域引入一套平行的 `application/domain/infrastructure/adapter` 分层。
- 让外部模块直接依赖 `*-boot` 的 service 或 mapper必须走 `*-api`)。
## 分层职责
| 层 | 职责 | 红线 |
|---|---|---|
| `rdms-framework` | 基础能力 | 不承载业务语义;除非框架级缺陷或全局基础设施,不要把业务判断塞进来 |
| `rdms-gateway` | 入口、令牌校验、登录用户透传、路由、横切 | 不要在这里承载组织/成员/负责人/项目/产品/工作流状态/数据可见性 |
| Controller | HTTP 暴露、参数校验、权限注解、结果封装 | 不要编排复杂业务流程,不要直接操作多个 mapper`ReqVO`/`RespVO`,不要直接暴露 DO |
| Service | 业务规则、事务、缓存、领域编排 | 已有领域优先扩展,不要为"整齐"平移;不要把规则散到 controller / mapper / util |
| DAL | DO + Mapper | Mapper 继承 `BaseMapperX<T>`;查询优先 `LambdaQueryWrapperX` 与默认方法封装;非必要不回退 XML不承担领域校验 |
| Convert | 已有 `convert` 风格继续沿用,简单场景直接 `BeanUtils` | 不要强推全员 MapStruct也不要反过来把已有 convert 全删 |
## 认证与跨模块调用
- 默认沿用 OAuth2 / Token / `LoginUser` / `login-user` 透传主链。**不要**另造 ThreadLocal / Session / 自定义 header。
- 接口级权限走 `@PreAuthorize("@ss.hasPermission(...)")`,不要绕开。
- 跨模块/跨服务必须通过 `*-api` 模块定义契约;不要直接依赖别人的 `*-boot`
## 数据与 SQL
- 新表 DO 复用现有 `BaseDO` / 审计字段风格,不要再引一套审计基类(除非该表本身明确不需要逻辑删除)。
- **不要假设运行时自动数据库迁移**:依赖新表/新字段/新索引时,必须同步补 SQL 脚本与文档。
- SQL 放在目标模块 `src/main/resources/sql/...`,可审阅、可单独执行。
- 缓存/日志/审计变更优先沿用既有机制,不要绕开登录上下文与审计字段填充。
## 注释与编码
- 关键字段、关键分支、关键约束、非直观实现补**简洁中文**注释。
- 不要为省事删除原有有效注释;也不要写无信息量的注释。
- 中文写入必须 UTF-8并自查显示是否正常不要用"改成英文"规避乱码。
- superpowers 产出的功能文档(设计/实施/联调默认中文落地代码标识、文件路径、接口路径、SQL、命令保持原样不意译。
## 工作规则(执行前对照)
1. 优先做有边界的模块内改动,避免跨模块扩散。
2. 业务逻辑落 `*-boot`;可复用契约落 `*-api`;可复用框架能力落 `rdms-framework`
3. **不要修改** `application-local.yaml` / `application-dev.yaml`,除非任务本身就是环境配置调整。
4. 把本地 YAML 当作可能带机器差异的文件,改前先查 git 状态。
5. 包结构:`controller` / `service` / `dal` / `convert`,保持不变。
6. 新增共享能力优先扩展现有 `rdms-spring-boot-starter-*`,不要在业务服务里重复堆配置。
7. 改跨模块 API 时,提供方实现与对应 `*-api` 契约同步更新。
8. **未经用户明确同意,不执行任何 `mvn`、启动命令、脚本等会实际运行项目的命令。**
## 验证默认动作
先定义验证方式,再做修改。默认静态验证:
- 调用链是否闭环、是否符合模块边界
- 配置项 / 接口契约 / 权限标识 / 路由 / 资源注册前后是否一致
- 改动是否控制在最小集合
- 文档 / SQL / 配置 / 接口说明是否需要同步更新
如果改动涉及 Spring 配置、序列化、安全、路由、RPC 契约、MyBatis 行为或跨模块 API必须**显式区分**哪些是已静态检查、哪些尚未实际运行验证。
## 给后续我自己的提醒
- 仓库可能有未提交的本地改动,不要顺手覆盖与当前任务无关的编辑。
- `docs/` 是当前工作上下文的一部分,不是归档;架构级修改前先查阅。
-`pom.xml` 统一版本与依赖;版本调整改根 pom不要散落到子模块。
- 推荐使用 `Glob` / `Grep` / `Read` 等专用工具,避免用 Bash 做文件搜索/读取/编辑。

10
pom.xml
View File

@@ -9,6 +9,7 @@
<packaging>pom</packaging>
<modules>
<module>rdms-system</module>
<module>rdms-project</module>
<module>rdms-framework</module>
<module>rdms-gateway</module>
</modules>
@@ -118,6 +119,15 @@
<artifactId>rdms-spring-boot-starter-websocket</artifactId>
<version>${revision}</version>
</dependency>
<!-- 热部署依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>${spring.boot.version}</version>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>

View File

@@ -26,5 +26,17 @@ public interface RpcConstants {
*/
String SYSTEM_PREFIX = RPC_API_PREFIX + "/system";
/**
* project 服务名
*
* 注意,需要保证和 spring.application.name 保持一致
*/
String PROJECT_NAME = "rdms-project-server";
/**
* project 服务的前缀
*/
String PROJECT_PREFIX = RPC_API_PREFIX + "/project";
}

View File

@@ -0,0 +1 @@
package com.njcn.rdms.framework.env.core;

View File

@@ -10,6 +10,8 @@ import java.util.List;
public class InDictCollectionValidator implements ConstraintValidator<InDict, Collection<?>> {
private static final String MISSING_DICT_DATA_MESSAGE = "字典数据缺失,请联系管理员维护";
private String dictType;
@Override
@@ -25,6 +27,12 @@ public class InDictCollectionValidator implements ConstraintValidator<InDict, Co
}
// 校验全部通过
List<String> dbValues = DictFrameworkUtils.getDictDataValueList(dictType);
if (dbValues.isEmpty()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(MISSING_DICT_DATA_MESSAGE)
.addConstraintViolation();
return false;
}
boolean match = list.stream().allMatch(v -> dbValues.stream()
.anyMatch(dbValue -> dbValue.equalsIgnoreCase(v.toString())));
if (match) {
@@ -40,4 +48,3 @@ public class InDictCollectionValidator implements ConstraintValidator<InDict, Co
}
}

View File

@@ -9,6 +9,8 @@ import java.util.List;
public class InDictValidator implements ConstraintValidator<InDict, Object> {
private static final String MISSING_DICT_DATA_MESSAGE = "字典数据缺失,请联系管理员维护";
private String dictType;
@Override
@@ -24,6 +26,12 @@ public class InDictValidator implements ConstraintValidator<InDict, Object> {
}
// 校验通过
final List<String> values = DictFrameworkUtils.getDictDataValueList(dictType);
if (values.isEmpty()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(MISSING_DICT_DATA_MESSAGE)
.addConstraintViolation();
return false;
}
boolean match = values.stream().anyMatch(v -> StrUtil.equalsIgnoreCase(v, value.toString()));
if (match) {
return true;
@@ -38,4 +46,3 @@ public class InDictValidator implements ConstraintValidator<InDict, Object> {
}
}

View File

@@ -6,10 +6,10 @@ spring:
username: # Nacos 账号
password: # Nacos 密码
discovery: # 【配置中心】配置项
namespace: 1e0fcd92-49b4-4cda-b531-828c7d36fef5 # 命名空间。这里使用 dev 开发环境
namespace: 1924bcfb-4eab-4c58-9003-4a37d5fc2949 # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
config: # 【注册中心】配置项
namespace: 1e0fcd92-49b4-4cda-b531-828c7d36fef5 # 命名空间。这里使用 dev 开发环境
namespace: 1924bcfb-4eab-4c58-9003-4a37d5fc2949 # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
#################### 监控相关配置 ####################

View File

@@ -6,10 +6,10 @@ spring:
username: # Nacos 账号
password: # Nacos 密码
discovery: # 【配置中心】配置项
namespace: 1e0fcd92-49b4-4cda-b531-828c7d36fef5 # 命名空间。这里使用 dev 开发环境
namespace: 1924bcfb-4eab-4c58-9003-4a37d5fc2949 # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
config: # 【注册中心】配置项
namespace: 1e0fcd92-49b4-4cda-b531-828c7d36fef5 # 命名空间。这里使用 dev 开发环境
namespace: 1924bcfb-4eab-4c58-9003-4a37d5fc2949 # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
#################### 监控相关配置 ####################

View File

@@ -49,6 +49,19 @@ spring:
uri: grayLb://rdms-system-server
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/system/ws/**
## project-server 服务
- id: project-admin-api # 路由的编号
uri: grayLb://rdms-project-server
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/admin-api/project/**
filters:
- RewritePath=/admin-api/project/v3/api-docs, /v3/api-docs
- id: project-app-api # 路由的编号
uri: grayLb://rdms-project-server
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
- Path=/app-api/project/**
filters:
- RewritePath=/app-api/project/v3/api-docs, /v3/api-docs
## bpm-server 服务
- id: bpm-admin-api # 路由的编号
uri: grayLb://rdms-bpm-server
@@ -76,6 +89,9 @@ knife4j:
- name: system-server
service-name: rdms-system-server
url: /admin-api/system/v3/api-docs
- name: project-server
service-name: rdms-project-server
url: /admin-api/project/v3/api-docs
- name: bpm-server
service-name: bpm-server
url: /admin-api/bpm/v3/api-docs

30
rdms-project/pom.xml Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn</groupId>
<artifactId>cn-rdms</artifactId>
<version>${revision}</version>
</parent>
<artifactId>rdms-project</artifactId>
<packaging>pom</packaging>
<name>${project.artifactId}</name>
<description>
RDMS 项目交付域模块
该模块承载项目集、项目、产品、需求、任务、工单、执行等 RDMS 核心交付业务能力。
</description>
<modules>
<module>rdms-project-boot</module>
<module>rdms-project-api</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn</groupId>
<artifactId>rdms-project</artifactId>
<version>${revision}</version>
</parent>
<artifactId>rdms-project-api</artifactId>
<description>
项目交付域接口,暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-common</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
<!-- 参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<optional>true</optional>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,4 @@
/**
* Project API 包,定义暴露给其它模块的 API
*/
package com.njcn.rdms.module.project.api;

View File

@@ -0,0 +1,24 @@
package com.njcn.rdms.module.project.enums;
import com.njcn.rdms.framework.common.enums.RpcConstants;
/**
* API 相关的枚举
*/
public class ApiConstants {
/**
* 服务名
*
* 注意,需要保证和 spring.application.name 保持一致
*/
public static final String NAME = RpcConstants.PROJECT_NAME;
public static final String PREFIX = RpcConstants.PROJECT_PREFIX;
public static final String VERSION = "1.0.0";
private ApiConstants() {
}
}

View File

@@ -0,0 +1,137 @@
package com.njcn.rdms.module.project.enums;
import com.njcn.rdms.framework.common.exception.ErrorCode;
/**
* Project 错误码枚举类
*
* 产品管理当前使用 1-008-001-000 段。
*/
public interface ErrorCodeConstants {
// ========== 产品管理 1-008-001-000 ==========
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_008_001_000, "产品不存在");
ErrorCode PRODUCT_CODE_DUPLICATE = new ErrorCode(1_008_001_001, "已经存在编码为【{}】的产品");
ErrorCode PRODUCT_NAME_DUPLICATE = new ErrorCode(1_008_001_002, "已经存在名称为【{}】的产品");
ErrorCode PRODUCT_CODE_NOT_MODIFIABLE = new ErrorCode(1_008_001_003, "产品编码创建后不允许修改");
ErrorCode PRODUCT_STATUS_ACTION_NOT_ALLOWED = new ErrorCode(1_008_001_004, "当前产品状态不支持动作【{}】");
ErrorCode PRODUCT_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_001_005, "动作【{}】必须填写原因");
ErrorCode PRODUCT_DELETE_NAME_MISMATCH = new ErrorCode(1_008_001_006, "删除确认名称与当前产品名称不一致");
ErrorCode PRODUCT_STATUS_NOT_ALLOW_EDIT = new ErrorCode(1_008_001_007, "当前产品状态不允许编辑");
ErrorCode PRODUCT_PAUSED_ONLY_ALLOW_LIMITED_UPDATE = new ErrorCode(1_008_001_008, "产品暂停后仅允许修正描述,产品经理请通过产品团队维护");
ErrorCode PRODUCT_MEMBER_NOT_EXISTS = new ErrorCode(1_008_001_009, "产品团队成员不存在");
ErrorCode PRODUCT_MEMBER_ALREADY_EXISTS = new ErrorCode(1_008_001_010, "该用户已是当前产品的有效团队成员");
ErrorCode PRODUCT_MEMBER_ROLE_INVALID = new ErrorCode(1_008_001_011, "角色不存在或不属于产品对象角色");
ErrorCode PRODUCT_MANAGER_TRANSFER_INFO_REQUIRED = new ErrorCode(1_008_001_013, "切换产品经理时必须同时传入原产品经理用户和交接后角色");
ErrorCode PRODUCT_MANAGER_MEMBER_NOT_ALLOW_REMOVE = new ErrorCode(1_008_001_014, "当前产品经理不能移出产品团队,请先完成经理转交");
ErrorCode PRODUCT_MANAGER_MEMBER_NOT_ALLOW_DOWNGRADE = new ErrorCode(1_008_001_015, "当前产品经理不能直接调整为非经理角色,请先完成经理转交");
ErrorCode PRODUCT_MEMBER_NOT_ACTIVE = new ErrorCode(1_008_001_016, "当前产品团队成员已失效");
ErrorCode PRODUCT_MANAGER_TRANSFER_SOURCE_INVALID = new ErrorCode(1_008_001_017, "原产品经理信息与当前产品经理不一致");
ErrorCode PRODUCT_MANAGER_TRANSFER_ROLE_INVALID = new ErrorCode(1_008_001_018, "原产品经理交接后的角色不能仍为产品经理");
ErrorCode PRODUCT_MANAGER_NOT_MODIFIABLE = new ErrorCode(1_008_001_019, "产品主数据编辑不允许直接变更产品经理,请通过产品团队维护");
ErrorCode PRODUCT_OBJECT_PERMISSION_DENIED = new ErrorCode(1_008_001_020, "当前用户不具备该产品的操作权限【{}】");
ErrorCode PRODUCT_DELETE_CONFIRM_TEXT_INVALID = new ErrorCode(1_008_001_021, "删除确认口令不正确");
ErrorCode PRODUCT_STATUS_CONCURRENT_MODIFIED = new ErrorCode(1_008_001_022, "产品状态已发生变化,请刷新后重试");
ErrorCode PRODUCT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_001_023, "产品状态定义不存在或已停用");
// ========== 产品需求 1-008-002-000 ==========
ErrorCode REQUIREMENT_NOT_EXISTS = new ErrorCode(1_008_002_000, "产品需求不存在");
ErrorCode REQUIREMENT_STATUS_ACTION_NOT_ALLOWED = new ErrorCode(1_008_002_001, "当前需求状态不支持动作【{}】");
ErrorCode REQUIREMENT_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_002_002, "动作【{}】必须填写原因");
ErrorCode REQUIREMENT_STATUS_CONCURRENT_MODIFIED = new ErrorCode(1_008_002_003, "需求状态已发生变化,请刷新后重试");
ErrorCode REQUIREMENT_STATUS_NOT_ALLOW_EDIT = new ErrorCode(1_008_002_004, "当前需求状态为终态,不允许编辑");
ErrorCode REQUIREMENT_STATUS_NOT_ALLOW_CLOSE = new ErrorCode(1_008_002_005, "只有已验收的需求才能关闭");
ErrorCode REQUIREMENT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_002_006, "需求状态定义不存在或已停用");
ErrorCode REQUIREMENT_PARENT_NOT_ALLOW_SPLIT = new ErrorCode(1_008_002_007, "父需求状态不是待分流或实施中,不允许拆分");
ErrorCode REQUIREMENT_CHILD_NOT_ALLOW_CLOSE = new ErrorCode(1_008_002_008, "存在子需求状态不对,请先处理子需求");
ErrorCode REQUIREMENT_MODULE_NOT_EXISTS = new ErrorCode(1_008_002_009, "需求模块不存在");
ErrorCode REQUIREMENT_MODULE_NAME_DUPLICATE = new ErrorCode(1_008_002_010, "已经存在名称为【{}】的模块");
ErrorCode REQUIREMENT_MODULE_NOT_BELONG_TO_PRODUCT = new ErrorCode(1_008_002_011, "模块不属于当前产品");
ErrorCode REQUIREMENT_MODULE_HAS_NON_TERMINAL_REQUIREMENTS = new ErrorCode(1_008_002_012, "模块下存在非终态需求,不可删除");
ErrorCode REQUIREMENT_HAS_CHILDREN = new ErrorCode(1_008_002_013, "存在子需求,请先删除子需求");
ErrorCode REQUIREMENT_STATUS_NOT_ALLOW_DELETE = new ErrorCode(1_008_002_014, "只有待确认、待评审、待分流状态的需求才能删除");
ErrorCode REQUIREMENT_MODULE_HAS_CHILDREN = new ErrorCode(1_008_002_015, "存在子模块,请先删除子模块");
ErrorCode REQUIREMENT_MODULE_HAS_REQUIREMENTS = new ErrorCode(1_008_002_016, "模块下存在需求,请先删除需求");
// ========== 项目管理 1-008-002-000 ==========
ErrorCode PROJECT_NOT_EXISTS = new ErrorCode(1_008_002_000, "项目不存在");
ErrorCode PROJECT_CODE_DUPLICATE = new ErrorCode(1_008_002_001, "已经存在编码为【{}】的项目");
ErrorCode PROJECT_NAME_DUPLICATE = new ErrorCode(1_008_002_002, "当前产品下已经存在名称为【{}】的项目");
ErrorCode PROJECT_CODE_NOT_MODIFIABLE = new ErrorCode(1_008_002_003, "项目编码创建后不允许修改");
ErrorCode PROJECT_PRODUCT_NOT_MODIFIABLE = new ErrorCode(1_008_002_004, "项目所属产品第一期不允许直接修改");
ErrorCode PROJECT_PRODUCT_NOT_EXISTS = new ErrorCode(1_008_002_005, "所属产品不存在或不可用");
ErrorCode PROJECT_TYPE_INVALID = new ErrorCode(1_008_002_006, "项目类型不是有效字典值");
ErrorCode PROJECT_MANAGER_USER_INVALID = new ErrorCode(1_008_002_007, "项目负责人不是有效系统用户");
ErrorCode PROJECT_MEMBER_USER_INVALID = new ErrorCode(1_008_002_008, "项目成员不是有效系统用户");
ErrorCode PROJECT_STATUS_ACTION_NOT_ALLOWED = new ErrorCode(1_008_002_009, "当前项目状态不支持动作【{}】");
ErrorCode PROJECT_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_002_010, "动作【{}】必须填写原因");
ErrorCode PROJECT_STATUS_CONCURRENT_MODIFIED = new ErrorCode(1_008_002_011, "项目状态已发生变化,请刷新后重试");
ErrorCode PROJECT_STATUS_NOT_ALLOW_EDIT = new ErrorCode(1_008_002_012, "当前项目状态不允许编辑");
ErrorCode PROJECT_MEMBER_NOT_EXISTS = new ErrorCode(1_008_002_013, "项目成员不存在");
ErrorCode PROJECT_MEMBER_ALREADY_EXISTS = new ErrorCode(1_008_002_014, "该用户已是当前项目的有效成员");
ErrorCode PROJECT_MEMBER_ROLE_INVALID = new ErrorCode(1_008_002_015, "角色不存在或不属于项目对象角色");
ErrorCode PROJECT_MANAGER_MEMBER_NOT_ALLOW_REMOVE = new ErrorCode(1_008_002_016, "当前项目负责人不能移出项目团队,请先完成负责人变更");
ErrorCode PROJECT_MEMBER_NOT_ACTIVE = new ErrorCode(1_008_002_017, "当前项目成员已失效");
ErrorCode PROJECT_DELETE_CONFIRM_TEXT_INVALID = new ErrorCode(1_008_002_018, "删除确认口令不正确");
ErrorCode PROJECT_DELETE_NAME_MISMATCH = new ErrorCode(1_008_002_019, "删除确认名称与当前项目名称不一致");
ErrorCode PROJECT_NOT_ALLOW_DELETE = new ErrorCode(1_008_002_020, "当前项目不允许删除");
ErrorCode PROJECT_OBJECT_PERMISSION_DENIED = new ErrorCode(1_008_002_021, "当前用户不具备该项目的操作权限【{}】");
ErrorCode PROJECT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_002_022, "项目状态定义不存在或已停用");
ErrorCode PROJECT_DIRECTION_INVALID = new ErrorCode(1_008_002_023, "项目方向不是有效字典值");
ErrorCode PROJECT_MANAGER_TRANSFER_INFO_REQUIRED = new ErrorCode(1_008_002_024, "切换项目经理时必须同时传入原项目经理用户和交接后角色");
ErrorCode PROJECT_MANAGER_TRANSFER_SOURCE_INVALID = new ErrorCode(1_008_002_025, "原项目经理信息与当前项目经理不一致");
ErrorCode PROJECT_MANAGER_TRANSFER_ROLE_INVALID = new ErrorCode(1_008_002_026, "原项目经理交接后的角色不能仍为项目经理");
ErrorCode PROJECT_MANAGER_MEMBER_NOT_ALLOW_DOWNGRADE = new ErrorCode(1_008_002_027, "当前项目经理不能直接调整为非经理角色,请先完成经理转交");
ErrorCode PROJECT_MAINLINE_DUPLICATE = new ErrorCode(1_008_002_028, "当前产品下已存在未作废的主线项目");
// ========== 执行管理 1-008-003-000 ==========
ErrorCode PROJECT_EXECUTION_NOT_EXISTS = new ErrorCode(1_008_003_000, "执行不存在");
ErrorCode PROJECT_EXECUTION_NAME_DUPLICATE = new ErrorCode(1_008_003_001, "当前项目下已经存在名称为【{}】的执行");
ErrorCode PROJECT_EXECUTION_OWNER_INVALID = new ErrorCode(1_008_003_002, "执行负责人必须是当前项目的有效成员");
ErrorCode PROJECT_EXECUTION_MEMBER_INVALID = new ErrorCode(1_008_003_003, "执行成员必须是当前项目的有效成员");
ErrorCode PROJECT_EXECUTION_MEMBER_ALREADY_EXISTS = new ErrorCode(1_008_003_004, "该用户已是当前执行的有效成员");
ErrorCode PROJECT_EXECUTION_MEMBER_NOT_EXISTS = new ErrorCode(1_008_003_005, "执行成员不存在");
ErrorCode PROJECT_EXECUTION_MEMBER_NOT_ACTIVE = new ErrorCode(1_008_003_006, "当前执行成员已失效");
ErrorCode PROJECT_EXECUTION_REQUIREMENT_NOT_READY = new ErrorCode(1_008_003_007, "当前阶段不支持给执行绑定项目需求");
ErrorCode PROJECT_EXECUTION_NOT_ALLOW_EDIT = new ErrorCode(1_008_003_008, "当前项目状态不允许维护执行");
ErrorCode PROJECT_EXECUTION_OWNER_HANDOFF_REQUIRED = new ErrorCode(1_008_003_009, "该项目成员仍担任未终态执行负责人,请先完成执行负责人交接");
ErrorCode PROJECT_EXECUTION_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_003_010, "执行状态定义不存在或已停用");
ErrorCode PROJECT_EXECUTION_STATUS_ACTION_NOT_ALLOWED = new ErrorCode(1_008_003_011, "当前执行状态不支持动作【{}】");
ErrorCode PROJECT_EXECUTION_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_003_012, "动作【{}】必须填写原因");
ErrorCode PROJECT_EXECUTION_STATUS_CONCURRENT_MODIFIED = new ErrorCode(1_008_003_013, "执行状态已发生变化,请刷新后重试");
ErrorCode PROJECT_EXECUTION_STATUS_NOT_ALLOW_EDIT = new ErrorCode(1_008_003_014, "当前执行状态不允许维护执行");
ErrorCode PROJECT_EXECUTION_TYPE_INVALID = new ErrorCode(1_008_003_015, "执行类型不是有效字典值");
ErrorCode PROJECT_EXECUTION_MEMBER_REQUIRED = new ErrorCode(1_008_003_016, "创建执行时必须至少选择一名执行成员");
// ========== 任务管理 1-008-004-000 ==========
ErrorCode PROJECT_TASK_NOT_EXISTS = new ErrorCode(1_008_004_000, "任务不存在");
ErrorCode PROJECT_TASK_OWNER_INVALID = new ErrorCode(1_008_004_001, "任务负责人必须是当前执行的有效成员");
ErrorCode PROJECT_TASK_PARENT_INVALID = new ErrorCode(1_008_004_002, "父任务必须属于当前项目和执行");
ErrorCode PROJECT_TASK_NOT_ALLOW_EDIT = new ErrorCode(1_008_004_003, "当前项目或执行状态不允许维护任务");
ErrorCode PROJECT_TASK_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_004_004, "任务状态定义不存在或已停用");
ErrorCode PROJECT_TASK_STATUS_ACTION_NOT_ALLOWED = new ErrorCode(1_008_004_005, "当前任务状态不支持动作【{}】");
ErrorCode PROJECT_TASK_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_004_006, "动作【{}】必须填写原因");
ErrorCode PROJECT_TASK_STATUS_CONCURRENT_MODIFIED = new ErrorCode(1_008_004_007, "任务状态已发生变化,请刷新后重试");
ErrorCode PROJECT_TASK_STATUS_NOT_ALLOW_EDIT = new ErrorCode(1_008_004_008, "当前任务状态不允许维护任务");
ErrorCode PROJECT_TASK_COMPLETE_CHILDREN_REQUIRED = new ErrorCode(1_008_004_010, "父任务完成前,子任务必须全部完成或取消");
ErrorCode PROJECT_TASK_PROGRESS_PARENT_NOT_EDITABLE = new ErrorCode(1_008_004_011, "父任务进度由子任务自动汇总,不允许手工修改");
ErrorCode PROJECT_TASK_LEAF_TO_PARENT_FORBIDDEN_PROGRESS = new ErrorCode(1_008_004_012, "拆子任务前请先将父任务进度清零");
ErrorCode PROJECT_TASK_LEAF_TO_PARENT_FORBIDDEN_WORKLOG = new ErrorCode(1_008_004_013, "拆子任务前请先删除父任务下已填的工时记录");
// ========== 任务协办人 1_008_005_xxx ==========
ErrorCode PROJECT_TASK_ASSIGNEE_NOT_EXISTS = new ErrorCode(1_008_005_001, "任务协办人记录不存在");
ErrorCode PROJECT_TASK_ASSIGNEE_INVALID_MEMBER = new ErrorCode(1_008_005_002, "任务协办人必须是当前有效的执行成员");
ErrorCode PROJECT_TASK_ASSIGNEE_OWNER_CONFLICT = new ErrorCode(1_008_005_003, "任务协办人不能与任务负责人重复");
ErrorCode PROJECT_TASK_ASSIGNEE_ALREADY_ACTIVE = new ErrorCode(1_008_005_004, "该用户已是当前任务的活跃协办人");
ErrorCode PROJECT_TASK_ASSIGNEE_NOT_ACTIVE = new ErrorCode(1_008_005_005, "任务协办人记录已失效,无需重复操作");
ErrorCode PROJECT_TASK_ASSIGNEE_REASON_REQUIRED = new ErrorCode(1_008_005_006, "退出协办必须填写原因");
// ========== 任务工时 1_008_006_xxx ==========
ErrorCode PROJECT_TASK_WORKLOG_NOT_EXISTS = new ErrorCode(1_008_006_001, "任务工时记录不存在");
ErrorCode PROJECT_TASK_WORKLOG_NOT_LEAF_TASK = new ErrorCode(1_008_006_002, "父任务不允许填报工时,请到具体子任务填报");
ErrorCode PROJECT_TASK_WORKLOG_DURATION_INVALID = new ErrorCode(1_008_006_003, "工时时长必须大于 0 且为 30 分钟的整数倍");
ErrorCode PROJECT_TASK_WORKLOG_NOT_OWNER_OR_ASSIGNEE = new ErrorCode(1_008_006_004, "仅任务负责人或在岗协办人可填报工时");
ErrorCode PROJECT_TASK_WORKLOG_EDIT_NOT_OWN = new ErrorCode(1_008_006_005, "只能修改自己填报的工时记录");
ErrorCode PROJECT_TASK_WORKLOG_DELETE_FORBIDDEN = new ErrorCode(1_008_006_006, "仅记录填报人或任务负责人可删除该工时记录");
}

View File

@@ -0,0 +1,18 @@
package com.njcn.rdms.module.project.enums;
/**
* 项目域字典类型常量。
*/
public interface ProjectDictTypeConstants {
/**
* 项目类型。
*/
String PROJECT_TYPE = "rdms_project_type";
/**
* 执行类型。
*/
String EXECUTION_TYPE = "rdms_project_execution_type";
}

View File

@@ -0,0 +1,116 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn</groupId>
<artifactId>rdms-project</artifactId>
<version>${revision}</version>
</parent>
<artifactId>rdms-project-boot</artifactId>
<description>项目交付域功能服务模块</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Cloud 基础 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-env</artifactId>
</dependency>
<!-- 依赖服务 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-project-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-system-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-redis</artifactId>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-rpc</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-excel</artifactId>
</dependency>
<!-- Registry 注册中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Config 配置中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 热部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<addResources>true</addResources>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,16 @@
package com.njcn.rdms.module.project;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 项目交付域服务启动类
*/
@SpringBootApplication
public class ProjectServerApplication {
public static void main(String[] args) {
SpringApplication.run(ProjectServerApplication.class, args);
}
}

View File

@@ -0,0 +1,4 @@
/**
* Project API 实现包,放置对外暴露 RPC 接口的实现类
*/
package com.njcn.rdms.module.project.api;

View File

@@ -0,0 +1,135 @@
package com.njcn.rdms.module.project.constant;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Set;
/**
* 对象动态常量
*
* 说明:
* 1. 当前先承载产品对象首页时间线使用的 activityType / actionType 常量
* 2. 后续项目等对象复用同类动态能力时,继续按前缀分组扩展,不单独拆分枚举
*/
public final class ObjectActivityConstants {
private ObjectActivityConstants() {
}
// ========== 动态来源类型 ==========
public static final String ACTIVITY_TYPE_STATUS = "status";
public static final String ACTIVITY_TYPE_PRODUCT = "product";
public static final String ACTIVITY_TYPE_MEMBER = "member";
// ========== 审计业务类型 ==========
public static final String PRODUCT_BIZ_TYPE = "product";
public static final String PROJECT_BIZ_TYPE = "project";
public static final String MEMBER_BIZ_TYPE = "rdms_user_object_role";
// ========== 产品对象动作 ==========
public static final String PRODUCT_ACTION_CREATE = "create";
public static final String PRODUCT_ACTION_UPDATE = "update";
public static final String PRODUCT_ACTION_DELETE = "delete";
public static final String PRODUCT_ACTION_CHANGE_MANAGER = "change_manager";
// ========== 项目对象动作 ==========
public static final String PROJECT_ACTION_CREATE = "create";
public static final String PROJECT_ACTION_UPDATE = "update";
public static final String PROJECT_ACTION_DELETE = "delete";
public static final String PROJECT_ACTION_CHANGE_MANAGER = "change_manager";
public static final String PROJECT_ACTION_AUTO_START = "auto_start";
// ========== 项目自动推进触发动作 ==========
public static final String PROJECT_TRIGGER_CREATE_EXECUTION = "create_execution";
public static final String PROJECT_TRIGGER_CREATE_TASK = "create_task";
public static final String PROJECT_TRIGGER_SCHEDULE_REQUIREMENT = "schedule_requirement";
// ========== 状态动作 ==========
public static final String STATUS_ACTION_PAUSE = "pause";
public static final String STATUS_ACTION_RESUME = "resume";
public static final String STATUS_ACTION_ARCHIVE = "archive";
public static final String STATUS_ACTION_ABANDON = "abandon";
// ========== 成员动作 ==========
public static final String MEMBER_ACTION_ADD = "add_member";
public static final String MEMBER_ACTION_UPDATE = "update_member";
public static final String MEMBER_ACTION_REMOVE = "remove_member";
public static final String EXECUTION_ACTION_CREATE = "create_execution_entity";
public static final String EXECUTION_ACTION_UPDATE = "update_execution_entity";
public static final String EXECUTION_ACTION_CHANGE_OWNER = "change_execution_owner";
public static final String EXECUTION_MEMBER_ACTION_ADD = "add_execution_member";
public static final String EXECUTION_MEMBER_ACTION_REMOVE = "remove_execution_member";
public static final String TASK_ACTION_CREATE = "create_task_entity";
public static final String TASK_ACTION_UPDATE = "update_task_entity";
// ========== 任务协办人事件类型B 模型 - 多行周期记录) ==========
public static final String TASK_ASSIGNEE_ACTION_JOIN = "join";
public static final String TASK_ASSIGNEE_ACTION_INACTIVE = "inactive";
// ========== 执行成员事件类型B 模型 - 多行周期记录) ==========
public static final String EXECUTION_MEMBER_LOG_ACTION_JOIN = "join";
public static final String EXECUTION_MEMBER_LOG_ACTION_INACTIVE = "inactive";
public static final String EXECUTION_MEMBER_LOG_ACTION_OWNER_TRANSFER_IN = "owner_transfer_in";
public static final String EXECUTION_MEMBER_LOG_ACTION_OWNER_TRANSFER_OUT = "owner_transfer_out";
public static final List<String> STATUS_ACTION_TYPES = List.of(
STATUS_ACTION_PAUSE, STATUS_ACTION_RESUME, STATUS_ACTION_ARCHIVE, STATUS_ACTION_ABANDON);
public static final List<String> PRODUCT_TIMELINE_ACTION_TYPES = List.of(
PRODUCT_ACTION_CREATE, PRODUCT_ACTION_CHANGE_MANAGER);
public static final List<String> MEMBER_TIMELINE_ACTION_TYPES = List.of(
MEMBER_ACTION_ADD, MEMBER_ACTION_UPDATE, MEMBER_ACTION_REMOVE);
private static final Set<String> STATUS_ACTION_TYPE_SET = Set.copyOf(STATUS_ACTION_TYPES);
public static boolean isStatusAction(String actionType) {
return STATUS_ACTION_TYPE_SET.contains(normalize(actionType));
}
public static String resolveActionName(String actionType) {
String normalizedActionType = normalize(actionType);
if (!StringUtils.hasText(normalizedActionType)) {
return actionType;
}
return switch (normalizedActionType) {
case PRODUCT_ACTION_CREATE -> "创建";
case PRODUCT_ACTION_UPDATE -> "更新";
case PRODUCT_ACTION_DELETE -> "删除";
case PROJECT_ACTION_AUTO_START -> "自动进入进行中";
case PROJECT_TRIGGER_CREATE_EXECUTION -> "创建执行";
case PROJECT_TRIGGER_CREATE_TASK -> "创建任务";
case PROJECT_TRIGGER_SCHEDULE_REQUIREMENT -> "项目需求排期";
case EXECUTION_ACTION_CREATE -> "创建执行";
case EXECUTION_ACTION_UPDATE -> "更新执行";
case EXECUTION_ACTION_CHANGE_OWNER -> "变更执行负责人";
case EXECUTION_MEMBER_ACTION_ADD -> "新增执行成员";
case EXECUTION_MEMBER_ACTION_REMOVE -> "移出执行成员";
case TASK_ACTION_CREATE -> "创建任务";
case TASK_ACTION_UPDATE -> "更新任务";
case TASK_ASSIGNEE_ACTION_JOIN -> "加入";
case TASK_ASSIGNEE_ACTION_INACTIVE -> "退出";
case EXECUTION_MEMBER_LOG_ACTION_OWNER_TRANSFER_IN -> "转入负责人";
case EXECUTION_MEMBER_LOG_ACTION_OWNER_TRANSFER_OUT -> "转出负责人";
case "start" -> "开始";
case "block" -> "阻塞";
case "complete" -> "完成";
case "cancel" -> "取消";
case STATUS_ACTION_PAUSE -> "暂停";
case STATUS_ACTION_RESUME -> "恢复";
case STATUS_ACTION_ARCHIVE -> "归档";
case STATUS_ACTION_ABANDON -> "废弃";
case PRODUCT_ACTION_CHANGE_MANAGER -> "切换负责人";
case MEMBER_ACTION_ADD -> "新增成员";
case MEMBER_ACTION_UPDATE -> "调整成员";
case MEMBER_ACTION_REMOVE -> "移出成员";
default -> normalizedActionType;
};
}
private static String normalize(String value) {
return StringUtils.hasText(value) ? value.trim() : null;
}
}

View File

@@ -0,0 +1,28 @@
package com.njcn.rdms.module.project.constant;
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
/**
* RDMS 对象角色与成员关系常量。
*/
public final class ObjectRoleConstants {
private ObjectRoleConstants() {
}
/**
* 对象级权限作用域,对应 sys_menu.scope_type = object。
*/
public static final String ROLE_SCOPE_OBJECT = PermissionScopeTypeEnum.OBJECT.getScopeType();
/**
* 对象成员有效状态,对应 rdms_user_object_role.status = 0。
*/
public static final Integer MEMBER_STATUS_ACTIVE = 0;
/**
* 对象成员失效状态,对应 rdms_user_object_role.status = 1。
*/
public static final Integer MEMBER_STATUS_INACTIVE = 1;
}

View File

@@ -0,0 +1,51 @@
package com.njcn.rdms.module.project.constant;
/**
* 产品对象常量。
*/
public final class ProductObjectConstants {
private ProductObjectConstants() {
}
/**
* 产品对象类型,对应 rdms_user_object_role.object_type、rdms_object_status_model.object_type。
*/
public static final String OBJECT_TYPE = "product";
/**
* 产品经理对象角色编码。该角色用于初始化与识别产品负责人。
*/
public static final String MANAGER_ROLE_CODE = "product_manager";
/**
* 产品暂停状态。
*/
public static final String STATUS_PAUSED = "paused";
/**
* 产品自动编码前缀。
*/
public static final String CODE_PREFIX = "CNPD";
/**
* 产品编辑权限码。
*/
public static final String PERMISSION_UPDATE = "project:product:update";
/**
* 产品状态动作权限码。
*/
public static final String PERMISSION_STATUS = "project:product:status";
/**
* 产品删除权限码。
*/
public static final String PERMISSION_DELETE = "project:product:delete";
/**
* 产品删除确认口令。
*/
public static final String DELETE_CONFIRM_TEXT = "DELETE";
}

View File

@@ -0,0 +1,46 @@
package com.njcn.rdms.module.project.constant;
/**
* 执行对象常量。
*/
public final class ProjectExecutionConstants {
private ProjectExecutionConstants() {
}
/**
* 执行对象类型,对应 rdms_object_status_model.object_type。
*/
public static final String OBJECT_TYPE = "execution";
/**
* 执行业务类型。
*/
public static final String BIZ_TYPE = "project_execution";
/**
* 创建执行权限码。
*/
public static final String PERMISSION_CREATE = "project:execution:create";
/**
* 编辑执行权限码。
*/
public static final String PERMISSION_UPDATE = "project:execution:update";
/**
* 执行负责人治理权限码。
*/
public static final String PERMISSION_OWNER = "project:execution:owner";
/**
* 执行成员治理权限码。
*/
public static final String PERMISSION_MEMBER = "project:execution:member";
/**
* 执行状态动作权限码。
*/
public static final String PERMISSION_STATUS = "project:execution:status";
}

View File

@@ -0,0 +1,76 @@
package com.njcn.rdms.module.project.constant;
import java.util.Set;
/**
* 项目对象常量。
*/
public final class ProjectObjectConstants {
private ProjectObjectConstants() {
}
/**
* 项目对象类型,对应 rdms_user_object_role.object_type、rdms_object_status_model.object_type。
*/
public static final String OBJECT_TYPE = "project";
/**
* 项目经理对象角色编码。该角色用于初始化与识别项目负责人。
*/
public static final String MANAGER_ROLE_CODE = "project_manager";
/**
* 项目游客对象角色编码。创建人未成为项目成员时,用于返回只读上下文菜单。
*/
public static final String VISITOR_ROLE_CODE = "visitor";
/**
* 产品主线项目类型字典值。后续字典值收敛后,只需调整这里。
*/
public static final Set<String> MAINLINE_PROJECT_TYPE_CODES = Set.of("mainline", "main", "product_mainline", "主线");
/**
* 已作废项目状态编码。当前作废项目不占用产品主线项目名额。
*/
public static final String STATUS_CANCELLED = "cancelled";
/**
* 项目自动编码前缀。
*/
public static final String CODE_PREFIX = "CNPJ";
/**
* 项目编辑权限码。
*/
public static final String PERMISSION_UPDATE = "project:project:update";
/**
* 项目成员维护权限码。
*/
public static final String PERMISSION_MEMBER = "project:project:member";
/**
* 项目状态动作权限码。
*/
public static final String PERMISSION_STATUS = "project:project:status";
/**
* 项目删除权限码。
*/
public static final String PERMISSION_DELETE = "project:project:delete";
/**
* 项目删除确认口令。
*/
public static final String DELETE_CONFIRM_TEXT = "DELETE";
/**
* 可触发项目从待开始自动进入进行中的后端业务动作。
*/
public static final Set<String> AUTO_START_TRIGGERS = Set.of(
ObjectActivityConstants.PROJECT_TRIGGER_CREATE_EXECUTION,
ObjectActivityConstants.PROJECT_TRIGGER_CREATE_TASK,
ObjectActivityConstants.PROJECT_TRIGGER_SCHEDULE_REQUIREMENT);
}

View File

@@ -0,0 +1,46 @@
package com.njcn.rdms.module.project.constant;
/**
* 任务对象常量。
*/
public final class ProjectTaskConstants {
private ProjectTaskConstants() {
}
/**
* 任务对象类型,对应 rdms_object_status_model.object_type。
*/
public static final String OBJECT_TYPE = "task";
/**
* 任务业务类型。
*/
public static final String BIZ_TYPE = "project_task";
/**
* 创建任务权限码。
*/
public static final String PERMISSION_CREATE = "project:task:create";
/**
* 编辑任务权限码。
*/
public static final String PERMISSION_UPDATE = "project:task:update";
/**
* 任务状态动作权限码。
*/
public static final String PERMISSION_STATUS = "project:task:status";
/**
* 任务协办人维护权限码。
*/
public static final String PERMISSION_ASSIGNEE = "project:task:assignee";
/**
* 任务工时维护权限码。
*/
public static final String PERMISSION_WORKLOG = "project:task:worklog";
}

View File

@@ -0,0 +1,4 @@
/**
* 管理端控制器包
*/
package com.njcn.rdms.module.project.controller.admin;

View File

@@ -0,0 +1,91 @@
package com.njcn.rdms.module.project.controller.admin.product;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRespVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductDeleteReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductOverviewSummaryRespVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductRespVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductStatusActionReqVO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
import com.njcn.rdms.module.project.service.product.ProductService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 产品管理")
@RestController
@RequestMapping("/project/product")
@Validated
public class ProductController {
@Resource
private ProductService productService;
@PostMapping("/create")
@Operation(summary = "创建产品")
@PreAuthorize("@ss.hasPermission('project:product:create')")
public CommonResult<Long> createProduct(@Valid @RequestBody ProductSaveReqVO createReqVO) {
return success(productService.createProduct(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新产品")
public CommonResult<Boolean> updateProduct(@Valid @RequestBody ProductSaveReqVO updateReqVO) {
productService.updateProduct(updateReqVO);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获取产品详情")
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
public CommonResult<ProductRespVO> getProduct(@RequestParam("id") Long id) {
ProductDO product = productService.getProduct(id);
return success(BeanUtils.toBean(product, ProductRespVO.class));
}
@GetMapping("/{id}/context")
@Operation(summary = "获取产品上下文")
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
public CommonResult<ProductContextRespVO> getProductContext(@PathVariable("id") Long id) {
return success(productService.getProductContext(id));
}
@GetMapping("/page")
@Operation(summary = "获取产品分页")
public CommonResult<PageResult<ProductRespVO>> getProductPage(@Valid ProductPageReqVO pageReqVO) {
PageResult<ProductDO> pageResult = productService.getProductPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ProductRespVO.class));
}
@GetMapping("/overview-summary")
@Operation(summary = "获取产品入口页概览统计")
public CommonResult<ProductOverviewSummaryRespVO> getProductOverviewSummary() {
return success(productService.getProductOverviewSummary());
}
@PostMapping("/change-status")
@Operation(summary = "变更产品状态")
public CommonResult<Boolean> changeProductStatus(@Valid @RequestBody ProductStatusActionReqVO reqVO) {
productService.changeProductStatus(reqVO);
return success(true);
}
@PostMapping("/delete")
@Operation(summary = "删除产品")
public CommonResult<Boolean> deleteProduct(@Valid @RequestBody ProductDeleteReqVO reqVO) {
productService.deleteProduct(reqVO);
return success(true);
}
}

View File

@@ -0,0 +1,63 @@
package com.njcn.rdms.module.project.controller.admin.product;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberInactiveReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberRespVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.member.ProductMemberUpdateReqVO;
import com.njcn.rdms.module.project.service.product.ProductMemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 产品团队")
@RestController
@RequestMapping("/project/product")
@Validated
public class ProductMemberController {
@Resource
private ProductMemberService productMemberService;
@GetMapping("/{id}/members")
@Operation(summary = "获取产品团队成员列表")
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
public CommonResult<List<ProductMemberRespVO>> getProductMemberList(@PathVariable("id") Long productId) {
return success(productMemberService.getProductMemberList(productId));
}
@PostMapping("/{id}/members")
@Operation(summary = "新增产品团队成员")
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
public CommonResult<Long> createProductMember(@PathVariable("id") Long productId,
@Valid @RequestBody ProductMemberSaveReqVO reqVO) {
return success(productMemberService.createProductMember(productId, reqVO));
}
@PutMapping("/{id}/members/{memberId}")
@Operation(summary = "调整产品团队成员角色")
public CommonResult<Boolean> updateProductMember(@PathVariable("id") Long productId,
@PathVariable("memberId") Long memberId,
@Valid @RequestBody ProductMemberUpdateReqVO reqVO) {
productMemberService.updateProductMember(productId, memberId, reqVO);
return success(true);
}
@PostMapping("/{id}/members/{memberId}/inactive")
@Operation(summary = "移出产品团队成员")
public CommonResult<Boolean> inactiveProductMember(@PathVariable("id") Long productId,
@PathVariable("memberId") Long memberId,
@Valid @RequestBody ProductMemberInactiveReqVO reqVO) {
productMemberService.inactiveProductMember(productId, memberId, reqVO);
return success(true);
}
}

View File

@@ -0,0 +1,157 @@
package com.njcn.rdms.module.project.controller.admin.product;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.*;
import com.njcn.rdms.module.project.service.product.ProductRequirementService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
/**
* 管理后台 - 产品需求控制器
*/
@Tag(name = "管理后台 - 产品需求")
@RestController
@RequestMapping("/project/product/requirement")
@Validated
public class ProductRequirementController {
@Resource
private ProductRequirementService requirementService;
// ========== 需求管理 ==========
@PostMapping("/create")
@Operation(summary = "创建产品需求")
public CommonResult<Long> createRequirement(@Valid @RequestBody ProductRequirementSaveReqVO createReqVO) {
return success(requirementService.createRequirement(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新产品需求")
public CommonResult<Boolean> updateRequirement(@Valid @RequestBody ProductRequirementUpdateReqVO updateReqVO) {
requirementService.updateRequirement(updateReqVO);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获取需求详情")
@Parameter(name = "id", description = "需求编号", required = true, example = "1024")
@Parameter(name = "productId", description = "产品编号", required = true, example = "1024")
public CommonResult<ProductRequirementRespVO> getRequirement(@RequestParam("id") Long id,
@RequestParam("productId") Long productId) {
return success(requirementService.getRequirement(id, productId));
}
@GetMapping("/page")
@Operation(summary = "获取需求分页列表")
public CommonResult<PageResult<ProductRequirementRespVO>> getRequirementPage(@Valid ProductRequirementPageReqVO pageReqVO) {
return success(requirementService.getRequirementPage(pageReqVO));
}
@GetMapping("/tree")
@Operation(summary = "获取需求树形列表(分页)")
public CommonResult<PageResult<ProductRequirementRespVO>> getRequirementTree(@Valid ProductRequirementPageReqVO pageReqVO) {
return success(requirementService.getRequirementTree(pageReqVO));
}
@PostMapping("/change-status")
@Operation(summary = "变更需求状态")
public CommonResult<Boolean> changeRequirementStatus(@Valid @RequestBody ProductRequirementStatusActionReqVO reqVO) {
requirementService.changeRequirementStatus(reqVO);
return success(true);
}
@PostMapping("/delete")
@Operation(summary = "删除产品需求")
public CommonResult<Boolean> deleteRequirement(@Valid @RequestBody ProductRequirementDeleteReqVO reqVO) {
requirementService.deleteRequirement(reqVO.getId(), reqVO.getProductId());
return success(true);
}
@PostMapping("/split")
@Operation(summary = "拆分产品需求")
public CommonResult<Long> splitRequirement(@Valid @RequestBody ProductRequirementSplitReqVO reqVO) {
System.out.println("-----------------------");
System.out.println(reqVO);
return success(requirementService.splitRequirement(reqVO));
}
@PostMapping("/close")
@Operation(summary = "关闭产品需求")
public CommonResult<Boolean> closeRequirement(@Valid @RequestBody ProductRequirementCloseReqVO reqVO) {
requirementService.closeRequirement(reqVO);
return success(true);
}
@GetMapping("/allowed-transitions")
@Operation(summary = "获取需求可执行的状态动作列表")
@Parameter(name = "requirementId", description = "需求编号", required = true, example = "1024")
@Parameter(name = "productId", description = "产品编号", required = true, example = "1024")
public CommonResult<List<ProductRequirementStatusTransitionRespVO>> getAllowedTransitions(
@RequestParam("requirementId") Long requirementId,
@RequestParam("productId") Long productId) {
return success(requirementService.getAllowedTransitions(requirementId, productId));
}
@GetMapping("/lifecycle")
@Operation(summary = "获取需求生命周期信息")
@Parameter(name = "requirementId", description = "需求编号", required = true, example = "1024")
@Parameter(name = "productId", description = "产品编号", required = true, example = "1024")
public CommonResult<ProductRequirementLifecycleRespVO> getRequirementLifecycle(
@RequestParam("requirementId") Long requirementId,
@RequestParam("productId") Long productId) {
return success(requirementService.getRequirementLifecycle(requirementId, productId));
}
// ========== 模块管理 ==========
@PostMapping("/module/create")
@Operation(summary = "创建需求模块")
public CommonResult<Long> createRequirementModule(@Valid @RequestBody ProductRequirementModuleReqVO reqVO) {
return success(requirementService.createRequirementModule(reqVO));
}
@PutMapping("/module/update")
@Operation(summary = "更新需求模块")
public CommonResult<Boolean> updateRequirementModule(@Valid @RequestBody ProductRequirementModuleReqVO reqVO) {
requirementService.updateRequirementModule(reqVO);
return success(true);
}
@PostMapping("/module/delete")
@Operation(summary = "删除需求模块")
public CommonResult<Boolean> deleteRequirementModule(@Valid @RequestBody ProductRequirementModuleDeleteReqVO reqVO) {
requirementService.deleteRequirementModule(reqVO.getId(), reqVO.getProductId());
return success(true);
}
@GetMapping("/module/tree")
@Operation(summary = "获取需求模块树")
@Parameter(name = "productId", description = "产品编号", required = true, example = "1024")
public CommonResult<List<ProductRequirementModuleRespVO>> getRequirementModuleTree(@RequestParam("productId") Long productId) {
return success(requirementService.getRequirementModuleTree(productId));
}
@GetMapping("/status/dict")
@Operation(summary = "获取需求所有状态字典")
public CommonResult<List<ProductRequirementStatusDictRespVO>> getRequirementStatusDict() {
return success(requirementService.getRequirementStatusDict());
}
@GetMapping("/status/dict/terminal")
@Operation(summary = "获取需求终止态状态字典")
public CommonResult<List<ProductRequirementStatusDictRespVO>> getRequirementTerminalStatusDict() {
return success(requirementService.getRequirementTerminalStatusDict());
}
}

View File

@@ -0,0 +1,71 @@
package com.njcn.rdms.module.project.controller.admin.product;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityPageReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityRespVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityTimelinePageReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityTimelineRespVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingBaseInfoUpdateReqVO;
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingRespVO;
import com.njcn.rdms.module.project.service.product.ProductService;
import com.njcn.rdms.module.project.service.product.ProductSettingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 产品设置")
@RestController
@RequestMapping("/project/product")
@Validated
public class ProductSettingController {
@Resource
private ProductSettingService productSettingService;
@Resource
private ProductService productService;
@GetMapping("/{id}/settings")
@Operation(summary = "获取产品设置")
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
public CommonResult<ProductSettingRespVO> getProductSettings(@PathVariable("id") Long id) {
return success(productSettingService.getProductSettings(id));
}
@GetMapping("/{id}/activities")
@Operation(summary = "获取产品动态")
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
public CommonResult<PageResult<ProductActivityRespVO>> getProductActivities(@PathVariable("id") Long id,
@Valid ProductActivityPageReqVO reqVO) {
return success(productSettingService.getProductActivities(id, reqVO));
}
@GetMapping("/{id}/activities/page")
@Operation(summary = "获取产品动态时间线分页")
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
public CommonResult<PageResult<ProductActivityTimelineRespVO>> getProductActivityTimelinePage(
@PathVariable("id") Long id, @Valid ProductActivityTimelinePageReqVO reqVO) {
return success(productSettingService.getProductActivityTimelinePage(id, reqVO));
}
@PutMapping("/{id}/settings/base-info")
@Operation(summary = "更新产品设置基础信息")
@Parameter(name = "id", description = "产品编号", required = true, example = "1024")
public CommonResult<Boolean> updateProductBaseInfo(@PathVariable("id") Long id,
@Valid @RequestBody ProductSettingBaseInfoUpdateReqVO reqVO) {
productService.updateProductBaseInfo(id, reqVO);
return success(true);
}
}

View File

@@ -0,0 +1,31 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.activity;
import com.njcn.rdms.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 产品动态分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProductActivityPageReqVO extends PageParam {
@Schema(description = "动态类型", example = "status")
@Size(max = 16, message = "动态类型长度不能超过16个字符")
private String activityType;
@Schema(description = "动作编码", example = "pause")
@Size(max = 32, message = "动作编码长度不能超过32个字符")
private String actionType;
@Schema(description = "操作时间区间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] operateTime;
}

View File

@@ -0,0 +1,45 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 产品动态 Response VO")
@Data
public class ProductActivityRespVO {
@Schema(description = "动态类型", example = "status")
private String type;
@Schema(description = "动作编码", example = "pause")
private String actionType;
@Schema(description = "动作名称", example = "暂停")
private String actionName;
@Schema(description = "原状态", example = "active")
private String fromStatus;
@Schema(description = "目标状态", example = "paused")
private String toStatus;
@Schema(description = "动作原因", example = "资源不足")
private String reason;
@Schema(description = "操作人用户编号", example = "1024")
private Long operatorUserId;
@Schema(description = "操作人名称", example = "张三")
private String operatorName;
@Schema(description = "操作时间", example = "2026-04-21 12:00:00")
private LocalDateTime operateTime;
@Schema(description = "展示摘要", example = "张三执行了【暂停】:资源不足")
private String summary;
@Schema(description = "补充详情")
private String details;
}

View File

@@ -0,0 +1,35 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.activity;
import com.njcn.rdms.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 产品动态时间线分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProductActivityTimelinePageReqVO extends PageParam {
@Schema(description = "动态类型", example = "status")
@Size(max = 16, message = "动态类型长度不能超过16个字符")
private String activityType;
@Schema(description = "动作编码数组")
private List<@Size(max = 32, message = "动作编码长度不能超过32个字符") String> actionTypes;
@Schema(description = "开始时间", example = "2026-03-24 00:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime startTime;
@Schema(description = "结束时间", example = "2026-04-23 23:59:59")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime endTime;
}

View File

@@ -0,0 +1,54 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 产品动态时间线 Response VO")
@Data
public class ProductActivityTimelineRespVO {
@Schema(description = "动态唯一标识", example = "status:11")
private String id;
@Schema(description = "动态类型", example = "status")
private String type;
@Schema(description = "动作编码", example = "pause")
private String actionType;
@Schema(description = "动作名称", example = "暂停")
private String actionName;
@Schema(description = "操作人用户编号", example = "1024")
private Long operatorUserId;
@Schema(description = "操作人名称", example = "张三")
private String operatorName;
@Schema(description = "目标成员用户编号,仅 member 类型返回", example = "2043945809271713793")
private Long targetUserId;
@Schema(description = "目标成员名称,仅 member 类型返回,读取缓存实时转换", example = "张三")
private String targetUserName;
@Schema(description = "发生时间", example = "2026-04-21 12:00:00")
private LocalDateTime occurredAt;
@Schema(description = "展示摘要", example = "张三执行了【暂停】:资源不足")
private String summary;
@Schema(description = "动作原因", example = "资源不足")
private String reason;
@Schema(description = "原状态", example = "active")
private String fromStatus;
@Schema(description = "目标状态", example = "paused")
private String toStatus;
@Schema(description = "补充详情")
private String details;
}

View File

@@ -0,0 +1,15 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.member;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 产品团队成员移出 Request VO")
@Data
public class ProductMemberInactiveReqVO {
@Schema(description = "移出原因", example = "已退出当前产品协作")
@Size(max = 500, message = "移出原因长度不能超过500个字符")
private String reason;
}

View File

@@ -0,0 +1,45 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.member;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 产品团队成员 Response VO")
@Data
public class ProductMemberRespVO {
@Schema(description = "团队关系编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long userId;
@Schema(description = "用户昵称", example = "小王")
private String userNickname;
@Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3100000002001")
private Long roleId;
@Schema(description = "角色名称", example = "产品经理")
private String roleName;
@Schema(description = "角色编码", example = "product_manager")
private String roleCode;
@Schema(description = "是否当前产品经理", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean managerFlag;
@Schema(description = "状态0有效 1失效", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status;
@Schema(description = "加入时间")
private LocalDateTime joinedTime;
@Schema(description = "退出时间")
private LocalDateTime leftTime;
@Schema(description = "备注", example = "当前负责需求收敛")
private String remark;
}

View File

@@ -0,0 +1,30 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.member;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 产品团队成员新增 Request VO")
@Data
public class ProductMemberSaveReqVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "用户编号不能为空")
private Long userId;
@Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3100000002001")
@NotNull(message = "角色编号不能为空")
private Long roleId;
@Schema(description = "备注", example = "来自产品团队维护")
@Size(max = 500, message = "备注长度不能超过500个字符")
private String remark;
@Schema(description = "原产品经理用户编号,仅切换产品经理时传递", example = "2048")
private Long previousManagerUserId;
@Schema(description = "原产品经理交接后的角色编号,仅切换产品经理时传递", example = "3100000002002")
private Long previousManagerRoleId;
}

View File

@@ -0,0 +1,30 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.member;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 产品团队成员更新 Request VO")
@Data
public class ProductMemberUpdateReqVO {
@Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3100000002002")
@NotNull(message = "角色编号不能为空")
private Long roleId;
@Schema(description = "备注", example = "调整为产品观察者")
@Size(max = 500, message = "备注长度不能超过500个字符")
private String remark;
@Schema(description = "变更原因", example = "职责调整")
@Size(max = 500, message = "变更原因长度不能超过500个字符")
private String reason;
@Schema(description = "原产品经理用户编号,仅切换产品经理时传递", example = "2048")
private Long previousManagerUserId;
@Schema(description = "原产品经理交接后的角色编号,仅切换产品经理时传递", example = "3100000002002")
private Long previousManagerRoleId;
}

View File

@@ -0,0 +1,25 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 产品上下文导航 Response VO")
@Data
public class ProductContextNavRespVO {
@Schema(description = "菜单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3201")
private Long id;
@Schema(description = "菜单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "概览")
private String name;
@Schema(description = "菜单路径", example = "/project/product/overview")
private String path;
@Schema(description = "菜单图标", example = "mdi:view-dashboard-outline")
private String icon;
@Schema(description = "显示顺序", example = "10")
private Integer sort;
}

View File

@@ -0,0 +1,28 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 产品上下文中的当前产品摘要 Response VO")
@Data
public class ProductContextProductRespVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNPD2026001")
private String code;
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "direction_value")
private String directionCode;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
private String name;
@Schema(description = "产品经理用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long managerUserId;
@Schema(description = "产品状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "active")
private String statusCode;
}

View File

@@ -0,0 +1,26 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Schema(description = "管理后台 - 产品上下文 Response VO")
@Data
@NoArgsConstructor
public class ProductContextRespVO {
@Schema(description = "当前产品摘要")
private ProductContextProductRespVO currentProduct;
@Schema(description = "当前用户在该产品下的角色信息")
private ProductContextRoleRespVO currentRole;
@Schema(description = "当前产品下可见导航集合")
private List<ProductContextNavRespVO> navs;
@Schema(description = "当前产品下按钮权限码集合")
private List<String> buttons;
}

View File

@@ -0,0 +1,19 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 产品上下文中的当前角色 Response VO")
@Data
public class ProductContextRoleRespVO {
@Schema(description = "对象角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3201")
private Long roleId;
@Schema(description = "对象角色编码", example = "product_manager")
private String roleCode;
@Schema(description = "对象角色名称", example = "产品经理")
private String roleName;
}

View File

@@ -0,0 +1,32 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 产品删除 Request VO")
@Data
public class ProductDeleteReqVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品编号不能为空")
private Long id;
@Schema(description = "确认输入的产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
@NotBlank(message = "确认产品名称不能为空")
@Size(max = 128, message = "确认产品名称长度不能超过128个字符")
private String productName;
@Schema(description = "删除原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品录入错误")
@NotBlank(message = "删除原因不能为空")
@Size(max = 500, message = "删除原因长度不能超过500个字符")
private String reason;
@Schema(description = "删除确认口令,当前固定输入 DELETE", requiredMode = Schema.RequiredMode.REQUIRED, example = "DELETE")
@NotBlank(message = "删除确认口令不能为空")
@Size(max = 32, message = "删除确认口令长度不能超过32个字符")
private String confirmText;
}

View File

@@ -0,0 +1,15 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.Map;
@Schema(description = "管理后台 - 产品入口页概览统计 Response VO")
@Data
public class ProductOverviewSummaryRespVO {
@Schema(description = "产品状态数量,按当前启用的产品状态模型返回")
private Map<String, Long> statusCounts;
}

View File

@@ -0,0 +1,39 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import com.njcn.rdms.framework.common.pojo.PageParam;
import com.njcn.rdms.framework.dict.validation.InDict;
import com.njcn.rdms.module.system.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 产品分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProductPageReqVO extends PageParam {
@Schema(description = "关键词,匹配产品编码或产品名称", example = "CNPD2026001")
private String keyword;
@Schema(description = "产品方向字典值", example = "direction_value")
@InDict(type = DictTypeConstants.OBJECT_DIRECTION)
private String directionCode;
@Schema(description = "产品经理用户编号", example = "1024")
private Long managerUserId;
@Schema(description = "产品状态编码", example = "active")
@Size(max = 32, message = "产品状态编码长度不能超过32个字符")
private String statusCode;
@Schema(description = "更新时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] updateTime;
}

View File

@@ -0,0 +1,42 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 产品 Response VO")
@Data
public class ProductRespVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNPD2026001")
private String code;
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "direction_value")
private String directionCode;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
private String name;
@Schema(description = "产品经理用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long managerUserId;
@Schema(description = "产品描述", example = "面向研发管理的一体化产品")
private String description;
@Schema(description = "产品状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "active")
private String statusCode;
@Schema(description = "最近一次状态动作原因", example = "阶段性暂停")
private String lastStatusReason;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,40 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import com.njcn.rdms.framework.dict.validation.InDict;
import com.njcn.rdms.module.system.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 产品保存 Request VO")
@Data
public class ProductSaveReqVO {
@Schema(description = "产品编号", example = "1024")
private Long id;
@Schema(description = "产品编码,为空时由系统自动生成", example = "CNPD2026001")
@Size(max = 64, message = "产品编码长度不能超过64个字符")
private String code;
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "direction_value")
@NotBlank(message = "产品方向不能为空")
@Size(max = 32, message = "产品方向长度不能超过32个字符")
@InDict(type = DictTypeConstants.OBJECT_DIRECTION)
private String directionCode;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
@NotBlank(message = "产品名称不能为空")
@Size(max = 128, message = "产品名称长度不能超过128个字符")
private String name;
@Schema(description = "产品经理用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品经理不能为空")
private Long managerUserId;
@Schema(description = "产品描述", example = "面向研发管理的一体化产品")
private String description;
}

View File

@@ -0,0 +1,26 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.product;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 产品状态动作 Request VO")
@Data
public class ProductStatusActionReqVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品编号不能为空")
private Long id;
@Schema(description = "动作编码,如 pause、resume、archive、abandon", requiredMode = Schema.RequiredMode.REQUIRED, example = "pause")
@NotBlank(message = "动作编码不能为空")
@Size(max = 32, message = "动作编码长度不能超过32个字符")
private String actionCode;
@Schema(description = "动作原因;是否必填由状态流转配置决定", example = "当前阶段受环境限制暂停推进")
@Size(max = 500, message = "动作原因长度不能超过500个字符")
private String reason;
}

View File

@@ -0,0 +1,29 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 管理后台 - 产品需求关闭 Request VO
*/
@Schema(description = "管理后台 - 产品需求关闭 Request VO")
@Data
public class ProductRequirementCloseReqVO {
@Schema(description = "需求编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "需求编号不能为空")
private Long id;
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品ID不能为空")
private Long productId;
@Schema(description = "关闭原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "需求已完成验收")
@NotBlank(message = "关闭原因不能为空")
@Size(max = 255, message = "关闭原因长度不能超过255个字符")
private String reason;
}

View File

@@ -0,0 +1,19 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 管理后台 - 产品需求删除 Request VO
*/
@Schema(description = "管理后台 - 产品需求删除 Request VO")
@Data
public class ProductRequirementDeleteReqVO {
@Schema(description = "需求ID编辑时传入", example = "1024")
private Long id;
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品ID不能为空")
private Long productId;
}

View File

@@ -0,0 +1,33 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 管理后台 - 产品需求生命周期 Response VO
*/
@Schema(description = "管理后台 - 产品需求生命周期 Response VO")
@Data
public class ProductRequirementLifecycleRespVO {
@Schema(description = "当前状态编码", example = "pending_dispatch")
private String statusCode;
@Schema(description = "当前状态名称", example = "待分流")
private String statusName;
@Schema(description = "最近一次状态动作原因", example = "评审通过")
private String lastStatusReason;
@Schema(description = "是否终态", example = "false")
private Boolean terminal;
@Schema(description = "是否允许编辑", example = "true")
private Boolean allowEdit;
@Schema(description = "当前状态可执行动作列表")
private List<ProductRequirementStatusTransitionRespVO> availableActions;
}

View File

@@ -0,0 +1,19 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 管理后台 - 产品需求模块删除 Request VO
*/
@Schema(description = "管理后台 - 产品需求模块删除 Request VO")
@Data
public class ProductRequirementModuleDeleteReqVO {
@Schema(description = "模块ID编辑时传入", example = "1024")
private Long id;
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品ID不能为空")
private Long productId;
}

View File

@@ -0,0 +1,40 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 管理后台 - 产品需求模块保存 Request VO
*/
@Schema(description = "管理后台 - 产品需求模块保存 Request VO")
@Data
public class ProductRequirementModuleReqVO {
@Schema(description = "模块ID编辑时传入", example = "1024")
private Long id;
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品ID不能为空")
private Long productId;
@Schema(description = "父模块ID0表示顶级", example = "0")
private Long parentId;
@Schema(description = "模块名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "核心功能")
@NotBlank(message = "模块名称不能为空")
@Size(max = 100, message = "模块名称长度不能超过100个字符")
private String moduleName;
@Schema(description = "模块说明", example = "产品核心功能模块")
private String remark;
@Schema(description = "图标", example = "icon-function")
private String icon;
@Schema(description = "排序值", example = "0")
private Integer sort;
}

View File

@@ -0,0 +1,39 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 管理后台 - 产品需求模块 Response VO
*/
@Schema(description = "管理后台 - 产品需求模块 Response VO")
@Data
public class ProductRequirementModuleRespVO {
@Schema(description = "模块ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "父模块ID0表示顶级", example = "0")
private Long parentId;
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long productId;
@Schema(description = "模块名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "核心功能")
private String moduleName;
@Schema(description = "模块说明", example = "产品核心功能模块")
private String remark;
@Schema(description = "图标", example = "icon-function")
private String icon;
@Schema(description = "排序值", example = "0")
private Integer sort;
@Schema(description = "子模块列表")
private List<ProductRequirementModuleRespVO> children;
}

View File

@@ -0,0 +1,49 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import com.baomidou.mybatisplus.annotation.TableField;
import com.njcn.rdms.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
/**
* 管理后台 - 产品需求分页 Request VO
*/
@Schema(description = "管理后台 - 产品需求分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProductRequirementPageReqVO extends PageParam {
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long productId;
@Schema(description = "所属模块ID", example = "1024")
private Long moduleId;
@Schema(description = "所属模块ID列表包含子模块用于IN查询", example = "[1024, 1025]")
private List<Long> moduleIds;
@Schema(description = "父需求ID查询子需求时使用", example = "1024")
private Long parentId;
@Schema(description = "标题关键词", example = "模块")
private String title;
@Schema(description = "需求分类字典值", example = "function")
private String category;
@Schema(description = "优先级0低 1中 2高 3紧急", example = "1")
private Integer priority;
@Schema(description = "状态编码", example = "pending_dispatch")
private String statusCode;
@Schema(description = "当前处理人用户编号", example = "1024")
private Long currentHandlerUserId;
@Schema(description = "来源类型manual:手工新增, work_order:工单流转)", example = "manual")
private String sourceType;
}

View File

@@ -0,0 +1,97 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 管理后台 - 产品需求 Response VO
*/
@Schema(description = "管理后台 - 产品需求 Response VO")
@Data
public class ProductRequirementRespVO {
@Schema(description = "需求ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "父需求ID0表示顶级需求", example = "0")
private Long parentId;
@Schema(description = "所属模块ID", example = "1024")
private Long moduleId;
@Schema(description = "是否需要评审0不需要1需要", example = "0")
private Integer reviewRequired;
@Schema(description = "需求标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "支持需求模块化管理")
private String title;
@Schema(description = "需求描述(富文本)", example = "<p>详细描述需求内容</p>")
private String description;
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
private String category;
@Schema(description = "需求分类名称", example = "功能需求")
private String categoryName;
@Schema(description = "来源类型manual:手工新增, work_order:工单流转)", requiredMode = Schema.RequiredMode.REQUIRED, example = "manual")
private String sourceType;
@Schema(description = "来源业务ID", example = "1024")
private Long sourceBizId;
@Schema(description = "优先级0低 1中 2高 3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer priority;
@Schema(description = "优先级名称", example = "")
private String priorityName;
@Schema(description = "当前状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "pending_dispatch")
private String statusCode;
@Schema(description = "当前状态名称", example = "待分流")
private String statusName;
@Schema(description = "最近一次状态动作原因", example = "评审通过")
private String lastStatusReason;
@Schema(description = "提出人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long proposerId;
@Schema(description = "提出人用户姓名", example = "张三")
private String proposerNickname;
@Schema(description = "当前处理人用户编号", example = "1024")
private Long currentHandlerUserId;
@Schema(description = "当前处理人姓名", example = "李四")
private String currentHandlerUserNickname;
@Schema(description = "默认实现项目编号", example = "1024")
private Long implementProjectId;
@Schema(description = "实现项目名称", example = "NPQS-10086")
private String implementProjectName;
@Schema(description = "预期完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime completionDate;
@Schema(description = "排序值", example = "0")
private Integer sort;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
@Schema(description = "子需求列表(树形结构)")
private List<ProductRequirementRespVO> children;
@Schema(description = "是否为终态(已拒绝、已取消、已关闭)", example = "false")
private Boolean terminal;
}

View File

@@ -0,0 +1,66 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 管理后台 - 产品需求保存 Request VO
*/
@Schema(description = "管理后台 - 产品需求保存 Request VO")
@Data
public class ProductRequirementSaveReqVO {
@Schema(description = "需求ID", example = "1024")
private Long id;
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品编号不能为空")
private Long productId;
@Schema(description = "所属模块ID为空时归入全部需求", example = "1024")
private Long moduleId;
@Schema(description = "是否需要评审0不需要1需要", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "是否需要评审不能为空")
private Integer reviewRequired;
@Schema(description = "需求标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "支持需求模块化管理")
@NotBlank(message = "需求标题不能为空")
@Size(max = 200, message = "需求标题长度不能超过200个字符")
private String title;
@Schema(description = "需求描述(富文本)", example = "<p>详细描述需求内容</p>")
private String description;
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
@NotBlank(message = "需求分类不能为空")
@Size(max = 64, message = "需求分类长度不能超过64个字符")
private String category;
@Schema(description = "优先级0低 1中 2高 3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "优先级不能为空")
private Integer priority;
@Schema(description = "提出人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "提出人不能为空")
private Long proposerId;
@Schema(description = "当前处理人用户编号", example = "1024")
private Long currentHandlerUserId;
@Schema(description = "默认实现项目编号", example = "1024")
private Long implementProjectId;
@Schema(description = "预期完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "预期完成时间不能为空")
private LocalDateTime completionDate;
@Schema(description = "排序值(越小越靠前)", example = "0")
private Integer sort;
}

View File

@@ -0,0 +1,67 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 管理后台 - 产品需求拆分 Request VO
*/
@Schema(description = "管理后台 - 产品需求拆分 Request VO")
@Data
public class ProductRequirementSplitReqVO {
@Schema(description = "父需求ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "父需求编号不能为空")
private Long parentId;
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品编号不能为空")
private Long productId;
@Schema(description = "所属模块ID为空时归入全部需求", example = "1024")
private Long moduleId;
@Schema(description = "是否需要评审0不需要1需要", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "是否需要评审不能为空")
private Integer reviewRequired;
@Schema(description = "需求标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "支持需求模块化管理")
@NotBlank(message = "需求标题不能为空")
@Size(max = 200, message = "需求标题长度不能超过200个字符")
private String title;
@Schema(description = "需求描述(富文本)", example = "<p>详细描述需求内容</p>")
private String description;
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
@NotBlank(message = "需求分类不能为空")
@Size(max = 64, message = "需求分类长度不能超过64个字符")
private String category;
@Schema(description = "优先级0低 1中 2高 3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "优先级不能为空")
private Integer priority;
@Schema(description = "提出人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "提出人不能为空")
private Long proposerId;
@Schema(description = "当前处理人用户编号", example = "1024")
private Long currentHandlerUserId;
@Schema(description = "默认实现项目编号", example = "1024")
private Long implementProjectId;
@Schema(description = "预期完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "预期完成时间不能为空")
private LocalDateTime completionDate;
@Schema(description = "排序值(越小越靠前)", example = "0")
private Integer sort;
}

View File

@@ -0,0 +1,35 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 管理后台 - 产品需求状态变更 Request VO
* 注意:需求不直接关联产品,通过模块间接关联
*/
@Schema(description = "管理后台 - 产品需求状态变更 Request VO")
@Data
public class ProductRequirementStatusActionReqVO {
@Schema(description = "需求ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "需求ID不能为空")
private Long id;
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品编号不能为空")
private Long productId;
@Schema(description = "动作编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "dispatch")
@NotBlank(message = "动作编码不能为空")
@Size(max = 32, message = "动作编码长度不能超过32个字符")
private String actionCode;
@Schema(description = "状态变更原因", example = "评审通过,进入分流阶段")
private String reason;
@Schema(description = "实现项目编号dispatch动作时可选", example = "1024")
private Long implementProjectId;
}

View File

@@ -0,0 +1,28 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 管理后台 - 产品需求状态字典 Response VO
*/
@Schema(description = "管理后台 - 产品需求状态字典 Response VO")
@Data
public class ProductRequirementStatusDictRespVO {
@Schema(description = "状态编码", example = "pending_confirm")
private String statusCode;
@Schema(description = "状态名称", example = "待确认")
private String statusName;
@Schema(description = "排序值", example = "1")
private Integer sort;
@Schema(description = "是否初始状态", example = "true")
private Boolean initialFlag;
@Schema(description = "是否终态", example = "false")
private Boolean terminalFlag;
}

View File

@@ -0,0 +1,30 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 管理后台 - 产品需求状态可执行动作 Response VO
*/
@Schema(description = "管理后台 - 产品需求状态可执行动作 Response VO")
@Data
@NoArgsConstructor
public class ProductRequirementStatusTransitionRespVO {
@Schema(description = "动作编码", example = "dispatch")
private String actionCode;
@Schema(description = "动作名称", example = "明确分流/拆分")
private String actionName;
@Schema(description = "目标状态编码", example = "implementing")
private String toStatusCode;
@Schema(description = "目标状态名称", example = "实施中")
private String toStatusName;
@Schema(description = "是否必须填写原因", example = "false")
private Boolean needReason;
}

View File

@@ -0,0 +1,67 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 管理后台 - 产品需求编辑 Request VO
*/
@Schema(description = "管理后台 - 产品需求编辑 Request VO")
@Data
public class ProductRequirementUpdateReqVO {
@Schema(description = "需求ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "需求ID不能为空")
private Long id;
@Schema(description = "所属产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品ID不能为空")
private Long productId;
@Schema(description = "所属模块ID为空时归入全部需求", example = "1024")
private Long moduleId;
@Schema(description = "是否需要评审0不需要1需要", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "是否需要评审不能为空")
private Integer reviewRequired;
@Schema(description = "需求标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "支持需求模块化管理")
@NotBlank(message = "需求标题不能为空")
@Size(max = 200, message = "需求标题长度不能超过200个字符")
private String title;
@Schema(description = "需求描述(富文本)", example = "<p>详细描述需求内容</p>")
private String description;
@Schema(description = "需求分类字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "function")
@NotBlank(message = "需求分类不能为空")
@Size(max = 64, message = "需求分类长度不能超过64个字符")
private String category;
@Schema(description = "优先级0低 1中 2高 3紧急", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "优先级不能为空")
private Integer priority;
@Schema(description = "提出人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "提出人不能为空")
private Long proposerId;
@Schema(description = "当前处理人用户编号", example = "1024")
private Long currentHandlerUserId;
@Schema(description = "默认实现项目编号", example = "1024")
private Long implementProjectId;
@Schema(description = "预期完成时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "预期完成时间不能为空")
private LocalDateTime completionDate;
@Schema(description = "排序值(越小越靠前)", example = "0")
private Integer sort;
}

View File

@@ -0,0 +1,21 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.setting;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 产品设置生命周期动作 Response VO")
@Data
@NoArgsConstructor
public class ProductSettingActionRespVO {
@Schema(description = "动作编码", example = "archive")
private String actionCode;
@Schema(description = "动作名称", example = "归档")
private String actionName;
@Schema(description = "是否必须填写原因", example = "true")
private Boolean needReason;
}

View File

@@ -0,0 +1,39 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.setting;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 产品设置基础信息 Response VO")
@Data
@NoArgsConstructor
public class ProductSettingBaseInfoRespVO {
@Schema(description = "产品编号", example = "1024")
private Long id;
@Schema(description = "产品编码", example = "CNPD2026001")
private String code;
@Schema(description = "产品方向字典值", example = "direction_value")
private String directionCode;
@Schema(description = "产品名称", example = "统一交付平台")
private String name;
@Schema(description = "产品经理用户编号", example = "10001")
private Long managerUserId;
@Schema(description = "产品经理昵称", example = "张三")
private String managerUserNickname;
@Schema(description = "产品描述", example = "产品描述")
private String description;
@Schema(description = "产品状态编码", example = "active")
private String statusCode;
@Schema(description = "最近一次状态动作原因", example = "恢复正常推进")
private String lastStatusReason;
}

View File

@@ -0,0 +1,28 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.setting;
import com.njcn.rdms.framework.dict.validation.InDict;
import com.njcn.rdms.module.system.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 产品设置基础信息更新 Request VO")
@Data
public class ProductSettingBaseInfoUpdateReqVO {
@Schema(description = "产品方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "direction_value")
@NotBlank(message = "产品方向不能为空")
@Size(max = 32, message = "产品方向长度不能超过32个字符")
@InDict(type = DictTypeConstants.OBJECT_DIRECTION)
private String directionCode;
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "RDMS产品平台")
@NotBlank(message = "产品名称不能为空")
@Size(max = 128, message = "产品名称长度不能超过128个字符")
private String name;
@Schema(description = "产品描述", example = "面向研发管理的一体化产品")
private String description;
}

View File

@@ -0,0 +1,32 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.setting;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Schema(description = "管理后台 - 产品设置生命周期 Response VO")
@Data
@NoArgsConstructor
public class ProductSettingLifecycleRespVO {
@Schema(description = "当前状态编码", example = "active")
private String statusCode;
@Schema(description = "当前状态名称", example = "启用")
private String statusName;
@Schema(description = "最近一次状态动作原因", example = "恢复正常推进")
private String lastStatusReason;
@Schema(description = "是否终态", example = "false")
private Boolean terminal;
@Schema(description = "是否允许编辑", example = "true")
private Boolean allowEdit;
@Schema(description = "当前状态可执行动作")
private List<ProductSettingActionRespVO> availableActions;
}

View File

@@ -0,0 +1,18 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.setting;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 产品设置 Response VO")
@Data
@NoArgsConstructor
public class ProductSettingRespVO {
@Schema(description = "产品基础信息")
private ProductSettingBaseInfoRespVO baseInfo;
@Schema(description = "产品生命周期信息")
private ProductSettingLifecycleRespVO lifecycle;
}

View File

@@ -0,0 +1,90 @@
package com.njcn.rdms.module.project.controller.admin.project;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectContextRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectDeleteReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectOverviewSummaryRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectPageReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectStatusActionReqVO;
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO;
import com.njcn.rdms.module.project.service.project.ProjectService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 项目管理")
@RestController
@RequestMapping("/project/project")
@Validated
public class ProjectController {
@Resource
private ProjectService projectService;
@PostMapping("/create")
@Operation(summary = "创建项目")
@PreAuthorize("@ss.hasPermission('project:project:create')")
public CommonResult<Long> createProject(@Valid @RequestBody ProjectSaveReqVO createReqVO) {
return success(projectService.createProject(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新项目")
public CommonResult<Boolean> updateProject(@Valid @RequestBody ProjectSaveReqVO updateReqVO) {
projectService.updateProject(updateReqVO);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获取项目详情")
@Parameter(name = "id", description = "项目编号", required = true, example = "1024")
public CommonResult<ProjectRespVO> getProject(@RequestParam("id") Long id) {
return success(projectService.getProjectDetail(id));
}
@GetMapping("/{id}/context")
@Operation(summary = "获取项目上下文")
@Parameter(name = "id", description = "项目编号", required = true, example = "1024")
public CommonResult<ProjectContextRespVO> getProjectContext(@PathVariable("id") Long id) {
return success(projectService.getProjectContext(id));
}
@GetMapping("/page")
@Operation(summary = "获取项目分页")
public CommonResult<PageResult<ProjectRespVO>> getProjectPage(@Valid ProjectPageReqVO pageReqVO) {
PageResult<ProjectDO> pageResult = projectService.getProjectPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ProjectRespVO.class));
}
@GetMapping("/overview-summary")
@Operation(summary = "获取项目入口页概览统计")
public CommonResult<ProjectOverviewSummaryRespVO> getProjectOverviewSummary() {
return success(projectService.getProjectOverviewSummary());
}
@PostMapping("/change-status")
@Operation(summary = "变更项目状态")
public CommonResult<Boolean> changeProjectStatus(@Valid @RequestBody ProjectStatusActionReqVO reqVO) {
projectService.changeProjectStatus(reqVO);
return success(true);
}
@PostMapping("/delete")
@Operation(summary = "删除项目")
public CommonResult<Boolean> deleteProject(@Valid @RequestBody ProjectDeleteReqVO reqVO) {
projectService.deleteProject(reqVO);
return success(true);
}
}

View File

@@ -0,0 +1,63 @@
package com.njcn.rdms.module.project.controller.admin.project;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberInactiveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.member.ProjectMemberUpdateReqVO;
import com.njcn.rdms.module.project.service.project.ProjectMemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 项目成员")
@RestController
@RequestMapping("/project/project")
@Validated
public class ProjectMemberController {
@Resource
private ProjectMemberService projectMemberService;
@GetMapping("/{id}/members")
@Operation(summary = "获取项目成员列表")
@Parameter(name = "id", description = "项目编号", required = true, example = "1024")
public CommonResult<List<ProjectMemberRespVO>> getProjectMemberList(@PathVariable("id") Long projectId) {
return success(projectMemberService.getProjectMemberList(projectId));
}
@PostMapping("/{id}/members")
@Operation(summary = "新增项目成员")
@Parameter(name = "id", description = "项目编号", required = true, example = "1024")
public CommonResult<Long> createProjectMember(@PathVariable("id") Long projectId,
@Valid @RequestBody ProjectMemberSaveReqVO reqVO) {
return success(projectMemberService.createProjectMember(projectId, reqVO));
}
@PutMapping("/{id}/members/{memberId}")
@Operation(summary = "调整项目成员角色")
public CommonResult<Boolean> updateProjectMember(@PathVariable("id") Long projectId,
@PathVariable("memberId") Long memberId,
@Valid @RequestBody ProjectMemberUpdateReqVO reqVO) {
projectMemberService.updateProjectMember(projectId, memberId, reqVO);
return success(true);
}
@PostMapping("/{id}/members/{memberId}/inactive")
@Operation(summary = "移出项目成员")
public CommonResult<Boolean> inactiveProjectMember(@PathVariable("id") Long projectId,
@PathVariable("memberId") Long memberId,
@Valid @RequestBody ProjectMemberInactiveReqVO reqVO) {
projectMemberService.inactiveProjectMember(projectId, memberId, reqVO);
return success(true);
}
}

View File

@@ -0,0 +1,90 @@
package com.njcn.rdms.module.project.controller.admin.project.execution;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution.ProjectExecutionOwnerChangeReqVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution.ProjectExecutionPageReqVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution.ProjectExecutionStatusBoardReqVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution.ProjectExecutionStatusBoardRespVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution.ProjectExecutionRespVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution.ProjectExecutionSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution.ProjectExecutionStatusActionReqVO;
import com.njcn.rdms.module.project.service.project.ProjectStatusBoardService;
import com.njcn.rdms.module.project.service.project.execution.ProjectExecutionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 项目执行")
@RestController
@RequestMapping("/project/project/{projectId}/executions")
@Validated
public class ProjectExecutionController {
@Resource
private ProjectExecutionService projectExecutionService;
@Resource
private ProjectStatusBoardService projectStatusBoardService;
@PostMapping
@Operation(summary = "创建执行")
public CommonResult<Long> createExecution(@PathVariable("projectId") Long projectId,
@Valid @RequestBody ProjectExecutionSaveReqVO reqVO) {
return success(projectExecutionService.createExecution(projectId, reqVO));
}
@PutMapping("/{executionId}")
@Operation(summary = "编辑执行")
public CommonResult<Boolean> updateExecution(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@Valid @RequestBody ProjectExecutionSaveReqVO reqVO) {
reqVO.setId(executionId);
projectExecutionService.updateExecution(projectId, reqVO);
return success(true);
}
@GetMapping("/{executionId}")
@Operation(summary = "获取执行详情")
public CommonResult<ProjectExecutionRespVO> getExecution(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId) {
return success(projectExecutionService.getExecutionRespVO(projectId, executionId));
}
@GetMapping("/page")
@Operation(summary = "获取执行分页")
public CommonResult<PageResult<ProjectExecutionRespVO>> getExecutionPage(@PathVariable("projectId") Long projectId,
@Valid ProjectExecutionPageReqVO reqVO) {
return success(projectExecutionService.getExecutionRespVOPage(projectId, reqVO));
}
@GetMapping("/status-board")
@Operation(summary = "获取执行状态看板")
public CommonResult<ProjectExecutionStatusBoardRespVO> getExecutionStatusBoard(@PathVariable("projectId") Long projectId,
@Valid ProjectExecutionStatusBoardReqVO reqVO) {
return success(projectStatusBoardService.getExecutionStatusBoard(projectId, reqVO));
}
@PostMapping("/{executionId}/change-owner")
@Operation(summary = "变更执行负责人")
public CommonResult<Boolean> changeOwner(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@Valid @RequestBody ProjectExecutionOwnerChangeReqVO reqVO) {
projectExecutionService.changeOwner(projectId, executionId, reqVO);
return success(true);
}
@PostMapping("/{executionId}/change-status")
@Operation(summary = "变更执行状态")
public CommonResult<Boolean> changeStatus(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@Valid @RequestBody ProjectExecutionStatusActionReqVO reqVO) {
projectExecutionService.changeExecutionStatus(projectId, executionId, reqVO);
return success(true);
}
}

View File

@@ -0,0 +1,70 @@
package com.njcn.rdms.module.project.controller.admin.project.execution;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.member.ExecutionMemberInactiveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.member.ExecutionMemberLogPageReqVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.member.ExecutionMemberLogRespVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.member.ExecutionMemberRespVO;
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.member.ExecutionMemberSaveReqVO;
import com.njcn.rdms.module.project.service.project.execution.ProjectExecutionMemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 执行成员")
@RestController
@RequestMapping("/project/project/{projectId}/executions/{executionId}")
@Validated
public class ProjectExecutionMemberController {
@Resource
private ProjectExecutionMemberService projectExecutionMemberService;
@GetMapping("/members")
@Operation(summary = "获取执行成员列表(仅当前活跃)")
public CommonResult<List<ExecutionMemberRespVO>> getExecutionMemberList(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId) {
return success(projectExecutionMemberService.getExecutionMemberList(projectId, executionId));
}
@PostMapping("/members")
@Operation(summary = "新增执行成员B 模型 - 每次新增一段)")
public CommonResult<Long> createExecutionMember(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@Valid @RequestBody ExecutionMemberSaveReqVO reqVO) {
return success(projectExecutionMemberService.createExecutionMember(projectId, executionId, reqVO));
}
@PostMapping("/members/{memberId}/inactive")
@Operation(summary = "失效执行成员(永久保留 removedReason")
public CommonResult<Boolean> inactiveExecutionMember(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("memberId") Long memberId,
@Valid @RequestBody ExecutionMemberInactiveReqVO reqVO) {
projectExecutionMemberService.inactiveExecutionMember(projectId, executionId, memberId, reqVO);
return success(true);
}
@GetMapping("/member-logs")
@Operation(summary = "获取执行成员变更历史(分页)")
public CommonResult<PageResult<ExecutionMemberLogRespVO>> getExecutionMemberLogPage(
@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@Valid ExecutionMemberLogPageReqVO reqVO) {
return success(projectExecutionMemberService.getExecutionMemberLogPage(projectId, executionId, reqVO));
}
}

View File

@@ -0,0 +1,21 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 执行生命周期动作 Response VO")
@Data
@NoArgsConstructor
public class ProjectExecutionLifecycleActionRespVO {
@Schema(description = "动作编码", example = "pause")
private String actionCode;
@Schema(description = "动作名称", example = "暂停")
private String actionName;
@Schema(description = "是否必须填写原因", example = "true")
private Boolean needReason;
}

View File

@@ -0,0 +1,20 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 执行负责人变更 Request VO")
@Data
public class ProjectExecutionOwnerChangeReqVO {
@Schema(description = "新负责人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3002")
@NotNull(message = "新负责人不能为空")
private Long newOwnerId;
@Schema(description = "变更原因", example = "负责人调整")
@Size(max = 500, message = "变更原因长度不能超过500个字符")
private String reason;
}

View File

@@ -0,0 +1,37 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution;
import com.njcn.rdms.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 执行分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProjectExecutionPageReqVO extends PageParam {
@Schema(description = "关键词,匹配执行名称", example = "联调")
private String keyword;
@Schema(description = "执行类型", example = "feature")
@Size(max = 32, message = "执行类型长度不能超过32个字符")
private String executionType;
@Schema(description = "执行负责人用户编号", example = "3001")
private Long ownerId;
@Schema(description = "执行状态编码", example = "pending")
@Size(max = 32, message = "执行状态编码长度不能超过32个字符")
private String statusCode;
@Schema(description = "更新时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] updateTime;
}

View File

@@ -0,0 +1,58 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 执行 Response VO")
@Data
public class ProjectExecutionRespVO {
@Schema(description = "执行编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5001")
private Long id;
@Schema(description = "所属项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2001")
private Long projectId;
@Schema(description = "关联项目需求编号")
private Long projectRequirementId;
@Schema(description = "执行名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "后端接口联调")
private String executionName;
@Schema(description = "执行类型", example = "feature")
private String executionType;
@Schema(description = "执行负责人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3001")
private Long ownerId;
@Schema(description = "执行负责人昵称", example = "张三")
private String ownerNickname;
@Schema(description = "执行状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "pending")
private String statusCode;
@Schema(description = "执行状态名称", example = "待开始")
private String statusName;
@Schema(description = "是否终态", example = "false")
private Boolean terminal;
@Schema(description = "当前状态是否允许编辑", example = "true")
private Boolean allowEdit;
@Schema(description = "当前状态可执行动作")
private List<ProjectExecutionLifecycleActionRespVO> availableActions;
@Schema(description = "计划开始日期")
private LocalDate plannedStartDate;
@Schema(description = "计划结束日期")
private LocalDate plannedEndDate;
@Schema(description = "实际开始日期")
private LocalDate actualStartDate;
@Schema(description = "实际结束日期")
private LocalDate actualEndDate;
@Schema(description = "执行进度缓存值")
private BigDecimal progressRate;
@Schema(description = "执行说明")
private String executionDesc;
@Schema(description = "最近一次状态动作原因")
private String lastStatusReason;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,50 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
@Schema(description = "管理后台 - 执行保存 Request VO")
@Data
public class ProjectExecutionSaveReqVO {
@Schema(description = "执行编号", example = "5001")
private Long id;
@Schema(description = "执行名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "后端接口联调")
@NotBlank(message = "执行名称不能为空")
@Size(max = 200, message = "执行名称长度不能超过200个字符")
private String executionName;
@Schema(description = "执行类型,取值来自字典 rdms_project_execution_type", requiredMode = Schema.RequiredMode.REQUIRED, example = "feature")
@NotBlank(message = "执行类型不能为空")
@Size(max = 32, message = "执行类型长度不能超过32个字符")
private String executionType;
@Schema(description = "执行负责人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3001")
@NotNull(message = "执行负责人不能为空")
private Long ownerId;
@Schema(description = "关联项目需求编号,第一阶段只接受空值", example = "9001")
private Long projectRequirementId;
@Schema(description = "计划开始日期")
private LocalDate plannedStartDate;
@Schema(description = "计划结束日期")
private LocalDate plannedEndDate;
@Schema(description = "执行说明(接受 HTML 富文本,图片走 URL 引用;后端经全局 XSS Safelist 自动净化)",
example = "接口联调与问题跟踪")
@Size(max = 200000, message = "执行说明长度不能超过200000个字符")
private String executionDesc;
@Schema(description = "创建执行时同步写入的成员用户编号列表;编辑执行主数据时不维护成员", example = "[3002,3003]")
private List<Long> memberUserIds;
}

View File

@@ -0,0 +1,21 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 执行状态动作 Request VO")
@Data
public class ProjectExecutionStatusActionReqVO {
@Schema(description = "动作编码,如 start、pause、resume、cancel", requiredMode = Schema.RequiredMode.REQUIRED, example = "pause")
@NotBlank(message = "动作编码不能为空")
@Size(max = 32, message = "动作编码长度不能超过32个字符")
private String actionCode;
@Schema(description = "动作原因;是否必填由状态流转配置决定", example = "依赖环境暂不可用")
@Size(max = 500, message = "动作原因长度不能超过500个字符")
private String reason;
}

View File

@@ -0,0 +1,32 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 执行状态看板 Request VO")
@Data
@EqualsAndHashCode(callSuper = false)
public class ProjectExecutionStatusBoardReqVO {
@Schema(description = "关键字,匹配执行名称", example = "联调")
private String keyword;
@Schema(description = "执行类型", example = "feature")
@Size(max = 32, message = "执行类型长度不能超过32个字符")
private String executionType;
@Schema(description = "执行负责人用户编号", example = "3001")
private Long ownerId;
@Schema(description = "更新时间", example = "[2026-05-01 00:00:00, 2026-05-31 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] updateTime;
}

View File

@@ -0,0 +1,35 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.execution;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 执行状态看板 Response VO")
@Data
public class ProjectExecutionStatusBoardRespVO {
@Schema(description = "当前筛选条件下的执行总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "18")
private Long total;
@Schema(description = "状态项列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ProjectStatusBoardItemVO> items;
@Schema(description = "项目状态项")
@Data
public static class ProjectStatusBoardItemVO {
@Schema(description = "状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "pending")
private String statusCode;
@Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "待开始")
private String statusName;
@Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Long count;
@Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer sort;
@Schema(description = "是否终态", example = "false")
private Boolean terminal;
}
}

View File

@@ -0,0 +1,17 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.member;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 执行成员失效 Request VO")
@Data
public class ExecutionMemberInactiveReqVO {
@Schema(description = "失效原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "阶段性退出")
@NotBlank(message = "失效原因不能为空")
@Size(max = 500, message = "失效原因长度不能超过500个字符")
private String reason;
}

View File

@@ -0,0 +1,29 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.member;
import com.njcn.rdms.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 执行成员变更历史分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ExecutionMemberLogPageReqVO extends PageParam {
@Schema(description = "事件类型多选;不传表示全部",
example = "[\"join\",\"inactive\",\"owner_transfer_in\",\"owner_transfer_out\"]")
private List<String> actionTypes;
@Schema(description = "成员用户编号;不传表示全部")
private Long userId;
@Schema(description = "起始时间(含),按 actionTime 比较")
private LocalDateTime startTime;
@Schema(description = "截止时间(含),按 actionTime 比较")
private LocalDateTime endTime;
}

View File

@@ -0,0 +1,39 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.member;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 执行成员变更历史 Response VO")
@Data
public class ExecutionMemberLogRespVO {
@Schema(description = "日志编号", example = "12001")
private Long id;
@Schema(description = "所属执行编号", example = "5001")
private Long executionId;
@Schema(description = "事件类型join / inactive / owner_transfer_in / owner_transfer_out", example = "join")
private String actionType;
@Schema(description = "被操作人用户编号", example = "3002")
private Long userId;
@Schema(description = "被操作人昵称,返回时按当前用户信息回填")
private String userNicknameSnapshot;
@Schema(description = "操作人用户编号", example = "3001")
private Long operatorUserId;
@Schema(description = "操作人昵称,返回时按当前用户信息回填")
private String operatorNicknameSnapshot;
@Schema(description = "事件时间")
private LocalDateTime actionTime;
@Schema(description = "原因inactive 必填,其余可空")
private String reason;
}

View File

@@ -0,0 +1,27 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.member;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 执行成员 Response VO")
@Data
public class ExecutionMemberRespVO {
@Schema(description = "成员关系编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7001")
private Long id;
@Schema(description = "所属执行编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5001")
private Long executionId;
@Schema(description = "成员用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3002")
private Long userId;
@Schema(description = "成员用户昵称")
private String userNickname;
@Schema(description = "加入时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime joinedAt;
@Schema(description = "失效时间")
private LocalDateTime removedAt;
@Schema(description = "失效原因")
private String removedReason;
}

View File

@@ -0,0 +1,15 @@
package com.njcn.rdms.module.project.controller.admin.project.execution.vo.member;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - 执行成员新增 Request VO")
@Data
public class ExecutionMemberSaveReqVO {
@Schema(description = "成员用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3002")
@NotNull(message = "成员用户不能为空")
private Long userId;
}

View File

@@ -0,0 +1,86 @@
package com.njcn.rdms.module.project.controller.admin.project.task;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.ProjectTaskPageReqVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.ProjectTaskStatusBoardReqVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.ProjectTaskStatusBoardRespVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.ProjectTaskRespVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.ProjectTaskSaveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.ProjectTaskStatusActionReqVO;
import com.njcn.rdms.module.project.service.project.ProjectStatusBoardService;
import com.njcn.rdms.module.project.service.project.task.ProjectTaskService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 项目任务")
@RestController
@RequestMapping("/project/project/{projectId}/executions/{executionId}/tasks")
@Validated
public class ProjectTaskController {
@Resource
private ProjectTaskService projectTaskService;
@Resource
private ProjectStatusBoardService projectStatusBoardService;
@PostMapping
@Operation(summary = "创建任务")
public CommonResult<Long> createTask(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@Valid @RequestBody ProjectTaskSaveReqVO reqVO) {
return success(projectTaskService.createTask(projectId, executionId, reqVO));
}
@PutMapping("/{taskId}")
@Operation(summary = "编辑任务")
public CommonResult<Boolean> updateTask(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId,
@Valid @RequestBody ProjectTaskSaveReqVO reqVO) {
reqVO.setId(taskId);
projectTaskService.updateTask(projectId, executionId, reqVO);
return success(true);
}
@GetMapping("/{taskId}")
@Operation(summary = "获取任务详情")
public CommonResult<ProjectTaskRespVO> getTask(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId) {
return success(projectTaskService.getTaskRespVO(projectId, executionId, taskId));
}
@GetMapping("/page")
@Operation(summary = "获取任务分页")
public CommonResult<PageResult<ProjectTaskRespVO>> getTaskPage(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@Valid ProjectTaskPageReqVO reqVO) {
return success(projectTaskService.getTaskRespVOPage(projectId, executionId, reqVO));
}
@GetMapping("/status-board")
@Operation(summary = "获取任务状态看板")
public CommonResult<ProjectTaskStatusBoardRespVO> getTaskStatusBoard(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@Valid ProjectTaskStatusBoardReqVO reqVO) {
return success(projectStatusBoardService.getTaskStatusBoard(projectId, executionId, reqVO));
}
@PostMapping("/{taskId}/change-status")
@Operation(summary = "变更任务状态")
public CommonResult<Boolean> changeStatus(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId,
@Valid @RequestBody ProjectTaskStatusActionReqVO reqVO) {
projectTaskService.changeTaskStatus(projectId, executionId, taskId, reqVO);
return success(true);
}
}

View File

@@ -0,0 +1,76 @@
package com.njcn.rdms.module.project.controller.admin.project.task;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeInactiveReqVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeLogPageReqVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeLogRespVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeRespVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeSaveReqVO;
import com.njcn.rdms.module.project.service.project.task.assignee.TaskAssigneeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 任务协办人")
@RestController
@RequestMapping("/project/project/{projectId}/executions/{executionId}/tasks/{taskId}")
@Validated
public class TaskAssigneeController {
@Resource
private TaskAssigneeService taskAssigneeService;
@GetMapping("/assignees")
@Operation(summary = "获取任务协办人列表(仅当前活跃)")
public CommonResult<List<TaskAssigneeRespVO>> getAssigneeList(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId) {
return success(taskAssigneeService.getAssigneeList(projectId, executionId, taskId));
}
@PostMapping("/assignees")
@Operation(summary = "加入任务协办人")
@PreAuthorize("@ss.hasPermission('project:task:assignee')")
public CommonResult<Long> createAssignee(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId,
@Valid @RequestBody TaskAssigneeSaveReqVO reqVO) {
return success(taskAssigneeService.createAssignee(projectId, executionId, taskId, reqVO));
}
@PostMapping("/assignees/{assigneeId}/inactive")
@Operation(summary = "退出任务协办人")
@PreAuthorize("@ss.hasPermission('project:task:assignee')")
public CommonResult<Boolean> inactiveAssignee(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId,
@PathVariable("assigneeId") Long assigneeId,
@Valid @RequestBody TaskAssigneeInactiveReqVO reqVO) {
taskAssigneeService.inactiveAssignee(projectId, executionId, taskId, assigneeId, reqVO);
return success(true);
}
@GetMapping("/assignee-logs")
@Operation(summary = "获取任务协办人变更历史(分页)")
public CommonResult<PageResult<TaskAssigneeLogRespVO>> getAssigneeLogPage(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId,
@Valid TaskAssigneeLogPageReqVO reqVO) {
return success(taskAssigneeService.getAssigneeLogPage(projectId, executionId, taskId, reqVO));
}
}

View File

@@ -0,0 +1,77 @@
package com.njcn.rdms.module.project.controller.admin.project.task;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.worklog.TaskWorklogPageReqVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.worklog.TaskWorklogRespVO;
import com.njcn.rdms.module.project.controller.admin.project.task.vo.worklog.TaskWorklogSaveReqVO;
import com.njcn.rdms.module.project.service.project.task.worklog.TaskWorklogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 任务工时")
@RestController
@RequestMapping("/project/project/{projectId}/executions/{executionId}/tasks/{taskId}")
@Validated
public class TaskWorklogController {
@Resource
private TaskWorklogService taskWorklogService;
@GetMapping("/worklogs")
@Operation(summary = "获取任务工时分页")
public CommonResult<PageResult<TaskWorklogRespVO>> getWorklogPage(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId,
@Valid TaskWorklogPageReqVO reqVO) {
return success(taskWorklogService.getWorklogPage(projectId, executionId, taskId, reqVO));
}
@PostMapping("/worklogs")
@Operation(summary = "新增任务工时")
@PreAuthorize("@ss.hasPermission('project:task:worklog')")
public CommonResult<Long> createWorklog(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId,
@Valid @RequestBody TaskWorklogSaveReqVO reqVO) {
return success(taskWorklogService.createWorklog(projectId, executionId, taskId, reqVO));
}
@PutMapping("/worklogs/{worklogId}")
@Operation(summary = "修改任务工时(仅自己)")
@PreAuthorize("@ss.hasPermission('project:task:worklog')")
public CommonResult<Boolean> updateWorklog(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId,
@PathVariable("worklogId") Long worklogId,
@Valid @RequestBody TaskWorklogSaveReqVO reqVO) {
taskWorklogService.updateWorklog(projectId, executionId, taskId, worklogId, reqVO);
return success(true);
}
@DeleteMapping("/worklogs/{worklogId}")
@Operation(summary = "删除任务工时(自己或任务负责人)")
@PreAuthorize("@ss.hasPermission('project:task:worklog')")
public CommonResult<Boolean> deleteWorklog(@PathVariable("projectId") Long projectId,
@PathVariable("executionId") Long executionId,
@PathVariable("taskId") Long taskId,
@PathVariable("worklogId") Long worklogId) {
taskWorklogService.deleteWorklog(projectId, executionId, taskId, worklogId);
return success(true);
}
}

View File

@@ -0,0 +1,21 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 任务生命周期动作 Response VO")
@Data
@NoArgsConstructor
public class ProjectTaskLifecycleActionRespVO {
@Schema(description = "动作编码", example = "complete")
private String actionCode;
@Schema(description = "动作名称", example = "完成")
private String actionName;
@Schema(description = "是否必须填写原因", example = "true")
private Boolean needReason;
}

View File

@@ -0,0 +1,36 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo;
import com.njcn.rdms.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 任务分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class ProjectTaskPageReqVO extends PageParam {
@Schema(description = "关键词,匹配任务标题", example = "联调")
private String keyword;
@Schema(description = "父任务编号")
private Long parentTaskId;
@Schema(description = "任务负责人用户编号", example = "3002")
private Long ownerId;
@Schema(description = "任务状态编码", example = "pending")
@Size(max = 32, message = "任务状态编码长度不能超过32个字符")
private String statusCode;
@Schema(description = "更新时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] updateTime;
}

View File

@@ -0,0 +1,74 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 任务 Response VO")
@Data
public class ProjectTaskRespVO {
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9001")
private Long id;
@Schema(description = "所属项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2001")
private Long projectId;
@Schema(description = "所属执行编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "5001")
private Long executionId;
@Schema(description = "父任务编号")
private Long parentTaskId;
@Schema(description = "任务标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "接口联调任务")
private String taskTitle;
@Schema(description = "任务负责人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3002")
private Long ownerId;
@Schema(description = "任务负责人昵称", example = "李四")
private String ownerNickname;
@Schema(description = "任务状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "pending")
private String statusCode;
@Schema(description = "任务状态名称", example = "待开始")
private String statusName;
@Schema(description = "是否终态", example = "false")
private Boolean terminal;
@Schema(description = "当前状态是否允许编辑", example = "true")
private Boolean allowEdit;
@Schema(description = "当前状态可执行动作")
private List<ProjectTaskLifecycleActionRespVO> availableActions;
@Schema(description = "任务进度")
private BigDecimal progressRate;
@Schema(description = "计划开始日期")
private LocalDate plannedStartDate;
@Schema(description = "计划结束日期")
private LocalDate plannedEndDate;
@Schema(description = "实际开始日期")
private LocalDate actualStartDate;
@Schema(description = "实际结束日期")
private LocalDate actualEndDate;
@Schema(description = "任务说明")
private String taskDesc;
@Schema(description = "最近一次状态动作原因")
private String lastStatusReason;
@Schema(description = "当前活跃协办人列表;详细变更历史见 assignee-logs 接口")
private List<TaskAssigneeView> assignees;
@Schema(description = "已填报工时合计(分钟);逻辑删除的工时记录不计入。无记录默认为 0",
example = "300")
private Long totalSpentMinutes;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
@Schema(description = "任务协办人轻量视图")
@Data
public static class TaskAssigneeView {
@Schema(description = "协办关系编号", example = "7001")
private Long id;
@Schema(description = "协办人用户编号", example = "3002")
private Long userId;
@Schema(description = "协办人昵称", example = "张三")
private String nickname;
}
}

View File

@@ -0,0 +1,53 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
@Schema(description = "管理后台 - 任务保存 Request VO")
@Data
public class ProjectTaskSaveReqVO {
@Schema(description = "任务编号", example = "9001")
private Long id;
@Schema(description = "父任务编号")
private Long parentTaskId;
@Schema(description = "任务标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "接口联调任务")
@NotBlank(message = "任务标题不能为空")
@Size(max = 300, message = "任务标题长度不能超过300个字符")
private String taskTitle;
@Schema(description = "任务负责人用户编号;子任务不传时继承父任务负责人", example = "3002")
private Long ownerId;
@Schema(description = "任务进度", example = "0.00")
@DecimalMin(value = "0.00", message = "任务进度不能小于0")
@DecimalMax(value = "100.00", message = "任务进度不能大于100")
private BigDecimal progressRate;
@Schema(description = "计划开始日期")
private LocalDate plannedStartDate;
@Schema(description = "计划结束日期")
private LocalDate plannedEndDate;
@Schema(description = "任务说明(接受 HTML 富文本,图片走 URL 引用;后端经全局 XSS Safelist 自动净化)",
example = "完成接口联调")
@Size(max = 200000, message = "任务说明长度不能超过200000个字符")
private String taskDesc;
@Schema(description = "初始协办人用户编号列表;仅在创建任务时生效,编辑任务时静默忽略。"
+ "协办人通过独立接口管理,详见 /tasks/{id}/assignees")
private List<Long> assigneeUserIds;
}

View File

@@ -0,0 +1,21 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 任务状态动作 Request VO")
@Data
public class ProjectTaskStatusActionReqVO {
@Schema(description = "动作编码,如 start、block、resume、complete、cancel", requiredMode = Schema.RequiredMode.REQUIRED, example = "complete")
@NotBlank(message = "动作编码不能为空")
@Size(max = 32, message = "动作编码长度不能超过32个字符")
private String actionCode;
@Schema(description = "动作原因;是否必填由状态流转配置决定", example = "任务取消")
@Size(max = 500, message = "动作原因长度不能超过500个字符")
private String reason;
}

View File

@@ -0,0 +1,31 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 任务状态看板 Request VO")
@Data
@EqualsAndHashCode(callSuper = false)
public class ProjectTaskStatusBoardReqVO {
@Schema(description = "关键字,匹配任务标题", example = "联调")
private String keyword;
@Schema(description = "父任务编号", example = "9001")
private Long parentTaskId;
@Schema(description = "任务负责人用户编号", example = "3002")
private Long ownerId;
@Schema(description = "更新时间", example = "[2026-05-01 00:00:00, 2026-05-31 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] updateTime;
}

View File

@@ -0,0 +1,35 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 任务状态看板 Response VO")
@Data
public class ProjectTaskStatusBoardRespVO {
@Schema(description = "当前筛选条件下的任务总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "24")
private Long total;
@Schema(description = "状态项列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ProjectStatusBoardItemVO> items;
@Schema(description = "任务状态项")
@Data
public static class ProjectStatusBoardItemVO {
@Schema(description = "状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "pending")
private String statusCode;
@Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "待开始")
private String statusName;
@Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Long count;
@Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer sort;
@Schema(description = "是否终态", example = "false")
private Boolean terminal;
}
}

View File

@@ -0,0 +1,17 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 任务协办人退出 Request VO")
@Data
public class TaskAssigneeInactiveReqVO {
@Schema(description = "退出协办的原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "调整到其他任务")
@NotBlank(message = "退出协办必须填写原因")
@Size(max = 500, message = "原因长度不能超过500个字符")
private String reason;
}

View File

@@ -0,0 +1,28 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee;
import com.njcn.rdms.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 任务协办人变更历史分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class TaskAssigneeLogPageReqVO extends PageParam {
@Schema(description = "事件类型多选;不传表示全部")
private List<String> actionTypes;
@Schema(description = "成员用户编号;不传表示全部")
private Long userId;
@Schema(description = "起始时间(含),按 actionTime 比较")
private LocalDateTime startTime;
@Schema(description = "截止时间(含),按 actionTime 比较")
private LocalDateTime endTime;
}

View File

@@ -0,0 +1,39 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 任务协办人变更历史 Response VO")
@Data
public class TaskAssigneeLogRespVO {
@Schema(description = "日志编号", example = "8001")
private Long id;
@Schema(description = "任务编号", example = "9001")
private Long taskId;
@Schema(description = "事件类型join / inactive", example = "join")
private String actionType;
@Schema(description = "被操作人用户编号", example = "3002")
private Long userId;
@Schema(description = "被操作人昵称快照")
private String userNicknameSnapshot;
@Schema(description = "操作人用户编号", example = "3001")
private Long operatorUserId;
@Schema(description = "操作人昵称快照")
private String operatorNicknameSnapshot;
@Schema(description = "事件时间")
private LocalDateTime actionTime;
@Schema(description = "原因inactive 必填join 可空")
private String reason;
}

View File

@@ -0,0 +1,27 @@
package com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 任务协办人 Response VO")
@Data
public class TaskAssigneeRespVO {
@Schema(description = "协办关系编号", example = "7001")
private Long id;
@Schema(description = "任务编号", example = "9001")
private Long taskId;
@Schema(description = "协办人用户编号", example = "3002")
private Long userId;
@Schema(description = "协办人昵称")
private String userNickname;
@Schema(description = "加入时间")
private LocalDateTime joinedAt;
}

Some files were not shown because too many files have changed in this diff Show More