feat(project): 补充项目、执行、任务相关能力
This commit is contained in:
@@ -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));
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.njcn.rdms.module.project.service.product;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityPageReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.activity.ProductActivityTimelinePageReqVO;
|
||||
@@ -23,9 +22,6 @@ import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil
|
||||
@Service
|
||||
public class ProductSettingServiceImpl implements ProductSettingService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
private static final String PRODUCT_QUERY_PERMISSION = "project:product:query";
|
||||
|
||||
@Resource
|
||||
private ProductMapper productMapper;
|
||||
@Resource
|
||||
@@ -38,8 +34,6 @@ public class ProductSettingServiceImpl implements ProductSettingService {
|
||||
private ProductActivityTimelineQueryService productActivityTimelineQueryService;
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public ProductSettingRespVO getProductSettings(Long productId) {
|
||||
ProductDO product = validateProductExists(productId);
|
||||
ProductSettingRespVO respVO = new ProductSettingRespVO();
|
||||
@@ -49,16 +43,12 @@ public class ProductSettingServiceImpl implements ProductSettingService {
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public PageResult<ProductActivityRespVO> getProductActivities(Long productId, ProductActivityPageReqVO reqVO) {
|
||||
validateProductExists(productId);
|
||||
return productActivityQueryService.getProductActivities(productId, reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#productId",
|
||||
permission = PRODUCT_QUERY_PERMISSION)
|
||||
public PageResult<ProductActivityTimelineRespVO> getProductActivityTimelinePage(
|
||||
Long productId, ProductActivityTimelinePageReqVO reqVO) {
|
||||
validateProductExists(productId);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.njcn.rdms.module.project.service.product;
|
||||
|
||||
import com.njcn.rdms.module.project.constant.ProductObjectConstants;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingActionRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.product.vo.setting.ProductSettingLifecycleRespVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
|
||||
@@ -18,8 +19,6 @@ import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil
|
||||
@Service
|
||||
public class ProductStatusViewService {
|
||||
|
||||
private static final String PRODUCT_OBJECT_TYPE = "product";
|
||||
|
||||
@Resource
|
||||
private ObjectStatusModelMapper objectStatusModelMapper;
|
||||
@Resource
|
||||
@@ -27,7 +26,7 @@ public class ProductStatusViewService {
|
||||
|
||||
public ProductSettingLifecycleRespVO getLifecycle(String statusCode, String lastStatusReason) {
|
||||
ObjectStatusModelDO statusModel = objectStatusModelMapper
|
||||
.selectByObjectTypeAndStatusCodeEnabled(PRODUCT_OBJECT_TYPE, statusCode);
|
||||
.selectByObjectTypeAndStatusCodeEnabled(ProductObjectConstants.OBJECT_TYPE, statusCode);
|
||||
if (statusModel == null) {
|
||||
throw exception(ErrorCodeConstants.PRODUCT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
@@ -44,7 +43,7 @@ public class ProductStatusViewService {
|
||||
|
||||
private List<ProductSettingActionRespVO> buildAvailableActions(String statusCode) {
|
||||
List<ObjectStatusTransitionDO> transitions = objectStatusTransitionMapper
|
||||
.selectListByObjectTypeAndFromStatus(PRODUCT_OBJECT_TYPE, statusCode);
|
||||
.selectListByObjectTypeAndFromStatus(ProductObjectConstants.OBJECT_TYPE, statusCode);
|
||||
if (transitions == null || transitions.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.njcn.rdms.module.project.service.project;
|
||||
|
||||
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 java.util.List;
|
||||
|
||||
/**
|
||||
* 项目成员 Service 接口
|
||||
*/
|
||||
public interface ProjectMemberService {
|
||||
|
||||
List<ProjectMemberRespVO> getProjectMemberList(Long projectId);
|
||||
|
||||
Long createProjectMember(Long projectId, ProjectMemberSaveReqVO reqVO);
|
||||
|
||||
void updateProjectMember(Long projectId, Long memberId, ProjectMemberUpdateReqVO reqVO);
|
||||
|
||||
void inactiveProjectMember(Long projectId, Long memberId, ProjectMemberInactiveReqVO reqVO);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
package com.njcn.rdms.module.project.service.project;
|
||||
|
||||
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.ProjectExecutionConstants;
|
||||
import com.njcn.rdms.module.project.constant.ProjectObjectConstants;
|
||||
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.dal.dataobject.audit.BizAuditLogDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO;
|
||||
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.project.ProjectMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.project.execution.ProjectExecutionMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission;
|
||||
import com.njcn.rdms.module.system.api.permission.ObjectPermissionApi;
|
||||
import com.njcn.rdms.module.system.api.permission.dto.ObjectRoleRespDTO;
|
||||
import com.njcn.rdms.module.system.api.user.AdminUserApi;
|
||||
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
/**
|
||||
* 项目成员 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
public class ProjectMemberServiceImpl implements ProjectMemberService {
|
||||
|
||||
@Resource
|
||||
private ProjectMapper projectMapper;
|
||||
@Resource
|
||||
private UserObjectRoleMapper userObjectRoleMapper;
|
||||
@Resource
|
||||
private ObjectPermissionApi objectPermissionApi;
|
||||
@Resource
|
||||
private BizAuditLogMapper bizAuditLogMapper;
|
||||
@Resource
|
||||
private ObjectStatusModelMapper objectStatusModelMapper;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@Resource
|
||||
private ProjectExecutionMapper projectExecutionMapper;
|
||||
|
||||
@Override
|
||||
public List<ProjectMemberRespVO> getProjectMemberList(Long projectId) {
|
||||
ProjectDO project = validateProjectExists(projectId);
|
||||
List<UserObjectRoleDO> members = userObjectRoleMapper.selectListByObject(ProjectObjectConstants.OBJECT_TYPE, projectId);
|
||||
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 -> {
|
||||
ProjectMemberRespVO respVO = new ProjectMemberRespVO();
|
||||
respVO.setId(member.getId());
|
||||
respVO.setUserId(member.getUserId());
|
||||
AdminUserRespDTO user = userMap.get(member.getUserId());
|
||||
respVO.setUserNickname(user == null ? null : user.getNickname());
|
||||
respVO.setRoleId(member.getRoleId());
|
||||
ObjectRoleRespDTO role = roleMap.get(member.getRoleId());
|
||||
respVO.setRoleName(role == null ? null : role.getName());
|
||||
respVO.setRoleCode(role == null ? null : role.getCode());
|
||||
respVO.setManagerFlag(Objects.equals(member.getUserId(), project.getManagerUserId())
|
||||
&& Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE));
|
||||
respVO.setStatus(member.getStatus());
|
||||
respVO.setJoinedTime(member.getJoinedTime());
|
||||
respVO.setLeftTime(member.getLeftTime());
|
||||
respVO.setRemark(member.getRemark());
|
||||
return respVO;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId",
|
||||
permission = ProjectObjectConstants.PERMISSION_MEMBER)
|
||||
public Long createProjectMember(Long projectId, ProjectMemberSaveReqVO reqVO) {
|
||||
ProjectDO project = validateProjectEditable(projectId);
|
||||
validateMemberUser(reqVO.getUserId());
|
||||
ObjectRoleRespDTO targetRole = validateProjectRole(reqVO.getRoleId());
|
||||
UserObjectRoleDO existingMember = userObjectRoleMapper
|
||||
.selectByObjectAndUserId(ProjectObjectConstants.OBJECT_TYPE, projectId, reqVO.getUserId());
|
||||
if (existingMember != null && Objects.equals(existingMember.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MEMBER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
UserObjectRoleDO before = existingMember == null ? null : cloneMember(existingMember);
|
||||
UserObjectRoleDO member = existingMember == null ? new UserObjectRoleDO() : existingMember;
|
||||
member.setUserId(reqVO.getUserId());
|
||||
member.setObjectType(ProjectObjectConstants.OBJECT_TYPE);
|
||||
member.setObjectId(projectId);
|
||||
member.setRoleId(targetRole.getId());
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(LocalDateTime.now());
|
||||
member.setLeftTime(null);
|
||||
member.setRemark(normalizeNullableText(reqVO.getRemark()));
|
||||
if (existingMember == null) {
|
||||
userObjectRoleMapper.insert(member);
|
||||
} else {
|
||||
userObjectRoleMapper.updateById(member);
|
||||
}
|
||||
writeMemberAuditLog(member, ObjectActivityConstants.MEMBER_ACTION_ADD, before, member, null);
|
||||
|
||||
if (isManagerRole(targetRole)) {
|
||||
transferManager(project, member, reqVO.getPreviousManagerUserId(), reqVO.getPreviousManagerRoleId(), null);
|
||||
}
|
||||
return member.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId",
|
||||
permission = ProjectObjectConstants.PERMISSION_MEMBER)
|
||||
public void updateProjectMember(Long projectId, Long memberId, ProjectMemberUpdateReqVO reqVO) {
|
||||
ProjectDO project = validateProjectEditable(projectId);
|
||||
UserObjectRoleDO member = validateMemberExists(projectId, memberId);
|
||||
if (!Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MEMBER_NOT_ACTIVE);
|
||||
}
|
||||
ObjectRoleRespDTO targetRole = validateProjectRole(reqVO.getRoleId());
|
||||
UserObjectRoleDO before = cloneMember(member);
|
||||
member.setRemark(normalizeNullableText(reqVO.getRemark()));
|
||||
|
||||
if (isManagerRole(targetRole)) {
|
||||
// 项目经理交接只切换负责人并调整原经理角色,不再把原经理自动移出项目团队。
|
||||
member.setRoleId(targetRole.getId());
|
||||
userObjectRoleMapper.updateById(member);
|
||||
transferManager(project, member, reqVO.getPreviousManagerUserId(),
|
||||
reqVO.getPreviousManagerRoleId(), normalizeNullableText(reqVO.getReason()));
|
||||
} else {
|
||||
if (Objects.equals(member.getUserId(), project.getManagerUserId())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MANAGER_MEMBER_NOT_ALLOW_DOWNGRADE);
|
||||
}
|
||||
member.setRoleId(targetRole.getId());
|
||||
userObjectRoleMapper.updateById(member);
|
||||
}
|
||||
|
||||
writeMemberAuditLog(member, ObjectActivityConstants.MEMBER_ACTION_UPDATE,
|
||||
before, member, normalizeNullableText(reqVO.getReason()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE, objectId = "#projectId",
|
||||
permission = ProjectObjectConstants.PERMISSION_MEMBER)
|
||||
public void inactiveProjectMember(Long projectId, Long memberId, ProjectMemberInactiveReqVO reqVO) {
|
||||
ProjectDO project = validateProjectEditable(projectId);
|
||||
UserObjectRoleDO member = validateMemberExists(projectId, memberId);
|
||||
if (!Objects.equals(member.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MEMBER_NOT_ACTIVE);
|
||||
}
|
||||
if (Objects.equals(member.getUserId(), project.getManagerUserId())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MANAGER_MEMBER_NOT_ALLOW_REMOVE);
|
||||
}
|
||||
validateNoOpenOwnedExecutions(projectId, member.getUserId());
|
||||
|
||||
UserObjectRoleDO before = cloneMember(member);
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_INACTIVE);
|
||||
member.setLeftTime(LocalDateTime.now());
|
||||
userObjectRoleMapper.updateById(member);
|
||||
writeMemberAuditLog(member, ObjectActivityConstants.MEMBER_ACTION_REMOVE, before, member,
|
||||
normalizeNullableText(reqVO.getReason()));
|
||||
}
|
||||
|
||||
private ProjectDO validateProjectExists(Long projectId) {
|
||||
if (projectId == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_NOT_EXISTS);
|
||||
}
|
||||
ProjectDO project = projectMapper.selectById(projectId);
|
||||
if (project == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_NOT_EXISTS);
|
||||
}
|
||||
return project;
|
||||
}
|
||||
|
||||
private ProjectDO validateProjectEditable(Long projectId) {
|
||||
ProjectDO project = validateProjectExists(projectId);
|
||||
// 项目成员维护当前完全跟随状态模型 allow_edit,不再按具体状态编码写死可编辑范围。
|
||||
ObjectStatusModelDO statusModel = objectStatusModelMapper
|
||||
.selectByObjectTypeAndStatusCodeEnabled(ProjectObjectConstants.OBJECT_TYPE, project.getStatusCode());
|
||||
if (statusModel == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
if (!Boolean.TRUE.equals(statusModel.getAllowEdit())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_NOT_ALLOW_EDIT);
|
||||
}
|
||||
return project;
|
||||
}
|
||||
|
||||
private void validateMemberUser(Long userId) {
|
||||
try {
|
||||
Boolean valid = adminUserApi.validateUserList(List.of(userId)).getCheckedData();
|
||||
if (Boolean.TRUE.equals(valid)) {
|
||||
return;
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MEMBER_USER_INVALID);
|
||||
}
|
||||
throw exception(ErrorCodeConstants.PROJECT_MEMBER_USER_INVALID);
|
||||
}
|
||||
|
||||
private UserObjectRoleDO validateMemberExists(Long projectId, Long memberId) {
|
||||
UserObjectRoleDO member = userObjectRoleMapper.selectByIdAndObject(memberId, ProjectObjectConstants.OBJECT_TYPE, projectId);
|
||||
if (member == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MEMBER_NOT_EXISTS);
|
||||
}
|
||||
return member;
|
||||
}
|
||||
|
||||
private void validateNoOpenOwnedExecutions(Long projectId, Long userId) {
|
||||
Integer count = projectExecutionMapper.countNonTerminalByProjectIdAndOwnerId(projectId, userId,
|
||||
getExecutionTerminalStatusCodes());
|
||||
if (count != null && count > 0) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_EXECUTION_OWNER_HANDOFF_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getExecutionTerminalStatusCodes() {
|
||||
List<ObjectStatusModelDO> statusModels = objectStatusModelMapper
|
||||
.selectListByObjectType(ProjectExecutionConstants.OBJECT_TYPE);
|
||||
if (statusModels == null || statusModels.isEmpty()) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_EXECUTION_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
List<String> terminalStatusCodes = statusModels.stream()
|
||||
.filter(statusModel -> Boolean.TRUE.equals(statusModel.getTerminalFlag()))
|
||||
.map(ObjectStatusModelDO::getStatusCode)
|
||||
.filter(StringUtils::hasText)
|
||||
.toList();
|
||||
if (terminalStatusCodes.isEmpty()) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_EXECUTION_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
return terminalStatusCodes;
|
||||
}
|
||||
|
||||
private ObjectRoleRespDTO validateProjectRole(Long roleId) {
|
||||
ObjectRoleRespDTO role = objectPermissionApi
|
||||
.getObjectRoleById(roleId, ObjectRoleConstants.ROLE_SCOPE_OBJECT, ProjectObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
if (role == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MEMBER_ROLE_INVALID);
|
||||
}
|
||||
return role;
|
||||
}
|
||||
|
||||
private void transferManager(ProjectDO project,
|
||||
UserObjectRoleDO targetManagerMember,
|
||||
Long previousManagerUserId,
|
||||
Long previousManagerRoleId,
|
||||
String reason) {
|
||||
Long currentManagerUserId = project.getManagerUserId();
|
||||
Long targetManagerUserId = targetManagerMember.getUserId();
|
||||
if (Objects.equals(currentManagerUserId, targetManagerUserId)) {
|
||||
project.setManagerUserId(targetManagerUserId);
|
||||
projectMapper.updateById(project);
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectRoleRespDTO previousManagerRole = validatePreviousManagerTransfer(currentManagerUserId,
|
||||
previousManagerUserId, previousManagerRoleId);
|
||||
transferPreviousManager(project.getId(), previousManagerUserId, previousManagerRole.getId(), reason);
|
||||
|
||||
project.setManagerUserId(targetManagerUserId);
|
||||
projectMapper.updateById(project);
|
||||
writeManagerChangeAuditLog(project.getId(), currentManagerUserId, targetManagerUserId, reason);
|
||||
}
|
||||
|
||||
private ObjectRoleRespDTO validatePreviousManagerTransfer(Long currentManagerUserId,
|
||||
Long previousManagerUserId,
|
||||
Long previousManagerRoleId) {
|
||||
if (currentManagerUserId == null
|
||||
|| previousManagerUserId == null
|
||||
|| previousManagerRoleId == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MANAGER_TRANSFER_INFO_REQUIRED);
|
||||
}
|
||||
if (!Objects.equals(currentManagerUserId, previousManagerUserId)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MANAGER_TRANSFER_SOURCE_INVALID);
|
||||
}
|
||||
ObjectRoleRespDTO previousManagerRole = validateProjectRole(previousManagerRoleId);
|
||||
if (isManagerRole(previousManagerRole)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MANAGER_TRANSFER_ROLE_INVALID);
|
||||
}
|
||||
return previousManagerRole;
|
||||
}
|
||||
|
||||
private void transferPreviousManager(Long projectId, Long previousManagerUserId, Long previousManagerRoleId, String reason) {
|
||||
UserObjectRoleDO existingMember = userObjectRoleMapper
|
||||
.selectByObjectAndUserId(ProjectObjectConstants.OBJECT_TYPE, projectId, previousManagerUserId);
|
||||
UserObjectRoleDO before = existingMember == null ? null : cloneMember(existingMember);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
UserObjectRoleDO member;
|
||||
String actionType;
|
||||
if (existingMember == null) {
|
||||
member = new UserObjectRoleDO();
|
||||
member.setUserId(previousManagerUserId);
|
||||
member.setObjectType(ProjectObjectConstants.OBJECT_TYPE);
|
||||
member.setObjectId(projectId);
|
||||
member.setRoleId(previousManagerRoleId);
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(now);
|
||||
member.setLeftTime(null);
|
||||
member.setRemark(null);
|
||||
userObjectRoleMapper.insert(member);
|
||||
actionType = ObjectActivityConstants.MEMBER_ACTION_ADD;
|
||||
} else {
|
||||
member = existingMember;
|
||||
member.setRoleId(previousManagerRoleId);
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
member.setLeftTime(null);
|
||||
if (!Objects.equals(before.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
|
||||
member.setJoinedTime(now);
|
||||
actionType = ObjectActivityConstants.MEMBER_ACTION_ADD;
|
||||
} else {
|
||||
actionType = ObjectActivityConstants.MEMBER_ACTION_UPDATE;
|
||||
}
|
||||
userObjectRoleMapper.updateById(member);
|
||||
}
|
||||
writeMemberAuditLog(member, actionType, before, member, reason);
|
||||
}
|
||||
|
||||
private boolean isManagerRole(ObjectRoleRespDTO role) {
|
||||
return Objects.equals(ProjectObjectConstants.MANAGER_ROLE_CODE, role.getCode());
|
||||
}
|
||||
|
||||
private Map<Long, ObjectRoleRespDTO> getRoleMap(Set<Long> roleIds) {
|
||||
if (roleIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<ObjectRoleRespDTO> roles = objectPermissionApi
|
||||
.getObjectRoleList(roleIds, ObjectRoleConstants.ROLE_SCOPE_OBJECT, ProjectObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
if (roles == null || roles.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return roles.stream().collect(Collectors.toMap(ObjectRoleRespDTO::getId, Function.identity()));
|
||||
}
|
||||
|
||||
private Map<Long, AdminUserRespDTO> getUserMap(Set<Long> userIds) {
|
||||
if (userIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return adminUserApi.getUserMap(userIds);
|
||||
}
|
||||
|
||||
private void writeMemberAuditLog(UserObjectRoleDO member,
|
||||
String actionType,
|
||||
UserObjectRoleDO before,
|
||||
UserObjectRoleDO after,
|
||||
String reason) {
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(ObjectActivityConstants.MEMBER_BIZ_TYPE);
|
||||
auditLog.setBizId(member.getId());
|
||||
auditLog.setActionType(actionType);
|
||||
auditLog.setFieldChanges(buildMemberFieldChanges(before, after));
|
||||
auditLog.setReason(reason);
|
||||
auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||
auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||
bizAuditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
private void writeManagerChangeAuditLog(Long projectId, Long beforeManagerUserId, Long afterManagerUserId, String reason) {
|
||||
if (Objects.equals(beforeManagerUserId, afterManagerUserId)) {
|
||||
return;
|
||||
}
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(ProjectObjectConstants.OBJECT_TYPE);
|
||||
auditLog.setBizId(projectId);
|
||||
auditLog.setActionType(ObjectActivityConstants.PROJECT_ACTION_CHANGE_MANAGER);
|
||||
auditLog.setFieldChanges(buildManagerFieldChanges(beforeManagerUserId, afterManagerUserId));
|
||||
auditLog.setReason(reason);
|
||||
auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||
auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||
bizAuditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
private String buildMemberFieldChanges(UserObjectRoleDO before, UserObjectRoleDO after) {
|
||||
Map<String, Object> fieldChanges = new LinkedHashMap<>();
|
||||
appendFieldChange(fieldChanges, "userId", valueOf(before, UserObjectRoleDO::getUserId),
|
||||
valueOf(after, UserObjectRoleDO::getUserId));
|
||||
appendFieldChange(fieldChanges, "roleId", valueOf(before, UserObjectRoleDO::getRoleId),
|
||||
valueOf(after, UserObjectRoleDO::getRoleId));
|
||||
appendFieldChange(fieldChanges, "status", valueOf(before, UserObjectRoleDO::getStatus),
|
||||
valueOf(after, UserObjectRoleDO::getStatus));
|
||||
appendFieldChange(fieldChanges, "joinedTime", valueOf(before, UserObjectRoleDO::getJoinedTime),
|
||||
valueOf(after, UserObjectRoleDO::getJoinedTime));
|
||||
appendFieldChange(fieldChanges, "leftTime", valueOf(before, UserObjectRoleDO::getLeftTime),
|
||||
valueOf(after, UserObjectRoleDO::getLeftTime));
|
||||
appendFieldChange(fieldChanges, "remark", valueOf(before, UserObjectRoleDO::getRemark),
|
||||
valueOf(after, UserObjectRoleDO::getRemark));
|
||||
return fieldChanges.isEmpty() ? null : JsonUtils.toJsonString(fieldChanges);
|
||||
}
|
||||
|
||||
private String buildManagerFieldChanges(Long beforeManagerUserId, Long afterManagerUserId) {
|
||||
Map<String, Object> fieldChanges = new LinkedHashMap<>();
|
||||
appendFieldChange(fieldChanges, "managerUserId", beforeManagerUserId, afterManagerUserId);
|
||||
return JsonUtils.toJsonString(fieldChanges);
|
||||
}
|
||||
|
||||
private UserObjectRoleDO cloneMember(UserObjectRoleDO source) {
|
||||
UserObjectRoleDO clone = new UserObjectRoleDO();
|
||||
clone.setId(source.getId());
|
||||
clone.setUserId(source.getUserId());
|
||||
clone.setObjectType(source.getObjectType());
|
||||
clone.setObjectId(source.getObjectId());
|
||||
clone.setRoleId(source.getRoleId());
|
||||
clone.setStatus(source.getStatus());
|
||||
clone.setJoinedTime(source.getJoinedTime());
|
||||
clone.setLeftTime(source.getLeftTime());
|
||||
clone.setRemark(source.getRemark());
|
||||
return clone;
|
||||
}
|
||||
|
||||
private <T> T valueOf(UserObjectRoleDO member, Function<UserObjectRoleDO, T> getter) {
|
||||
return member == null ? null : getter.apply(member);
|
||||
}
|
||||
|
||||
private void appendFieldChange(Map<String, Object> fieldChanges, String fieldName, Object before, Object after) {
|
||||
if (Objects.equals(before, after)) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("before", before);
|
||||
value.put("after", after);
|
||||
fieldChanges.put(fieldName, value);
|
||||
}
|
||||
|
||||
private String normalizeNullableText(String value) {
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private String defaultText(String value) {
|
||||
return StringUtils.hasText(value) ? value : "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.njcn.rdms.module.project.service.project;
|
||||
|
||||
import com.njcn.rdms.framework.common.pojo.PageResult;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 项目 Service 接口
|
||||
*/
|
||||
public interface ProjectService {
|
||||
|
||||
Long createProject(ProjectSaveReqVO createReqVO);
|
||||
|
||||
void updateProject(ProjectSaveReqVO updateReqVO);
|
||||
|
||||
ProjectDO getProject(Long id);
|
||||
|
||||
ProjectRespVO getProjectDetail(Long id);
|
||||
|
||||
ProjectContextRespVO getProjectContext(Long id);
|
||||
|
||||
PageResult<ProjectDO> getProjectPage(ProjectPageReqVO pageReqVO);
|
||||
|
||||
ProjectOverviewSummaryRespVO getProjectOverviewSummary();
|
||||
|
||||
void changeProjectStatus(ProjectStatusActionReqVO reqVO);
|
||||
|
||||
void deleteProject(ProjectDeleteReqVO reqVO);
|
||||
|
||||
void autoStartProjectIfPending(Long projectId, String triggerAction);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,930 @@
|
||||
package com.njcn.rdms.module.project.service.project;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.njcn.rdms.framework.common.pojo.CommonResult;
|
||||
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.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.ProjectObjectConstants;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectContextNavRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectContextProjectRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectContextRespVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectContextRoleRespVO;
|
||||
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.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.project.ProjectDO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.project.ProjectStatusLogDO;
|
||||
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.project.ProjectMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.project.ProjectStatusLogMapper;
|
||||
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.enums.ProjectDictTypeConstants;
|
||||
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.permission.dto.ObjectMenuRespDTO;
|
||||
import com.njcn.rdms.module.system.api.permission.dto.ObjectRolePermissionRespDTO;
|
||||
import com.njcn.rdms.module.system.api.permission.dto.ObjectRoleRespDTO;
|
||||
import com.njcn.rdms.module.system.api.user.AdminUserApi;
|
||||
import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO;
|
||||
import com.njcn.rdms.module.system.enums.DictTypeConstants;
|
||||
import com.njcn.rdms.module.system.enums.permission.MenuTypeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
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 实现类
|
||||
*/
|
||||
@Service
|
||||
public class ProjectServiceImpl implements ProjectService {
|
||||
|
||||
@Resource
|
||||
private ProjectMapper projectMapper;
|
||||
@Resource
|
||||
private ProjectStatusLogMapper projectStatusLogMapper;
|
||||
@Resource
|
||||
private ProductMapper productMapper;
|
||||
@Resource
|
||||
private BizAuditLogMapper bizAuditLogMapper;
|
||||
@Resource
|
||||
private ObjectStatusTransitionMapper objectStatusTransitionMapper;
|
||||
@Resource
|
||||
private ObjectStatusModelMapper objectStatusModelMapper;
|
||||
@Resource
|
||||
private UserObjectRoleMapper userObjectRoleMapper;
|
||||
@Resource
|
||||
private ProjectStatusViewService projectStatusViewService;
|
||||
@Resource
|
||||
private ObjectPermissionApi objectPermissionApi;
|
||||
@Resource
|
||||
private AdminUserApi adminUserApi;
|
||||
@Resource
|
||||
private DictDataApi dictDataApi;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createProject(ProjectSaveReqVO createReqVO) {
|
||||
validateCreateReqVO(createReqVO);
|
||||
validateProjectType(createReqVO.getProjectType());
|
||||
String finalDirectionCode = resolveProjectDirectionCode(createReqVO.getProductId(), createReqVO.getDirectionCode());
|
||||
validateMainlineProjectUnique(null, createReqVO.getProductId(), createReqVO.getProjectType());
|
||||
validateManagerUser(createReqVO.getManagerUserId());
|
||||
|
||||
ProjectDO project = new ProjectDO();
|
||||
project.setProjectCode(generateProjectCode(createReqVO.getProjectCode()));
|
||||
project.setProjectName(createReqVO.getProjectName().trim());
|
||||
project.setProjectType(createReqVO.getProjectType().trim());
|
||||
project.setDirectionCode(finalDirectionCode);
|
||||
project.setProjectSetId(createReqVO.getProjectSetId());
|
||||
project.setProductId(createReqVO.getProductId());
|
||||
project.setProductVersionId(createReqVO.getProductVersionId());
|
||||
project.setManagerUserId(createReqVO.getManagerUserId());
|
||||
String initialStatus = getInitialStatusCode();
|
||||
project.setStatusCode(initialStatus);
|
||||
project.setPlannedStartDate(createReqVO.getPlannedStartDate());
|
||||
project.setPlannedEndDate(createReqVO.getPlannedEndDate());
|
||||
project.setActualStartDate(createReqVO.getActualStartDate());
|
||||
project.setActualEndDate(createReqVO.getActualEndDate());
|
||||
project.setProgressRate(BigDecimal.ZERO);
|
||||
project.setProjectDesc(normalizeNullableText(createReqVO.getProjectDesc()));
|
||||
projectMapper.insert(project);
|
||||
|
||||
initManagerMemberRelation(project);
|
||||
writeBizAuditLog(project, ObjectActivityConstants.PROJECT_ACTION_CREATE, null, initialStatus,
|
||||
buildProjectFieldChanges(null, project), null);
|
||||
return project.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE,
|
||||
objectId = "#updateReqVO.id", permission = ProjectObjectConstants.PERMISSION_UPDATE)
|
||||
public void updateProject(ProjectSaveReqVO updateReqVO) {
|
||||
if (updateReqVO.getId() == null) {
|
||||
throw invalidParamException("项目编号不能为空");
|
||||
}
|
||||
ProjectDO project = validateProjectExists(updateReqVO.getId());
|
||||
validateProjectEditable(project);
|
||||
validateProjectCodeUnchanged(project, updateReqVO.getProjectCode());
|
||||
validateProductUnchanged(project, updateReqVO.getProductId());
|
||||
validateProjectType(updateReqVO.getProjectType());
|
||||
validateManagerUser(updateReqVO.getManagerUserId());
|
||||
validateProjectNameUnique(project.getId(), project.getProductId(), updateReqVO.getProjectName());
|
||||
validateMainlineProjectUnique(project.getId(), project.getProductId(), updateReqVO.getProjectType());
|
||||
validateDateRange(updateReqVO.getPlannedStartDate(), updateReqVO.getPlannedEndDate(), "计划结束日期不能早于计划开始日期");
|
||||
validateDateRange(updateReqVO.getActualStartDate(), updateReqVO.getActualEndDate(), "实际结束日期不能早于实际开始日期");
|
||||
String finalDirectionCode = resolveProjectDirectionCode(project.getProductId(), updateReqVO.getDirectionCode());
|
||||
|
||||
ProjectDO before = cloneProject(project);
|
||||
project.setProjectName(updateReqVO.getProjectName().trim());
|
||||
project.setProjectType(updateReqVO.getProjectType().trim());
|
||||
project.setDirectionCode(finalDirectionCode);
|
||||
project.setProjectSetId(updateReqVO.getProjectSetId());
|
||||
project.setProductVersionId(updateReqVO.getProductVersionId());
|
||||
project.setPlannedStartDate(updateReqVO.getPlannedStartDate());
|
||||
project.setPlannedEndDate(updateReqVO.getPlannedEndDate());
|
||||
project.setActualStartDate(updateReqVO.getActualStartDate());
|
||||
project.setActualEndDate(updateReqVO.getActualEndDate());
|
||||
project.setProjectDesc(normalizeNullableText(updateReqVO.getProjectDesc()));
|
||||
if (!Objects.equals(project.getManagerUserId(), updateReqVO.getManagerUserId())) {
|
||||
changeManager(project, updateReqVO.getManagerUserId(), null);
|
||||
}
|
||||
project.setManagerUserId(updateReqVO.getManagerUserId());
|
||||
projectMapper.updateById(project);
|
||||
|
||||
writeBizAuditLog(project, ObjectActivityConstants.PROJECT_ACTION_UPDATE, before.getStatusCode(), project.getStatusCode(),
|
||||
buildProjectFieldChanges(before, project), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectDO getProject(Long id) {
|
||||
return validateProjectExists(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectRespVO getProjectDetail(Long id) {
|
||||
ProjectDO project = validateProjectExists(id);
|
||||
ProjectRespVO respVO = BeanUtils.toBean(project, ProjectRespVO.class);
|
||||
respVO.setProductName(getProductName(project.getProductId()));
|
||||
respVO.setManagerUserNickname(getManagerNickname(project.getManagerUserId()));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectContextRespVO getProjectContext(Long id) {
|
||||
ProjectDO project = validateProjectExists(id);
|
||||
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
|
||||
UserObjectRoleDO currentMember = userObjectRoleMapper
|
||||
.selectActiveByObjectAndUserId(ProjectObjectConstants.OBJECT_TYPE, id, loginUserId);
|
||||
if (currentMember != null) {
|
||||
return buildProjectContext(project, currentMember.getRoleId(), false, null);
|
||||
}
|
||||
|
||||
ObjectRoleRespDTO visitorRole = objectPermissionApi
|
||||
.getObjectRoleByCode(ProjectObjectConstants.VISITOR_ROLE_CODE, ObjectRoleConstants.ROLE_SCOPE_OBJECT,
|
||||
ProjectObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
if (visitorRole == null || visitorRole.getId() == null) {
|
||||
return buildProjectContextWithoutMenus(project, true);
|
||||
}
|
||||
return buildProjectContext(project, visitorRole.getId(), true, visitorRole);
|
||||
}
|
||||
|
||||
private ProjectContextRespVO buildProjectContext(ProjectDO project, Long roleId, boolean guestFlag,
|
||||
ObjectRoleRespDTO fallbackRole) {
|
||||
ProjectContextRespVO respVO = new ProjectContextRespVO();
|
||||
respVO.setCurrentProject(buildCurrentProject(project));
|
||||
|
||||
ObjectRolePermissionRespDTO permissionDetail = objectPermissionApi
|
||||
.getObjectRolePermissionDetail(roleId, ObjectRoleConstants.ROLE_SCOPE_OBJECT,
|
||||
ProjectObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
ObjectRoleRespDTO currentRole = permissionDetail == null || permissionDetail.getCurrentRole() == null
|
||||
? fallbackRole
|
||||
: permissionDetail.getCurrentRole();
|
||||
List<ObjectMenuRespDTO> menus = permissionDetail == null || permissionDetail.getMenus() == null
|
||||
? Collections.emptyList()
|
||||
: permissionDetail.getMenus();
|
||||
respVO.setCurrentRole(buildCurrentRole(roleId, currentRole, guestFlag));
|
||||
respVO.setNavs(buildContextNavs(menus));
|
||||
respVO.setButtons(buildContextButtons(menus));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<ProjectDO> getProjectPage(ProjectPageReqVO pageReqVO) {
|
||||
return projectMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectOverviewSummaryRespVO getProjectOverviewSummary() {
|
||||
ProjectOverviewSummaryRespVO respVO = new ProjectOverviewSummaryRespVO();
|
||||
respVO.setStatusCounts(buildProjectStatusCounts(objectStatusModelMapper
|
||||
.selectListByObjectType(ProjectObjectConstants.OBJECT_TYPE), projectMapper.selectStatusCountList()));
|
||||
return respVO;
|
||||
}
|
||||
|
||||
private String getProductName(Long productId) {
|
||||
if (productId == null) {
|
||||
return null;
|
||||
}
|
||||
ProductDO product = productMapper.selectById(productId);
|
||||
return product == null ? null : product.getName();
|
||||
}
|
||||
|
||||
private String getManagerNickname(Long managerUserId) {
|
||||
if (managerUserId == null) {
|
||||
return null;
|
||||
}
|
||||
CommonResult<AdminUserRespDTO> result = adminUserApi.getUser(managerUserId);
|
||||
AdminUserRespDTO user = result == null ? null : result.getCheckedData();
|
||||
return user == null ? null : user.getNickname();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE,
|
||||
objectId = "#reqVO.id", permission = ProjectObjectConstants.PERMISSION_STATUS)
|
||||
public void changeProjectStatus(ProjectStatusActionReqVO reqVO) {
|
||||
ProjectDO project = validateProjectExists(reqVO.getId());
|
||||
String actionCode = reqVO.getActionCode().trim();
|
||||
if (ObjectActivityConstants.PROJECT_ACTION_AUTO_START.equals(actionCode)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_ACTION_NOT_ALLOWED, actionCode);
|
||||
}
|
||||
changeStatus(project, actionCode, normalizeNullableText(reqVO.getReason()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@com.njcn.rdms.module.project.framework.security.annotation.CheckObjectPermission(objectType = ProjectObjectConstants.OBJECT_TYPE,
|
||||
objectId = "#reqVO.id", permission = ProjectObjectConstants.PERMISSION_DELETE)
|
||||
public void deleteProject(ProjectDeleteReqVO reqVO) {
|
||||
ProjectDO project = validateProjectExists(reqVO.getId());
|
||||
validateDeleteConfirmText(reqVO.getConfirmText());
|
||||
if (!Objects.equals(project.getProjectName(), reqVO.getProjectName().trim())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_DELETE_NAME_MISMATCH);
|
||||
}
|
||||
if (!isInitialStatus(project.getStatusCode())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_NOT_ALLOW_DELETE);
|
||||
}
|
||||
|
||||
String reason = reqVO.getReason().trim();
|
||||
String fromStatus = project.getStatusCode();
|
||||
int deleteCount = projectMapper.deleteByIdAndStatus(reqVO.getId(), fromStatus);
|
||||
if (deleteCount != 1) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_CONCURRENT_MODIFIED);
|
||||
}
|
||||
writeProjectStatusLog(project, ObjectActivityConstants.PROJECT_ACTION_DELETE, fromStatus, null, reason);
|
||||
writeBizAuditLog(project, ObjectActivityConstants.PROJECT_ACTION_DELETE, fromStatus, null, null, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void autoStartProjectIfPending(Long projectId, String triggerAction) {
|
||||
// auto_start 只允许由后端业务动作内部触发,前端不应直接透传该动作。
|
||||
if (!ProjectObjectConstants.AUTO_START_TRIGGERS.contains(triggerAction)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_ACTION_NOT_ALLOWED, triggerAction);
|
||||
}
|
||||
ProjectDO project = validateProjectExists(projectId);
|
||||
ObjectStatusTransitionDO transition = objectStatusTransitionMapper
|
||||
.selectByObjectTypeAndFromStatusAndAction(ProjectObjectConstants.OBJECT_TYPE, project.getStatusCode(),
|
||||
ObjectActivityConstants.PROJECT_ACTION_AUTO_START);
|
||||
if (transition == null) {
|
||||
// 未配置 auto_start 时,只要项目已离开初始态且仍可编辑,就允许真实业务动作继续推进,不强制补自动启动流转。
|
||||
ObjectStatusModelDO statusModel = validateEnabledStatusModel(project.getStatusCode());
|
||||
if (Boolean.TRUE.equals(statusModel.getInitialFlag())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_ACTION_NOT_ALLOWED,
|
||||
ObjectActivityConstants.PROJECT_ACTION_AUTO_START);
|
||||
}
|
||||
if (!Boolean.TRUE.equals(statusModel.getAllowEdit())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_NOT_ALLOW_EDIT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isInitialStatus(project.getStatusCode())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_NOT_ALLOW_EDIT);
|
||||
}
|
||||
String reason = ObjectActivityConstants.resolveActionName(triggerAction);
|
||||
changeStatus(project, transition, ObjectActivityConstants.PROJECT_ACTION_AUTO_START, reason);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateCreateReqVO(ProjectSaveReqVO createReqVO) {
|
||||
validateProjectCodeUnique(null, createReqVO.getProjectCode());
|
||||
validateProjectNameUnique(null, createReqVO.getProductId(), createReqVO.getProjectName());
|
||||
validateDateRange(createReqVO.getPlannedStartDate(), createReqVO.getPlannedEndDate(), "计划结束日期不能早于计划开始日期");
|
||||
validateDateRange(createReqVO.getActualStartDate(), createReqVO.getActualEndDate(), "实际结束日期不能早于实际开始日期");
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ProjectDO validateProjectExists(Long id) {
|
||||
if (id == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_NOT_EXISTS);
|
||||
}
|
||||
ProjectDO project = projectMapper.selectById(id);
|
||||
if (project == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_NOT_EXISTS);
|
||||
}
|
||||
return project;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateProjectCodeUnique(Long id, String projectCode) {
|
||||
if (!StringUtils.hasText(projectCode)) {
|
||||
return;
|
||||
}
|
||||
String normalizedCode = projectCode.trim();
|
||||
ProjectDO project = projectMapper.selectByCode(normalizedCode);
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
if (id == null || !project.getId().equals(id)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_CODE_DUPLICATE, normalizedCode);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateProjectNameUnique(Long id, Long productId, String projectName) {
|
||||
String normalizedName = projectName.trim();
|
||||
ProjectDO project = projectMapper.selectActiveByProductIdAndName(productId, normalizedName,
|
||||
ProjectObjectConstants.STATUS_CANCELLED);
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
if (id == null || !project.getId().equals(id)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_NAME_DUPLICATE, normalizedName);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateMainlineProjectUnique(Long id, Long productId, String projectType) {
|
||||
if (productId == null || !isMainlineProjectType(projectType)) {
|
||||
return;
|
||||
}
|
||||
List<ProjectDO> projects = projectMapper.selectActiveMainlineListByProductId(productId,
|
||||
ProjectObjectConstants.MAINLINE_PROJECT_TYPE_CODES, ProjectObjectConstants.STATUS_CANCELLED);
|
||||
if (projects == null || projects.isEmpty()
|
||||
|| projects.stream().allMatch(project -> Objects.equals(project.getId(), id))) {
|
||||
return;
|
||||
}
|
||||
throw exception(ErrorCodeConstants.PROJECT_MAINLINE_DUPLICATE);
|
||||
}
|
||||
|
||||
private boolean isMainlineProjectType(String projectType) {
|
||||
return StringUtils.hasText(projectType)
|
||||
&& ProjectObjectConstants.MAINLINE_PROJECT_TYPE_CODES.contains(projectType.trim());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateProjectType(String projectType) {
|
||||
try {
|
||||
Boolean valid = dictDataApi.validateDictDataList(ProjectDictTypeConstants.PROJECT_TYPE,
|
||||
List.of(projectType.trim())).getCheckedData();
|
||||
if (Boolean.TRUE.equals(valid)) {
|
||||
return;
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_TYPE_INVALID);
|
||||
}
|
||||
throw exception(ErrorCodeConstants.PROJECT_TYPE_INVALID);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateProjectDirection(String directionCode) {
|
||||
try {
|
||||
Boolean valid = dictDataApi.validateDictDataList(DictTypeConstants.OBJECT_DIRECTION,
|
||||
List.of(directionCode)).getCheckedData();
|
||||
if (Boolean.TRUE.equals(valid)) {
|
||||
return;
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_DIRECTION_INVALID);
|
||||
}
|
||||
throw exception(ErrorCodeConstants.PROJECT_DIRECTION_INVALID);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ObjectStatusTransitionDO validateProjectTransition(String fromStatusCode, String actionCode, String reason) {
|
||||
ObjectStatusTransitionDO transition = objectStatusTransitionMapper
|
||||
.selectByObjectTypeAndFromStatusAndAction(ProjectObjectConstants.OBJECT_TYPE, fromStatusCode, actionCode);
|
||||
if (transition == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_ACTION_NOT_ALLOWED, actionCode);
|
||||
}
|
||||
if (Boolean.TRUE.equals(transition.getNeedReason()) && !StringUtils.hasText(reason)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_ACTION_REASON_REQUIRED, transition.getActionCode());
|
||||
}
|
||||
return transition;
|
||||
}
|
||||
|
||||
private ProductDO validateProductUsable(Long productId) {
|
||||
if (productId == null) {
|
||||
return null;
|
||||
}
|
||||
ProductDO product = productMapper.selectById(productId);
|
||||
if (product == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_PRODUCT_NOT_EXISTS);
|
||||
}
|
||||
return product;
|
||||
}
|
||||
|
||||
private String resolveProjectDirectionCode(Long productId, String directionCode) {
|
||||
// 关联产品的项目方向始终继承产品,避免项目方向与所属产品方向出现漂移。
|
||||
if (productId == null) {
|
||||
if (!StringUtils.hasText(directionCode)) {
|
||||
throw invalidParamException("项目方向不能为空");
|
||||
}
|
||||
String normalizedDirectionCode = directionCode.trim();
|
||||
if (normalizedDirectionCode.length() > 32) {
|
||||
throw invalidParamException("项目方向长度不能超过32个字符");
|
||||
}
|
||||
validateProjectDirection(normalizedDirectionCode);
|
||||
return normalizedDirectionCode;
|
||||
}
|
||||
|
||||
ProductDO product = validateProductUsable(productId);
|
||||
String productDirectionCode = normalizeNullableText(product == null ? null : product.getDirectionCode());
|
||||
if (!StringUtils.hasText(productDirectionCode)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_DIRECTION_INVALID);
|
||||
}
|
||||
return productDirectionCode;
|
||||
}
|
||||
|
||||
private void validateManagerUser(Long managerUserId) {
|
||||
try {
|
||||
Boolean valid = adminUserApi.validateUserList(List.of(managerUserId)).getCheckedData();
|
||||
if (Boolean.TRUE.equals(valid)) {
|
||||
return;
|
||||
}
|
||||
} catch (RuntimeException ex) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_MANAGER_USER_INVALID);
|
||||
}
|
||||
throw exception(ErrorCodeConstants.PROJECT_MANAGER_USER_INVALID);
|
||||
}
|
||||
|
||||
private void validateProjectCodeUnchanged(ProjectDO project, String projectCode) {
|
||||
if (!StringUtils.hasText(projectCode)) {
|
||||
return;
|
||||
}
|
||||
if (!Objects.equals(project.getProjectCode(), projectCode.trim())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_CODE_NOT_MODIFIABLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProductUnchanged(ProjectDO project, Long productId) {
|
||||
if (!Objects.equals(project.getProductId(), productId)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_PRODUCT_NOT_MODIFIABLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateProjectEditable(ProjectDO project) {
|
||||
ObjectStatusModelDO statusModel = validateEnabledStatusModel(project.getStatusCode());
|
||||
if (!Boolean.TRUE.equals(statusModel.getAllowEdit())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_NOT_ALLOW_EDIT);
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectStatusModelDO validateEnabledStatusModel(String statusCode) {
|
||||
ObjectStatusModelDO statusModel = objectStatusModelMapper
|
||||
.selectByObjectTypeAndStatusCodeEnabled(ProjectObjectConstants.OBJECT_TYPE, statusCode);
|
||||
if (statusModel == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
return statusModel;
|
||||
}
|
||||
|
||||
private String getInitialStatusCode() {
|
||||
ObjectStatusModelDO statusModel = objectStatusModelMapper
|
||||
.selectInitialByObjectTypeEnabled(ProjectObjectConstants.OBJECT_TYPE);
|
||||
if (statusModel == null || !StringUtils.hasText(statusModel.getStatusCode())) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
return statusModel.getStatusCode();
|
||||
}
|
||||
|
||||
private boolean isInitialStatus(String statusCode) {
|
||||
return Boolean.TRUE.equals(validateEnabledStatusModel(statusCode).getInitialFlag());
|
||||
}
|
||||
|
||||
private void validateDateRange(LocalDate startDate, LocalDate endDate, String message) {
|
||||
if (startDate != null && endDate != null && endDate.isBefore(startDate)) {
|
||||
throw invalidParamException(message);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateProjectCode(String projectCode) {
|
||||
String normalizedCode = normalizeNullableText(projectCode);
|
||||
if (StringUtils.hasText(normalizedCode)) {
|
||||
validateProjectCodeUnique(null, normalizedCode);
|
||||
return normalizedCode;
|
||||
}
|
||||
|
||||
String year = String.valueOf(LocalDate.now().getYear());
|
||||
String codePrefix = ProjectObjectConstants.CODE_PREFIX + year;
|
||||
int nextSequence = 1;
|
||||
for (ProjectDO project : projectMapper.selectListByCodePrefix(codePrefix)) {
|
||||
String existedCode = project.getProjectCode();
|
||||
if (!StringUtils.hasText(existedCode) || !existedCode.matches(codePrefix + "\\d{3}")) {
|
||||
continue;
|
||||
}
|
||||
nextSequence = Integer.parseInt(existedCode.substring(codePrefix.length())) + 1;
|
||||
break;
|
||||
}
|
||||
if (nextSequence > 999) {
|
||||
throw invalidParamException("{} 年项目自动编码序号已用尽", year);
|
||||
}
|
||||
String generatedCode = codePrefix + String.format("%03d", nextSequence);
|
||||
validateProjectCodeUnique(null, generatedCode);
|
||||
return generatedCode;
|
||||
}
|
||||
|
||||
private void initManagerMemberRelation(ProjectDO project) {
|
||||
// 创建项目后要同步补齐负责人成员关系和审计日志,保证负责人与成员数据链路一致。
|
||||
ObjectRoleRespDTO managerRole = getManagerRole();
|
||||
UserObjectRoleDO member = new UserObjectRoleDO();
|
||||
member.setUserId(project.getManagerUserId());
|
||||
member.setObjectType(ProjectObjectConstants.OBJECT_TYPE);
|
||||
member.setObjectId(project.getId());
|
||||
member.setRoleId(managerRole.getId());
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(LocalDateTime.now());
|
||||
member.setLeftTime(null);
|
||||
userObjectRoleMapper.insert(member);
|
||||
|
||||
writeMemberAuditLog(member, ObjectActivityConstants.MEMBER_ACTION_ADD, null, member, null);
|
||||
writeManagerChangeAuditLog(project.getId(), null, project.getManagerUserId(), null);
|
||||
}
|
||||
|
||||
private ObjectRoleRespDTO getManagerRole() {
|
||||
ObjectRoleRespDTO managerRole = objectPermissionApi
|
||||
.getObjectRoleByCode(ProjectObjectConstants.MANAGER_ROLE_CODE, ObjectRoleConstants.ROLE_SCOPE_OBJECT,
|
||||
ProjectObjectConstants.OBJECT_TYPE)
|
||||
.getCheckedData();
|
||||
if (managerRole == null) {
|
||||
throw invalidParamException("未找到项目经理对象角色配置:{}", ProjectObjectConstants.MANAGER_ROLE_CODE);
|
||||
}
|
||||
return managerRole;
|
||||
}
|
||||
|
||||
private void changeManager(ProjectDO project, Long newManagerUserId, String reason) {
|
||||
// 负责人切换不能只改项目主表,还要同步调整成员关系并记录负责人变更审计。
|
||||
Long oldManagerUserId = project.getManagerUserId();
|
||||
if (Objects.equals(oldManagerUserId, newManagerUserId)) {
|
||||
return;
|
||||
}
|
||||
ObjectRoleRespDTO managerRole = getManagerRole();
|
||||
inactiveOldManagerRelation(project.getId(), oldManagerUserId, reason);
|
||||
ensureManagerRelation(project.getId(), newManagerUserId, managerRole.getId(), reason);
|
||||
writeManagerChangeAuditLog(project.getId(), oldManagerUserId, newManagerUserId, reason);
|
||||
}
|
||||
|
||||
private void inactiveOldManagerRelation(Long projectId, Long oldManagerUserId, String reason) {
|
||||
if (oldManagerUserId == null) {
|
||||
return;
|
||||
}
|
||||
UserObjectRoleDO oldMember = userObjectRoleMapper.selectByObjectAndUserId(ProjectObjectConstants.OBJECT_TYPE,
|
||||
projectId, oldManagerUserId);
|
||||
if (oldMember == null || !Objects.equals(oldMember.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
|
||||
return;
|
||||
}
|
||||
UserObjectRoleDO before = cloneMember(oldMember);
|
||||
oldMember.setStatus(ObjectRoleConstants.MEMBER_STATUS_INACTIVE);
|
||||
oldMember.setLeftTime(LocalDateTime.now());
|
||||
userObjectRoleMapper.updateById(oldMember);
|
||||
writeMemberAuditLog(oldMember, ObjectActivityConstants.MEMBER_ACTION_REMOVE, before, oldMember, reason);
|
||||
}
|
||||
|
||||
private void ensureManagerRelation(Long projectId, Long managerUserId, Long managerRoleId, String reason) {
|
||||
UserObjectRoleDO existingMember = userObjectRoleMapper.selectByObjectAndUserId(ProjectObjectConstants.OBJECT_TYPE,
|
||||
projectId, managerUserId);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (existingMember == null) {
|
||||
UserObjectRoleDO member = new UserObjectRoleDO();
|
||||
member.setUserId(managerUserId);
|
||||
member.setObjectType(ProjectObjectConstants.OBJECT_TYPE);
|
||||
member.setObjectId(projectId);
|
||||
member.setRoleId(managerRoleId);
|
||||
member.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
member.setJoinedTime(now);
|
||||
member.setLeftTime(null);
|
||||
userObjectRoleMapper.insert(member);
|
||||
writeMemberAuditLog(member, ObjectActivityConstants.MEMBER_ACTION_ADD, null, member, reason);
|
||||
return;
|
||||
}
|
||||
UserObjectRoleDO before = cloneMember(existingMember);
|
||||
existingMember.setRoleId(managerRoleId);
|
||||
existingMember.setStatus(ObjectRoleConstants.MEMBER_STATUS_ACTIVE);
|
||||
existingMember.setLeftTime(null);
|
||||
if (!Objects.equals(before.getStatus(), ObjectRoleConstants.MEMBER_STATUS_ACTIVE)) {
|
||||
existingMember.setJoinedTime(now);
|
||||
}
|
||||
userObjectRoleMapper.updateById(existingMember);
|
||||
writeMemberAuditLog(existingMember, ObjectActivityConstants.MEMBER_ACTION_UPDATE, before, existingMember, reason);
|
||||
}
|
||||
|
||||
private void changeStatus(ProjectDO project, String actionCode, String reason) {
|
||||
String fromStatus = project.getStatusCode();
|
||||
ObjectStatusTransitionDO transition = validateProjectTransition(fromStatus, actionCode, reason);
|
||||
changeStatus(project, transition, actionCode, reason);
|
||||
}
|
||||
|
||||
private void changeStatus(ProjectDO project, ObjectStatusTransitionDO transition, String actionCode, String reason) {
|
||||
String fromStatus = project.getStatusCode();
|
||||
String toStatus = transition.getToStatusCode();
|
||||
// 状态更新必须携带 fromStatus 条件,避免并发状态变更时发生后写覆盖先写。
|
||||
int updateCount = projectMapper.updateStatusByIdAndStatus(project.getId(), fromStatus, toStatus, reason);
|
||||
if (updateCount != 1) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_CONCURRENT_MODIFIED);
|
||||
}
|
||||
project.setStatusCode(toStatus);
|
||||
project.setLastStatusReason(reason);
|
||||
|
||||
writeProjectStatusLog(project, actionCode, fromStatus, toStatus, reason);
|
||||
writeBizAuditLog(project, actionCode, fromStatus, toStatus, null, reason);
|
||||
}
|
||||
|
||||
private ProjectContextProjectRespVO buildCurrentProject(ProjectDO project) {
|
||||
// 项目上下文返回的是主表快照叠加运行时生命周期读模型,状态名称和动作列表不在主表写死。
|
||||
ProjectContextProjectRespVO currentProject = BeanUtils.toBean(project, ProjectContextProjectRespVO.class);
|
||||
ProjectStatusViewService.ProjectLifecycleView lifecycle = projectStatusViewService.getLifecycle(project.getStatusCode());
|
||||
currentProject.setStatusName(lifecycle.statusName());
|
||||
currentProject.setTerminal(lifecycle.terminal());
|
||||
currentProject.setAllowEdit(lifecycle.allowEdit());
|
||||
currentProject.setAvailableActions(lifecycle.availableActions());
|
||||
return currentProject;
|
||||
}
|
||||
|
||||
private ProjectContextRespVO buildProjectContextWithoutMenus(ProjectDO project, boolean guestFlag) {
|
||||
ProjectContextRespVO respVO = new ProjectContextRespVO();
|
||||
respVO.setCurrentProject(buildCurrentProject(project));
|
||||
respVO.setCurrentRole(buildCurrentRole(null, null, guestFlag));
|
||||
respVO.setNavs(Collections.emptyList());
|
||||
respVO.setButtons(Collections.emptyList());
|
||||
return respVO;
|
||||
}
|
||||
|
||||
private ProjectContextRoleRespVO buildCurrentRole(Long roleId, ObjectRoleRespDTO currentRole, boolean guestFlag) {
|
||||
ProjectContextRoleRespVO roleRespVO = new ProjectContextRoleRespVO();
|
||||
roleRespVO.setRoleId(roleId);
|
||||
roleRespVO.setGuestFlag(guestFlag);
|
||||
if (currentRole != null) {
|
||||
roleRespVO.setRoleCode(currentRole.getCode());
|
||||
roleRespVO.setRoleName(currentRole.getName());
|
||||
}
|
||||
return roleRespVO;
|
||||
}
|
||||
|
||||
private List<ProjectContextNavRespVO> buildContextNavs(List<ObjectMenuRespDTO> menus) {
|
||||
if (menus.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<ProjectContextNavRespVO> navs = menus.stream()
|
||||
.filter(menu -> !MenuTypeEnum.BUTTON.getType().equals(menu.getType()))
|
||||
.filter(menu -> !Boolean.FALSE.equals(menu.getVisible()))
|
||||
.map(menu -> {
|
||||
ProjectContextNavRespVO nav = new ProjectContextNavRespVO();
|
||||
nav.setId(menu.getId());
|
||||
nav.setName(menu.getName());
|
||||
nav.setPath(menu.getPath());
|
||||
nav.setIcon(menu.getIcon());
|
||||
nav.setSort(menu.getSort());
|
||||
return nav;
|
||||
})
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
navs.sort(Comparator.comparing(ProjectContextNavRespVO::getSort, Comparator.nullsLast(Integer::compareTo))
|
||||
.thenComparing(ProjectContextNavRespVO::getId, Comparator.nullsLast(Long::compareTo)));
|
||||
return navs;
|
||||
}
|
||||
|
||||
private List<String> buildContextButtons(List<ObjectMenuRespDTO> menus) {
|
||||
if (menus.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return menus.stream()
|
||||
.filter(menu -> MenuTypeEnum.BUTTON.getType().equals(menu.getType()))
|
||||
.map(ObjectMenuRespDTO::getPermission)
|
||||
.filter(StringUtils::hasText)
|
||||
.map(String::trim)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Map<String, Long> buildProjectStatusCounts(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 validateDeleteConfirmText(String confirmText) {
|
||||
String normalizedConfirmText = normalizeNullableText(confirmText);
|
||||
if (!Objects.equals(ProjectObjectConstants.DELETE_CONFIRM_TEXT, normalizedConfirmText)) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_DELETE_CONFIRM_TEXT_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeProjectStatusLog(ProjectDO project, String actionType, String fromStatus,
|
||||
String toStatus, String reason) {
|
||||
ProjectStatusLogDO statusLog = new ProjectStatusLogDO();
|
||||
statusLog.setProjectId(project.getId());
|
||||
statusLog.setActionType(actionType);
|
||||
statusLog.setFromStatus(fromStatus);
|
||||
statusLog.setToStatus(toStatus);
|
||||
statusLog.setReason(defaultText(reason));
|
||||
statusLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||
statusLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||
statusLog.setProjectCodeSnapshot(project.getProjectCode());
|
||||
statusLog.setProjectNameSnapshot(project.getProjectName());
|
||||
projectStatusLogMapper.insert(statusLog);
|
||||
}
|
||||
|
||||
private void writeBizAuditLog(ProjectDO project, String actionType, String fromStatus, String toStatus,
|
||||
String fieldChanges, String reason) {
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(ProjectObjectConstants.OBJECT_TYPE);
|
||||
auditLog.setBizId(project.getId());
|
||||
auditLog.setActionType(actionType);
|
||||
auditLog.setFromStatus(fromStatus);
|
||||
auditLog.setToStatus(toStatus);
|
||||
auditLog.setFieldChanges(fieldChanges);
|
||||
auditLog.setReason(reason);
|
||||
auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||
auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||
bizAuditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
private void writeMemberAuditLog(UserObjectRoleDO member, String actionType,
|
||||
UserObjectRoleDO before, UserObjectRoleDO after, String reason) {
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(ObjectActivityConstants.MEMBER_BIZ_TYPE);
|
||||
auditLog.setBizId(member.getId());
|
||||
auditLog.setActionType(actionType);
|
||||
auditLog.setFieldChanges(buildMemberFieldChanges(before, after));
|
||||
auditLog.setReason(reason);
|
||||
auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||
auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||
bizAuditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
private void writeManagerChangeAuditLog(Long projectId, Long beforeManagerUserId, Long afterManagerUserId, String reason) {
|
||||
if (Objects.equals(beforeManagerUserId, afterManagerUserId)) {
|
||||
return;
|
||||
}
|
||||
BizAuditLogDO auditLog = new BizAuditLogDO();
|
||||
auditLog.setBizType(ProjectObjectConstants.OBJECT_TYPE);
|
||||
auditLog.setBizId(projectId);
|
||||
auditLog.setActionType(ObjectActivityConstants.PROJECT_ACTION_CHANGE_MANAGER);
|
||||
auditLog.setFieldChanges(buildManagerFieldChanges(beforeManagerUserId, afterManagerUserId));
|
||||
auditLog.setReason(reason);
|
||||
auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId());
|
||||
auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname()));
|
||||
bizAuditLogMapper.insert(auditLog);
|
||||
}
|
||||
|
||||
private ProjectDO cloneProject(ProjectDO source) {
|
||||
ProjectDO target = new ProjectDO();
|
||||
target.setId(source.getId());
|
||||
target.setProjectCode(source.getProjectCode());
|
||||
target.setProjectName(source.getProjectName());
|
||||
target.setProjectType(source.getProjectType());
|
||||
target.setDirectionCode(source.getDirectionCode());
|
||||
target.setProjectSetId(source.getProjectSetId());
|
||||
target.setProductId(source.getProductId());
|
||||
target.setProductVersionId(source.getProductVersionId());
|
||||
target.setManagerUserId(source.getManagerUserId());
|
||||
target.setStatusCode(source.getStatusCode());
|
||||
target.setPlannedStartDate(source.getPlannedStartDate());
|
||||
target.setPlannedEndDate(source.getPlannedEndDate());
|
||||
target.setActualStartDate(source.getActualStartDate());
|
||||
target.setActualEndDate(source.getActualEndDate());
|
||||
target.setProgressRate(source.getProgressRate());
|
||||
target.setProjectDesc(source.getProjectDesc());
|
||||
target.setLastStatusReason(source.getLastStatusReason());
|
||||
return target;
|
||||
}
|
||||
|
||||
private UserObjectRoleDO cloneMember(UserObjectRoleDO source) {
|
||||
UserObjectRoleDO clone = new UserObjectRoleDO();
|
||||
clone.setId(source.getId());
|
||||
clone.setUserId(source.getUserId());
|
||||
clone.setObjectType(source.getObjectType());
|
||||
clone.setObjectId(source.getObjectId());
|
||||
clone.setRoleId(source.getRoleId());
|
||||
clone.setStatus(source.getStatus());
|
||||
clone.setJoinedTime(source.getJoinedTime());
|
||||
clone.setLeftTime(source.getLeftTime());
|
||||
clone.setRemark(source.getRemark());
|
||||
return clone;
|
||||
}
|
||||
|
||||
private String buildProjectFieldChanges(ProjectDO before, ProjectDO after) {
|
||||
Map<String, Object> fieldChanges = new LinkedHashMap<>();
|
||||
appendFieldChange(fieldChanges, "projectCode", valueOf(before, ProjectDO::getProjectCode),
|
||||
valueOf(after, ProjectDO::getProjectCode));
|
||||
appendFieldChange(fieldChanges, "projectName", valueOf(before, ProjectDO::getProjectName),
|
||||
valueOf(after, ProjectDO::getProjectName));
|
||||
appendFieldChange(fieldChanges, "projectType", valueOf(before, ProjectDO::getProjectType),
|
||||
valueOf(after, ProjectDO::getProjectType));
|
||||
appendFieldChange(fieldChanges, "directionCode", valueOf(before, ProjectDO::getDirectionCode),
|
||||
valueOf(after, ProjectDO::getDirectionCode));
|
||||
appendFieldChange(fieldChanges, "productId", valueOf(before, ProjectDO::getProductId),
|
||||
valueOf(after, ProjectDO::getProductId));
|
||||
appendFieldChange(fieldChanges, "managerUserId", valueOf(before, ProjectDO::getManagerUserId),
|
||||
valueOf(after, ProjectDO::getManagerUserId));
|
||||
appendFieldChange(fieldChanges, "statusCode", valueOf(before, ProjectDO::getStatusCode),
|
||||
valueOf(after, ProjectDO::getStatusCode));
|
||||
appendFieldChange(fieldChanges, "projectDesc", valueOf(before, ProjectDO::getProjectDesc),
|
||||
valueOf(after, ProjectDO::getProjectDesc));
|
||||
appendFieldChange(fieldChanges, "lastStatusReason", valueOf(before, ProjectDO::getLastStatusReason),
|
||||
valueOf(after, ProjectDO::getLastStatusReason));
|
||||
return fieldChanges.isEmpty() ? null : JsonUtils.toJsonString(fieldChanges);
|
||||
}
|
||||
|
||||
private String buildMemberFieldChanges(UserObjectRoleDO before, UserObjectRoleDO after) {
|
||||
Map<String, Object> fieldChanges = new LinkedHashMap<>();
|
||||
appendFieldChange(fieldChanges, "userId", valueOf(before, UserObjectRoleDO::getUserId),
|
||||
valueOf(after, UserObjectRoleDO::getUserId));
|
||||
appendFieldChange(fieldChanges, "roleId", valueOf(before, UserObjectRoleDO::getRoleId),
|
||||
valueOf(after, UserObjectRoleDO::getRoleId));
|
||||
appendFieldChange(fieldChanges, "status", valueOf(before, UserObjectRoleDO::getStatus),
|
||||
valueOf(after, UserObjectRoleDO::getStatus));
|
||||
appendFieldChange(fieldChanges, "joinedTime", valueOf(before, UserObjectRoleDO::getJoinedTime),
|
||||
valueOf(after, UserObjectRoleDO::getJoinedTime));
|
||||
appendFieldChange(fieldChanges, "leftTime", valueOf(before, UserObjectRoleDO::getLeftTime),
|
||||
valueOf(after, UserObjectRoleDO::getLeftTime));
|
||||
appendFieldChange(fieldChanges, "remark", valueOf(before, UserObjectRoleDO::getRemark),
|
||||
valueOf(after, UserObjectRoleDO::getRemark));
|
||||
return fieldChanges.isEmpty() ? null : JsonUtils.toJsonString(fieldChanges);
|
||||
}
|
||||
|
||||
private String buildManagerFieldChanges(Long beforeManagerUserId, Long afterManagerUserId) {
|
||||
Map<String, Object> fieldChanges = new LinkedHashMap<>();
|
||||
appendFieldChange(fieldChanges, "managerUserId", beforeManagerUserId, afterManagerUserId);
|
||||
return JsonUtils.toJsonString(fieldChanges);
|
||||
}
|
||||
|
||||
private <T> T valueOf(ProjectDO project, Function<ProjectDO, T> getter) {
|
||||
return project == null ? null : getter.apply(project);
|
||||
}
|
||||
|
||||
private <T> T valueOf(UserObjectRoleDO member, Function<UserObjectRoleDO, T> getter) {
|
||||
return member == null ? null : getter.apply(member);
|
||||
}
|
||||
|
||||
private void appendFieldChange(Map<String, Object> fieldChanges, String fieldName, Object before, Object after) {
|
||||
if (Objects.equals(before, after)) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> value = new LinkedHashMap<>();
|
||||
value.put("before", before);
|
||||
value.put("after", after);
|
||||
fieldChanges.put(fieldName, value);
|
||||
}
|
||||
|
||||
private String normalizeNullableText(String value) {
|
||||
if (!StringUtils.hasText(value)) {
|
||||
return null;
|
||||
}
|
||||
return value.trim();
|
||||
}
|
||||
|
||||
private String defaultText(String value) {
|
||||
return StringUtils.hasText(value) ? value : "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.njcn.rdms.module.project.service.project;
|
||||
|
||||
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.task.vo.ProjectTaskStatusBoardReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.task.vo.ProjectTaskStatusBoardRespVO;
|
||||
|
||||
public interface ProjectStatusBoardService {
|
||||
|
||||
ProjectExecutionStatusBoardRespVO getExecutionStatusBoard(Long projectId, ProjectExecutionStatusBoardReqVO reqVO);
|
||||
|
||||
ProjectTaskStatusBoardRespVO getTaskStatusBoard(Long projectId, Long executionId, ProjectTaskStatusBoardReqVO reqVO);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.njcn.rdms.module.project.service.project;
|
||||
|
||||
import com.njcn.rdms.module.project.constant.ProjectExecutionConstants;
|
||||
import com.njcn.rdms.module.project.constant.ProjectTaskConstants;
|
||||
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.task.vo.ProjectTaskStatusBoardReqVO;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.task.vo.ProjectTaskStatusBoardRespVO;
|
||||
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO;
|
||||
import com.njcn.rdms.module.project.dal.mysql.project.execution.ProjectExecutionMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.project.task.ProjectTaskMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class ProjectStatusBoardServiceImpl implements ProjectStatusBoardService {
|
||||
|
||||
@Resource
|
||||
private ObjectStatusModelMapper objectStatusModelMapper;
|
||||
@Resource
|
||||
private ProjectExecutionMapper projectExecutionMapper;
|
||||
@Resource
|
||||
private ProjectTaskMapper projectTaskMapper;
|
||||
|
||||
@Override
|
||||
public ProjectExecutionStatusBoardRespVO getExecutionStatusBoard(Long projectId, ProjectExecutionStatusBoardReqVO reqVO) {
|
||||
List<ObjectStatusModelDO> statusModels = objectStatusModelMapper.selectListByObjectTypeEnabled(ProjectExecutionConstants.OBJECT_TYPE);
|
||||
return buildExecutionStatusBoard(projectId, reqVO, statusModels);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProjectTaskStatusBoardRespVO getTaskStatusBoard(Long projectId, Long executionId, ProjectTaskStatusBoardReqVO reqVO) {
|
||||
List<ObjectStatusModelDO> statusModels = objectStatusModelMapper.selectListByObjectTypeEnabled(ProjectTaskConstants.OBJECT_TYPE);
|
||||
return buildTaskStatusBoard(projectId, executionId, reqVO, statusModels);
|
||||
}
|
||||
|
||||
private ProjectExecutionStatusBoardRespVO buildExecutionStatusBoard(Long projectId, ProjectExecutionStatusBoardReqVO reqVO,
|
||||
List<ObjectStatusModelDO> statusModels) {
|
||||
ProjectExecutionStatusBoardRespVO respVO = new ProjectExecutionStatusBoardRespVO();
|
||||
List<ProjectExecutionStatusBoardRespVO.ProjectStatusBoardItemVO> items = statusModels.stream().map(statusModel -> {
|
||||
ProjectExecutionStatusBoardRespVO.ProjectStatusBoardItemVO item =
|
||||
new ProjectExecutionStatusBoardRespVO.ProjectStatusBoardItemVO();
|
||||
item.setStatusCode(statusModel.getStatusCode());
|
||||
item.setStatusName(statusModel.getStatusName());
|
||||
item.setCount(projectExecutionMapper.countByProjectIdAndStatusCode(projectId, reqVO, statusModel.getStatusCode()).longValue());
|
||||
item.setSort(statusModel.getSort());
|
||||
item.setTerminal(statusModel.getTerminalFlag());
|
||||
return item;
|
||||
}).toList();
|
||||
respVO.setItems(items);
|
||||
respVO.setTotal(items.stream().mapToLong(ProjectExecutionStatusBoardRespVO.ProjectStatusBoardItemVO::getCount).sum());
|
||||
return respVO;
|
||||
}
|
||||
|
||||
private ProjectTaskStatusBoardRespVO buildTaskStatusBoard(Long projectId, Long executionId, ProjectTaskStatusBoardReqVO reqVO,
|
||||
List<ObjectStatusModelDO> statusModels) {
|
||||
ProjectTaskStatusBoardRespVO respVO = new ProjectTaskStatusBoardRespVO();
|
||||
List<ProjectTaskStatusBoardRespVO.ProjectStatusBoardItemVO> items = statusModels.stream().map(statusModel -> {
|
||||
ProjectTaskStatusBoardRespVO.ProjectStatusBoardItemVO item = new ProjectTaskStatusBoardRespVO.ProjectStatusBoardItemVO();
|
||||
item.setStatusCode(statusModel.getStatusCode());
|
||||
item.setStatusName(statusModel.getStatusName());
|
||||
item.setCount(projectTaskMapper.countByProjectIdAndExecutionIdAndStatusCode(projectId, executionId, reqVO,
|
||||
statusModel.getStatusCode()).longValue());
|
||||
item.setSort(statusModel.getSort());
|
||||
item.setTerminal(statusModel.getTerminalFlag());
|
||||
return item;
|
||||
}).toList();
|
||||
respVO.setItems(items);
|
||||
respVO.setTotal(items.stream().mapToLong(ProjectTaskStatusBoardRespVO.ProjectStatusBoardItemVO::getCount).sum());
|
||||
return respVO;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.njcn.rdms.module.project.service.project;
|
||||
|
||||
import com.njcn.rdms.module.project.constant.ObjectActivityConstants;
|
||||
import com.njcn.rdms.module.project.constant.ProjectObjectConstants;
|
||||
import com.njcn.rdms.module.project.controller.admin.project.vo.project.ProjectLifecycleActionRespVO;
|
||||
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.status.ObjectStatusModelMapper;
|
||||
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
|
||||
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
@Service
|
||||
public class ProjectStatusViewService {
|
||||
|
||||
@Resource
|
||||
private ObjectStatusModelMapper objectStatusModelMapper;
|
||||
@Resource
|
||||
private ObjectStatusTransitionMapper objectStatusTransitionMapper;
|
||||
|
||||
public ProjectLifecycleView getLifecycle(String statusCode) {
|
||||
ObjectStatusModelDO statusModel = objectStatusModelMapper
|
||||
.selectByObjectTypeAndStatusCodeEnabled(ProjectObjectConstants.OBJECT_TYPE, statusCode);
|
||||
if (statusModel == null) {
|
||||
throw exception(ErrorCodeConstants.PROJECT_STATUS_MODEL_NOT_EXISTS_OR_DISABLED);
|
||||
}
|
||||
return new ProjectLifecycleView(
|
||||
statusModel.getStatusName(),
|
||||
statusModel.getTerminalFlag(),
|
||||
statusModel.getAllowEdit(),
|
||||
buildAvailableActions(statusCode)
|
||||
);
|
||||
}
|
||||
|
||||
private List<ProjectLifecycleActionRespVO> buildAvailableActions(String statusCode) {
|
||||
List<ObjectStatusTransitionDO> transitions = objectStatusTransitionMapper
|
||||
.selectListByObjectTypeAndFromStatus(ProjectObjectConstants.OBJECT_TYPE, statusCode);
|
||||
if (transitions == null || transitions.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return transitions.stream()
|
||||
// auto_start 是后端内部推进动作,允许存在于流转配置中,但不应直接暴露给前端按钮。
|
||||
.filter(transition -> !Objects.equals(transition.getActionCode(), ObjectActivityConstants.PROJECT_ACTION_AUTO_START))
|
||||
.map(transition -> {
|
||||
ProjectLifecycleActionRespVO action = new ProjectLifecycleActionRespVO();
|
||||
action.setActionCode(transition.getActionCode());
|
||||
action.setActionName(transition.getActionName());
|
||||
action.setNeedReason(transition.getNeedReason());
|
||||
return action;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
public record ProjectLifecycleView(String statusName,
|
||||
Boolean terminal,
|
||||
Boolean allowEdit,
|
||||
List<ProjectLifecycleActionRespVO> availableActions) {
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user