feat(project): 补充项目、执行、任务相关能力
This commit is contained in:
31
.claude/settings.local.json
Normal file
31
.claude/settings.local.json
Normal 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)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,13 @@
|
||||
|
||||
## 本地环境约定
|
||||
|
||||
- 本仓库要求使用 `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 命令时,才使用上述路径执行
|
||||
|
||||
顶层模块:
|
||||
|
||||
110
CLAUDE.md
Normal file
110
CLAUDE.md
Normal 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 17,Spring 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 做文件搜索/读取/编辑。
|
||||
@@ -6,10 +6,10 @@ spring:
|
||||
username: # Nacos 账号
|
||||
password: # Nacos 密码
|
||||
discovery: # 【配置中心】配置项
|
||||
namespace: dev # 命名空间。这里使用 dev 开发环境
|
||||
namespace: 1924bcfb-4eab-4c58-9003-4a37d5fc2949 # 命名空间。这里使用 dev 开发环境
|
||||
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
config: # 【注册中心】配置项
|
||||
namespace: dev # 命名空间。这里使用 dev 开发环境
|
||||
namespace: 1924bcfb-4eab-4c58-9003-4a37d5fc2949 # 命名空间。这里使用 dev 开发环境
|
||||
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
|
||||
#################### 监控相关配置 ####################
|
||||
|
||||
@@ -6,10 +6,10 @@ spring:
|
||||
username: # Nacos 账号
|
||||
password: # Nacos 密码
|
||||
discovery: # 【配置中心】配置项
|
||||
namespace: dev # 命名空间。这里使用 dev 开发环境
|
||||
namespace: 1924bcfb-4eab-4c58-9003-4a37d5fc2949 # 命名空间。这里使用 dev 开发环境
|
||||
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
config: # 【注册中心】配置项
|
||||
namespace: dev # 命名空间。这里使用 dev 开发环境
|
||||
namespace: 1924bcfb-4eab-4c58-9003-4a37d5fc2949 # 命名空间。这里使用 dev 开发环境
|
||||
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
|
||||
|
||||
#################### 监控相关配置 ####################
|
||||
|
||||
@@ -1,270 +0,0 @@
|
||||
# 产品动态时间线后端接口需求说明
|
||||
|
||||
日期:2026-04-23
|
||||
|
||||
## 1. 背景
|
||||
|
||||
当前产品对象首页中的“产品动态时间线”模块,用户期望不是展示几条前端拼装的摘要,而是一个可以在首页内直接查询的正式动态模块。
|
||||
|
||||
本轮已确认的目标能力包括:
|
||||
|
||||
- 首页内直接展示产品动态时间线
|
||||
- 默认查询最近 `30 天`
|
||||
- 支持自定义起止时间筛选
|
||||
- 支持事件类型多选筛选
|
||||
- 支持分页
|
||||
- 第一版事件范围收敛在产品对象与团队关系,不混入需求池事件
|
||||
|
||||
## 2. 当前前端现状
|
||||
|
||||
当前前端可直接使用的真实接口只有:
|
||||
|
||||
- `GET /project/product/get`
|
||||
- `GET /project/product/{id}/settings`
|
||||
- `GET /project/product/{id}/members`
|
||||
|
||||
这些接口目前只能提供:
|
||||
|
||||
- 产品当前状态
|
||||
- 最近一次状态原因
|
||||
- 产品经理
|
||||
- 成员加入时间
|
||||
- 成员退出时间
|
||||
- 当前成员角色
|
||||
|
||||
它们可以勉强拼出少量“最近动态摘要”,但不足以支撑正式时间线查询模块。
|
||||
|
||||
## 3. 当前接口为什么不够
|
||||
|
||||
如果继续只依赖现有接口,前端存在以下硬缺口:
|
||||
|
||||
1. 没有统一的产品动态分页接口
|
||||
2. 没有事件类型维度,无法支持类型筛选
|
||||
3. 没有统一发生时间字段集合,无法稳定支持时间筛选
|
||||
4. 没有事件操作人字段,无法明确展示“谁做了什么”
|
||||
5. 没有统一的事件摘要字段,前端只能自己硬拼文案
|
||||
6. 没有产品状态变更前后值,无法展示“从什么状态变更为什么状态”
|
||||
7. 没有产品经理变更前后值,无法展示交接关系
|
||||
8. 没有分页总数字段,无法做首页内翻页
|
||||
|
||||
因此,现有接口只适合做“概览摘要”,不适合做“可查询产品动态时间线”。
|
||||
|
||||
## 4. 后端接口交付要求
|
||||
|
||||
后端必须新增一条专用分页接口:
|
||||
|
||||
`GET /project/product/{id}/activities/page`
|
||||
|
||||
这条接口只服务“产品动态时间线”能力,不承担需求池动态,不承担首页其它概览指标。
|
||||
|
||||
前端要求后端单独提供这条接口,原因如下:
|
||||
|
||||
- 语义清晰,前后端都容易维护
|
||||
- 首页时间线可直接消费
|
||||
- 后续如果要做独立的产品动态页,也可以继续复用这条接口
|
||||
- 不需要继续让前端从多个接口里拼装事件
|
||||
|
||||
## 5. 第一版事件范围
|
||||
|
||||
第一版事件类型确认收敛为 5 类:
|
||||
|
||||
- `product_created`:产品创建
|
||||
- `product_status_changed`:产品状态变更
|
||||
- `product_manager_changed`:产品经理变更
|
||||
- `product_member_joined`:成员加入
|
||||
- `product_member_removed`:成员移出
|
||||
|
||||
第一版明确不纳入:
|
||||
|
||||
- 成员角色调整
|
||||
- 需求新增
|
||||
- 需求状态流转
|
||||
- 需求关闭
|
||||
- 里程碑事件
|
||||
- 风险点事件
|
||||
|
||||
## 6. 查询参数要求
|
||||
|
||||
接口必须支持以下查询参数:
|
||||
|
||||
- `pageNo`:页码
|
||||
- `pageSize`:每页数量
|
||||
- `startTime`:开始时间
|
||||
- `endTime`:结束时间
|
||||
- `types`:事件类型数组,多选
|
||||
|
||||
补充要求:
|
||||
|
||||
- 当前端未传时间范围时,后端默认按最近 `30 天` 返回
|
||||
- 返回结果按 `occurredAt desc` 倒序排列
|
||||
- `types` 支持多选,不要求前端单选
|
||||
|
||||
示例:
|
||||
|
||||
```http
|
||||
GET /project/product/1001/activities/page?pageNo=1&pageSize=10&startTime=2026-03-24 00:00:00&endTime=2026-04-23 23:59:59&types=product_status_changed&types=product_manager_changed
|
||||
```
|
||||
|
||||
## 7. 返回结构要求
|
||||
|
||||
接口返回必须支持分页,分页结构至少应满足:
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 128,
|
||||
"list": [
|
||||
{
|
||||
"id": "act_001",
|
||||
"type": "product_status_changed",
|
||||
"title": "产品状态变更",
|
||||
"operatorId": "10001",
|
||||
"operatorName": "张敏",
|
||||
"occurredAt": "2026-04-23 10:32:15",
|
||||
"summary": "产品状态由暂停变更为启用",
|
||||
"reason": "测试恢复",
|
||||
"beforeStatus": "paused",
|
||||
"afterStatus": "active"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
分页顶层字段至少包括:
|
||||
|
||||
- `total`
|
||||
- `list`
|
||||
|
||||
如果后端已有统一分页模型,可以沿用现有分页结构,但前端必须能稳定拿到总数和列表。
|
||||
|
||||
## 8. 事件通用字段要求
|
||||
|
||||
无论哪种事件类型,后端都应统一返回以下字段:
|
||||
|
||||
- `id`:事件唯一 ID
|
||||
- `type`:事件类型编码
|
||||
- `title`:事件标题
|
||||
- `operatorId`:操作人 ID
|
||||
- `operatorName`:操作人名称
|
||||
- `occurredAt`:发生时间
|
||||
- `summary`:事件摘要
|
||||
- `reason`:原因或备注,可为空
|
||||
|
||||
这些字段是首页时间线最小可展示集合。
|
||||
|
||||
## 9. 各事件类型的专属字段要求
|
||||
|
||||
### 9.1 产品创建 `product_created`
|
||||
|
||||
必须补充:
|
||||
|
||||
- `creatorUserId`
|
||||
- `creatorUserName`
|
||||
|
||||
### 9.2 产品状态变更 `product_status_changed`
|
||||
|
||||
这是当前最关键的一类事件,后端必须返回:
|
||||
|
||||
- `beforeStatus`
|
||||
- `afterStatus`
|
||||
- `reason`
|
||||
|
||||
前端需要用这组字段明确表达:
|
||||
|
||||
- 变更前状态
|
||||
- 变更后状态
|
||||
- 本次变更原因
|
||||
|
||||
例如:
|
||||
|
||||
- `暂停 -> 启用`
|
||||
- `启用 -> 归档`
|
||||
|
||||
### 9.3 产品经理变更 `product_manager_changed`
|
||||
|
||||
必须返回:
|
||||
|
||||
- `beforeManagerUserId`
|
||||
- `beforeManagerUserName`
|
||||
- `afterManagerUserId`
|
||||
- `afterManagerUserName`
|
||||
- `reason`
|
||||
|
||||
否则前端无法准确展示交接关系,只能看到当前经理,不能看到变更前后关系。
|
||||
|
||||
### 9.4 成员加入 `product_member_joined`
|
||||
|
||||
必须返回:
|
||||
|
||||
- `memberUserId`
|
||||
- `memberUserName`
|
||||
- `roleId`
|
||||
- `roleName`
|
||||
- `remark`
|
||||
|
||||
### 9.5 成员移出 `product_member_removed`
|
||||
|
||||
必须返回:
|
||||
|
||||
- `memberUserId`
|
||||
- `memberUserName`
|
||||
- `roleId`
|
||||
- `roleName`
|
||||
- `reason`
|
||||
|
||||
## 10. 前端展示口径
|
||||
|
||||
前端首页时间线模块第一版会直接基于这条接口支持:
|
||||
|
||||
- 默认最近 `30 天`
|
||||
- 自定义时间范围筛选
|
||||
- 事件类型多选筛选
|
||||
- 分页切换
|
||||
|
||||
每条记录最少展示:
|
||||
|
||||
- 事件类型
|
||||
- 事件标题
|
||||
- 操作人
|
||||
- 发生时间
|
||||
- 变更摘要
|
||||
- 原因/备注
|
||||
|
||||
其中“产品状态变更”需要明确体现:
|
||||
|
||||
- 变更前状态
|
||||
- 变更后状态
|
||||
- 变更原因
|
||||
|
||||
## 11. 为什么不建议继续让前端拼装
|
||||
|
||||
如果继续沿用当前前端拼装方案,会有这些问题:
|
||||
|
||||
- 产品状态变更前后值无法补齐
|
||||
- 产品经理变更前后值无法补齐
|
||||
- 无法支持分页
|
||||
- 无法支持统一时间筛选
|
||||
- 无法支持统一类型筛选
|
||||
- 不同事件文案会在前端散落拼装,长期维护成本高
|
||||
|
||||
因此这里的前后端边界应明确为:
|
||||
|
||||
- 后端提供统一产品动态分页接口
|
||||
- 前端负责筛选条件组织、分页交互和时间线展示
|
||||
|
||||
## 12. 本轮需求结论
|
||||
|
||||
本轮给后端的结论可以直接收敛为:
|
||||
|
||||
1. 当前前端已有接口不满足正式产品动态时间线需求
|
||||
2. 后端新增 `GET /project/product/{id}/activities/page`
|
||||
3. 接口必须支持分页、默认最近 `30 天`、自定义时间范围、事件类型多选
|
||||
4. 第一版事件类型只做:
|
||||
- 产品创建
|
||||
- 产品状态变更
|
||||
- 产品经理变更
|
||||
- 成员加入
|
||||
- 成员移出
|
||||
5. 产品状态变更必须提供前后状态和原因
|
||||
6. 产品经理变更必须提供前后经理信息
|
||||
|
||||
这条接口交付后,前端才能把当前“产品动态时间线”从拼装摘要升级成正式可查询模块。
|
||||
@@ -1,292 +0,0 @@
|
||||
# 产品对象首页改版设计说明
|
||||
|
||||
日期:2026-04-23
|
||||
|
||||
## 1. 目标
|
||||
|
||||
本设计用于收敛 RDMS 产品对象上下文默认首页的改版方向。
|
||||
|
||||
本轮目标不是继续做“说明型占位页”,而是明确把当前 `/product/dashboard?objectId=...` 改成一个真正可用的产品对象首页:
|
||||
|
||||
- 第一眼先让用户知道当前看的是什么产品
|
||||
- 第二眼能快速判断对象最近发生了什么
|
||||
- 第三眼能看出需求池现在的经营状态和最近变化
|
||||
- 底部为后续业务模块保留正式挂载位,而不是临时拼接入口
|
||||
|
||||
## 2. 已确认诉求
|
||||
|
||||
基于本轮对话,已确认以下用户诉求:
|
||||
|
||||
1. 首页顶部必须先展示产品基础概述,而不是先铺统计卡片
|
||||
2. 基础概述至少包含:名称、编号、团队、产品经理等对象基础信息
|
||||
3. 页面需要一块明显的时间线,用于承接产品对象与团队变更动态
|
||||
4. 页面需要承接需求池管理情况,重点看总量、状态、待处理等统计信息
|
||||
5. 需求相关事件不要混入对象时间线,应单独作为需求池最近变化区域
|
||||
6. 快捷入口不要保留
|
||||
7. 底部允许保留后续扩展区,重点预留给里程碑、风险点管理、产品资料等模块
|
||||
8. 能接真实接口就接真实接口,当前没有稳定接口的区域允许先用假数据,但结构必须按正式首页来设计
|
||||
|
||||
## 3. 首页定位结论
|
||||
|
||||
本页定位不是:
|
||||
|
||||
- 纯报表看板
|
||||
- 纯审计日志页
|
||||
- 设置页搬运版
|
||||
- 导航入口集合页
|
||||
|
||||
本页定位应当是:
|
||||
|
||||
- 产品对象首页
|
||||
- 偏统计,也带审计
|
||||
- 但页面主语始终是“当前产品对象”
|
||||
|
||||
换句话说,这个页面要同时回答三个问题:
|
||||
|
||||
1. 我现在看的是什么产品?
|
||||
2. 这个产品对象最近发生了什么?
|
||||
3. 这个产品的需求池现在处于什么状态?
|
||||
|
||||
## 4. 页面结构
|
||||
|
||||
### 4.1 桌面端结构
|
||||
|
||||
桌面端建议采用三层结构:
|
||||
|
||||
1. 顶部 `对象基础概述横幅`
|
||||
2. 中部 `左时间线 + 右需求池双模块`
|
||||
3. 底部 `扩展信息区`
|
||||
|
||||
推荐布局比例:
|
||||
|
||||
- 顶部横幅:`24 / 24`
|
||||
- 中部主区:左 `16 / 24`,右 `8 / 24`
|
||||
- 底部扩展区:`24 / 24`
|
||||
|
||||
中部左侧时间线高度应明显高于右侧任一单模块,形成首页主阅读区。
|
||||
|
||||
### 4.2 移动端结构
|
||||
|
||||
移动端统一退化为单列纵向布局,顺序为:
|
||||
|
||||
1. 对象基础概述横幅
|
||||
2. 对象 / 团队动态时间线
|
||||
3. 需求池管理概览
|
||||
4. 需求池最近变化
|
||||
5. 扩展信息区
|
||||
|
||||
移动端不强撑左右栏并排,不做卡片墙式压缩。
|
||||
|
||||
## 5. 模块设计
|
||||
|
||||
### 5.1 对象基础概述横幅
|
||||
|
||||
顶部采用“档案横幅型”,不采用纯指标卡片型。
|
||||
|
||||
横幅左侧承接对象身份信息:
|
||||
|
||||
- 产品名称
|
||||
- 产品编号
|
||||
- 当前状态标签
|
||||
- 产品经理
|
||||
- 团队规模
|
||||
- 团队角色摘要
|
||||
- 简短描述或备注
|
||||
|
||||
横幅右侧承接 4 个摘要指标:
|
||||
|
||||
- 团队人数
|
||||
- 需求总量
|
||||
- 待处理需求
|
||||
- 最近动态时间
|
||||
|
||||
设计原则:
|
||||
|
||||
- 左侧负责建立对象识别
|
||||
- 右侧负责快速判断当前概况
|
||||
- 右侧指标只保留 4 项,不堆成报表卡片墙
|
||||
|
||||
### 5.2 对象 / 团队动态时间线
|
||||
|
||||
该区域位于中部左侧,是首页的主阅读区。
|
||||
|
||||
这条时间线只承接对象与团队变化,不承接需求事件。
|
||||
|
||||
第一版事件范围收敛为:
|
||||
|
||||
- 产品创建
|
||||
- 产品状态变更
|
||||
- 产品经理变更
|
||||
- 成员加入
|
||||
- 成员移出
|
||||
- 成员角色调整
|
||||
|
||||
每条时间线建议展示:
|
||||
|
||||
- 事件标题
|
||||
- 事件类型标签
|
||||
- 发生时间
|
||||
- 操作摘要
|
||||
- 必要时展示原因或备注
|
||||
|
||||
表达目标是“业务时间线”,不是后台审计表格。
|
||||
|
||||
### 5.3 需求池管理概览
|
||||
|
||||
该区域位于中部右侧上半块,用于表达需求池的经营状态。
|
||||
|
||||
第一版首页需要优先看到的内容:
|
||||
|
||||
- 需求总量
|
||||
- 各状态数量
|
||||
- 待处理数量
|
||||
- 高优先级待处理数量
|
||||
|
||||
展示方式建议为“摘要指标 + 状态分布列表”,不直接在首页展开完整需求表格。
|
||||
|
||||
这一块回答的是:
|
||||
|
||||
- 需求池是否健康
|
||||
- 当前待处理压力大不大
|
||||
- 是否存在需要优先关注的积压
|
||||
|
||||
### 5.4 需求池最近变化
|
||||
|
||||
该区域位于中部右侧下半块,与需求池管理概览上下分层,但属于同一侧栏语义。
|
||||
|
||||
该区域不重复展示总量,而是展示需求池最近发生的变化。
|
||||
|
||||
第一版建议承接:
|
||||
|
||||
- 最近新增需求
|
||||
- 最近状态流转
|
||||
- 最近关闭或完成
|
||||
|
||||
每条记录建议至少展示:
|
||||
|
||||
- 需求标题
|
||||
- 动作类型
|
||||
- 时间
|
||||
- 当前状态或状态变更摘要
|
||||
|
||||
若当前没有真实数据,仍保留正式模块壳,不退化成“待开发”一句话。
|
||||
|
||||
### 5.5 扩展信息区
|
||||
|
||||
底部不再保留快捷入口,改为正式扩展信息区。
|
||||
|
||||
当前优先预留 3 类模块位:
|
||||
|
||||
- 里程碑
|
||||
- 风险点管理
|
||||
- 产品资料
|
||||
|
||||
这一层的作用是:
|
||||
|
||||
- 为后续对象级信息继续扩展留下稳定挂载位
|
||||
- 不把中部主结构挤成信息大杂烩
|
||||
- 避免为了未来模块提前做假导航入口
|
||||
|
||||
如果当前没有稳定接口,可先保留正式卡片结构与空态说明。
|
||||
|
||||
## 6. 数据策略
|
||||
|
||||
### 6.1 真实接口优先
|
||||
|
||||
当前首页优先消费现有真实接口:
|
||||
|
||||
- `fetchGetProduct`
|
||||
- `fetchGetProductSettings`
|
||||
- `fetchGetProductMembers`
|
||||
|
||||
这些接口足以支撑:
|
||||
|
||||
- 对象基础概述中的名称、编号、状态、产品经理、描述
|
||||
- 团队人数与角色摘要
|
||||
- 最近动态中的产品创建、状态变化、成员加入/移出
|
||||
|
||||
### 6.2 假数据使用边界
|
||||
|
||||
当前没有稳定真实接口的区域,允许先用假数据,但边界必须明确:
|
||||
|
||||
- 需求池管理概览
|
||||
- 需求池最近变化
|
||||
- 扩展信息区中的里程碑、风险点管理、产品资料摘要
|
||||
|
||||
假数据的使用原则:
|
||||
|
||||
1. 只补“当前没有稳定接口”的区域
|
||||
2. 不反向污染对象基础信息
|
||||
3. 不把假数据混入对象上下文 store
|
||||
4. 数据源要集中放在概览页自己的 mock 模块中,方便后续替换
|
||||
|
||||
### 6.3 不推荐的做法
|
||||
|
||||
以下做法应避免:
|
||||
|
||||
- 把需求假数据散落写进页面组件
|
||||
- 用对象 demo 数据冒充真实产品详情
|
||||
- 把对象时间线和需求时间线混成一条
|
||||
- 用快捷入口伪装成首页内容
|
||||
|
||||
## 7. 空态规则
|
||||
|
||||
首页至少要区分三种状态:
|
||||
|
||||
1. 能力未接入,只能先显示正式占位信息
|
||||
2. 能力已接入,但当前该产品暂无业务数据
|
||||
3. 当前用户无权限查看该模块
|
||||
|
||||
这三种状态不能共用一套模糊文案。
|
||||
|
||||
对需求池和扩展信息区,当前阶段更推荐“正式空态”而不是“待开发”。
|
||||
|
||||
## 8. 页面边界
|
||||
|
||||
首页明确不承接以下内容:
|
||||
|
||||
- 快捷入口导航区
|
||||
- 完整团队成员表格
|
||||
- 完整需求列表表格
|
||||
- 设置页重表单
|
||||
- 完整审计日志明细页
|
||||
|
||||
首页要做的是概述、判断与阅读,不是重操作页。
|
||||
|
||||
## 9. 实施建议
|
||||
|
||||
第一阶段建议先完成结构性改造:
|
||||
|
||||
1. 重做顶部横幅,建立对象档案感
|
||||
2. 保留中部左高右双块结构
|
||||
3. 用真实接口接通对象概述与对象 / 团队时间线
|
||||
4. 用局部 mock 数据先接通需求池两块和底部扩展区
|
||||
|
||||
第二阶段再逐步替换需求池与扩展区数据源:
|
||||
|
||||
- 接真实需求池统计接口
|
||||
- 接真实需求动态接口
|
||||
- 接里程碑、风险点、产品资料摘要接口
|
||||
|
||||
## 10. 验证标准
|
||||
|
||||
本设计是否成立,可按以下标准判断:
|
||||
|
||||
1. 进入首页后,第一眼能认出当前产品对象
|
||||
2. 用户能自然读到对象 / 团队最近发生了什么
|
||||
3. 右侧能快速判断需求池当前压力与最近变化
|
||||
4. 页面看起来像“对象首页”,而不是“普通后台卡片堆叠页”
|
||||
5. 当前没有真实接口的区域也保留正式结构,不显得像临时占位
|
||||
6. 后续新增里程碑、风险点管理、产品资料等能力时,不需要推翻整页结构
|
||||
|
||||
## 11. 本轮设计结论
|
||||
|
||||
本轮最终设计结论如下:
|
||||
|
||||
- 首页定位为“产品对象首页”,偏统计,也带审计,但不做纯报表页
|
||||
- 顶部采用档案横幅型,先立住对象身份信息
|
||||
- 中部左侧是高权重的对象 / 团队动态时间线
|
||||
- 中部右侧拆为“需求池管理概览 + 需求池最近变化”上下两块
|
||||
- 底部去掉快捷入口,改为正式扩展信息区
|
||||
- 当前有真实接口的模块优先接真实接口
|
||||
- 当前没有稳定接口的区域允许先用假数据,但必须隔离在概览页局部 mock 数据源中
|
||||
@@ -53,4 +53,85 @@ public interface ErrorCodeConstants {
|
||||
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, "仅记录填报人或任务负责人可删除该工时记录");
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ public final class ObjectActivityConstants {
|
||||
|
||||
// ========== 审计业务类型 ==========
|
||||
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";
|
||||
|
||||
// ========== 产品对象动作 ==========
|
||||
@@ -32,6 +33,18 @@ public final class ObjectActivityConstants {
|
||||
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";
|
||||
@@ -42,6 +55,23 @@ public final class ObjectActivityConstants {
|
||||
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);
|
||||
@@ -67,11 +97,30 @@ public final class ObjectActivityConstants {
|
||||
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 PRODUCT_ACTION_CHANGE_MANAGER -> "切换负责人";
|
||||
case MEMBER_ACTION_ADD -> "新增成员";
|
||||
case MEMBER_ACTION_UPDATE -> "调整成员";
|
||||
case MEMBER_ACTION_REMOVE -> "移出成员";
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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;
|
||||
@@ -62,12 +63,17 @@ public class ProductController {
|
||||
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "获取产品分页")
|
||||
@PreAuthorize("@ss.hasPermission('project:product:query')")
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 任务协办人加入 Request VO")
|
||||
@Data
|
||||
public class TaskAssigneeSaveReqVO {
|
||||
|
||||
@Schema(description = "协办人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3002")
|
||||
@NotNull(message = "协办人用户编号不能为空")
|
||||
private Long userId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.task.vo.worklog;
|
||||
|
||||
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.LocalDate;
|
||||
|
||||
@Schema(description = "管理后台 - 任务工时分页 Request VO")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TaskWorklogPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "填报人用户编号;不传表示全部")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "起始日期(含),按 workDate 比较")
|
||||
private LocalDate startDate;
|
||||
|
||||
@Schema(description = "截止日期(含),按 workDate 比较")
|
||||
private LocalDate endDate;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.task.vo.worklog;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 任务工时 Response VO")
|
||||
@Data
|
||||
public class TaskWorklogRespVO {
|
||||
|
||||
@Schema(description = "工时记录编号", example = "11001")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "任务编号", example = "9001")
|
||||
private Long taskId;
|
||||
|
||||
@Schema(description = "填报人用户编号", example = "3002")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "填报人昵称", example = "张三")
|
||||
private String userNickname;
|
||||
|
||||
@Schema(description = "工作日期", example = "2026-05-08")
|
||||
private LocalDate workDate;
|
||||
|
||||
@Schema(description = "时长(分钟)", example = "150")
|
||||
private Integer durationMinutes;
|
||||
|
||||
@Schema(description = "工作内容描述")
|
||||
private String workContent;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.task.vo.worklog;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 任务工时新增/更新请求。同表共用:updateWorklog 不接受 taskId / userId 切换,前端无需也无法传。
|
||||
* 时长颗粒(30 分钟整数倍)由 Service 层校验。
|
||||
*/
|
||||
@Schema(description = "管理后台 - 任务工时 Save Request VO")
|
||||
@Data
|
||||
public class TaskWorklogSaveReqVO {
|
||||
|
||||
@Schema(description = "工作日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-05-08")
|
||||
@NotNull(message = "工作日期不能为空")
|
||||
private LocalDate workDate;
|
||||
|
||||
@Schema(description = "时长(分钟),> 0 且必须为 30 的整数倍",
|
||||
requiredMode = Schema.RequiredMode.REQUIRED, example = "150")
|
||||
@NotNull(message = "工时时长不能为空")
|
||||
@Min(value = 30, message = "工时时长必须大于 0 且为 30 分钟的整数倍")
|
||||
private Integer durationMinutes;
|
||||
|
||||
@Schema(description = "工作内容描述", example = "完成接口联调与冒烟测试")
|
||||
@Size(max = 2000, message = "工作内容长度不能超过 2000 个字符")
|
||||
private String workContent;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.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 ProjectMemberInactiveReqVO {
|
||||
|
||||
@Schema(description = "移出原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "已退出当前项目协作")
|
||||
@NotBlank(message = "移出原因不能为空")
|
||||
@Size(max = 500, message = "移出原因长度不能超过500个字符")
|
||||
private String reason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.member;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 项目成员 Response VO")
|
||||
@Data
|
||||
public class ProjectMemberRespVO {
|
||||
|
||||
@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 = "project_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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.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 ProjectMemberSaveReqVO {
|
||||
|
||||
@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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.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 ProjectMemberUpdateReqVO {
|
||||
|
||||
@Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3100000002002")
|
||||
@NotNull(message = "角色编号不能为空")
|
||||
private Long roleId;
|
||||
|
||||
@Schema(description = "变更原因", example = "职责调整")
|
||||
@Size(max = 500, message = "变更原因长度不能超过500个字符")
|
||||
private String reason;
|
||||
|
||||
@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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 项目上下文导航 Response VO")
|
||||
@Data
|
||||
public class ProjectContextNavRespVO {
|
||||
|
||||
@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/project/overview")
|
||||
private String path;
|
||||
@Schema(description = "菜单图标", example = "mdi:view-dashboard-outline")
|
||||
private String icon;
|
||||
@Schema(description = "显示顺序", example = "10")
|
||||
private Integer sort;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Schema(description = "管理后台 - 项目上下文中的当前项目摘要 Response VO")
|
||||
@Data
|
||||
public class ProjectContextProjectRespVO {
|
||||
|
||||
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
@Schema(description = "项目编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNPJ2026001")
|
||||
private String projectCode;
|
||||
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户交付项目")
|
||||
private String projectName;
|
||||
@Schema(description = "项目类型字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "delivery")
|
||||
private String projectType;
|
||||
@Schema(description = "项目方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "direction_value")
|
||||
private String directionCode;
|
||||
@Schema(description = "所属产品编号")
|
||||
private Long productId;
|
||||
@Schema(description = "项目负责人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long managerUserId;
|
||||
@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<ProjectLifecycleActionRespVO> availableActions;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
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 ProjectContextRespVO {
|
||||
|
||||
@Schema(description = "当前项目摘要")
|
||||
private ProjectContextProjectRespVO currentProject;
|
||||
@Schema(description = "当前用户在该项目下的角色信息")
|
||||
private ProjectContextRoleRespVO currentRole;
|
||||
@Schema(description = "当前项目下可见导航集合")
|
||||
private List<ProjectContextNavRespVO> navs;
|
||||
@Schema(description = "当前项目下按钮权限码集合")
|
||||
private List<String> buttons;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Schema(description = "管理后台 - 项目上下文中的当前角色 Response VO")
|
||||
@Data
|
||||
public class ProjectContextRoleRespVO {
|
||||
|
||||
@Schema(description = "对象角色编号", example = "3201")
|
||||
private Long roleId;
|
||||
@Schema(description = "对象角色编码", example = "project_manager")
|
||||
private String roleCode;
|
||||
@Schema(description = "对象角色名称", example = "项目经理")
|
||||
private String roleName;
|
||||
@Schema(description = "是否游客上下文", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
|
||||
private Boolean guestFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
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 ProjectDeleteReqVO {
|
||||
|
||||
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "确认输入的项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户交付项目")
|
||||
@NotBlank(message = "确认项目名称不能为空")
|
||||
@Size(max = 200, message = "确认项目名称长度不能超过200个字符")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "删除确认口令,当前固定输入 DELETE", requiredMode = Schema.RequiredMode.REQUIRED, example = "DELETE")
|
||||
@NotBlank(message = "删除确认口令不能为空")
|
||||
@Size(max = 32, message = "删除确认口令长度不能超过32个字符")
|
||||
private String confirmText;
|
||||
|
||||
@Schema(description = "删除原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "项目录入错误")
|
||||
@NotBlank(message = "删除原因不能为空")
|
||||
@Size(max = 500, message = "删除原因长度不能超过500个字符")
|
||||
private String reason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Schema(description = "管理后台 - 项目生命周期动作 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class ProjectLifecycleActionRespVO {
|
||||
|
||||
@Schema(description = "动作编码", example = "archive")
|
||||
private String actionCode;
|
||||
|
||||
@Schema(description = "动作名称", example = "归档")
|
||||
private String actionName;
|
||||
|
||||
@Schema(description = "是否必须填写原因", example = "true")
|
||||
private Boolean needReason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@Schema(description = "管理后台 - 项目入口页概览统计 Response VO")
|
||||
@Data
|
||||
public class ProjectOverviewSummaryRespVO {
|
||||
|
||||
@Schema(description = "项目状态数量,按当前启用的项目状态模型返回")
|
||||
private Map<String, Long> statusCounts;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
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 ProjectPageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "关键词,匹配项目编码或项目名称", example = "CNPJ2026001")
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "项目类型字典值", example = "delivery")
|
||||
@Size(max = 32, message = "项目类型长度不能超过32个字符")
|
||||
private String projectType;
|
||||
|
||||
@Schema(description = "项目方向字典值", example = "direction_value")
|
||||
@InDict(type = DictTypeConstants.OBJECT_DIRECTION)
|
||||
private String directionCode;
|
||||
|
||||
@Schema(description = "所属产品编号", example = "1001")
|
||||
private Long productId;
|
||||
|
||||
@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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - 项目 Response VO")
|
||||
@Data
|
||||
public class ProjectRespVO {
|
||||
|
||||
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
@Schema(description = "项目编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "CNPJ2026001")
|
||||
private String projectCode;
|
||||
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户交付项目")
|
||||
private String projectName;
|
||||
@Schema(description = "项目类型字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "delivery")
|
||||
private String projectType;
|
||||
@Schema(description = "项目方向字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "direction_value")
|
||||
private String directionCode;
|
||||
@Schema(description = "所属项目集编号")
|
||||
private Long projectSetId;
|
||||
@Schema(description = "所属产品编号")
|
||||
private Long productId;
|
||||
@Schema(description = "所属产品名称", example = "统一交付平台")
|
||||
private String productName;
|
||||
@Schema(description = "所属产品版本编号")
|
||||
private Long productVersionId;
|
||||
@Schema(description = "项目负责人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long managerUserId;
|
||||
@Schema(description = "项目负责人昵称", example = "张三")
|
||||
private String managerUserNickname;
|
||||
@Schema(description = "项目状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "pending")
|
||||
private String statusCode;
|
||||
@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 projectDesc;
|
||||
@Schema(description = "最近一次状态动作原因")
|
||||
private String lastStatusReason;
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
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;
|
||||
|
||||
@Schema(description = "管理后台 - 项目保存 Request VO")
|
||||
@Data
|
||||
public class ProjectSaveReqVO {
|
||||
|
||||
@Schema(description = "项目编号", example = "1024")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "项目编码,为空时由系统自动生成", example = "CNPJ2026001")
|
||||
@Size(max = 64, message = "项目编码长度不能超过64个字符")
|
||||
private String projectCode;
|
||||
|
||||
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户交付项目")
|
||||
@NotBlank(message = "项目名称不能为空")
|
||||
@Size(max = 200, message = "项目名称长度不能超过200个字符")
|
||||
private String projectName;
|
||||
|
||||
@Schema(description = "项目类型字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "delivery")
|
||||
@NotBlank(message = "项目类型不能为空")
|
||||
@Size(max = 32, message = "项目类型长度不能超过32个字符")
|
||||
private String projectType;
|
||||
|
||||
@Schema(description = "项目方向字典值;未选产品时必填,选产品时以后端产品方向为准", example = "direction_value")
|
||||
private String directionCode;
|
||||
|
||||
@Schema(description = "所属项目集编号,第一期预留", example = "1001")
|
||||
private Long projectSetId;
|
||||
|
||||
@Schema(description = "所属产品编号,可为空", example = "1001")
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "所属产品版本编号,第一期预留", example = "1001")
|
||||
private Long productVersionId;
|
||||
|
||||
@Schema(description = "项目负责人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目负责人不能为空")
|
||||
private Long managerUserId;
|
||||
|
||||
@Schema(description = "计划开始日期")
|
||||
private LocalDate plannedStartDate;
|
||||
|
||||
@Schema(description = "计划结束日期")
|
||||
private LocalDate plannedEndDate;
|
||||
|
||||
@Schema(description = "实际开始日期")
|
||||
private LocalDate actualStartDate;
|
||||
|
||||
@Schema(description = "实际结束日期")
|
||||
private LocalDate actualEndDate;
|
||||
|
||||
@Schema(description = "项目说明", example = "客户定制交付")
|
||||
@Size(max = 4000, message = "项目说明长度不能超过4000个字符")
|
||||
private String projectDesc;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.njcn.rdms.module.project.controller.admin.project.vo.project;
|
||||
|
||||
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 ProjectStatusActionReqVO {
|
||||
|
||||
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "项目编号不能为空")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "动作编码,如 pause、resume、complete、cancel、reopen、archive", 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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 项目主表
|
||||
*/
|
||||
@TableName("rdms_project")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 项目编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 项目编码
|
||||
*/
|
||||
private String projectCode;
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
private String projectName;
|
||||
/**
|
||||
* 项目类型字典值
|
||||
*/
|
||||
private String projectType;
|
||||
/**
|
||||
* 项目方向字典值
|
||||
*/
|
||||
private String directionCode;
|
||||
/**
|
||||
* 所属项目集编号,第一期预留
|
||||
*/
|
||||
private Long projectSetId;
|
||||
/**
|
||||
* 所属产品编号,可为空
|
||||
*/
|
||||
private Long productId;
|
||||
/**
|
||||
* 所属产品版本编号,第一期预留
|
||||
*/
|
||||
private Long productVersionId;
|
||||
/**
|
||||
* 项目负责人用户编号
|
||||
*/
|
||||
private Long managerUserId;
|
||||
/**
|
||||
* 项目状态编码
|
||||
*/
|
||||
private String statusCode;
|
||||
/**
|
||||
* 计划开始日期
|
||||
*/
|
||||
private LocalDate plannedStartDate;
|
||||
/**
|
||||
* 计划结束日期
|
||||
*/
|
||||
private LocalDate plannedEndDate;
|
||||
/**
|
||||
* 实际开始日期
|
||||
*/
|
||||
private LocalDate actualStartDate;
|
||||
/**
|
||||
* 实际结束日期
|
||||
*/
|
||||
private LocalDate actualEndDate;
|
||||
/**
|
||||
* 项目进度缓存值
|
||||
*/
|
||||
private BigDecimal progressRate;
|
||||
/**
|
||||
* 项目说明
|
||||
*/
|
||||
private String projectDesc;
|
||||
/**
|
||||
* 最近一次状态动作原因
|
||||
*/
|
||||
private String lastStatusReason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 项目状态日志表
|
||||
*/
|
||||
@TableName("rdms_project_status_log")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectStatusLogDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
/**
|
||||
* 动作类型
|
||||
*/
|
||||
private String actionType;
|
||||
/**
|
||||
* 变更前状态编码
|
||||
*/
|
||||
private String fromStatus;
|
||||
/**
|
||||
* 变更后状态编码
|
||||
*/
|
||||
private String toStatus;
|
||||
/**
|
||||
* 动作原因
|
||||
*/
|
||||
private String reason;
|
||||
/**
|
||||
* 操作人用户ID
|
||||
*/
|
||||
private Long operatorUserId;
|
||||
/**
|
||||
* 操作人名称快照
|
||||
*/
|
||||
private String operatorName;
|
||||
/**
|
||||
* 项目编码快照
|
||||
*/
|
||||
private String projectCodeSnapshot;
|
||||
/**
|
||||
* 项目名称快照
|
||||
*/
|
||||
private String projectNameSnapshot;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project.execution;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 执行成员关系表。
|
||||
*/
|
||||
@TableName("rdms_execution_member")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ExecutionMemberDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 所属执行编号
|
||||
*/
|
||||
private Long executionId;
|
||||
/**
|
||||
* 成员用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 加入时间
|
||||
*/
|
||||
private LocalDateTime joinedAt;
|
||||
/**
|
||||
* 失效时间,为空表示当前有效
|
||||
*/
|
||||
private LocalDateTime removedAt;
|
||||
/**
|
||||
* 失效原因
|
||||
*/
|
||||
private String removedReason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project.execution;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 执行成员变更历史日志(B 模型 - 全量事件记录)。
|
||||
* 每次 join / inactive / owner_transfer_in / owner_transfer_out 独立成一条记录,昵称展示由查询阶段按用户编号回填。
|
||||
*/
|
||||
@TableName("rdms_execution_member_log")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ExecutionMemberLogDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键 ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 所属执行编号
|
||||
*/
|
||||
private Long executionId;
|
||||
/**
|
||||
* 被操作的成员用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 被操作人昵称冗余字段。当前业务写入不再落昵称,返回时按 userId 实时回填。
|
||||
*/
|
||||
private String userNicknameSnapshot;
|
||||
/**
|
||||
* 事件类型:join / inactive / owner_transfer_in / owner_transfer_out
|
||||
*/
|
||||
private String actionType;
|
||||
/**
|
||||
* 事件原因,inactive 必填,其余可空
|
||||
*/
|
||||
private String reason;
|
||||
/**
|
||||
* 操作人用户编号
|
||||
*/
|
||||
private Long operatorUserId;
|
||||
/**
|
||||
* 操作人昵称冗余字段。当前业务写入不再落昵称,返回时按 operatorUserId 实时回填。
|
||||
*/
|
||||
private String operatorNicknameSnapshot;
|
||||
/**
|
||||
* 事件时间
|
||||
*/
|
||||
private LocalDateTime actionTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project.execution;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 项目执行主表。
|
||||
*/
|
||||
@TableName("rdms_project_execution")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectExecutionDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 执行编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 所属项目编号
|
||||
*/
|
||||
private Long projectId;
|
||||
/**
|
||||
* 关联项目需求编号,第一阶段仅保留字段
|
||||
*/
|
||||
private Long projectRequirementId;
|
||||
/**
|
||||
* 执行名称
|
||||
*/
|
||||
private String executionName;
|
||||
/**
|
||||
* 执行类型
|
||||
*/
|
||||
private String executionType;
|
||||
/**
|
||||
* 执行负责人用户编号
|
||||
*/
|
||||
private Long ownerId;
|
||||
/**
|
||||
* 执行状态编码
|
||||
*/
|
||||
private String statusCode;
|
||||
/**
|
||||
* 计划开始日期
|
||||
*/
|
||||
private LocalDate plannedStartDate;
|
||||
/**
|
||||
* 计划结束日期
|
||||
*/
|
||||
private LocalDate plannedEndDate;
|
||||
/**
|
||||
* 实际开始日期
|
||||
*/
|
||||
private LocalDate actualStartDate;
|
||||
/**
|
||||
* 实际结束日期
|
||||
*/
|
||||
private LocalDate actualEndDate;
|
||||
/**
|
||||
* 执行进度缓存值
|
||||
*/
|
||||
private BigDecimal progressRate;
|
||||
/**
|
||||
* 执行说明
|
||||
*/
|
||||
private String executionDesc;
|
||||
/**
|
||||
* 最近一次状态动作原因
|
||||
*/
|
||||
private String lastStatusReason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project.execution;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 执行状态日志表。
|
||||
*/
|
||||
@TableName("rdms_execution_status_log")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectExecutionStatusLogDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 执行ID
|
||||
*/
|
||||
private Long executionId;
|
||||
/**
|
||||
* 动作编码
|
||||
*/
|
||||
private String actionType;
|
||||
/**
|
||||
* 变更前状态编码
|
||||
*/
|
||||
private String fromStatus;
|
||||
/**
|
||||
* 变更后状态编码
|
||||
*/
|
||||
private String toStatus;
|
||||
/**
|
||||
* 动作原因
|
||||
*/
|
||||
private String reason;
|
||||
/**
|
||||
* 操作人用户ID
|
||||
*/
|
||||
private Long operatorUserId;
|
||||
/**
|
||||
* 操作人名称快照
|
||||
*/
|
||||
private String operatorName;
|
||||
/**
|
||||
* 执行名称快照
|
||||
*/
|
||||
private String executionNameSnapshot;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project.task;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 项目任务主表。
|
||||
*/
|
||||
@TableName("rdms_task")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectTaskDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 任务编号
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 所属项目编号
|
||||
*/
|
||||
private Long projectId;
|
||||
/**
|
||||
* 所属执行编号
|
||||
*/
|
||||
private Long executionId;
|
||||
/**
|
||||
* 父任务编号
|
||||
*/
|
||||
private Long parentTaskId;
|
||||
/**
|
||||
* 任务标题
|
||||
*/
|
||||
private String taskTitle;
|
||||
/**
|
||||
* 任务负责人用户编号
|
||||
*/
|
||||
private Long ownerId;
|
||||
/**
|
||||
* 任务状态编码
|
||||
*/
|
||||
private String statusCode;
|
||||
/**
|
||||
* 任务进度
|
||||
*/
|
||||
private BigDecimal progressRate;
|
||||
/**
|
||||
* 计划开始日期
|
||||
*/
|
||||
private LocalDate plannedStartDate;
|
||||
/**
|
||||
* 计划结束日期
|
||||
*/
|
||||
private LocalDate plannedEndDate;
|
||||
/**
|
||||
* 实际开始日期
|
||||
*/
|
||||
private LocalDate actualStartDate;
|
||||
/**
|
||||
* 实际结束日期
|
||||
*/
|
||||
private LocalDate actualEndDate;
|
||||
/**
|
||||
* 任务说明
|
||||
*/
|
||||
private String taskDesc;
|
||||
/**
|
||||
* 最近一次状态动作原因
|
||||
*/
|
||||
private String lastStatusReason;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project.task;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 任务状态日志表。
|
||||
*/
|
||||
@TableName("rdms_task_status_log")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ProjectTaskStatusLogDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 任务ID
|
||||
*/
|
||||
private Long taskId;
|
||||
/**
|
||||
* 动作编码
|
||||
*/
|
||||
private String actionType;
|
||||
/**
|
||||
* 变更前状态编码
|
||||
*/
|
||||
private String fromStatus;
|
||||
/**
|
||||
* 变更后状态编码
|
||||
*/
|
||||
private String toStatus;
|
||||
/**
|
||||
* 动作原因
|
||||
*/
|
||||
private String reason;
|
||||
/**
|
||||
* 操作人用户ID
|
||||
*/
|
||||
private Long operatorUserId;
|
||||
/**
|
||||
* 操作人名称快照
|
||||
*/
|
||||
private String operatorName;
|
||||
/**
|
||||
* 任务标题快照
|
||||
*/
|
||||
private String taskTitleSnapshot;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project.task;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 任务协办人活跃记录表(B 模型 - 多行周期记录)。
|
||||
* 同一 userId 在同一任务内允许多条历史记录,但任意时刻只有一段 removedAt 为 null(活跃)。
|
||||
* 失效时设置 removedAt(不动 BaseDO.deleted),失效原因和时间快照同步写入 {@link TaskAssigneeLogDO}。
|
||||
*/
|
||||
@TableName("rdms_task_assignee")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TaskAssigneeDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键 ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 任务编号
|
||||
*/
|
||||
private Long taskId;
|
||||
/**
|
||||
* 协办人用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 加入时间
|
||||
*/
|
||||
private LocalDateTime joinedAt;
|
||||
/**
|
||||
* 失效时间,为 null 表示当前活跃
|
||||
*/
|
||||
private LocalDateTime removedAt;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project.task;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 任务协办人变更历史日志表(B 模型 - 全量事件记录)。
|
||||
* 每次 join / inactive 都独立成一条记录,nickname 在写入时快照不再回查。
|
||||
*/
|
||||
@TableName("rdms_task_assignee_log")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TaskAssigneeLogDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键 ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 任务编号
|
||||
*/
|
||||
private Long taskId;
|
||||
/**
|
||||
* 被操作的协办人用户编号
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 被操作人昵称快照
|
||||
*/
|
||||
private String userNicknameSnapshot;
|
||||
/**
|
||||
* 事件类型:join / inactive
|
||||
*/
|
||||
private String actionType;
|
||||
/**
|
||||
* 原因:inactive 必填,join 可空
|
||||
*/
|
||||
private String reason;
|
||||
/**
|
||||
* 操作人用户编号
|
||||
*/
|
||||
private Long operatorUserId;
|
||||
/**
|
||||
* 操作人昵称快照
|
||||
*/
|
||||
private String operatorNicknameSnapshot;
|
||||
/**
|
||||
* 事件时间
|
||||
*/
|
||||
private LocalDateTime actionTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.njcn.rdms.module.project.dal.dataobject.project.task;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 任务工时记录表。仅挂在叶子任务上;同一 user × task × work_date 允许多条。
|
||||
* 时长按分钟存(duration_minutes 必须 > 0 且为 30 的整数倍),前端展示为小时。
|
||||
*/
|
||||
@TableName("rdms_task_worklog")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class TaskWorklogDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 主键 ID
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 任务编号(必须为叶子任务)
|
||||
*/
|
||||
private Long taskId;
|
||||
/**
|
||||
* 填报人用户编号(owner 或在岗协办人)
|
||||
*/
|
||||
private Long userId;
|
||||
/**
|
||||
* 工作日期
|
||||
*/
|
||||
private LocalDate workDate;
|
||||
/**
|
||||
* 时长(分钟为单位),必须 > 0 且为 30 的整数倍
|
||||
*/
|
||||
private Integer durationMinutes;
|
||||
/**
|
||||
* 工作内容描述
|
||||
*/
|
||||
private String workContent;
|
||||
|
||||
}
|
||||
@@ -52,4 +52,11 @@ public interface UserObjectRoleMapper extends BaseMapperX<UserObjectRoleDO> {
|
||||
.eq(UserObjectRoleDO::getObjectId, objectId));
|
||||
}
|
||||
|
||||
default List<UserObjectRoleDO> selectActiveListByObjectTypeAndUserId(String objectType, Long userId) {
|
||||
return selectList(new LambdaQueryWrapperX<UserObjectRoleDO>()
|
||||
.eq(UserObjectRoleDO::getObjectType, objectType)
|
||||
.eq(UserObjectRoleDO::getUserId, userId)
|
||||
.eq(UserObjectRoleDO::getStatus, 0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,9 +7,11 @@ import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductPageReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface ProductMapper extends BaseMapperX<ProductDO> {
|
||||
@@ -25,7 +27,7 @@ public interface ProductMapper extends BaseMapperX<ProductDO> {
|
||||
.eqIfPresent(ProductDO::getManagerUserId, reqVO.getManagerUserId())
|
||||
.eqIfPresent(ProductDO::getStatusCode, reqVO.getStatusCode())
|
||||
.betweenIfPresent(BaseDO::getUpdateTime, reqVO.getUpdateTime())
|
||||
.orderByDesc(BaseDO::getUpdateTime);
|
||||
.orderByDesc(BaseDO::getCreateTime);
|
||||
return selectPage(reqVO, queryWrapper);
|
||||
}
|
||||
|
||||
@@ -43,6 +45,14 @@ public interface ProductMapper extends BaseMapperX<ProductDO> {
|
||||
.orderByDesc(ProductDO::getCode));
|
||||
}
|
||||
|
||||
@Select("""
|
||||
SELECT status_code AS statusCode, COUNT(*) AS countValue
|
||||
FROM rdms_product
|
||||
WHERE deleted = b'0'
|
||||
GROUP BY status_code
|
||||
""")
|
||||
List<Map<String, Object>> selectStatusCountList();
|
||||
|
||||
default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) {
|
||||
ProductDO update = new ProductDO();
|
||||
update.setStatusCode(toStatus);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectPageReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface ProjectMapper extends BaseMapperX<ProjectDO> {
|
||||
|
||||
default PageResult<ProjectDO> selectPage(ProjectPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<ProjectDO> queryWrapper = new LambdaQueryWrapperX<>();
|
||||
if (StringUtils.hasText(reqVO.getKeyword())) {
|
||||
queryWrapper.and(wrapper -> wrapper.like(ProjectDO::getProjectCode, reqVO.getKeyword())
|
||||
.or()
|
||||
.like(ProjectDO::getProjectName, reqVO.getKeyword()));
|
||||
}
|
||||
queryWrapper.eqIfPresent(ProjectDO::getProjectType, reqVO.getProjectType())
|
||||
.eqIfPresent(ProjectDO::getDirectionCode, reqVO.getDirectionCode())
|
||||
.eqIfPresent(ProjectDO::getProductId, reqVO.getProductId())
|
||||
.eqIfPresent(ProjectDO::getManagerUserId, reqVO.getManagerUserId())
|
||||
.eqIfPresent(ProjectDO::getStatusCode, reqVO.getStatusCode())
|
||||
.betweenIfPresent(BaseDO::getUpdateTime, reqVO.getUpdateTime())
|
||||
.orderByDesc(BaseDO::getCreateTime);
|
||||
return selectPage(reqVO, queryWrapper);
|
||||
}
|
||||
|
||||
default ProjectDO selectByCode(String projectCode) {
|
||||
return selectOne(ProjectDO::getProjectCode, projectCode);
|
||||
}
|
||||
|
||||
default ProjectDO selectActiveByProductIdAndName(Long productId, String projectName, String excludedStatusCode) {
|
||||
LambdaQueryWrapperX<ProjectDO> queryWrapper = new LambdaQueryWrapperX<>();
|
||||
queryWrapper.eq(ProjectDO::getProjectName, projectName)
|
||||
.ne(ProjectDO::getStatusCode, excludedStatusCode);
|
||||
if (productId == null) {
|
||||
queryWrapper.isNull(ProjectDO::getProductId);
|
||||
} else {
|
||||
queryWrapper.eq(ProjectDO::getProductId, productId);
|
||||
}
|
||||
return selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
default List<ProjectDO> selectActiveMainlineListByProductId(Long productId, Collection<String> projectTypes,
|
||||
String excludedStatusCode) {
|
||||
return selectList(new LambdaQueryWrapperX<ProjectDO>()
|
||||
.eq(ProjectDO::getProductId, productId)
|
||||
.in(ProjectDO::getProjectType, projectTypes)
|
||||
.ne(ProjectDO::getStatusCode, excludedStatusCode));
|
||||
}
|
||||
|
||||
@Select("""
|
||||
SELECT status_code AS statusCode, COUNT(*) AS countValue
|
||||
FROM rdms_project
|
||||
WHERE deleted = b'0'
|
||||
GROUP BY status_code
|
||||
""")
|
||||
List<Map<String, Object>> selectStatusCountList();
|
||||
|
||||
default List<ProjectDO> selectListByCodePrefix(String codePrefix) {
|
||||
return selectList(new LambdaQueryWrapperX<ProjectDO>()
|
||||
.likeRight(ProjectDO::getProjectCode, codePrefix)
|
||||
.orderByDesc(ProjectDO::getProjectCode));
|
||||
}
|
||||
|
||||
default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) {
|
||||
ProjectDO update = new ProjectDO();
|
||||
update.setStatusCode(toStatus);
|
||||
update.setLastStatusReason(lastStatusReason);
|
||||
return update(update, new LambdaQueryWrapperX<ProjectDO>()
|
||||
.eq(ProjectDO::getId, id)
|
||||
.eq(ProjectDO::getStatusCode, fromStatus));
|
||||
}
|
||||
|
||||
default int deleteByIdAndStatus(Long id, String statusCode) {
|
||||
return delete(new LambdaQueryWrapperX<ProjectDO>()
|
||||
.eq(ProjectDO::getId, id)
|
||||
.eq(ProjectDO::getStatusCode, statusCode));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectStatusLogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProjectStatusLogMapper extends BaseMapperX<ProjectStatusLogDO> {
|
||||
|
||||
default List<ProjectStatusLogDO> selectListByProjectId(Long projectId, String actionType, LocalDateTime[] operateTime) {
|
||||
return selectList(new LambdaQueryWrapperX<ProjectStatusLogDO>()
|
||||
.eq(ProjectStatusLogDO::getProjectId, projectId)
|
||||
.eqIfPresent(ProjectStatusLogDO::getActionType, actionType)
|
||||
.betweenIfPresent(BaseDO::getCreateTime, operateTime)
|
||||
.orderByDesc(BaseDO::getCreateTime)
|
||||
.orderByDesc(ProjectStatusLogDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project.execution;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.execution.vo.member.ExecutionMemberLogPageReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.execution.ExecutionMemberLogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ExecutionMemberLogMapper extends BaseMapperX<ExecutionMemberLogDO> {
|
||||
|
||||
/**
|
||||
* 分页查询执行成员变更历史,按 actionTime DESC, id DESC 排序。
|
||||
* 支持按 actionType[] / userId / 时间范围筛选。
|
||||
*/
|
||||
default PageResult<ExecutionMemberLogDO> selectPageByExecutionId(Long executionId,
|
||||
ExecutionMemberLogPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<ExecutionMemberLogDO> queryWrapper = new LambdaQueryWrapperX<ExecutionMemberLogDO>()
|
||||
.eq(ExecutionMemberLogDO::getExecutionId, executionId)
|
||||
.inIfPresent(ExecutionMemberLogDO::getActionType, reqVO.getActionTypes())
|
||||
.eqIfPresent(ExecutionMemberLogDO::getUserId, reqVO.getUserId())
|
||||
.geIfPresent(ExecutionMemberLogDO::getActionTime, reqVO.getStartTime())
|
||||
.leIfPresent(ExecutionMemberLogDO::getActionTime, reqVO.getEndTime())
|
||||
.orderByDesc(ExecutionMemberLogDO::getActionTime)
|
||||
.orderByDesc(ExecutionMemberLogDO::getId);
|
||||
return selectPage(reqVO, queryWrapper);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project.execution;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.execution.ExecutionMemberDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ExecutionMemberMapper extends BaseMapperX<ExecutionMemberDO> {
|
||||
|
||||
default List<ExecutionMemberDO> selectListByExecutionId(Long executionId) {
|
||||
return selectList(new LambdaQueryWrapperX<ExecutionMemberDO>()
|
||||
.eq(ExecutionMemberDO::getExecutionId, executionId)
|
||||
.orderByAsc(ExecutionMemberDO::getRemovedAt)
|
||||
.orderByAsc(ExecutionMemberDO::getJoinedAt)
|
||||
.orderByAsc(ExecutionMemberDO::getId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅返当前活跃成员(removed_at IS NULL)。B 模型下同 userId 至多一段未失效。
|
||||
*/
|
||||
default List<ExecutionMemberDO> selectActiveListByExecutionId(Long executionId) {
|
||||
return selectList(new LambdaQueryWrapperX<ExecutionMemberDO>()
|
||||
.eq(ExecutionMemberDO::getExecutionId, executionId)
|
||||
.isNull(ExecutionMemberDO::getRemovedAt)
|
||||
.orderByAsc(ExecutionMemberDO::getJoinedAt)
|
||||
.orderByAsc(ExecutionMemberDO::getId));
|
||||
}
|
||||
|
||||
default ExecutionMemberDO selectByExecutionIdAndUserId(Long executionId, Long userId) {
|
||||
return selectOne(new LambdaQueryWrapperX<ExecutionMemberDO>()
|
||||
.eq(ExecutionMemberDO::getExecutionId, executionId)
|
||||
.eq(ExecutionMemberDO::getUserId, userId));
|
||||
}
|
||||
|
||||
default ExecutionMemberDO selectByIdAndExecutionId(Long id, Long executionId) {
|
||||
return selectOne(new LambdaQueryWrapperX<ExecutionMemberDO>()
|
||||
.eq(ExecutionMemberDO::getId, id)
|
||||
.eq(ExecutionMemberDO::getExecutionId, executionId));
|
||||
}
|
||||
|
||||
default ExecutionMemberDO selectActiveByExecutionIdAndUserId(Long executionId, Long userId) {
|
||||
return selectOne(new LambdaQueryWrapperX<ExecutionMemberDO>()
|
||||
.eq(ExecutionMemberDO::getExecutionId, executionId)
|
||||
.eq(ExecutionMemberDO::getUserId, userId)
|
||||
.isNull(ExecutionMemberDO::getRemovedAt));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project.execution;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
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.ProjectExecutionPageReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.execution.ProjectExecutionDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProjectExecutionMapper extends BaseMapperX<ProjectExecutionDO> {
|
||||
|
||||
default ProjectExecutionDO selectByProjectIdAndId(Long projectId, Long executionId) {
|
||||
return selectOne(new LambdaQueryWrapperX<ProjectExecutionDO>()
|
||||
.eq(ProjectExecutionDO::getProjectId, projectId)
|
||||
.eq(ProjectExecutionDO::getId, executionId));
|
||||
}
|
||||
|
||||
default ProjectExecutionDO selectByProjectIdAndName(Long projectId, String executionName) {
|
||||
return selectOne(new LambdaQueryWrapperX<ProjectExecutionDO>()
|
||||
.eq(ProjectExecutionDO::getProjectId, projectId)
|
||||
.eq(ProjectExecutionDO::getExecutionName, executionName));
|
||||
}
|
||||
|
||||
default PageResult<ProjectExecutionDO> selectPageByProjectId(Long projectId, ProjectExecutionPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<ProjectExecutionDO> queryWrapper = new LambdaQueryWrapperX<ProjectExecutionDO>()
|
||||
.eq(ProjectExecutionDO::getProjectId, projectId)
|
||||
.eqIfPresent(ProjectExecutionDO::getExecutionType, reqVO.getExecutionType())
|
||||
.eqIfPresent(ProjectExecutionDO::getOwnerId, reqVO.getOwnerId())
|
||||
.eqIfPresent(ProjectExecutionDO::getStatusCode, reqVO.getStatusCode())
|
||||
.betweenIfPresent(BaseDO::getUpdateTime, reqVO.getUpdateTime())
|
||||
.orderByDesc(BaseDO::getUpdateTime)
|
||||
.orderByDesc(ProjectExecutionDO::getId);
|
||||
if (StringUtils.hasText(reqVO.getKeyword())) {
|
||||
queryWrapper.and(wrapper -> wrapper.like(ProjectExecutionDO::getExecutionName, reqVO.getKeyword()));
|
||||
}
|
||||
return selectPage(reqVO, queryWrapper);
|
||||
}
|
||||
|
||||
default Integer countNonTerminalByProjectIdAndOwnerId(Long projectId, Long ownerId, List<String> terminalStatusCodes) {
|
||||
LambdaQueryWrapperX<ProjectExecutionDO> queryWrapper = new LambdaQueryWrapperX<ProjectExecutionDO>()
|
||||
.eq(ProjectExecutionDO::getProjectId, projectId)
|
||||
.eq(ProjectExecutionDO::getOwnerId, ownerId);
|
||||
if (terminalStatusCodes != null && !terminalStatusCodes.isEmpty()) {
|
||||
queryWrapper.notIn(ProjectExecutionDO::getStatusCode, terminalStatusCodes);
|
||||
}
|
||||
return Math.toIntExact(selectCount(queryWrapper));
|
||||
}
|
||||
|
||||
default Integer countByProjectIdAndStatusCode(Long projectId, ProjectExecutionStatusBoardReqVO reqVO, String statusCode) {
|
||||
LambdaQueryWrapperX<ProjectExecutionDO> queryWrapper = new LambdaQueryWrapperX<ProjectExecutionDO>()
|
||||
.eq(ProjectExecutionDO::getProjectId, projectId)
|
||||
.eq(ProjectExecutionDO::getStatusCode, statusCode)
|
||||
.eqIfPresent(ProjectExecutionDO::getExecutionType, reqVO.getExecutionType())
|
||||
.eqIfPresent(ProjectExecutionDO::getOwnerId, reqVO.getOwnerId())
|
||||
.betweenIfPresent(BaseDO::getUpdateTime, reqVO.getUpdateTime());
|
||||
if (StringUtils.hasText(reqVO.getKeyword())) {
|
||||
queryWrapper.and(wrapper -> wrapper.like(ProjectExecutionDO::getExecutionName, reqVO.getKeyword()));
|
||||
}
|
||||
return Math.toIntExact(selectCount(queryWrapper));
|
||||
}
|
||||
|
||||
default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) {
|
||||
ProjectExecutionDO update = new ProjectExecutionDO();
|
||||
update.setStatusCode(toStatus);
|
||||
update.setLastStatusReason(lastStatusReason);
|
||||
return update(update, new LambdaQueryWrapperX<ProjectExecutionDO>()
|
||||
.eq(ProjectExecutionDO::getId, id)
|
||||
.eq(ProjectExecutionDO::getStatusCode, fromStatus));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project.execution;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.execution.ProjectExecutionStatusLogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProjectExecutionStatusLogMapper extends BaseMapperX<ProjectExecutionStatusLogDO> {
|
||||
|
||||
default List<ProjectExecutionStatusLogDO> selectListByExecutionId(Long executionId, String actionType,
|
||||
LocalDateTime[] operateTime) {
|
||||
return selectList(new LambdaQueryWrapperX<ProjectExecutionStatusLogDO>()
|
||||
.eq(ProjectExecutionStatusLogDO::getExecutionId, executionId)
|
||||
.eqIfPresent(ProjectExecutionStatusLogDO::getActionType, actionType)
|
||||
.betweenIfPresent(BaseDO::getCreateTime, operateTime)
|
||||
.orderByDesc(BaseDO::getCreateTime)
|
||||
.orderByDesc(ProjectExecutionStatusLogDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project.task;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.task.vo.ProjectTaskStatusBoardReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.task.vo.ProjectTaskPageReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.task.ProjectTaskDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface ProjectTaskMapper extends BaseMapperX<ProjectTaskDO> {
|
||||
|
||||
default ProjectTaskDO selectByProjectIdAndExecutionIdAndId(Long projectId, Long executionId, Long taskId) {
|
||||
return selectOne(new LambdaQueryWrapperX<ProjectTaskDO>()
|
||||
.eq(ProjectTaskDO::getProjectId, projectId)
|
||||
.eq(ProjectTaskDO::getExecutionId, executionId)
|
||||
.eq(ProjectTaskDO::getId, taskId));
|
||||
}
|
||||
|
||||
default PageResult<ProjectTaskDO> selectPageByExecutionId(Long projectId, Long executionId, ProjectTaskPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<ProjectTaskDO> queryWrapper = new LambdaQueryWrapperX<>();
|
||||
queryWrapper.eq(ProjectTaskDO::getProjectId, projectId);
|
||||
queryWrapper.eq(ProjectTaskDO::getExecutionId, executionId);
|
||||
queryWrapper.eqIfPresent(ProjectTaskDO::getParentTaskId, reqVO.getParentTaskId());
|
||||
queryWrapper.eqIfPresent(ProjectTaskDO::getOwnerId, reqVO.getOwnerId());
|
||||
queryWrapper.eqIfPresent(ProjectTaskDO::getStatusCode, reqVO.getStatusCode());
|
||||
queryWrapper.betweenIfPresent(BaseDO::getUpdateTime, reqVO.getUpdateTime());
|
||||
queryWrapper.orderByAsc(ProjectTaskDO::getParentTaskId);
|
||||
queryWrapper.orderByDesc(BaseDO::getUpdateTime);
|
||||
queryWrapper.orderByDesc(ProjectTaskDO::getId);
|
||||
if (StringUtils.hasText(reqVO.getKeyword())) {
|
||||
queryWrapper.and(wrapper -> wrapper.like(ProjectTaskDO::getTaskTitle, reqVO.getKeyword()));
|
||||
}
|
||||
return selectPage(reqVO, queryWrapper);
|
||||
}
|
||||
|
||||
default int updateStatusByIdAndStatus(Long id, String fromStatus, String toStatus, String lastStatusReason) {
|
||||
ProjectTaskDO update = new ProjectTaskDO();
|
||||
update.setStatusCode(toStatus);
|
||||
update.setLastStatusReason(lastStatusReason);
|
||||
return update(update, new LambdaQueryWrapperX<ProjectTaskDO>()
|
||||
.eq(ProjectTaskDO::getId, id)
|
||||
.eq(ProjectTaskDO::getStatusCode, fromStatus));
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅更新实际开始/结束日期。null 字段依据全局 FieldStrategy 不会被覆盖。
|
||||
*/
|
||||
default int updateActualDatesById(Long id, LocalDate actualStartDate, LocalDate actualEndDate) {
|
||||
if (actualStartDate == null && actualEndDate == null) {
|
||||
return 0;
|
||||
}
|
||||
ProjectTaskDO update = new ProjectTaskDO();
|
||||
update.setActualStartDate(actualStartDate);
|
||||
update.setActualEndDate(actualEndDate);
|
||||
return update(update, new LambdaQueryWrapperX<ProjectTaskDO>()
|
||||
.eq(ProjectTaskDO::getId, id));
|
||||
}
|
||||
|
||||
default Integer countChildrenNotInStatus(Long parentTaskId, List<String> terminalStatusCodes) {
|
||||
LambdaQueryWrapperX<ProjectTaskDO> queryWrapper = new LambdaQueryWrapperX<ProjectTaskDO>()
|
||||
.eq(ProjectTaskDO::getParentTaskId, parentTaskId);
|
||||
if (terminalStatusCodes != null && !terminalStatusCodes.isEmpty()) {
|
||||
queryWrapper.notIn(ProjectTaskDO::getStatusCode, terminalStatusCodes);
|
||||
}
|
||||
return Math.toIntExact(selectCount(queryWrapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* 数指定父任务下的直接子任务(不区分状态)。用于"是否叶子任务"判定与进度汇总。
|
||||
*/
|
||||
default int countChildrenByParentTaskId(Long parentTaskId) {
|
||||
if (parentTaskId == null) {
|
||||
return 0;
|
||||
}
|
||||
return Math.toIntExact(selectCount(new LambdaQueryWrapperX<ProjectTaskDO>()
|
||||
.eq(ProjectTaskDO::getParentTaskId, parentTaskId)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取直接子任务的 progressRate 列表(用于父任务进度简单平均汇总)。
|
||||
* 只读取必要字段,避免拉回整行;逻辑删除的子任务由 BaseMapper 自动过滤。
|
||||
*/
|
||||
default List<ProjectTaskDO> selectChildrenProgressByParentTaskId(Long parentTaskId) {
|
||||
if (parentTaskId == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return selectList(new LambdaQueryWrapperX<ProjectTaskDO>()
|
||||
.select(ProjectTaskDO::getId, ProjectTaskDO::getProgressRate)
|
||||
.eq(ProjectTaskDO::getParentTaskId, parentTaskId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅更新单个任务的 progressRate,不动其他字段(避免污染 lastStatusReason 等)。
|
||||
*/
|
||||
default int updateProgressRateById(Long id, BigDecimal progressRate) {
|
||||
ProjectTaskDO update = new ProjectTaskDO();
|
||||
update.setProgressRate(progressRate);
|
||||
return update(update, new LambdaQueryWrapperX<ProjectTaskDO>()
|
||||
.eq(ProjectTaskDO::getId, id));
|
||||
}
|
||||
|
||||
default Integer countByProjectIdAndExecutionIdAndStatusCode(Long projectId, Long executionId,
|
||||
ProjectTaskStatusBoardReqVO reqVO,
|
||||
String statusCode) {
|
||||
LambdaQueryWrapperX<ProjectTaskDO> queryWrapper = new LambdaQueryWrapperX<ProjectTaskDO>()
|
||||
.eq(ProjectTaskDO::getProjectId, projectId)
|
||||
.eq(ProjectTaskDO::getExecutionId, executionId)
|
||||
.eq(ProjectTaskDO::getStatusCode, statusCode)
|
||||
.eqIfPresent(ProjectTaskDO::getParentTaskId, reqVO.getParentTaskId())
|
||||
.eqIfPresent(ProjectTaskDO::getOwnerId, reqVO.getOwnerId())
|
||||
.betweenIfPresent(BaseDO::getUpdateTime, reqVO.getUpdateTime());
|
||||
if (StringUtils.hasText(reqVO.getKeyword())) {
|
||||
queryWrapper.and(wrapper -> wrapper.like(ProjectTaskDO::getTaskTitle, reqVO.getKeyword()));
|
||||
}
|
||||
return Math.toIntExact(selectCount(queryWrapper));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project.task;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.task.ProjectTaskStatusLogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface ProjectTaskStatusLogMapper extends BaseMapperX<ProjectTaskStatusLogDO> {
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project.task;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.task.vo.assignee.TaskAssigneeLogPageReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.task.TaskAssigneeLogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface TaskAssigneeLogMapper extends BaseMapperX<TaskAssigneeLogDO> {
|
||||
|
||||
/**
|
||||
* 分页查询协办人变更历史,按 actionTime DESC, id DESC 排序。
|
||||
* 支持按 actionType[] / userId / 时间范围筛选。
|
||||
*/
|
||||
default PageResult<TaskAssigneeLogDO> selectPageByTaskId(Long taskId, TaskAssigneeLogPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<TaskAssigneeLogDO> queryWrapper = new LambdaQueryWrapperX<TaskAssigneeLogDO>()
|
||||
.eq(TaskAssigneeLogDO::getTaskId, taskId)
|
||||
.inIfPresent(TaskAssigneeLogDO::getActionType, reqVO.getActionTypes())
|
||||
.eqIfPresent(TaskAssigneeLogDO::getUserId, reqVO.getUserId())
|
||||
.geIfPresent(TaskAssigneeLogDO::getActionTime, reqVO.getStartTime())
|
||||
.leIfPresent(TaskAssigneeLogDO::getActionTime, reqVO.getEndTime())
|
||||
.orderByDesc(TaskAssigneeLogDO::getActionTime)
|
||||
.orderByDesc(TaskAssigneeLogDO::getId);
|
||||
return selectPage(reqVO, queryWrapper);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project.task;
|
||||
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.task.TaskAssigneeDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface TaskAssigneeMapper extends BaseMapperX<TaskAssigneeDO> {
|
||||
|
||||
/**
|
||||
* 查同一任务下指定 userId 当前活跃的协办段(removed_at IS NULL)。失效段不会命中。
|
||||
*/
|
||||
default TaskAssigneeDO selectActiveByTaskIdAndUserId(Long taskId, Long userId) {
|
||||
return selectOne(new LambdaQueryWrapperX<TaskAssigneeDO>()
|
||||
.eq(TaskAssigneeDO::getTaskId, taskId)
|
||||
.eq(TaskAssigneeDO::getUserId, userId)
|
||||
.isNull(TaskAssigneeDO::getRemovedAt));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查指定任务下所有当前活跃协办,按加入时间正序。
|
||||
*/
|
||||
default List<TaskAssigneeDO> selectActiveListByTaskId(Long taskId) {
|
||||
return selectList(new LambdaQueryWrapperX<TaskAssigneeDO>()
|
||||
.eq(TaskAssigneeDO::getTaskId, taskId)
|
||||
.isNull(TaskAssigneeDO::getRemovedAt)
|
||||
.orderByAsc(TaskAssigneeDO::getJoinedAt)
|
||||
.orderByAsc(TaskAssigneeDO::getId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查多个任务的活跃协办(分页装配 N+1 优化)。
|
||||
*/
|
||||
default List<TaskAssigneeDO> selectActiveListByTaskIds(Collection<Long> taskIds) {
|
||||
if (taskIds == null || taskIds.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return selectList(new LambdaQueryWrapperX<TaskAssigneeDO>()
|
||||
.in(TaskAssigneeDO::getTaskId, taskIds)
|
||||
.isNull(TaskAssigneeDO::getRemovedAt)
|
||||
.orderByAsc(TaskAssigneeDO::getTaskId)
|
||||
.orderByAsc(TaskAssigneeDO::getJoinedAt)
|
||||
.orderByAsc(TaskAssigneeDO::getId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 按主键 + 任务 ID 双键查;返回的记录可能已失效(removed_at != null),由调用方判断。
|
||||
*/
|
||||
default TaskAssigneeDO selectByIdAndTaskId(Long id, Long taskId) {
|
||||
return selectOne(new LambdaQueryWrapperX<TaskAssigneeDO>()
|
||||
.eq(TaskAssigneeDO::getId, id)
|
||||
.eq(TaskAssigneeDO::getTaskId, taskId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.njcn.rdms.module.project.dal.mysql.project.task;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.task.vo.worklog.TaskWorklogPageReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.task.TaskWorklogDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mapper
|
||||
public interface TaskWorklogMapper extends BaseMapperX<TaskWorklogDO> {
|
||||
|
||||
/**
|
||||
* 按主键 + 任务 ID 双键查;逻辑删除的记录不会命中(依赖 BaseMapper 自带的 deleted=0 过滤)。
|
||||
* 用于 update / delete 前的归属校验。
|
||||
*/
|
||||
default TaskWorklogDO selectByIdAndTaskId(Long id, Long taskId) {
|
||||
return selectOne(new LambdaQueryWrapperX<TaskWorklogDO>()
|
||||
.eq(TaskWorklogDO::getId, id)
|
||||
.eq(TaskWorklogDO::getTaskId, taskId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务工时分页:按 workDate DESC, id DESC;支持按填报人 / 日期区间筛选。
|
||||
*/
|
||||
default PageResult<TaskWorklogDO> selectPageByTaskId(Long taskId, TaskWorklogPageReqVO reqVO) {
|
||||
LambdaQueryWrapperX<TaskWorklogDO> queryWrapper = new LambdaQueryWrapperX<TaskWorklogDO>()
|
||||
.eq(TaskWorklogDO::getTaskId, taskId)
|
||||
.eqIfPresent(TaskWorklogDO::getUserId, reqVO.getUserId())
|
||||
.geIfPresent(TaskWorklogDO::getWorkDate, reqVO.getStartDate())
|
||||
.leIfPresent(TaskWorklogDO::getWorkDate, reqVO.getEndDate())
|
||||
.orderByDesc(TaskWorklogDO::getWorkDate)
|
||||
.orderByDesc(TaskWorklogDO::getId);
|
||||
return selectPage(reqVO, queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 单任务工时汇总(分钟)。无记录时返回 0;逻辑删除的记录不参与汇总。
|
||||
*/
|
||||
@Select("""
|
||||
SELECT COALESCE(SUM(duration_minutes), 0)
|
||||
FROM rdms_task_worklog
|
||||
WHERE deleted = b'0' AND task_id = #{taskId}
|
||||
""")
|
||||
Long sumDurationByTaskId(@Param("taskId") Long taskId);
|
||||
|
||||
/**
|
||||
* 批量任务工时汇总(分钟),返回 [{taskId, total}]。用于详情/分页装配避免 N+1。
|
||||
*/
|
||||
@Select("""
|
||||
<script>
|
||||
SELECT task_id AS taskId, COALESCE(SUM(duration_minutes), 0) AS total
|
||||
FROM rdms_task_worklog
|
||||
WHERE deleted = b'0' AND task_id IN
|
||||
<foreach collection="taskIds" item="id" open="(" separator="," close=")">#{id}</foreach>
|
||||
GROUP BY task_id
|
||||
</script>
|
||||
""")
|
||||
List<Map<String, Object>> sumDurationGroupByTaskIds(@Param("taskIds") Collection<Long> taskIds);
|
||||
|
||||
/**
|
||||
* 是否存在任意工时记录。叶子转父校验用(拆子任务前必须删完工时)。
|
||||
*/
|
||||
default boolean existsByTaskId(Long taskId) {
|
||||
if (taskId == null) {
|
||||
return false;
|
||||
}
|
||||
return selectCount(new LambdaQueryWrapperX<TaskWorklogDO>()
|
||||
.eq(TaskWorklogDO::getTaskId, taskId)) > 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Mapper
|
||||
public interface ObjectStatusModelMapper extends BaseMapperX<ObjectStatusModelDO> {
|
||||
@@ -23,10 +24,37 @@ public interface ObjectStatusModelMapper extends BaseMapperX<ObjectStatusModelDO
|
||||
.eq(ObjectStatusModelDO::getStatus, 0));
|
||||
}
|
||||
|
||||
default ObjectStatusModelDO selectInitialByObjectTypeEnabled(String objectType) {
|
||||
return selectOne(new LambdaQueryWrapperX<ObjectStatusModelDO>()
|
||||
.eq(ObjectStatusModelDO::getObjectType, objectType)
|
||||
.eq(ObjectStatusModelDO::getInitialFlag, true)
|
||||
.eq(ObjectStatusModelDO::getStatus, 0));
|
||||
}
|
||||
|
||||
default List<ObjectStatusModelDO> selectListByObjectType(String objectType) {
|
||||
return selectList(new LambdaQueryWrapperX<ObjectStatusModelDO>()
|
||||
.eq(ObjectStatusModelDO::getObjectType, objectType)
|
||||
.orderByAsc(ObjectStatusModelDO::getSort));
|
||||
}
|
||||
|
||||
default List<ObjectStatusModelDO> selectListByObjectTypeEnabled(String objectType) {
|
||||
return selectList(new LambdaQueryWrapperX<ObjectStatusModelDO>()
|
||||
.eq(ObjectStatusModelDO::getObjectType, objectType)
|
||||
.eq(ObjectStatusModelDO::getStatus, 0)
|
||||
.orderByAsc(ObjectStatusModelDO::getSort));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询某对象类型下所有已启用的终态状态码。
|
||||
*/
|
||||
default List<String> selectTerminalStatusCodesByObjectTypeEnabled(String objectType) {
|
||||
return selectList(new LambdaQueryWrapperX<ObjectStatusModelDO>()
|
||||
.eq(ObjectStatusModelDO::getObjectType, objectType)
|
||||
.eq(ObjectStatusModelDO::getStatus, 0)
|
||||
.eq(ObjectStatusModelDO::getTerminalFlag, true))
|
||||
.stream()
|
||||
.map(ObjectStatusModelDO::getStatusCode)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.njcn.rdms.module.project.framework.rpc.config;
|
||||
|
||||
import com.njcn.rdms.module.system.api.dict.DictDataApi;
|
||||
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
|
||||
import com.njcn.rdms.module.system.api.user.AdminUserApi;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
@@ -9,6 +10,6 @@ import org.springframework.context.annotation.Configuration;
|
||||
* Project 模块的 RPC 配置
|
||||
*/
|
||||
@Configuration(value = "projectRpcConfiguration", proxyBeanMethods = false)
|
||||
@EnableFeignClients(clients = {AdminUserApi.class, ObjectPermissionApi.class})
|
||||
@EnableFeignClients(clients = {AdminUserApi.class, ObjectPermissionApi.class, DictDataApi.class})
|
||||
public class RpcConfiguration {
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.njcn.rdms.module.project.framework.security.service;
|
||||
|
||||
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.rdms.module.project.constant.ObjectRoleConstants;
|
||||
import com.njcn.rdms.module.project.constant.ProductObjectConstants;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
|
||||
import com.njcn.rdms.module.system.enums.permission.PermissionScopeTypeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
@@ -24,9 +25,6 @@ import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil
|
||||
@Service
|
||||
public class ProductObjectPermissionService implements ObjectPermissionService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
private static final String ROLE_SCOPE_OBJECT = PermissionScopeTypeEnum.OBJECT.getScopeType();
|
||||
|
||||
@Resource
|
||||
private UserObjectRoleMapper userObjectRoleMapper;
|
||||
@Resource
|
||||
@@ -34,7 +32,7 @@ public class ProductObjectPermissionService implements ObjectPermissionService {
|
||||
|
||||
@Override
|
||||
public String getObjectType() {
|
||||
return PRODUCT_OBJECT_TYPE;
|
||||
return ProductObjectConstants.OBJECT_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,7 +42,7 @@ public class ProductObjectPermissionService implements ObjectPermissionService {
|
||||
}
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
UserObjectRoleDO currentMember = userObjectRoleMapper
|
||||
.selectActiveByObjectAndUserId(PRODUCT_OBJECT_TYPE, objectId, loginUserId);
|
||||
.selectActiveByObjectAndUserId(ProductObjectConstants.OBJECT_TYPE, objectId, loginUserId);
|
||||
if (currentMember == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_OBJECT_PERMISSION_DENIED,
|
||||
buildDeniedPermission(permission, memberOnly));
|
||||
@@ -60,7 +58,8 @@ public class ProductObjectPermissionService implements ObjectPermissionService {
|
||||
|
||||
private Set<String> getRolePermissions(Long roleId) {
|
||||
Set<String> permissions = objectPermissionApi
|
||||
.getObjectRolePermissions(roleId, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE)
|
||||
.getObjectRolePermissions(roleId, ObjectRoleConstants.ROLE_SCOPE_OBJECT,
|
||||
ProductObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
if (permissions == null || permissions.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.njcn.rdms.module.project.framework.security.service;
|
||||
|
||||
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.rdms.module.project.constant.ObjectRoleConstants;
|
||||
import com.njcn.rdms.module.project.constant.ProjectObjectConstants;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||
|
||||
/**
|
||||
* 项目对象权限服务。
|
||||
*/
|
||||
@Service
|
||||
public class ProjectObjectPermissionService implements ObjectPermissionService {
|
||||
|
||||
@Resource
|
||||
private UserObjectRoleMapper userObjectRoleMapper;
|
||||
@Resource
|
||||
private ObjectPermissionApi objectPermissionApi;
|
||||
|
||||
@Override
|
||||
public String getObjectType() {
|
||||
return ProjectObjectConstants.OBJECT_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkPermission(Long objectId, String permission, boolean memberOnly) {
|
||||
if (objectId == null) {
|
||||
throw invalidParamException("对象编号不能为空");
|
||||
}
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
UserObjectRoleDO currentMember = userObjectRoleMapper
|
||||
.selectActiveByObjectAndUserId(ProjectObjectConstants.OBJECT_TYPE, objectId, loginUserId);
|
||||
if (currentMember == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_OBJECT_PERMISSION_DENIED,
|
||||
buildDeniedPermission(permission, memberOnly));
|
||||
}
|
||||
if (memberOnly) {
|
||||
return;
|
||||
}
|
||||
String normalizedPermission = normalizePermission(permission);
|
||||
if (!getRolePermissions(currentMember.getRoleId()).contains(normalizedPermission)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_OBJECT_PERMISSION_DENIED, normalizedPermission);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getRolePermissions(Long roleId) {
|
||||
Set<String> permissions = objectPermissionApi
|
||||
.getObjectRolePermissions(roleId, ObjectRoleConstants.ROLE_SCOPE_OBJECT, ProjectObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
if (permissions == null || permissions.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return permissions.stream()
|
||||
.filter(StringUtils::hasText)
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
private String normalizePermission(String permission) {
|
||||
if (!StringUtils.hasText(permission)) {
|
||||
throw invalidParamException("对象权限码不能为空");
|
||||
}
|
||||
return permission.trim();
|
||||
}
|
||||
|
||||
private String buildDeniedPermission(String permission, boolean memberOnly) {
|
||||
return memberOnly ? "member" : normalizePermission(permission);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.common.util.object.PageUtils;
|
||||
import com.njcn.rdms.module.project.constant.ObjectActivityConstants;
|
||||
import com.njcn.rdms.module.project.constant.ObjectRoleConstants;
|
||||
import com.njcn.rdms.module.project.constant.ProductObjectConstants;
|
||||
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.dal.dataobject.audit.BizAuditLogDO;
|
||||
@@ -37,10 +39,8 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class ProductActivityQueryService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = ObjectActivityConstants.PRODUCT_BIZ_TYPE;
|
||||
private static final String MEMBER_BIZ_TYPE = ObjectActivityConstants.MEMBER_BIZ_TYPE;
|
||||
private static final String ACTIVITY_ROLE_NAME_CACHE = "project_activity_role_name#10m";
|
||||
private static final String ROLE_SCOPE_OBJECT = "object";
|
||||
|
||||
@Resource
|
||||
private ProductStatusLogMapper productStatusLogMapper;
|
||||
@@ -60,7 +60,8 @@ public class ProductActivityQueryService {
|
||||
.forEach(log -> items.add(new ActivityItem(log.getId(), log.getCreateTime(), toStatusActivity(log))));
|
||||
}
|
||||
if (includeType(reqVO.getActivityType(), ObjectActivityConstants.ACTIVITY_TYPE_PRODUCT)) {
|
||||
bizAuditLogMapper.selectListByBiz(PRODUCT_OBJECT_TYPE, productId, reqVO.getActionType(), reqVO.getOperateTime())
|
||||
bizAuditLogMapper.selectListByBiz(ProductObjectConstants.OBJECT_TYPE, productId,
|
||||
reqVO.getActionType(), reqVO.getOperateTime())
|
||||
.forEach(log -> items.add(new ActivityItem(log.getId(), log.getCreateTime(), toProductActivity(log))));
|
||||
}
|
||||
if (includeType(reqVO.getActivityType(), ObjectActivityConstants.ACTIVITY_TYPE_MEMBER)) {
|
||||
@@ -96,7 +97,7 @@ public class ProductActivityQueryService {
|
||||
}
|
||||
|
||||
Map<Long, UserObjectRoleDO> memberMap = userObjectRoleMapper
|
||||
.selectListByIdsAndObject(memberIds, PRODUCT_OBJECT_TYPE, productId)
|
||||
.selectListByIdsAndObject(memberIds, ProductObjectConstants.OBJECT_TYPE, productId)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(UserObjectRoleDO::getId, Function.identity()));
|
||||
memberLogs.stream()
|
||||
@@ -166,7 +167,8 @@ public class ProductActivityQueryService {
|
||||
return roleNameMap;
|
||||
}
|
||||
Map<Long, ObjectRoleRespDTO> roleMap = objectPermissionApi == null ? Map.of()
|
||||
: objectPermissionApi.getObjectRoleMap(missIds, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE);
|
||||
: objectPermissionApi.getObjectRoleMap(missIds, ObjectRoleConstants.ROLE_SCOPE_OBJECT,
|
||||
ProductObjectConstants.OBJECT_TYPE);
|
||||
if (roleMap == null || roleMap.isEmpty()) {
|
||||
return roleNameMap;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.common.util.object.PageUtils;
|
||||
import com.njcn.rdms.module.project.constant.ObjectActivityConstants;
|
||||
import com.njcn.rdms.module.project.constant.ObjectRoleConstants;
|
||||
import com.njcn.rdms.module.project.constant.ProductObjectConstants;
|
||||
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.dal.dataobject.audit.BizAuditLogDO;
|
||||
@@ -44,8 +46,6 @@ public class ProductActivityTimelineQueryService {
|
||||
*/
|
||||
private static final String TIMELINE_USER_NICKNAME_CACHE = "project_timeline_user_nickname#10m";
|
||||
private static final String TIMELINE_ROLE_NAME_CACHE = "project_timeline_role_name#10m";
|
||||
private static final String ROLE_SCOPE_OBJECT = "object";
|
||||
private static final String PRODUCT_OBJECT_TYPE = ObjectActivityConstants.PRODUCT_BIZ_TYPE;
|
||||
|
||||
@Resource
|
||||
private ProductStatusLogMapper productStatusLogMapper;
|
||||
@@ -85,10 +85,7 @@ public class ProductActivityTimelineQueryService {
|
||||
if (!includeType(activityType, ObjectActivityConstants.ACTIVITY_TYPE_STATUS)) {
|
||||
return;
|
||||
}
|
||||
List<String> statusActions = limitActions(actionTypes, ObjectActivityConstants.STATUS_ACTION_TYPES);
|
||||
if (shouldSkipByIntersection(actionTypes, statusActions)) {
|
||||
return;
|
||||
}
|
||||
List<String> statusActions = actionTypes == null || actionTypes.isEmpty() ? null : actionTypes;
|
||||
productStatusLogMapper.selectListByProductIdAndActions(productId, statusActions, timeRange[0], timeRange[1])
|
||||
.forEach(log -> items.add(new ActivityItem(log.getId(), log.getCreateTime(), toStatusTimeline(log))));
|
||||
}
|
||||
@@ -103,7 +100,7 @@ public class ProductActivityTimelineQueryService {
|
||||
return;
|
||||
}
|
||||
List<BizAuditLogDO> productLogs = bizAuditLogMapper.selectListByBizAndActions(
|
||||
ObjectActivityConstants.PRODUCT_BIZ_TYPE, productId, productActions, timeRange[0], timeRange[1]);
|
||||
ProductObjectConstants.OBJECT_TYPE, productId, productActions, timeRange[0], timeRange[1]);
|
||||
Set<CreateSignature> createSignatures = buildCreateSignatures(productLogs);
|
||||
for (BizAuditLogDO log : productLogs) {
|
||||
if (ObjectActivityConstants.isStatusAction(log.getActionType())) {
|
||||
@@ -205,7 +202,7 @@ public class ProductActivityTimelineQueryService {
|
||||
}
|
||||
Map<Long, UserObjectRoleDO> memberMap = new LinkedHashMap<>();
|
||||
for (UserObjectRoleDO member : userObjectRoleMapper.selectListByIdsAndObject(
|
||||
memberIds, ObjectActivityConstants.PRODUCT_BIZ_TYPE, productId)) {
|
||||
memberIds, ProductObjectConstants.OBJECT_TYPE, productId)) {
|
||||
memberMap.put(member.getId(), member);
|
||||
}
|
||||
return memberMap;
|
||||
@@ -213,7 +210,7 @@ public class ProductActivityTimelineQueryService {
|
||||
|
||||
private Set<CreateSignature> loadCreateSignatures(Long productId, LocalDateTime[] timeRange) {
|
||||
List<BizAuditLogDO> productLogs = bizAuditLogMapper.selectListByBizAndActions(
|
||||
ObjectActivityConstants.PRODUCT_BIZ_TYPE, productId,
|
||||
ProductObjectConstants.OBJECT_TYPE, productId,
|
||||
ObjectActivityConstants.PRODUCT_TIMELINE_ACTION_TYPES, timeRange[0], timeRange[1]);
|
||||
return buildCreateSignatures(productLogs);
|
||||
}
|
||||
@@ -402,7 +399,8 @@ public class ProductActivityTimelineQueryService {
|
||||
return roleNameMap;
|
||||
}
|
||||
Map<Long, ObjectRoleRespDTO> roleMap = objectPermissionApi == null ? Map.of()
|
||||
: objectPermissionApi.getObjectRoleMap(missIds, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE);
|
||||
: objectPermissionApi.getObjectRoleMap(missIds, ObjectRoleConstants.ROLE_SCOPE_OBJECT,
|
||||
ProductObjectConstants.OBJECT_TYPE);
|
||||
if (roleMap == null || roleMap.isEmpty()) {
|
||||
return roleNameMap;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.njcn.rdms.module.project.service.product;
|
||||
import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.rdms.module.project.constant.ObjectActivityConstants;
|
||||
import com.njcn.rdms.module.project.constant.ObjectRoleConstants;
|
||||
import com.njcn.rdms.module.project.constant.ProductObjectConstants;
|
||||
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;
|
||||
@@ -11,9 +13,11 @@ import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPer
|
||||
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
|
||||
import com.njcn.rdms.module.system.api.permission.dto.ObjectRoleRespDTO;
|
||||
@@ -42,16 +46,6 @@ import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil
|
||||
@Service
|
||||
public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
private static final String ROLE_SCOPE_OBJECT = "object";
|
||||
private static final String PRODUCT_QUERY_PERMISSION = "project:product:query";
|
||||
private static final String PRODUCT_UPDATE_PERMISSION = "project:product:update";
|
||||
|
||||
private static final Integer MEMBER_STATUS_ACTIVE = 0;
|
||||
private static final Integer MEMBER_STATUS_INACTIVE = 1;
|
||||
|
||||
private static final String PRODUCT_MANAGER_ROLE_CODE = "product_manager";
|
||||
|
||||
@Resource
|
||||
private ProductMapper productMapper;
|
||||
@Resource
|
||||
@@ -61,14 +55,14 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
@Resource
|
||||
private BizAuditLogMapper bizAuditLogMapper;
|
||||
@Resource
|
||||
private ObjectStatusModelMapper objectStatusModelMapper;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public List<ProductMemberRespVO> getProductMemberList(Long productId) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
List<UserObjectRoleDO> members = userObjectRoleMapper.selectListByObject(PRODUCT_OBJECT_TYPE, productId);
|
||||
List<UserObjectRoleDO> members = userObjectRoleMapper.selectListByObject(ProductObjectConstants.OBJECT_TYPE, productId);
|
||||
Map<Long, ObjectRoleRespDTO> roleMap = getRoleMap(members.stream().map(UserObjectRoleDO::getRoleId).collect(Collectors.toSet()));
|
||||
Map<Long, AdminUserRespDTO> userMap = getUserMap(members.stream().map(UserObjectRoleDO::getUserId).collect(Collectors.toSet()));
|
||||
return members.stream().map(member -> {
|
||||
@@ -82,7 +76,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
respVO.setRoleName(role == null ? null : role.getName());
|
||||
respVO.setRoleCode(role == null ? null : role.getCode());
|
||||
respVO.setManagerFlag(Objects.equals(member.getUserId(), product.getManagerUserId())
|
||||
&& Objects.equals(member.getStatus(), MEMBER_STATUS_ACTIVE));
|
||||
&& Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE));
|
||||
respVO.setStatus(member.getStatus());
|
||||
respVO.setJoinedTime(member.getJoinedTime());
|
||||
respVO.setLeftTime(member.getLeftTime());
|
||||
@@ -93,14 +87,14 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
@CheckObjectPermission(objectType = ProductObjectConstants.OBJECT_TYPE, objectId = "#productId",
|
||||
permission = ProductObjectConstants.PERMISSION_UPDATE)
|
||||
public Long createProductMember(Long productId, ProductMemberSaveReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
ProductDO product = validateProductEditable(productId);
|
||||
ObjectRoleRespDTO targetRole = validateProductRole(reqVO.getRoleId());
|
||||
UserObjectRoleDO existingMember = userObjectRoleMapper
|
||||
.selectByObjectAndUserId(PRODUCT_OBJECT_TYPE, productId, reqVO.getUserId());
|
||||
if (existingMember != null && Objects.equals(existingMember.getStatus(), MEMBER_STATUS_ACTIVE)) {
|
||||
.selectByObjectAndUserId(ProductObjectConstants.OBJECT_TYPE, productId, reqVO.getUserId());
|
||||
if (existingMember != null && Objects.equals(existingMember.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
@@ -110,10 +104,10 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
if (existingMember == null) {
|
||||
member = new UserObjectRoleDO();
|
||||
member.setUserId(reqVO.getUserId());
|
||||
member.setObjectType(PRODUCT_OBJECT_TYPE);
|
||||
member.setObjectType(ProductObjectConstants.OBJECT_TYPE);
|
||||
member.setObjectId(productId);
|
||||
member.setRoleId(targetRole.getId());
|
||||
member.setStatus(MEMBER_STATUS_ACTIVE);
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(now);
|
||||
member.setLeftTime(null);
|
||||
member.setRemark(normalizeNullableText(reqVO.getRemark()));
|
||||
@@ -122,7 +116,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
before = cloneMember(existingMember);
|
||||
member = existingMember;
|
||||
member.setRoleId(targetRole.getId());
|
||||
member.setStatus(MEMBER_STATUS_ACTIVE);
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(now);
|
||||
member.setLeftTime(null);
|
||||
member.setRemark(normalizeNullableText(reqVO.getRemark()));
|
||||
@@ -138,12 +132,12 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
@CheckObjectPermission(objectType = ProductObjectConstants.OBJECT_TYPE, objectId = "#productId",
|
||||
permission = ProductObjectConstants.PERMISSION_UPDATE)
|
||||
public void updateProductMember(Long productId, Long memberId, ProductMemberUpdateReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
ProductDO product = validateProductEditable(productId);
|
||||
UserObjectRoleDO member = validateMemberExists(productId, memberId);
|
||||
if (!Objects.equals(member.getStatus(), MEMBER_STATUS_ACTIVE)) {
|
||||
if (!Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_NOT_ACTIVE);
|
||||
}
|
||||
|
||||
@@ -170,12 +164,12 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
@CheckObjectPermission(objectType = ProductObjectConstants.OBJECT_TYPE, objectId = "#productId",
|
||||
permission = ProductObjectConstants.PERMISSION_UPDATE)
|
||||
public void inactiveProductMember(Long productId, Long memberId, ProductMemberInactiveReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
ProductDO product = validateProductEditable(productId);
|
||||
UserObjectRoleDO member = validateMemberExists(productId, memberId);
|
||||
if (!Objects.equals(member.getStatus(), MEMBER_STATUS_ACTIVE)) {
|
||||
if (!Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_NOT_ACTIVE);
|
||||
}
|
||||
if (Objects.equals(member.getUserId(), product.getManagerUserId())) {
|
||||
@@ -183,7 +177,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
}
|
||||
|
||||
UserObjectRoleDO before = cloneMember(member);
|
||||
member.setStatus(MEMBER_STATUS_INACTIVE);
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_INACTIVE);
|
||||
member.setLeftTime(LocalDateTime.now());
|
||||
userObjectRoleMapper.updateById(member);
|
||||
|
||||
@@ -202,8 +196,21 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
return product;
|
||||
}
|
||||
|
||||
private ProductDO validateProductEditable(Long productId) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
ObjectStatusModelDO statusModel = objectStatusModelMapper
|
||||
.selectByObjectTypeAndStatusCodeEnabled(ProductObjectConstants.OBJECT_TYPE, product.getStatusCode());
|
||||
if (statusModel == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
if (!Boolean.TRUE.equals(statusModel.getAllowEdit())) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_NOT_ALLOW_EDIT);
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
private UserObjectRoleDO validateMemberExists(Long productId, Long memberId) {
|
||||
UserObjectRoleDO member = userObjectRoleMapper.selectByIdAndObject(memberId, PRODUCT_OBJECT_TYPE, productId);
|
||||
UserObjectRoleDO member = userObjectRoleMapper.selectByIdAndObject(memberId, ProductObjectConstants.OBJECT_TYPE, productId);
|
||||
if (member == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_NOT_EXISTS);
|
||||
}
|
||||
@@ -212,7 +219,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
|
||||
private ObjectRoleRespDTO validateProductRole(Long roleId) {
|
||||
ObjectRoleRespDTO role = objectPermissionApi
|
||||
.getObjectRoleById(roleId, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE)
|
||||
.getObjectRoleById(roleId, ObjectRoleConstants.ROLE_SCOPE_OBJECT, ProductObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
if (role == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_MEMBER_ROLE_INVALID);
|
||||
@@ -262,7 +269,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
|
||||
private void transferPreviousManager(Long productId, Long previousManagerUserId, Long previousManagerRoleId, String reason) {
|
||||
UserObjectRoleDO existingMember = userObjectRoleMapper
|
||||
.selectByObjectAndUserId(PRODUCT_OBJECT_TYPE, productId, previousManagerUserId);
|
||||
.selectByObjectAndUserId(ProductObjectConstants.OBJECT_TYPE, productId, previousManagerUserId);
|
||||
UserObjectRoleDO before = existingMember == null ? null : cloneMember(existingMember);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
UserObjectRoleDO member;
|
||||
@@ -270,10 +277,10 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
if (existingMember == null) {
|
||||
member = new UserObjectRoleDO();
|
||||
member.setUserId(previousManagerUserId);
|
||||
member.setObjectType(PRODUCT_OBJECT_TYPE);
|
||||
member.setObjectType(ProductObjectConstants.OBJECT_TYPE);
|
||||
member.setObjectId(productId);
|
||||
member.setRoleId(previousManagerRoleId);
|
||||
member.setStatus(MEMBER_STATUS_ACTIVE);
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(now);
|
||||
member.setLeftTime(null);
|
||||
member.setRemark(null);
|
||||
@@ -282,9 +289,9 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
} else {
|
||||
member = existingMember;
|
||||
member.setRoleId(previousManagerRoleId);
|
||||
member.setStatus(MEMBER_STATUS_ACTIVE);
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
member.setLeftTime(null);
|
||||
if (!Objects.equals(before.getStatus(), MEMBER_STATUS_ACTIVE)) {
|
||||
if (!Objects.equals(before.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
|
||||
member.setJoinedTime(now);
|
||||
actionType = ObjectActivityConstants.MEMBER_ACTION_ADD;
|
||||
} else {
|
||||
@@ -296,7 +303,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
}
|
||||
|
||||
private boolean isManagerRole(ObjectRoleRespDTO role) {
|
||||
return Objects.equals(PRODUCT_MANAGER_ROLE_CODE, role.getCode());
|
||||
return Objects.equals(ProductObjectConstants.MANAGER_ROLE_CODE, role.getCode());
|
||||
}
|
||||
|
||||
private Map<Long, ObjectRoleRespDTO> getRoleMap(Set<Long> roleIds) {
|
||||
@@ -304,7 +311,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<ObjectRoleRespDTO> roles = objectPermissionApi
|
||||
.getObjectRoleList(roleIds, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE)
|
||||
.getObjectRoleList(roleIds, ObjectRoleConstants.ROLE_SCOPE_OBJECT, ProductObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
if (roles == null || roles.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
@@ -340,7 +347,7 @@ public class ProductMemberServiceImpl implements ProductMemberService {
|
||||
return;
|
||||
}
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(PRODUCT_OBJECT_TYPE);
|
||||
auditLog.setBizType(ProductObjectConstants.OBJECT_TYPE);
|
||||
auditLog.setBizId(productId);
|
||||
auditLog.setActionType(ObjectActivityConstants.PRODUCT_ACTION_CHANGE_MANAGER);
|
||||
auditLog.setFieldChanges(buildManagerFieldChanges(beforeManagerUserId, afterManagerUserId));
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.njcn.rdms.module.project.service.product;
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
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.ProductSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductStatusActionReqVO;
|
||||
@@ -61,6 +62,13 @@ public interface ProductService {
|
||||
*/
|
||||
PageResult<ProductDO> getProductPage(ProductPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获取产品入口页概览统计
|
||||
*
|
||||
* @return 产品入口页概览统计
|
||||
*/
|
||||
ProductOverviewSummaryRespVO getProductOverviewSummary();
|
||||
|
||||
/**
|
||||
* 变更产品状态
|
||||
*
|
||||
|
||||
@@ -6,18 +6,31 @@ import com.njcn.rdms.framework.common.util.json.JsonUtils;
|
||||
import com.njcn.rdms.framework.common.util.object.BeanUtils;
|
||||
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.njcn.rdms.module.project.constant.ObjectActivityConstants;
|
||||
import com.njcn.rdms.module.project.constant.ObjectRoleConstants;
|
||||
import com.njcn.rdms.module.project.constant.ProductObjectConstants;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextNavRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextProductRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductContextRoleRespVO;
|
||||
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.ProductSaveReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.ProductStatusActionReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.product.*;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingBaseInfoUpdateReqVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.product.ProductStatusLogDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.member.UserObjectRoleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductRequirementModuleMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.product.ProductStatusLogMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
|
||||
@@ -48,24 +61,6 @@ import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil
|
||||
@Service
|
||||
public class ProductServiceImpl implements ProductService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
private static final String ROLE_SCOPE_OBJECT = PermissionScopeTypeEnum.OBJECT.getScopeType();
|
||||
private static final String PRODUCT_MANAGER_ROLE_CODE = "product_manager";
|
||||
|
||||
private static final String PRODUCT_ACTIVE_STATUS = "active";
|
||||
private static final String PRODUCT_PAUSED_STATUS = "paused";
|
||||
private static final String PRODUCT_ARCHIVED_STATUS = "archived";
|
||||
private static final String PRODUCT_ABANDONED_STATUS = "abandoned";
|
||||
|
||||
private static final Integer MEMBER_STATUS_ACTIVE = 0;
|
||||
|
||||
private static final String PRODUCT_CODE_PREFIX = "CNPD";
|
||||
private static final String PRODUCT_QUERY_PERMISSION = "project:product:query";
|
||||
private static final String PRODUCT_UPDATE_PERMISSION = "project:product:update";
|
||||
private static final String PRODUCT_STATUS_PERMISSION = "project:product:status";
|
||||
private static final String PRODUCT_DELETE_PERMISSION = "project:product:delete";
|
||||
private static final String PRODUCT_DELETE_CONFIRM_TEXT = "DELETE";
|
||||
|
||||
@Resource
|
||||
private ProductMapper productMapper;
|
||||
@Resource
|
||||
@@ -75,6 +70,8 @@ public class ProductServiceImpl implements ProductService {
|
||||
@Resource
|
||||
private ObjectStatusTransitionMapper objectStatusTransitionMapper;
|
||||
@Resource
|
||||
private ObjectStatusModelMapper objectStatusModelMapper;
|
||||
@Resource
|
||||
private UserObjectRoleMapper userObjectRoleMapper;
|
||||
@Resource
|
||||
private ObjectPermissionApi objectPermissionApi;
|
||||
@@ -92,7 +89,8 @@ public class ProductServiceImpl implements ProductService {
|
||||
ProductDO product = new ProductDO();
|
||||
product.setCode(generateProductCode(createReqVO.getCode()));
|
||||
product.setDirectionCode(createReqVO.getDirectionCode());
|
||||
product.setStatusCode(PRODUCT_ACTIVE_STATUS);
|
||||
String initialStatus = getInitialStatusCode();
|
||||
product.setStatusCode(initialStatus);
|
||||
product.setName(createReqVO.getName().trim());
|
||||
product.setManagerUserId(createReqVO.getManagerUserId());
|
||||
product.setDescription(normalizeNullableText(createReqVO.getDescription()));
|
||||
@@ -100,15 +98,15 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
initManagerMemberRelation(product);
|
||||
initDefaultRequirementModule(product);
|
||||
writeBizAuditLog(product, ObjectActivityConstants.PRODUCT_ACTION_CREATE, null, PRODUCT_ACTIVE_STATUS,
|
||||
writeBizAuditLog(product, ObjectActivityConstants.PRODUCT_ACTION_CREATE, null, initialStatus,
|
||||
buildProductFieldChanges(null, product), null);
|
||||
return product.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#updateReqVO.id",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
@CheckObjectPermission(objectType = ProductObjectConstants.OBJECT_TYPE, objectId = "#updateReqVO.id",
|
||||
permission = ProductObjectConstants.PERMISSION_UPDATE)
|
||||
public void updateProduct(ProductSaveReqVO updateReqVO) {
|
||||
if (updateReqVO.getId() == null) {
|
||||
throw invalidParamException("产品编号不能为空");
|
||||
@@ -121,28 +119,25 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_UPDATE_PERMISSION)
|
||||
@CheckObjectPermission(objectType = ProductObjectConstants.OBJECT_TYPE, objectId = "#productId",
|
||||
permission = ProductObjectConstants.PERMISSION_UPDATE)
|
||||
public void updateProductBaseInfo(Long productId, ProductSettingBaseInfoUpdateReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
applyProductBaseInfoUpdate(product, reqVO.getDirectionCode(), reqVO.getName(), reqVO.getDescription());
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#id",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public ProductDO getProduct(Long id) {
|
||||
return validateProductExists(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#id", memberOnly = true)
|
||||
public ProductContextRespVO getProductContext(Long id) {
|
||||
ProductDO product = validateProductExists(id);
|
||||
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
UserObjectRoleDO currentMember = userObjectRoleMapper
|
||||
.selectActiveByObjectAndUserId(PRODUCT_OBJECT_TYPE, id, loginUserId);
|
||||
.selectActiveByObjectAndUserId(ProductObjectConstants.OBJECT_TYPE, id, loginUserId);
|
||||
ProductContextRespVO respVO = new ProductContextRespVO();
|
||||
respVO.setCurrentProduct(buildCurrentProduct(product));
|
||||
if (currentMember == null) {
|
||||
@@ -152,7 +147,8 @@ public class ProductServiceImpl implements ProductService {
|
||||
}
|
||||
|
||||
ObjectRolePermissionRespDTO permissionDetail = objectPermissionApi
|
||||
.getObjectRolePermissionDetail(currentMember.getRoleId(), ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE)
|
||||
.getObjectRolePermissionDetail(currentMember.getRoleId(), ObjectRoleConstants.ROLE_SCOPE_OBJECT,
|
||||
ProductObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
ObjectRoleRespDTO currentRole = permissionDetail == null ? null : permissionDetail.getCurrentRole();
|
||||
List<ObjectMenuRespDTO> menus = permissionDetail == null || permissionDetail.getMenus() == null
|
||||
@@ -169,10 +165,18 @@ public class ProductServiceImpl implements ProductService {
|
||||
return productMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProductOverviewSummaryRespVO getProductOverviewSummary() {
|
||||
ProductOverviewSummaryRespVO respVO = new ProductOverviewSummaryRespVO();
|
||||
respVO.setStatusCounts(buildProductStatusCounts(objectStatusModelMapper
|
||||
.selectListByObjectType(ProductObjectConstants.OBJECT_TYPE), productMapper.selectStatusCountList()));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.id",
|
||||
permission = PRODUCT_STATUS_PERMISSION)
|
||||
@CheckObjectPermission(objectType = ProductObjectConstants.OBJECT_TYPE, objectId = "#reqVO.id",
|
||||
permission = ProductObjectConstants.PERMISSION_STATUS)
|
||||
public void changeProductStatus(ProductStatusActionReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(reqVO.getId());
|
||||
String actionCode = reqVO.getActionCode().trim();
|
||||
@@ -195,8 +199,8 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.id",
|
||||
permission = PRODUCT_DELETE_PERMISSION)
|
||||
@CheckObjectPermission(objectType = ProductObjectConstants.OBJECT_TYPE, objectId = "#reqVO.id",
|
||||
permission = ProductObjectConstants.PERMISSION_DELETE)
|
||||
public void deleteProduct(ProductDeleteReqVO reqVO) {
|
||||
ProductDO product = validateProductExists(reqVO.getId());
|
||||
validateDeleteConfirmText(reqVO.getConfirmText());
|
||||
@@ -289,11 +293,11 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
@VisibleForTesting
|
||||
void validateProductEditable(ProductDO product, String directionCode, String name) {
|
||||
if (PRODUCT_ARCHIVED_STATUS.equals(product.getStatusCode())
|
||||
|| PRODUCT_ABANDONED_STATUS.equals(product.getStatusCode())) {
|
||||
ObjectStatusModelDO statusModel = validateEnabledStatusModel(product.getStatusCode());
|
||||
if (!Boolean.TRUE.equals(statusModel.getAllowEdit())) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_NOT_ALLOW_EDIT);
|
||||
}
|
||||
if (!PRODUCT_PAUSED_STATUS.equals(product.getStatusCode())) {
|
||||
if (!ProductObjectConstants.STATUS_PAUSED.equals(product.getStatusCode())) {
|
||||
return;
|
||||
}
|
||||
if (!Objects.equals(product.getDirectionCode(), directionCode)
|
||||
@@ -305,7 +309,7 @@ public class ProductServiceImpl implements ProductService {
|
||||
@VisibleForTesting
|
||||
ObjectStatusTransitionDO validateProductTransition(String fromStatusCode, String actionCode) {
|
||||
ObjectStatusTransitionDO transition = objectStatusTransitionMapper
|
||||
.selectByObjectTypeAndFromStatusAndAction(PRODUCT_OBJECT_TYPE, fromStatusCode, actionCode);
|
||||
.selectByObjectTypeAndFromStatusAndAction(ProductObjectConstants.OBJECT_TYPE, fromStatusCode, actionCode);
|
||||
if (transition == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_ACTION_NOT_ALLOWED, actionCode);
|
||||
}
|
||||
@@ -319,10 +323,30 @@ public class ProductServiceImpl implements ProductService {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ObjectStatusModelDO validateEnabledStatusModel(String statusCode) {
|
||||
ObjectStatusModelDO statusModel = objectStatusModelMapper
|
||||
.selectByObjectTypeAndStatusCodeEnabled(ProductObjectConstants.OBJECT_TYPE, statusCode);
|
||||
if (statusModel == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
return statusModel;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
String getInitialStatusCode() {
|
||||
ObjectStatusModelDO statusModel = objectStatusModelMapper
|
||||
.selectInitialByObjectTypeEnabled(ProductObjectConstants.OBJECT_TYPE);
|
||||
if (statusModel == null || !StringUtils.hasText(statusModel.getStatusCode())) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
return statusModel.getStatusCode();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateDeleteConfirmText(String confirmText) {
|
||||
String normalizedConfirmText = normalizeNullableText(confirmText);
|
||||
if (!Objects.equals(PRODUCT_DELETE_CONFIRM_TEXT, normalizedConfirmText)) {
|
||||
if (!Objects.equals(ProductObjectConstants.DELETE_CONFIRM_TEXT, normalizedConfirmText)) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_DELETE_CONFIRM_TEXT_INVALID);
|
||||
}
|
||||
}
|
||||
@@ -335,7 +359,7 @@ public class ProductServiceImpl implements ProductService {
|
||||
}
|
||||
|
||||
String year = String.valueOf(LocalDate.now().getYear());
|
||||
String codePrefix = PRODUCT_CODE_PREFIX + year;
|
||||
String codePrefix = ProductObjectConstants.CODE_PREFIX + year;
|
||||
int nextSequence = 1;
|
||||
for (ProductDO product : productMapper.selectListByCodePrefix(codePrefix)) {
|
||||
String existedCode = product.getCode();
|
||||
@@ -366,18 +390,19 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
private void initManagerMemberRelation(ProductDO product) {
|
||||
ObjectRoleRespDTO managerRole = objectPermissionApi
|
||||
.getObjectRoleByCode(PRODUCT_MANAGER_ROLE_CODE, ROLE_SCOPE_OBJECT, PRODUCT_OBJECT_TYPE)
|
||||
.getObjectRoleByCode(ProductObjectConstants.MANAGER_ROLE_CODE, ObjectRoleConstants.ROLE_SCOPE_OBJECT,
|
||||
ProductObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
if (managerRole == null) {
|
||||
throw invalidParamException("未找到产品经理对象角色配置:{}", PRODUCT_MANAGER_ROLE_CODE);
|
||||
throw invalidParamException("未找到产品经理对象角色配置:{}", ProductObjectConstants.MANAGER_ROLE_CODE);
|
||||
}
|
||||
|
||||
UserObjectRoleDO member = new UserObjectRoleDO();
|
||||
member.setUserId(product.getManagerUserId());
|
||||
member.setObjectType(PRODUCT_OBJECT_TYPE);
|
||||
member.setObjectType(ProductObjectConstants.OBJECT_TYPE);
|
||||
member.setObjectId(product.getId());
|
||||
member.setRoleId(managerRole.getId());
|
||||
member.setStatus(MEMBER_STATUS_ACTIVE);
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(LocalDateTime.now());
|
||||
member.setLeftTime(null);
|
||||
userObjectRoleMapper.insert(member);
|
||||
@@ -438,6 +463,35 @@ public class ProductServiceImpl implements ProductService {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Map<String, Long> buildProductStatusCounts(List<ObjectStatusModelDO> statusModels,
|
||||
List<Map<String, Object>> rows) {
|
||||
Map<String, Long> statusCounts = new LinkedHashMap<>();
|
||||
if (statusModels == null || statusModels.isEmpty()) {
|
||||
return statusCounts;
|
||||
}
|
||||
statusModels.stream()
|
||||
.filter(statusModel -> Objects.equals(statusModel.getStatus(), 0))
|
||||
.filter(statusModel -> StringUtils.hasText(statusModel.getStatusCode()))
|
||||
.sorted(Comparator.comparing(ObjectStatusModelDO::getSort, Comparator.nullsLast(Integer::compareTo))
|
||||
.thenComparing(ObjectStatusModelDO::getStatusCode))
|
||||
.forEach(statusModel -> statusCounts.put(statusModel.getStatusCode(), 0L));
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return statusCounts;
|
||||
}
|
||||
for (Map<String, Object> row : rows) {
|
||||
Object statusValue = row.getOrDefault("statusCode", row.get("status_code"));
|
||||
String statusCode = Objects.toString(statusValue, null);
|
||||
if (!statusCounts.containsKey(statusCode)) {
|
||||
continue;
|
||||
}
|
||||
Object countValue = row.getOrDefault("countValue", row.get("count_value"));
|
||||
if (countValue instanceof Number number) {
|
||||
statusCounts.put(statusCode, number.longValue());
|
||||
}
|
||||
}
|
||||
return statusCounts;
|
||||
}
|
||||
|
||||
private void writeProductStatusLog(ProductDO product, String actionType, String fromStatus,
|
||||
String toStatus, String reason) {
|
||||
ProductStatusLogDO statusLog = new ProductStatusLogDO();
|
||||
@@ -456,7 +510,7 @@ public class ProductServiceImpl implements ProductService {
|
||||
private void writeBizAuditLog(ProductDO product, String actionType, String fromStatus, String toStatus,
|
||||
String fieldChanges, String reason) {
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(PRODUCT_OBJECT_TYPE);
|
||||
auditLog.setBizType(ProductObjectConstants.OBJECT_TYPE);
|
||||
auditLog.setBizId(product.getId());
|
||||
auditLog.setActionType(actionType);
|
||||
auditLog.setFromStatus(fromStatus);
|
||||
@@ -481,7 +535,7 @@ public class ProductServiceImpl implements ProductService {
|
||||
|
||||
private void writeManagerInitAuditLog(Long productId, Long managerUserId) {
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(PRODUCT_OBJECT_TYPE);
|
||||
auditLog.setBizType(ProductObjectConstants.OBJECT_TYPE);
|
||||
auditLog.setBizId(productId);
|
||||
auditLog.setActionType(ObjectActivityConstants.PRODUCT_ACTION_CHANGE_MANAGER);
|
||||
auditLog.setFieldChanges(buildManagerFieldChanges(null, managerUserId));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user