diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/personal/vo/item/PersonalItemRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/personal/vo/item/PersonalItemRespVO.java index aeea773..5836c20 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/personal/vo/item/PersonalItemRespVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/personal/vo/item/PersonalItemRespVO.java @@ -19,6 +19,9 @@ public class PersonalItemRespVO { @Schema(description = "个人事项标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "整理供应商沟通纪要") private String taskTitle; + @Schema(description = "个人事项类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "todo") + private String type; + @Schema(description = "负责人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3001") private Long ownerId; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/personal/vo/item/PersonalItemSaveReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/personal/vo/item/PersonalItemSaveReqVO.java index 5313b63..f5cabe6 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/personal/vo/item/PersonalItemSaveReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/personal/vo/item/PersonalItemSaveReqVO.java @@ -25,6 +25,11 @@ public class PersonalItemSaveReqVO { @Size(max = 300, message = "个人事项标题长度不能超过300个字符") private String taskTitle; + @Schema(description = "个人事项类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "todo") + @NotBlank(message = "个人事项类型不能为空") + @Size(max = 32, message = "个人事项类型长度不能超过32个字符") + private String type; + @Schema(description = "个人事项进度(0~100)", example = "60.00") @DecimalMin(value = "0.00", message = "个人事项进度不能小于 0") @DecimalMax(value = "100.00", message = "个人事项进度不能大于 100") diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/task/vo/ProjectTaskRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/task/vo/ProjectTaskRespVO.java index 10bb362..64f2602 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/task/vo/ProjectTaskRespVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/task/vo/ProjectTaskRespVO.java @@ -31,6 +31,8 @@ public class ProjectTaskRespVO { private Long executionOwnerId; @Schema(description = "任务标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "接口联调任务") private String taskTitle; + @Schema(description = "任务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "feature") + private String type; @Schema(description = "任务负责人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3002") private Long ownerId; @Schema(description = "任务负责人昵称", example = "李四") diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/task/vo/ProjectTaskSaveReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/task/vo/ProjectTaskSaveReqVO.java index 9579dd9..c8f4828 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/task/vo/ProjectTaskSaveReqVO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/project/task/vo/ProjectTaskSaveReqVO.java @@ -25,6 +25,11 @@ public class ProjectTaskSaveReqVO { @Size(max = 300, message = "任务标题长度不能超过300个字符") private String taskTitle; + @Schema(description = "任务类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "feature") + @NotBlank(message = "任务类型不能为空") + @Size(max = 32, message = "任务类型长度不能超过32个字符") + private String type; + @Schema(description = "任务负责人用户编号;子任务不传时继承父任务负责人", example = "3002") private Long ownerId; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/personal/PersonalItemDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/personal/PersonalItemDO.java index 8c43d46..8b582f4 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/personal/PersonalItemDO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/personal/PersonalItemDO.java @@ -27,6 +27,8 @@ public class PersonalItemDO extends BaseDO { private String taskTitle; + private String type; + private Long ownerId; private String statusCode; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/project/task/ProjectTaskDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/project/task/ProjectTaskDO.java index 88d4f02..b70bf86 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/project/task/ProjectTaskDO.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/project/task/ProjectTaskDO.java @@ -42,6 +42,10 @@ public class ProjectTaskDO extends BaseDO { * 任务标题 */ private String taskTitle; + /** + * 任务类型 + */ + private String type; /** * 任务负责人用户编号 */ diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/personal/PersonalItemServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/personal/PersonalItemServiceImpl.java index 11d3342..de923b7 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/personal/PersonalItemServiceImpl.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/personal/PersonalItemServiceImpl.java @@ -94,6 +94,7 @@ public class PersonalItemServiceImpl implements PersonalItemService { PersonalItemDO item = new PersonalItemDO(); item.setTaskTitle(normalizeRequiredTitle(reqVO.getTaskTitle())); + item.setType(normalizeRequiredType(reqVO.getType(), "个人事项类型不能为空")); item.setOwnerId(loginUserId); item.setStatusCode(getInitialStatusCode()); item.setProgressRate(normalizeProgress(reqVO.getProgressRate())); @@ -123,6 +124,7 @@ public class PersonalItemServiceImpl implements PersonalItemService { PersonalItemDO before = cloneItem(item); item.setTaskTitle(normalizeRequiredTitle(reqVO.getTaskTitle())); + item.setType(normalizeRequiredType(reqVO.getType(), "个人事项类型不能为空")); item.setProgressRate(normalizeProgress(reqVO.getProgressRate())); item.setPlannedStartDate(reqVO.getPlannedStartDate()); item.setPlannedEndDate(reqVO.getPlannedEndDate()); @@ -361,6 +363,7 @@ public class PersonalItemServiceImpl implements PersonalItemService { task.setExecutionId(executionId); task.setParentTaskId(null); task.setTaskTitle(item.getTaskTitle()); + task.setType(item.getType()); task.setOwnerId(item.getOwnerId()); task.setStatusCode(item.getStatusCode()); task.setProgressRate(item.getProgressRate()); @@ -546,6 +549,7 @@ public class PersonalItemServiceImpl implements PersonalItemService { PersonalItemDO target = new PersonalItemDO(); target.setId(source.getId()); target.setTaskTitle(source.getTaskTitle()); + target.setType(source.getType()); target.setOwnerId(source.getOwnerId()); target.setStatusCode(source.getStatusCode()); target.setProgressRate(source.getProgressRate()); @@ -563,6 +567,8 @@ public class PersonalItemServiceImpl implements PersonalItemService { Map fieldChanges = new LinkedHashMap<>(); appendFieldChange(fieldChanges, "taskTitle", valueOf(before, PersonalItemDO::getTaskTitle), valueOf(after, PersonalItemDO::getTaskTitle)); + appendFieldChange(fieldChanges, "type", valueOf(before, PersonalItemDO::getType), + valueOf(after, PersonalItemDO::getType)); appendFieldChange(fieldChanges, "ownerId", valueOf(before, PersonalItemDO::getOwnerId), valueOf(after, PersonalItemDO::getOwnerId)); appendFieldChange(fieldChanges, "statusCode", valueOf(before, PersonalItemDO::getStatusCode), @@ -732,6 +738,13 @@ public class PersonalItemServiceImpl implements PersonalItemService { return value.trim(); } + private String normalizeRequiredType(String value, String message) { + if (!StringUtils.hasText(value)) { + throw invalidParamException(message); + } + return value.trim(); + } + private String normalizeNullableText(String value) { if (!StringUtils.hasText(value)) { return null; diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/task/ProjectTaskServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/task/ProjectTaskServiceImpl.java index 45aae55..06b0017 100644 --- a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/task/ProjectTaskServiceImpl.java +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/project/task/ProjectTaskServiceImpl.java @@ -139,6 +139,7 @@ public class ProjectTaskServiceImpl implements ProjectTaskService { task.setExecutionId(executionId); task.setParentTaskId(parentTask == null ? null : parentTask.getId()); task.setTaskTitle(normalizeRequiredTitle(reqVO.getTaskTitle())); + task.setType(normalizeRequiredType(reqVO.getType(), "任务类型不能为空")); task.setOwnerId(ownerId); task.setStatusCode(getInitialTaskStatusCode()); // 任务进度统一由 worklog 驱动;新建任务强制为 0 @@ -202,6 +203,7 @@ public class ProjectTaskServiceImpl implements ProjectTaskService { ProjectTaskDO before = cloneTask(task); task.setParentTaskId(newParentId); task.setTaskTitle(normalizeRequiredTitle(reqVO.getTaskTitle())); + task.setType(normalizeRequiredType(reqVO.getType(), "任务类型不能为空")); task.setOwnerId(ownerId); task.setPlannedStartDate(reqVO.getPlannedStartDate()); task.setPlannedEndDate(reqVO.getPlannedEndDate()); @@ -939,6 +941,7 @@ public class ProjectTaskServiceImpl implements ProjectTaskService { target.setExecutionId(source.getExecutionId()); target.setParentTaskId(source.getParentTaskId()); target.setTaskTitle(source.getTaskTitle()); + target.setType(source.getType()); target.setOwnerId(source.getOwnerId()); target.setStatusCode(source.getStatusCode()); target.setProgressRate(source.getProgressRate()); @@ -961,6 +964,8 @@ public class ProjectTaskServiceImpl implements ProjectTaskService { valueOf(after, ProjectTaskDO::getParentTaskId)); appendFieldChange(fieldChanges, "taskTitle", valueOf(before, ProjectTaskDO::getTaskTitle), valueOf(after, ProjectTaskDO::getTaskTitle)); + appendFieldChange(fieldChanges, "type", valueOf(before, ProjectTaskDO::getType), + valueOf(after, ProjectTaskDO::getType)); appendFieldChange(fieldChanges, "ownerId", valueOf(before, ProjectTaskDO::getOwnerId), valueOf(after, ProjectTaskDO::getOwnerId)); appendFieldChange(fieldChanges, "statusCode", valueOf(before, ProjectTaskDO::getStatusCode), @@ -1091,6 +1096,13 @@ public class ProjectTaskServiceImpl implements ProjectTaskService { return value.trim(); } + private String normalizeRequiredType(String value, String message) { + if (!StringUtils.hasText(value)) { + throw invalidParamException(message); + } + return value.trim(); + } + private String normalizeNullableText(String value) { if (!StringUtils.hasText(value)) { return null; diff --git a/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/project/task/ProjectTaskServiceImplTest.java b/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/project/task/ProjectTaskServiceImplTest.java deleted file mode 100644 index c31eef1..0000000 --- a/rdms-project/rdms-project-boot/src/test/java/com/njcn/rdms/module/project/service/project/task/ProjectTaskServiceImplTest.java +++ /dev/null @@ -1,691 +0,0 @@ -package com.njcn.rdms.module.project.service.project.task; - -import com.njcn.rdms.framework.common.exception.ServiceException; -import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils; -import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest; -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.dal.dataobject.audit.BizAuditLogDO; -import com.njcn.rdms.module.project.dal.dataobject.attachment.AttachmentItem; -import com.njcn.rdms.module.project.dal.dataobject.project.ProjectDO; -import com.njcn.rdms.module.project.dal.dataobject.project.execution.ExecutionAssigneeDO; -import com.njcn.rdms.module.project.dal.dataobject.project.execution.ProjectExecutionDO; -import com.njcn.rdms.module.project.dal.dataobject.project.task.ProjectTaskDO; -import com.njcn.rdms.module.project.dal.dataobject.project.task.ProjectTaskStatusLogDO; -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.project.ProjectMapper; -import com.njcn.rdms.module.project.dal.mysql.project.execution.ExecutionAssigneeMapper; -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.project.task.ProjectTaskStatusLogMapper; -import com.njcn.rdms.module.project.dal.mysql.project.task.TaskWorklogMapper; -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.attachment.AttachmentFileIdResolver; -import com.njcn.rdms.module.project.framework.security.service.ProjectObjectAuthorizationService; -import com.njcn.rdms.module.project.service.project.ProjectService; -import com.njcn.rdms.module.project.service.project.task.assignee.TaskAssigneeService; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockedStatic; - -import java.math.BigDecimal; -import java.util.Collections; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class ProjectTaskServiceImplTest extends BaseMockitoUnitTest { - - @InjectMocks - private ProjectTaskServiceImpl projectTaskService; - @Mock - private ProjectMapper projectMapper; - @Mock - private ProjectExecutionMapper projectExecutionMapper; - @Mock - private ExecutionAssigneeMapper executionAssigneeMapper; - @Mock - private ProjectTaskMapper projectTaskMapper; - @Mock - private TaskWorklogMapper taskWorklogMapper; - @Mock - private ProjectTaskStatusLogMapper projectTaskStatusLogMapper; - @Mock - private BizAuditLogMapper bizAuditLogMapper; - @Mock - private ObjectStatusModelMapper objectStatusModelMapper; - @Mock - private ObjectStatusTransitionMapper objectStatusTransitionMapper; - @Mock - private ProjectService projectService; - @Mock - private TaskAssigneeService taskAssigneeService; - @Mock - private AttachmentFileIdResolver attachmentFileIdResolver; - @Mock - private ProjectObjectAuthorizationService projectObjectAuthorizationService; - @Mock - private com.njcn.rdms.module.project.service.project.permission.VisibilityScopeResolver visibilityScopeResolver; - - /** - * 默认让 VisibilityScopeResolver 放行(seesAll=true),既有测试无需关心 scope。 - */ - @BeforeEach - void setupVisibilityScopeAll() { - lenient().when(visibilityScopeResolver.resolveForProject(any(), any())) - .thenReturn(com.njcn.rdms.module.project.service.project.permission.VisibilityScope.all()); - lenient().when(visibilityScopeResolver.resolveForExecution(any(), any(), any())) - .thenReturn(com.njcn.rdms.module.project.service.project.permission.VisibilityScope.all()); - } - - @Test - void createTask_shouldInsertPendingTaskAndAutoStartProject() { - Long projectId = 2001L; - Long executionId = 5001L; - ProjectTaskSaveReqVO reqVO = createTaskReqVO(); - reqVO.setOwnerId(3002L); - - when(projectMapper.selectById(projectId)).thenReturn(createEditableProject(projectId)); - when(projectExecutionMapper.selectByProjectIdAndId(projectId, executionId)) - .thenReturn(createEditableExecution(projectId, executionId)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "pending")) - .thenReturn(createStatus("project", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("execution", "pending")) - .thenReturn(createStatus("execution", "pending", true)); - when(objectStatusModelMapper.selectInitialByObjectTypeEnabled("task")) - .thenReturn(createStatus("task", "pending", true)); - when(executionAssigneeMapper.selectActiveByExecutionIdAndUserId(executionId, 3002L)) - .thenReturn(createExecutionAssignee(executionId, 3002L)); - when(projectTaskMapper.insert(any(ProjectTaskDO.class))).thenAnswer(invocation -> { - ProjectTaskDO task = invocation.getArgument(0); - task.setId(9001L); - return 1; - }); - - Long taskId = projectTaskService.createTask(projectId, executionId, reqVO); - - assertNotNull(taskId); - ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(ProjectTaskDO.class); - verify(projectTaskMapper).insert(taskCaptor.capture()); - assertEquals(projectId, taskCaptor.getValue().getProjectId()); - assertEquals(executionId, taskCaptor.getValue().getExecutionId()); - assertEquals("接口联调任务", taskCaptor.getValue().getTaskTitle()); - assertEquals(3002L, taskCaptor.getValue().getOwnerId()); - assertEquals("pending", taskCaptor.getValue().getStatusCode()); - assertEquals(BigDecimal.ZERO, taskCaptor.getValue().getProgressRate()); - verify(projectService).autoStartProjectIfPending(projectId, "create_task"); - verify(bizAuditLogMapper).insert(any(BizAuditLogDO.class)); - } - - @Test - void createTask_shouldPersistAttachmentFileId() { - Long projectId = 2001L; - Long executionId = 5001L; - ProjectTaskSaveReqVO reqVO = createTaskReqVO(); - reqVO.setOwnerId(3002L); - AttachmentItem attachment = new AttachmentItem(); - attachment.setId("10001"); - attachment.setUrl("http://oss.example.com/task/2026/05/report.txt"); - attachment.setName("report.txt"); - attachment.setSize(3164L); - attachment.setContentType("text/plain"); - reqVO.setAttachments(List.of(attachment)); - - when(projectMapper.selectById(projectId)).thenReturn(createEditableProject(projectId)); - when(projectExecutionMapper.selectByProjectIdAndId(projectId, executionId)) - .thenReturn(createEditableExecution(projectId, executionId)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "pending")) - .thenReturn(createStatus("project", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("execution", "pending")) - .thenReturn(createStatus("execution", "pending", true)); - when(objectStatusModelMapper.selectInitialByObjectTypeEnabled("task")) - .thenReturn(createStatus("task", "pending", true)); - when(executionAssigneeMapper.selectActiveByExecutionIdAndUserId(executionId, 3002L)) - .thenReturn(createExecutionAssignee(executionId, 3002L)); - when(projectTaskMapper.insert(any(ProjectTaskDO.class))).thenAnswer(invocation -> { - ProjectTaskDO task = invocation.getArgument(0); - task.setId(9001L); - return 1; - }); - - projectTaskService.createTask(projectId, executionId, reqVO); - - ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(ProjectTaskDO.class); - verify(projectTaskMapper).insert(taskCaptor.capture()); - verify(attachmentFileIdResolver).resolve(reqVO.getAttachments()); - assertEquals("10001", taskCaptor.getValue().getAttachments().get(0).getId()); - } - - @Test - void createSubTask_whenOwnerBlank_shouldInheritParentOwner() { - Long projectId = 2001L; - Long executionId = 5001L; - ProjectTaskDO parentTask = createTask(projectId, executionId, 8001L, 3002L); - ProjectTaskSaveReqVO reqVO = createTaskReqVO(); - reqVO.setParentTaskId(parentTask.getId()); - reqVO.setOwnerId(null); - - when(projectMapper.selectById(projectId)).thenReturn(createEditableProject(projectId)); - when(projectExecutionMapper.selectByProjectIdAndId(projectId, executionId)) - .thenReturn(createEditableExecution(projectId, executionId)); - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, parentTask.getId())) - .thenReturn(parentTask); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "pending")) - .thenReturn(createStatus("project", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("execution", "pending")) - .thenReturn(createStatus("execution", "pending", true)); - when(objectStatusModelMapper.selectInitialByObjectTypeEnabled("task")) - .thenReturn(createStatus("task", "pending", true)); - when(executionAssigneeMapper.selectActiveByExecutionIdAndUserId(executionId, 3002L)) - .thenReturn(createExecutionAssignee(executionId, 3002L)); - when(projectTaskMapper.insert(any(ProjectTaskDO.class))).thenAnswer(invocation -> { - ProjectTaskDO task = invocation.getArgument(0); - task.setId(9002L); - return 1; - }); - - projectTaskService.createTask(projectId, executionId, reqVO); - - ArgumentCaptor taskCaptor = ArgumentCaptor.forClass(ProjectTaskDO.class); - verify(projectTaskMapper).insert(taskCaptor.capture()); - assertEquals(3002L, taskCaptor.getValue().getOwnerId()); - assertEquals(parentTask.getId(), taskCaptor.getValue().getParentTaskId()); - } - - @Test - void createTask_whenOwnerNotActiveExecutionAssignee_shouldThrow() { - Long projectId = 2001L; - Long executionId = 5001L; - ProjectTaskSaveReqVO reqVO = createTaskReqVO(); - reqVO.setOwnerId(3999L); - - when(projectMapper.selectById(projectId)).thenReturn(createEditableProject(projectId)); - when(projectExecutionMapper.selectByProjectIdAndId(projectId, executionId)) - .thenReturn(createEditableExecution(projectId, executionId)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "pending")) - .thenReturn(createStatus("project", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("execution", "pending")) - .thenReturn(createStatus("execution", "pending", true)); - when(executionAssigneeMapper.selectActiveByExecutionIdAndUserId(executionId, 3999L)).thenReturn(null); - - ServiceException ex = assertThrows(ServiceException.class, - () -> projectTaskService.createTask(projectId, executionId, reqVO)); - - assertEquals(ErrorCodeConstants.PROJECT_TASK_OWNER_INVALID.getCode(), ex.getCode()); - } - - @Test - void createTask_whenParentNotInSameExecution_shouldThrow() { - Long projectId = 2001L; - Long executionId = 5001L; - ProjectTaskSaveReqVO reqVO = createTaskReqVO(); - reqVO.setParentTaskId(8001L); - reqVO.setOwnerId(3002L); - - when(projectMapper.selectById(projectId)).thenReturn(createEditableProject(projectId)); - when(projectExecutionMapper.selectByProjectIdAndId(projectId, executionId)) - .thenReturn(createEditableExecution(projectId, executionId)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "pending")) - .thenReturn(createStatus("project", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("execution", "pending")) - .thenReturn(createStatus("execution", "pending", true)); - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, 8001L)).thenReturn(null); - - ServiceException ex = assertThrows(ServiceException.class, - () -> projectTaskService.createTask(projectId, executionId, reqVO)); - - assertEquals(ErrorCodeConstants.PROJECT_TASK_PARENT_INVALID.getCode(), ex.getCode()); - } - - @Test - void changeTaskStatus_shouldUseTransitionAndWriteLogs() { - Long projectId = 2001L; - Long executionId = 5001L; - Long taskId = 9001L; - ProjectTaskDO task = createTask(projectId, executionId, taskId, 3002L); - ProjectTaskStatusActionReqVO reqVO = new ProjectTaskStatusActionReqVO(); - reqVO.setActionCode("start"); - - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, taskId)).thenReturn(task); - when(objectStatusTransitionMapper.selectByObjectTypeAndFromStatusAndAction("task", "pending", "start")) - .thenReturn(createTransition("start", "active", false)); - when(projectTaskMapper.updateStatusByIdAndStatus(taskId, "pending", "active", null)).thenReturn(1); - - projectTaskService.changeTaskStatus(projectId, executionId, taskId, reqVO); - - verify(projectObjectAuthorizationService).checkOwnerOrProjectPermission(projectId, 3002L, - "project:task:status"); - verify(projectTaskMapper).updateStatusByIdAndStatus(taskId, "pending", "active", null); - ArgumentCaptor statusCaptor = ArgumentCaptor.forClass(ProjectTaskStatusLogDO.class); - verify(projectTaskStatusLogMapper).insert(statusCaptor.capture()); - assertEquals("start", statusCaptor.getValue().getActionType()); - assertEquals("pending", statusCaptor.getValue().getFromStatus()); - assertEquals("active", statusCaptor.getValue().getToStatus()); - verify(bizAuditLogMapper).insert(any(BizAuditLogDO.class)); - } - - @Test - void changeTaskStatus_whenReasonRequiredButBlank_shouldThrow() { - Long projectId = 2001L; - Long executionId = 5001L; - Long taskId = 9001L; - ProjectTaskDO task = createTask(projectId, executionId, taskId, 3002L); - ProjectTaskStatusActionReqVO reqVO = new ProjectTaskStatusActionReqVO(); - reqVO.setActionCode("cancel"); - reqVO.setReason(" "); - - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, taskId)).thenReturn(task); - when(objectStatusTransitionMapper.selectByObjectTypeAndFromStatusAndAction("task", "pending", "cancel")) - .thenReturn(createTransition("cancel", "cancelled", true)); - - ServiceException ex = assertThrows(ServiceException.class, - () -> projectTaskService.changeTaskStatus(projectId, executionId, taskId, reqVO)); - - assertEquals(ErrorCodeConstants.PROJECT_TASK_STATUS_ACTION_REASON_REQUIRED.getCode(), ex.getCode()); - } - - @Test - void changeTaskStatus_whenConcurrentModified_shouldThrow() { - Long projectId = 2001L; - Long executionId = 5001L; - Long taskId = 9001L; - ProjectTaskDO task = createTask(projectId, executionId, taskId, 3002L); - ProjectTaskStatusActionReqVO reqVO = new ProjectTaskStatusActionReqVO(); - reqVO.setActionCode("start"); - - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, taskId)).thenReturn(task); - when(objectStatusTransitionMapper.selectByObjectTypeAndFromStatusAndAction("task", "pending", "start")) - .thenReturn(createTransition("start", "active", false)); - when(projectTaskMapper.updateStatusByIdAndStatus(taskId, "pending", "active", null)).thenReturn(0); - - ServiceException ex = assertThrows(ServiceException.class, - () -> projectTaskService.changeTaskStatus(projectId, executionId, taskId, reqVO)); - - assertEquals(ErrorCodeConstants.PROJECT_TASK_STATUS_CONCURRENT_MODIFIED.getCode(), ex.getCode()); - } - - // -------------------- Phase 3 进度自动汇总 + 叶子转父限制 -------------------- - - @Test - void changeTaskStatus_whenCancelChild_shouldRecalculateParentProgressWithExcludedStatuses() { - Long projectId = 2001L; - Long executionId = 5001L; - Long parentTaskId = 8001L; - Long taskId = 9001L; - ProjectTaskDO task = createTask(projectId, executionId, taskId, 3002L); - task.setParentTaskId(parentTaskId); - ProjectTaskDO parent = createTask(projectId, executionId, parentTaskId, 3002L); - parent.setProgressRate(new BigDecimal("50.00")); - ProjectTaskDO remainingChild = createTask(projectId, executionId, 9002L, 3002L); - remainingChild.setProgressRate(new BigDecimal("100.00")); - ProjectTaskStatusActionReqVO reqVO = new ProjectTaskStatusActionReqVO(); - reqVO.setActionCode("cancel"); - reqVO.setReason("任务取消"); - - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, taskId)).thenReturn(task); - when(objectStatusTransitionMapper.selectByObjectTypeAndFromStatusAndAction("task", "pending", "cancel")) - .thenReturn(createTransition("cancel", "cancelled", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("task", "cancelled")) - .thenReturn(createTerminalStatus("task", "cancelled")); - when(projectTaskMapper.updateStatusByIdAndStatus(taskId, "pending", "cancelled", "任务取消")).thenReturn(1); - when(objectStatusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled("task")) - .thenReturn(List.of("cancelled")); - when(projectTaskMapper.selectById(parentTaskId)).thenReturn(parent); - when(projectTaskMapper.selectChildrenProgressByParentTaskId(parentTaskId, List.of("cancelled"))) - .thenReturn(List.of(remainingChild)); - - try (MockedStatic mockedStatic = mockLoginUser(3002L)) { - projectTaskService.changeTaskStatus(projectId, executionId, taskId, reqVO); - } - - verify(projectTaskMapper).selectChildrenProgressByParentTaskId(parentTaskId, List.of("cancelled")); - verify(projectTaskMapper).updateProgressRateById(eq(parentTaskId), - argThat(v -> new BigDecimal("100.00").compareTo(v) == 0)); - } - - @Test - void changeTaskStatus_whenAllChildrenExcluded_shouldResetParentProgressToZero() { - Long projectId = 2001L; - Long executionId = 5001L; - Long parentTaskId = 8001L; - Long taskId = 9001L; - ProjectTaskDO task = createTask(projectId, executionId, taskId, 3002L); - task.setParentTaskId(parentTaskId); - ProjectTaskDO parent = createTask(projectId, executionId, parentTaskId, 3002L); - parent.setProgressRate(new BigDecimal("80.00")); - ProjectTaskStatusActionReqVO reqVO = new ProjectTaskStatusActionReqVO(); - reqVO.setActionCode("cancel"); - reqVO.setReason("任务取消"); - - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, taskId)).thenReturn(task); - when(objectStatusTransitionMapper.selectByObjectTypeAndFromStatusAndAction("task", "pending", "cancel")) - .thenReturn(createTransition("cancel", "cancelled", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("task", "cancelled")) - .thenReturn(createTerminalStatus("task", "cancelled")); - when(projectTaskMapper.updateStatusByIdAndStatus(taskId, "pending", "cancelled", "任务取消")).thenReturn(1); - when(objectStatusModelMapper.selectProgressExcludedStatusCodesByObjectTypeEnabled("task")) - .thenReturn(List.of("cancelled")); - when(projectTaskMapper.selectById(parentTaskId)).thenReturn(parent); - when(projectTaskMapper.selectChildrenProgressByParentTaskId(parentTaskId, List.of("cancelled"))) - .thenReturn(List.of()); - - try (MockedStatic mockedStatic = mockLoginUser(3002L)) { - projectTaskService.changeTaskStatus(projectId, executionId, taskId, reqVO); - } - - verify(projectTaskMapper).selectChildrenProgressByParentTaskId(parentTaskId, List.of("cancelled")); - verify(projectTaskMapper).updateProgressRateById(eq(parentTaskId), - argThat(v -> new BigDecimal("0.00").compareTo(v) == 0)); - } - - @Test - void createTask_whenParentIsLeafWithProgress_shouldThrowLeafToParentForbiddenProgress() { - Long projectId = 2001L; - Long executionId = 5001L; - Long parentTaskId = 8001L; - ProjectTaskDO parentTask = createTask(projectId, executionId, parentTaskId, 3002L); - parentTask.setProgressRate(new BigDecimal("30.00")); - ProjectTaskSaveReqVO reqVO = createTaskReqVO(); - reqVO.setParentTaskId(parentTaskId); - reqVO.setOwnerId(3002L); - - when(projectMapper.selectById(projectId)).thenReturn(createEditableProject(projectId)); - when(projectExecutionMapper.selectByProjectIdAndId(projectId, executionId)) - .thenReturn(createEditableExecution(projectId, executionId)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "pending")) - .thenReturn(createStatus("project", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("execution", "pending")) - .thenReturn(createStatus("execution", "pending", true)); - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, parentTaskId)) - .thenReturn(parentTask); - when(executionAssigneeMapper.selectActiveByExecutionIdAndUserId(executionId, 3002L)) - .thenReturn(createExecutionAssignee(executionId, 3002L)); - when(projectTaskMapper.countChildrenByParentTaskId(parentTaskId)).thenReturn(0); - - ServiceException ex = assertThrows(ServiceException.class, - () -> projectTaskService.createTask(projectId, executionId, reqVO)); - assertEquals(ErrorCodeConstants.PROJECT_TASK_LEAF_TO_PARENT_FORBIDDEN_PROGRESS.getCode(), ex.getCode()); - verify(projectTaskMapper, never()).insert(any(ProjectTaskDO.class)); - } - - @Test - void createTask_whenParentIsLeafWithWorklog_shouldThrowLeafToParentForbiddenWorklog() { - Long projectId = 2001L; - Long executionId = 5001L; - Long parentTaskId = 8001L; - ProjectTaskDO parentTask = createTask(projectId, executionId, parentTaskId, 3002L); - parentTask.setProgressRate(BigDecimal.ZERO); - ProjectTaskSaveReqVO reqVO = createTaskReqVO(); - reqVO.setParentTaskId(parentTaskId); - reqVO.setOwnerId(3002L); - - when(projectMapper.selectById(projectId)).thenReturn(createEditableProject(projectId)); - when(projectExecutionMapper.selectByProjectIdAndId(projectId, executionId)) - .thenReturn(createEditableExecution(projectId, executionId)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "pending")) - .thenReturn(createStatus("project", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("execution", "pending")) - .thenReturn(createStatus("execution", "pending", true)); - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, parentTaskId)) - .thenReturn(parentTask); - when(executionAssigneeMapper.selectActiveByExecutionIdAndUserId(executionId, 3002L)) - .thenReturn(createExecutionAssignee(executionId, 3002L)); - when(projectTaskMapper.countChildrenByParentTaskId(parentTaskId)).thenReturn(0); - when(taskWorklogMapper.existsByTaskId(parentTaskId)).thenReturn(true); - - ServiceException ex = assertThrows(ServiceException.class, - () -> projectTaskService.createTask(projectId, executionId, reqVO)); - assertEquals(ErrorCodeConstants.PROJECT_TASK_LEAF_TO_PARENT_FORBIDDEN_WORKLOG.getCode(), ex.getCode()); - verify(projectTaskMapper, never()).insert(any(ProjectTaskDO.class)); - } - - @Test - void createTask_whenParentAlreadyHasChildren_shouldSkipLeafCheckAndRecalc() { - Long projectId = 2001L; - Long executionId = 5001L; - Long parentTaskId = 8001L; - // 父任务进度 60%,但已有 1 个子任务,已是父任务,不再触发"叶子转父"校验 - ProjectTaskDO parentTask = createTask(projectId, executionId, parentTaskId, 3002L); - parentTask.setProgressRate(new BigDecimal("60.00")); - ProjectTaskSaveReqVO reqVO = createTaskReqVO(); - reqVO.setParentTaskId(parentTaskId); - reqVO.setOwnerId(3002L); - - when(projectMapper.selectById(projectId)).thenReturn(createEditableProject(projectId)); - when(projectExecutionMapper.selectByProjectIdAndId(projectId, executionId)) - .thenReturn(createEditableExecution(projectId, executionId)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "pending")) - .thenReturn(createStatus("project", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("execution", "pending")) - .thenReturn(createStatus("execution", "pending", true)); - when(objectStatusModelMapper.selectInitialByObjectTypeEnabled("task")) - .thenReturn(createStatus("task", "pending", true)); - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, parentTaskId)) - .thenReturn(parentTask); - when(executionAssigneeMapper.selectActiveByExecutionIdAndUserId(executionId, 3002L)) - .thenReturn(createExecutionAssignee(executionId, 3002L)); - // 已有 1 个子任务 → 跳过叶子转父校验 - when(projectTaskMapper.countChildrenByParentTaskId(parentTaskId)).thenReturn(1); - // 插入新任务(id=9002)后,父任务下有 2 个子,进度分别 60.00 与 0.00 → AVG 30.00 - when(projectTaskMapper.insert(any(ProjectTaskDO.class))).thenAnswer(inv -> { - ProjectTaskDO task = inv.getArgument(0); - task.setId(9002L); - return 1; - }); - when(projectTaskMapper.selectById(parentTaskId)).thenReturn(parentTask); - ProjectTaskDO existingChild = createTask(projectId, executionId, 9001L, 3002L); - existingChild.setProgressRate(new BigDecimal("60.00")); - ProjectTaskDO newChild = createTask(projectId, executionId, 9002L, 3002L); - newChild.setProgressRate(BigDecimal.ZERO); - when(projectTaskMapper.selectChildrenProgressByParentTaskId(parentTaskId, Collections.emptyList())) - .thenReturn(List.of(existingChild, newChild)); - - projectTaskService.createTask(projectId, executionId, reqVO); - - ArgumentCaptor progressCaptor = ArgumentCaptor.forClass(BigDecimal.class); - verify(projectTaskMapper).updateProgressRateById(eq(parentTaskId), progressCaptor.capture()); - assertEquals(0, new BigDecimal("30.00").compareTo(progressCaptor.getValue())); - verify(taskWorklogMapper, never()).existsByTaskId(parentTaskId); - } - - @Test - void createTask_withLeafParentZeroProgress_shouldRecalcGrandparentChain() { - Long projectId = 2001L; - Long executionId = 5001L; - Long grandparentId = 7000L; - Long parentTaskId = 8001L; - ProjectTaskDO parentTask = createTask(projectId, executionId, parentTaskId, 3002L); - parentTask.setProgressRate(BigDecimal.ZERO); - parentTask.setParentTaskId(grandparentId); - // 爷爷的初值刻意设为 50.00,便于稳定验证"递归爬到爷爷且发生写入" - ProjectTaskDO grandparent = createTask(projectId, executionId, grandparentId, 3002L); - grandparent.setProgressRate(new BigDecimal("50.00")); - ProjectTaskSaveReqVO reqVO = createTaskReqVO(); - reqVO.setParentTaskId(parentTaskId); - reqVO.setOwnerId(3002L); - - when(projectMapper.selectById(projectId)).thenReturn(createEditableProject(projectId)); - when(projectExecutionMapper.selectByProjectIdAndId(projectId, executionId)) - .thenReturn(createEditableExecution(projectId, executionId)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "pending")) - .thenReturn(createStatus("project", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("execution", "pending")) - .thenReturn(createStatus("execution", "pending", true)); - when(objectStatusModelMapper.selectInitialByObjectTypeEnabled("task")) - .thenReturn(createStatus("task", "pending", true)); - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, parentTaskId)) - .thenReturn(parentTask); - when(executionAssigneeMapper.selectActiveByExecutionIdAndUserId(executionId, 3002L)) - .thenReturn(createExecutionAssignee(executionId, 3002L)); - when(projectTaskMapper.countChildrenByParentTaskId(parentTaskId)).thenReturn(0); - when(taskWorklogMapper.existsByTaskId(parentTaskId)).thenReturn(false); - when(projectTaskMapper.insert(any(ProjectTaskDO.class))).thenAnswer(inv -> { - ProjectTaskDO t = inv.getArgument(0); - t.setId(9002L); - return 1; - }); - // 父链:parent(8001) -> grandparent(7000) -> null - when(projectTaskMapper.selectById(parentTaskId)).thenReturn(parentTask); - when(projectTaskMapper.selectById(grandparentId)).thenReturn(grandparent); - ProjectTaskDO newChild = createTask(projectId, executionId, 9002L, 3002L); - newChild.setProgressRate(new BigDecimal("80.00")); - when(projectTaskMapper.selectChildrenProgressByParentTaskId(parentTaskId, Collections.emptyList())) - .thenReturn(List.of(newChild)); - when(projectTaskMapper.selectChildrenProgressByParentTaskId(grandparentId, Collections.emptyList())) - .thenReturn(List.of(parentTask)); - - projectTaskService.createTask(projectId, executionId, reqVO); - - // parent: AVG([80.00]) = 80.00 - verify(projectTaskMapper).updateProgressRateById(eq(parentTaskId), - argThat(v -> new BigDecimal("80.00").compareTo(v) == 0)); - // 爷爷:mock 返回的 parentTask 仍是初始引用(progress=0,未被自动改写) - // → 爷爷应被设为 0;而其原值 50.00 不等,会触发更新 - verify(projectTaskMapper).updateProgressRateById(eq(grandparentId), any(BigDecimal.class)); - } - - @Test - void updateTask_movedToNewParent_shouldValidateAndRecalcBothChains() { - Long projectId = 2001L; - Long executionId = 5001L; - Long taskId = 9001L; - Long oldParentId = 8001L; - Long newParentId = 8002L; - ProjectTaskDO task = createTask(projectId, executionId, taskId, 3002L); - task.setParentTaskId(oldParentId); - task.setProgressRate(new BigDecimal("70.00")); - ProjectTaskDO oldParent = createTask(projectId, executionId, oldParentId, 3002L); - ProjectTaskDO newParent = createTask(projectId, executionId, newParentId, 3002L); - newParent.setProgressRate(BigDecimal.ZERO); - - ProjectTaskSaveReqVO reqVO = createTaskReqVO(); - reqVO.setId(taskId); - reqVO.setParentTaskId(newParentId); - reqVO.setOwnerId(3002L); - - when(projectMapper.selectById(projectId)).thenReturn(createEditableProject(projectId)); - when(projectExecutionMapper.selectByProjectIdAndId(projectId, executionId)) - .thenReturn(createEditableExecution(projectId, executionId)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("project", "pending")) - .thenReturn(createStatus("project", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("execution", "pending")) - .thenReturn(createStatus("execution", "pending", true)); - when(objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled("task", "pending")) - .thenReturn(createStatus("task", "pending", true)); - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, taskId)).thenReturn(task); - when(projectTaskMapper.selectByProjectIdAndExecutionIdAndId(projectId, executionId, newParentId)) - .thenReturn(newParent); - when(executionAssigneeMapper.selectActiveByExecutionIdAndUserId(executionId, 3002L)) - .thenReturn(createExecutionAssignee(executionId, 3002L)); - // 校验"叶子转父":新父 8002 当前是叶子,进度=0,无工时 → 通过 - when(projectTaskMapper.countChildrenByParentTaskId(newParentId)).thenReturn(0); - when(taskWorklogMapper.existsByTaskId(newParentId)).thenReturn(false); - // 当前任务自身:是叶子 - when(projectTaskMapper.countChildrenByParentTaskId(taskId)).thenReturn(0); - // 递归刷新两条链:旧父 8001 与 新父 8002 - when(projectTaskMapper.selectById(oldParentId)).thenReturn(oldParent); - when(projectTaskMapper.selectById(newParentId)).thenReturn(newParent); - when(projectTaskMapper.selectChildrenProgressByParentTaskId(oldParentId, Collections.emptyList())) - .thenReturn(List.of()); // 旧父在迁移后无有效子任务,按新口径归零;当前值已为 0 不重复更新 - when(projectTaskMapper.selectChildrenProgressByParentTaskId(newParentId, Collections.emptyList())) - .thenReturn(List.of(task)); - - projectTaskService.updateTask(projectId, executionId, reqVO); - - verify(projectTaskMapper).updateProgressRateById(eq(newParentId), - argThat(v -> new BigDecimal("70.00").compareTo(v) == 0)); - // 旧父无有效子任务且当前已为 0,不重复更新 - verify(projectTaskMapper, never()).updateProgressRateById(eq(oldParentId), any(BigDecimal.class)); - } - - private ProjectTaskSaveReqVO createTaskReqVO() { - ProjectTaskSaveReqVO reqVO = new ProjectTaskSaveReqVO(); - reqVO.setTaskTitle("接口联调任务"); - reqVO.setTaskDesc("完成接口联调"); - return reqVO; - } - - private ProjectDO createEditableProject(Long projectId) { - ProjectDO project = new ProjectDO(); - project.setId(projectId); - project.setProjectName("测试项目"); - project.setStatusCode("pending"); - return project; - } - - private ProjectExecutionDO createEditableExecution(Long projectId, Long executionId) { - ProjectExecutionDO execution = new ProjectExecutionDO(); - execution.setId(executionId); - execution.setProjectId(projectId); - execution.setExecutionName("接口联调"); - execution.setStatusCode("pending"); - return execution; - } - - private ProjectTaskDO createTask(Long projectId, Long executionId, Long taskId, Long ownerId) { - ProjectTaskDO task = new ProjectTaskDO(); - task.setId(taskId); - task.setProjectId(projectId); - task.setExecutionId(executionId); - task.setTaskTitle("接口联调任务"); - task.setOwnerId(ownerId); - task.setStatusCode("pending"); - task.setProgressRate(BigDecimal.ZERO); - return task; - } - - private ExecutionAssigneeDO createExecutionAssignee(Long executionId, Long userId) { - ExecutionAssigneeDO member = new ExecutionAssigneeDO(); - member.setExecutionId(executionId); - member.setUserId(userId); - return member; - } - - private ObjectStatusModelDO createStatus(String objectType, String statusCode, boolean allowEdit) { - ObjectStatusModelDO status = new ObjectStatusModelDO(); - status.setObjectType(objectType); - status.setStatusCode(statusCode); - status.setStatusName(statusCode); - status.setAllowEdit(allowEdit); - return status; - } - - private ObjectStatusModelDO createTerminalStatus(String objectType, String statusCode) { - ObjectStatusModelDO status = createStatus(objectType, statusCode, false); - status.setTerminalFlag(true); - return status; - } - - private ObjectStatusTransitionDO createTransition(String actionCode, String toStatus, boolean needReason) { - ObjectStatusTransitionDO transition = new ObjectStatusTransitionDO(); - transition.setActionCode(actionCode); - transition.setActionName(actionCode); - transition.setToStatusCode(toStatus); - transition.setNeedReason(needReason); - return transition; - } - - private MockedStatic mockLoginUser(Long loginUserId) { - MockedStatic mockedStatic = mockStatic(SecurityFrameworkUtils.class); - mockedStatic.when(SecurityFrameworkUtils::getLoginUserId).thenReturn(loginUserId); - return mockedStatic; - } - -}