diff --git a/src/main/java/clap/server/adapter/outbound/api/kakaoWork/KakaoWorkBlockBuilder.java b/src/main/java/clap/server/adapter/outbound/api/kakaoWork/KakaoWorkBlockBuilder.java index 9b450c61..55c9a704 100644 --- a/src/main/java/clap/server/adapter/outbound/api/kakaoWork/KakaoWorkBlockBuilder.java +++ b/src/main/java/clap/server/adapter/outbound/api/kakaoWork/KakaoWorkBlockBuilder.java @@ -405,7 +405,7 @@ private String makeTerminatedStatusBlock(PushNotificationTemplate request, Strin "inlines", new Object[]{ Map.of( "type", "styled", - "text", " - 거절 사유 : " + request.reason(), + "text", " - 반려 사유 : " + request.reason(), "bold", false ) } diff --git a/src/main/java/clap/server/adapter/outbound/persistense/mapper/TaskPersistenceMapper.java b/src/main/java/clap/server/adapter/outbound/persistense/mapper/TaskPersistenceMapper.java index 1f312abe..ca56d00e 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/mapper/TaskPersistenceMapper.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/mapper/TaskPersistenceMapper.java @@ -1,9 +1,7 @@ package clap.server.adapter.outbound.persistense.mapper; -import clap.server.adapter.outbound.persistense.entity.task.CommentEntity; import clap.server.adapter.outbound.persistense.entity.task.TaskEntity; import clap.server.adapter.outbound.persistense.mapper.common.PersistenceMapper; -import clap.server.domain.model.task.Comment; import clap.server.domain.model.task.Task; import org.mapstruct.Mapper; import org.mapstruct.Mapping; diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskRepository.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskRepository.java index 53d4c271..112cda40 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskRepository.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskRepository.java @@ -28,20 +28,6 @@ List findYesterdayTaskByUpdatedAtIsBetween( List findByProcessor_MemberIdAndTaskStatusIn(Long memberId, Collection taskStatuses); - - @Query("SELECT t FROM TaskEntity t " + - "WHERE t.processor.memberId = :processorId " + - "AND t.taskStatus IN :taskStatus " + - "AND (:fromDateTime IS NULL OR t.taskStatus != 'COMPLETED' OR " + - " (t.taskStatus = 'COMPLETED' AND t.finishedAt >= :fromDateTime)) " + - "ORDER BY t.processorOrder ASC ") - List findTasksWithTaskStatusAndCompletedAt( - @Param("processorId") Long processorId, - @Param("taskStatus") List taskStatus, - @Param("fromDateTime") LocalDateTime fromDateTime - ); - - Optional findByTaskIdAndTaskStatus(Long id, TaskStatus status); Optional findTopByProcessor_MemberIdAndTaskStatusAndProcessorOrderLessThanOrderByProcessorOrderAsc(Long processorId, TaskStatus taskStatus, Long processorOrder); diff --git a/src/main/java/clap/server/application/port/inbound/domain/TaskService.java b/src/main/java/clap/server/application/port/inbound/domain/TaskService.java index 852c214e..2234e667 100644 --- a/src/main/java/clap/server/application/port/inbound/domain/TaskService.java +++ b/src/main/java/clap/server/application/port/inbound/domain/TaskService.java @@ -1,5 +1,6 @@ package clap.server.application.port.inbound.domain; +import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; import clap.server.application.port.outbound.task.CommandTaskPort; import clap.server.application.port.outbound.task.LoadTaskPort; import clap.server.domain.model.task.Task; @@ -23,4 +24,8 @@ public Task findById(Long taskId) { public Task upsert(Task task) { return commandTaskPort.save(task); } + + public Task findByIdAndStatus(Long taskId, TaskStatus status) { + return loadTaskPort.findByIdAndStatus(taskId, status).orElseThrow(() -> new ApplicationException(TaskErrorCode.TASK_NOT_FOUND)); + } } diff --git a/src/main/java/clap/server/application/service/task/UpdateTaskBoardService.java b/src/main/java/clap/server/application/service/task/UpdateTaskBoardService.java index 44836fd6..d49d1695 100644 --- a/src/main/java/clap/server/application/service/task/UpdateTaskBoardService.java +++ b/src/main/java/clap/server/application/service/task/UpdateTaskBoardService.java @@ -1,49 +1,29 @@ package clap.server.application.service.task; import clap.server.adapter.inbound.web.dto.task.request.UpdateTaskOrderRequest; -import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole; -import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; -import clap.server.adapter.outbound.persistense.entity.task.constant.TaskHistoryType; -import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; import clap.server.application.port.inbound.domain.MemberService; import clap.server.application.port.inbound.domain.TaskService; import clap.server.application.port.inbound.task.UpdateTaskBoardUsecase; -import clap.server.application.port.inbound.task.UpdateTaskOrderAndStatusUsecase; import clap.server.application.port.outbound.task.LoadTaskPort; -import clap.server.application.port.outbound.taskhistory.CommandTaskHistoryPort; -import clap.server.application.service.webhook.SendNotificationService; import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.member.Member; import clap.server.domain.model.task.Task; -import clap.server.domain.model.task.TaskHistory; import clap.server.domain.policy.task.ProcessorValidationPolicy; import clap.server.domain.policy.task.TaskOrderCalculationPolicy; -import clap.server.domain.policy.task.TaskPolicyConstants; -import clap.server.exception.ApplicationException; -import clap.server.exception.code.TaskErrorCode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.transaction.annotation.Transactional; -import java.util.List; - @Slf4j @ApplicationService @RequiredArgsConstructor -class UpdateTaskBoardService implements UpdateTaskBoardUsecase, UpdateTaskOrderAndStatusUsecase { +public class UpdateTaskBoardService implements UpdateTaskBoardUsecase { private final MemberService memberService; private final TaskService taskService; private final LoadTaskPort loadTaskPort; - private final SendNotificationService sendNotificationService; - private final CommandTaskHistoryPort commandTaskHistoryPort; private final TaskOrderCalculationPolicy taskOrderCalculationPolicy; private final ProcessorValidationPolicy processorValidationPolicy; - - private Task findByIdAndStatus(Long taskId, TaskStatus status) { - return loadTaskPort.findByIdAndStatus(taskId, status).orElseThrow(() -> new ApplicationException(TaskErrorCode.TASK_NOT_FOUND)); - } - /** * 작업(Task)의 순서를 업데이트하는 메서드 * @@ -53,15 +33,13 @@ private Task findByIdAndStatus(Long taskId, TaskStatus status) { @Override @Transactional public void updateTaskOrder(Long processorId, UpdateTaskOrderRequest request) { - // 요청 유효성 검증 - validateRequest(request, null); Member processor = memberService.findActiveMember(processorId); Task targetTask = taskService.findById(request.targetTaskId()); processorValidationPolicy.validateProcessor(processorId, targetTask); // 가장 상위로 이동 if (request.prevTaskId() == 0) { - Task nextTask = findByIdAndStatus(request.nextTaskId(), targetTask.getTaskStatus()); + Task nextTask = taskService.findByIdAndStatus(request.nextTaskId(), targetTask.getTaskStatus()); // 해당 상태에서 바로 앞에 있는 작업 찾기 Task prevTask = loadTaskPort.findPrevOrderTaskByProcessorOrderAndStatus(processorId, targetTask.getTaskStatus(), nextTask.getProcessorOrder()).orElse(null); long newOrder = taskOrderCalculationPolicy.calculateOrderForTop(prevTask, nextTask); @@ -69,14 +47,14 @@ public void updateTaskOrder(Long processorId, UpdateTaskOrderRequest request) { } // 가장 하위로 이동 else if (request.nextTaskId() == 0) { - Task prevTask = findByIdAndStatus(request.prevTaskId(), targetTask.getTaskStatus()); + Task prevTask = taskService.findByIdAndStatus(request.prevTaskId(), targetTask.getTaskStatus()); // 해당 상태에서 바로 뒤에 있는 작업 찾기 Task nextTask = loadTaskPort.findNextOrderTaskByProcessorOrderAndStatus(processorId, targetTask.getTaskStatus(), prevTask.getProcessorOrder()).orElse(null); long newOrder = taskOrderCalculationPolicy.calculateOrderForBottom(prevTask, nextTask); updateNewTaskOrder(targetTask, newOrder); } else { - Task prevTask = findByIdAndStatus(request.prevTaskId(), targetTask.getTaskStatus()); - Task nextTask = findByIdAndStatus(request.nextTaskId(), targetTask.getTaskStatus()); + Task prevTask = taskService.findByIdAndStatus(request.prevTaskId(), targetTask.getTaskStatus()); + Task nextTask = taskService.findByIdAndStatus(request.nextTaskId(), targetTask.getTaskStatus()); long newOrder = taskOrderCalculationPolicy.calculateNewProcessorOrder(prevTask.getProcessorOrder(), nextTask.getProcessorOrder()); updateNewTaskOrder(targetTask, newOrder); } @@ -93,103 +71,5 @@ private void updateNewTaskOrder(Task targetTask, Long newOrder) { taskService.upsert(targetTask); } - /** - * 작업의 상태와 순서를 동시에 변경하는 메서드 - * - * @param processorId 작업을 수행하는 멤버 ID - * @param request 순서 변경 요청 객체 - * @param targetStatus 변경할 작업 상태 - */ - @Override - @Transactional - public void updateTaskOrderAndStatus(Long processorId, UpdateTaskOrderRequest request, TaskStatus targetStatus) { - validateRequest(request, targetStatus); - Member processor = memberService.findActiveMember(processorId); - Task targetTask = taskService.findById(request.targetTaskId()); - processorValidationPolicy.validateProcessor(processorId, targetTask); - - Task updatedTask; - Task prevTask; - Task nextTask; - - // 조회된 작업 보드에서 하나의 작업만 존재하고, 이 작업을 이동할 때 - if (request.prevTaskId() == 0 && request.nextTaskId() == 0) { - - // 요청 시간 기준으로 가장 가장 근접한 이전의 Task를 조회 - prevTask = loadTaskPort.findPrevOrderTaskByTaskIdAndStatus(processorId, targetStatus, targetTask.getTaskId()).orElse(null); - if (prevTask != null) { - // 이전 Task가 있다면 바로 다음의 Task 조회 - nextTask = loadTaskPort.findNextOrderTaskByProcessorOrderAndStatus(processorId, targetStatus, prevTask.getProcessorOrder()).orElse(null); - } // 요청 시간 기준으로 가장 가장 근접한 이후의 Task를 조회 - else - nextTask = loadTaskPort.findNextOrderTaskByTaskIdAndStatus(processorId, targetStatus, targetTask.getTaskId()).orElse(null); - - // 하나의 task만 존재할 경우 상태만 update - if (prevTask == null && nextTask == null) { - targetTask.updateTaskStatus(targetStatus); - updatedTask = taskService.upsert(targetTask); - } else if (prevTask == null) { - long newOrder = taskOrderCalculationPolicy.calculateOrderForBottom(null, nextTask); - updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); - } else if (nextTask == null) { - long newOrder = taskOrderCalculationPolicy.calculateOrderForBottom(prevTask, null); - updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); - } else { - long newOrder = taskOrderCalculationPolicy.calculateNewProcessorOrder(prevTask.getProcessorOrder(), nextTask.getProcessorOrder()); - updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); - } - } else if (request.prevTaskId() == 0) { - nextTask = findByIdAndStatus(request.nextTaskId(), targetStatus); - // 해당 상태에서 바로 앞 있는 작업 찾기 - prevTask = loadTaskPort.findPrevOrderTaskByProcessorOrderAndStatus(processorId, targetStatus, nextTask.getProcessorOrder()).orElse(null); - long newOrder = taskOrderCalculationPolicy.calculateOrderForTop(prevTask, nextTask); - updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); - } else if (request.nextTaskId() == 0) { - prevTask = findByIdAndStatus(request.prevTaskId(), targetStatus); - // 해당 상태에서 바로 뒤에 있는 작업 찾기 - nextTask = loadTaskPort.findNextOrderTaskByProcessorOrderAndStatus(processorId, targetStatus, prevTask.getProcessorOrder()).orElse(null); - long newOrder = taskOrderCalculationPolicy.calculateOrderForBottom(prevTask, nextTask); - updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); - } else { - prevTask = findByIdAndStatus(request.prevTaskId(), targetStatus); - nextTask = findByIdAndStatus(request.nextTaskId(), targetStatus); - long newOrder = taskOrderCalculationPolicy.calculateNewProcessorOrder(prevTask.getProcessorOrder(), nextTask.getProcessorOrder()); - updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); - } - - TaskHistory taskHistory = TaskHistory.createTaskHistory(TaskHistoryType.STATUS_SWITCHED, updatedTask, targetStatus.getDescription(), null,null); - commandTaskHistoryPort.save(taskHistory); - publishNotification(targetTask); - } - - /** - * 작업의 상태와 순서를 업데이트하는 메서드 - */ - private Task updateNewTaskOrderAndStatus(TaskStatus targetStatus, Task targetTask, long newOrder) { - targetTask.updateProcessorOrder(newOrder); - targetTask.updateTaskStatus(targetStatus); - return taskService.upsert(targetTask); - } - - /** - * 순서 변경 요청의 유효성을 검증하는 메서드 - */ - public void validateRequest(UpdateTaskOrderRequest request, TaskStatus targetStatus) { - // 타겟 상태가 유효한지 검증 - if (targetStatus != null && !TaskPolicyConstants.TASK_BOARD_STATUS_FILTER.contains(targetStatus)) { - throw new ApplicationException(TaskErrorCode.INVALID_TASK_STATUS_TRANSITION); - } - } - - private void publishNotification(Task task) { - List receivers = List.of(task.getRequester(), task.getProcessor()); - receivers.forEach(receiver -> { - boolean isManager = receiver.getMemberInfo().getRole() == MemberRole.ROLE_MANAGER; - sendNotificationService.sendPushNotification(receiver, NotificationType.STATUS_SWITCHED, task, String.valueOf(task.getTaskStatus()), null, null, isManager); - }); - sendNotificationService.sendAgitNotification(NotificationType.STATUS_SWITCHED, - task, String.valueOf(task.getTaskStatus()), null); - } - } diff --git a/src/main/java/clap/server/application/service/task/UpdateTaskOrderAndStstusService.java b/src/main/java/clap/server/application/service/task/UpdateTaskOrderAndStstusService.java new file mode 100644 index 00000000..465d350c --- /dev/null +++ b/src/main/java/clap/server/application/service/task/UpdateTaskOrderAndStstusService.java @@ -0,0 +1,151 @@ +package clap.server.application.service.task; + +import clap.server.adapter.inbound.web.dto.task.request.UpdateTaskOrderRequest; +import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole; +import clap.server.adapter.outbound.persistense.entity.notification.constant.NotificationType; +import clap.server.adapter.outbound.persistense.entity.task.constant.TaskHistoryType; +import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; +import clap.server.application.port.inbound.domain.MemberService; +import clap.server.application.port.inbound.domain.TaskService; +import clap.server.application.port.inbound.task.UpdateTaskOrderAndStatusUsecase; +import clap.server.application.port.outbound.task.LoadTaskPort; +import clap.server.application.port.outbound.taskhistory.CommandTaskHistoryPort; +import clap.server.application.service.webhook.SendNotificationService; +import clap.server.common.annotation.architecture.ApplicationService; +import clap.server.domain.model.member.Member; +import clap.server.domain.model.task.Task; +import clap.server.domain.model.task.TaskHistory; +import clap.server.domain.policy.task.ProcessorValidationPolicy; +import clap.server.domain.policy.task.TaskOrderCalculationPolicy; +import clap.server.domain.policy.task.TaskPolicyConstants; +import clap.server.exception.ApplicationException; +import clap.server.exception.code.TaskErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Slf4j +@ApplicationService +@RequiredArgsConstructor +public class UpdateTaskOrderAndStstusService implements UpdateTaskOrderAndStatusUsecase { + private final MemberService memberService; + private final TaskService taskService; + private final LoadTaskPort loadTaskPort; + private final SendNotificationService sendNotificationService; + private final CommandTaskHistoryPort commandTaskHistoryPort; + + private final TaskOrderCalculationPolicy taskOrderCalculationPolicy; + private final ProcessorValidationPolicy processorValidationPolicy; + + /** + * 작업의 상태와 순서를 동시에 변경하는 메서드 + * + * @param processorId 작업을 수행하는 멤버 ID + * @param request 순서 변경 요청 객체 + * @param targetStatus 변경할 작업 상태 + */ + @Override + @Transactional + public void updateTaskOrderAndStatus(Long processorId, UpdateTaskOrderRequest request, TaskStatus targetStatus) { + validateRequest(targetStatus); + Member processor = memberService.findActiveMember(processorId); + Task targetTask = taskService.findById(request.targetTaskId()); + processorValidationPolicy.validateProcessor(processorId, targetTask); + + Task updatedTask; + Task prevTask; + Task nextTask; + + if (request.prevTaskId() == 0 && request.nextTaskId() == 0) { + updatedTask = handleSingleTask(processorId, targetStatus, targetTask); + } else if (request.prevTaskId() == 0) { + nextTask = taskService.findByIdAndStatus(request.nextTaskId(), targetStatus); + // 해당 상태에서 바로 앞 있는 작업 찾기 + prevTask = loadTaskPort.findPrevOrderTaskByProcessorOrderAndStatus(processorId, targetStatus, nextTask.getProcessorOrder()).orElse(null); + long newOrder = taskOrderCalculationPolicy.calculateOrderForTop(prevTask, nextTask); + updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); + } else if (request.nextTaskId() == 0) { + prevTask = taskService.findByIdAndStatus(request.prevTaskId(), targetStatus); + // 해당 상태에서 바로 뒤에 있는 작업 찾기 + nextTask = loadTaskPort.findNextOrderTaskByProcessorOrderAndStatus(processorId, targetStatus, prevTask.getProcessorOrder()).orElse(null); + long newOrder = taskOrderCalculationPolicy.calculateOrderForBottom(prevTask, nextTask); + updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); + } else { + prevTask = taskService.findByIdAndStatus(request.prevTaskId(), targetStatus); + nextTask = taskService.findByIdAndStatus(request.nextTaskId(), targetStatus); + long newOrder = taskOrderCalculationPolicy.calculateNewProcessorOrder(prevTask.getProcessorOrder(), nextTask.getProcessorOrder()); + updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); + } + + TaskHistory taskHistory = TaskHistory.createTaskHistory(TaskHistoryType.STATUS_SWITCHED, updatedTask, targetStatus.getDescription(), null,null); + commandTaskHistoryPort.save(taskHistory); + publishNotification(targetTask, NotificationType.STATUS_SWITCHED, String.valueOf(updatedTask.getTaskStatus())); + } + + /** + * 작업 보드에 조회된 하나의 task를 이동하는 메서드 + */ + private Task handleSingleTask(Long processorId, TaskStatus targetStatus, Task targetTask) { + Task updatedTask; + Task nextTask; + Task prevTask; + + // 요청 시간 기준으로 가장 가장 근접한 이전의 Task를 조회 + prevTask = loadTaskPort.findPrevOrderTaskByTaskIdAndStatus(processorId, targetStatus, targetTask.getTaskId()).orElse(null); + if (prevTask != null) { + // 이전 Task가 있다면 바로 다음의 Task 조회 + nextTask = loadTaskPort.findNextOrderTaskByProcessorOrderAndStatus(processorId, targetStatus, prevTask.getProcessorOrder()).orElse(null); + } // 요청 시간 기준으로 가장 가장 근접한 이후의 Task를 조회 + else + nextTask = loadTaskPort.findNextOrderTaskByTaskIdAndStatus(processorId, targetStatus, targetTask.getTaskId()).orElse(null); + + // 하나의 task만 존재할 경우 상태만 update + if (prevTask == null && nextTask == null) { + targetTask.updateTaskStatus(targetStatus); + updatedTask = taskService.upsert(targetTask); + } else if (prevTask == null) { + long newOrder = taskOrderCalculationPolicy.calculateOrderForBottom(null, nextTask); + updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); + } else if (nextTask == null) { + long newOrder = taskOrderCalculationPolicy.calculateOrderForBottom(prevTask, null); + updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); + } else { + long newOrder = taskOrderCalculationPolicy.calculateNewProcessorOrder(prevTask.getProcessorOrder(), nextTask.getProcessorOrder()); + updatedTask = updateNewTaskOrderAndStatus(targetStatus, targetTask, newOrder); + } + return updatedTask; + } + + /** + * 작업의 상태와 순서를 업데이트하는 메서드 + */ + private Task updateNewTaskOrderAndStatus(TaskStatus targetStatus, Task targetTask, long newOrder) { + targetTask.updateProcessorOrder(newOrder); + targetTask.updateTaskStatus(targetStatus); + return taskService.upsert(targetTask); + } + + /** + * 순서 변경 요청의 유효성을 검증하는 메서드 + */ + public void validateRequest(TaskStatus targetStatus) { + // 타겟 상태가 유효한지 검증 + if (targetStatus != null && !TaskPolicyConstants.TASK_BOARD_STATUS_FILTER.contains(targetStatus)) { + throw new ApplicationException(TaskErrorCode.INVALID_TASK_STATUS_TRANSITION); + } + } + + private void publishNotification(Task task, NotificationType notificationType, String message) { + List receivers = List.of(task.getRequester(), task.getProcessor()); + receivers.forEach(receiver -> { + boolean isManager = receiver.getMemberInfo().getRole() == MemberRole.ROLE_MANAGER; + sendNotificationService.sendPushNotification(receiver, notificationType, task, message, null, null, isManager); + }); + sendNotificationService.sendAgitNotification(notificationType, + task, message, null); + } + +} + diff --git a/src/main/java/clap/server/domain/policy/task/TaskOrderCalculationPolicy.java b/src/main/java/clap/server/domain/policy/task/TaskOrderCalculationPolicy.java index 3bce8e73..9ddfb21f 100644 --- a/src/main/java/clap/server/domain/policy/task/TaskOrderCalculationPolicy.java +++ b/src/main/java/clap/server/domain/policy/task/TaskOrderCalculationPolicy.java @@ -10,21 +10,21 @@ @Policy public class TaskOrderCalculationPolicy { - public long calculateOrderForTop(Task prevTask, Task nextTask) { + public long calculateOrderForTop(final Task prevTask,final Task nextTask) { Long prevTaskOrder = prevTask == null ? null : prevTask.getProcessorOrder(); if (prevTaskOrder == null) { return nextTask.getProcessorOrder() - DEFAULT_PROCESSOR_ORDER_GAP; } else return calculateNewProcessorOrder(prevTaskOrder, nextTask.getProcessorOrder()); } - public long calculateOrderForBottom(Task prevTask, Task nextTask) { + public long calculateOrderForBottom(final Task prevTask,final Task nextTask) { Long nextTaskOrder = nextTask == null ? null : nextTask.getProcessorOrder(); if (nextTaskOrder == null) { return prevTask.getProcessorOrder() + DEFAULT_PROCESSOR_ORDER_GAP; } else return calculateNewProcessorOrder(prevTask.getProcessorOrder(), nextTaskOrder); } - public long calculateNewProcessorOrder(Long prevTaskOrder, Long nextTaskOrder) { + public long calculateNewProcessorOrder(final Long prevTaskOrder,final Long nextTaskOrder) { if (prevTaskOrder != null && nextTaskOrder != null) { if (nextTaskOrder - prevTaskOrder < 2) { throw new DomainException(TaskErrorCode.INVALID_TASK_ORDER); diff --git a/src/test/java/clap/server/application/service/task/UpdateTaskBoardServiceTest.java b/src/test/java/clap/server/application/service/task/UpdateTaskBoardServiceTest.java new file mode 100644 index 00000000..a9782e34 --- /dev/null +++ b/src/test/java/clap/server/application/service/task/UpdateTaskBoardServiceTest.java @@ -0,0 +1,140 @@ +package clap.server.application.service.task; + +import clap.server.adapter.inbound.web.dto.task.request.UpdateTaskOrderRequest; +import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; +import clap.server.application.port.inbound.domain.MemberService; +import clap.server.application.port.inbound.domain.TaskService; +import clap.server.application.port.outbound.task.LoadTaskPort; +import clap.server.domain.model.member.Member; +import clap.server.domain.model.task.Task; +import clap.server.domain.policy.task.ProcessorValidationPolicy; +import clap.server.domain.policy.task.TaskOrderCalculationPolicy; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.Optional; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@ExtendWith(MockitoExtension.class) +class UpdateTaskBoardServiceTest { + + @InjectMocks + private UpdateTaskBoardService updateTaskBoardService; + + @Mock + private MemberService memberService; + + @Mock + private TaskService taskService; + + @Mock + private LoadTaskPort loadTaskPort; + + @Mock + private TaskOrderCalculationPolicy taskOrderCalculationPolicy; + + @Mock + private ProcessorValidationPolicy processorValidationPolicy; + + @Test + @DisplayName("작업 순서를 업데이트 - 가장 상위로 이동") + void updateTaskOrder_moveToTop() { + // given + Long processorId = 1L; + UpdateTaskOrderRequest request = new UpdateTaskOrderRequest(0L, 2L, 1L); // prevTaskId = 0 (맨 위로 이동) + TaskStatus taskStatus = TaskStatus.IN_PROGRESS; + + Member processor = mock(Member.class); + Task targetTask = mock(Task.class); + Task nextTask = mock(Task.class); + Task prevTask = mock(Task.class); + + when(memberService.findActiveMember(processorId)).thenReturn(processor); + when(taskService.findById(request.targetTaskId())).thenReturn(targetTask); + when(targetTask.getTaskStatus()).thenReturn(taskStatus); + when(taskService.findByIdAndStatus(request.nextTaskId(), taskStatus)).thenReturn(nextTask); + when(loadTaskPort.findPrevOrderTaskByProcessorOrderAndStatus(processorId, taskStatus, nextTask.getProcessorOrder())) + .thenReturn(Optional.of(prevTask)); + when(taskOrderCalculationPolicy.calculateOrderForTop(prevTask, nextTask)).thenReturn(100L); + + // when + updateTaskBoardService.updateTaskOrder(processorId, request); + + // then + verify(targetTask).updateProcessorOrder(100L); + verify(taskService).upsert(targetTask); + verify(processorValidationPolicy).validateProcessor(processorId, targetTask); + } + + @Test + @DisplayName("작업 순서를 업데이트 - 가장 하위로 이동") + void updateTaskOrder_moveToBottom() { + // given + Long processorId = 1L; + UpdateTaskOrderRequest request = new UpdateTaskOrderRequest(1L, 2L, 0L); // nextTaskId = 0 (맨 아래로 이동) + TaskStatus taskStatus = TaskStatus.IN_PROGRESS; + + Member processor = mock(Member.class); + Task targetTask = mock(Task.class); + Task prevTask = mock(Task.class); + Task nextTask = mock(Task.class); + + when(memberService.findActiveMember(processorId)).thenReturn(processor); + when(taskService.findById(request.targetTaskId())).thenReturn(targetTask); + when(targetTask.getTaskStatus()).thenReturn(taskStatus); + when(taskService.findByIdAndStatus(request.prevTaskId(), taskStatus)).thenReturn(prevTask); + when(loadTaskPort.findNextOrderTaskByProcessorOrderAndStatus(processorId, taskStatus, prevTask.getProcessorOrder())) + .thenReturn(Optional.of(nextTask)); + when(taskOrderCalculationPolicy.calculateOrderForBottom(prevTask, nextTask)).thenReturn(200L); + + // when + updateTaskBoardService.updateTaskOrder(processorId, request); + + // then + verify(targetTask).updateProcessorOrder(200L); + verify(taskService).upsert(targetTask); + verify(processorValidationPolicy).validateProcessor(processorId, targetTask); + } + + + + @Test + @DisplayName("작업 순서를 업데이트 - 중간으로 이동") + void updateTaskOrder_moveToMiddle() { + // given + Long processorId = 1L; + UpdateTaskOrderRequest request = new UpdateTaskOrderRequest(2L, 3L, 1L); // prev와 next 둘 다 존재 (중간 이동) + TaskStatus taskStatus = TaskStatus.IN_PROGRESS; + + Member processor = mock(Member.class); + Task targetTask = mock(Task.class); + Task prevTask = mock(Task.class); + Task nextTask = mock(Task.class); + + when(memberService.findActiveMember(processorId)).thenReturn(processor); + when(taskService.findById(request.targetTaskId())).thenReturn(targetTask); + when(targetTask.getTaskStatus()).thenReturn(taskStatus); + when(taskService.findByIdAndStatus(request.prevTaskId(), taskStatus)).thenReturn(prevTask); + when(taskService.findByIdAndStatus(request.nextTaskId(), taskStatus)).thenReturn(nextTask); + when(prevTask.getProcessorOrder()).thenReturn(100L); + when(nextTask.getProcessorOrder()).thenReturn(200L); + when(taskOrderCalculationPolicy.calculateNewProcessorOrder(prevTask.getProcessorOrder(), nextTask.getProcessorOrder())) + .thenReturn(150L); + + // when + updateTaskBoardService.updateTaskOrder(processorId, request); + + // then + verify(targetTask).updateProcessorOrder(150L); + verify(taskService).upsert(targetTask); + verify(processorValidationPolicy).validateProcessor(processorId, targetTask); + } +} diff --git a/src/test/java/clap/server/domain/policy/task/TaskOrderCalculationPolicyTest.java b/src/test/java/clap/server/domain/policy/task/TaskOrderCalculationPolicyTest.java new file mode 100644 index 00000000..f813d41f --- /dev/null +++ b/src/test/java/clap/server/domain/policy/task/TaskOrderCalculationPolicyTest.java @@ -0,0 +1,92 @@ +package clap.server.domain.policy.task; + +import clap.server.domain.model.task.Task; +import clap.server.exception.DomainException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.lenient; + +@ExtendWith(MockitoExtension.class) +class TaskOrderCalculationPolicyTest { + + private static final long DEFAULT_PROCESSOR_ORDER_GAP = 64L; + + @InjectMocks + private TaskOrderCalculationPolicy policy; + + @Mock + private Task prevTask; + + @Mock + private Task nextTask; + + @BeforeEach + void setUp() { + lenient().when(prevTask.getProcessorOrder()).thenReturn(1000L); + lenient().when(nextTask.getProcessorOrder()).thenReturn(3000L); + } + + @Test + @DisplayName("맨 위에 새로운 Task 추가 - 이전 Task가 없는 경우") + void calculateOrderForTop_WhenPrevTaskIsNull() { + long order = policy.calculateOrderForTop(null, nextTask); + assertEquals(3000L - DEFAULT_PROCESSOR_ORDER_GAP, order); + } + + @Test + @DisplayName("맨 위에 새로운 Task 추가 - 이전 Task가 존재하는 경우") + void calculateOrderForTop_WhenPrevTaskExists() { + long order = policy.calculateOrderForTop(prevTask, nextTask); + assertEquals(2000L, order); + } + + @Test + @DisplayName("맨 아래에 새로운 Task 추가 - 다음 Task가 없는 경우") + void calculateOrderForBottom_WhenNextTaskIsNull() { + long order = policy.calculateOrderForBottom(prevTask, null); + assertEquals(1000L + DEFAULT_PROCESSOR_ORDER_GAP, order); + } + + @Test + @DisplayName("맨 아래에 새로운 Task 추가 - 다음 Task가 존재하는 경우") + void calculateOrderForBottom_WhenNextTaskExists() { + long order = policy.calculateOrderForBottom(prevTask, nextTask); + assertEquals(2000L, order); + } + + @Test + @DisplayName("새로운 순서를 계산 - 유효한 범위 내에서 계산") + void calculateNewProcessorOrder_WhenValid() { + long order = policy.calculateNewProcessorOrder(1000L, 3000L); + assertEquals(2000L, order); + } + + @Test + @DisplayName("새로운 순서를 계산 - 간격이 너무 좁을 경우 예외 발생") + void calculateNewProcessorOrder_WhenOrderGapIsTooSmall() { + assertThrows(DomainException.class, () -> + policy.calculateNewProcessorOrder(1000L, 1001L)); + } + + @Test + @DisplayName("새로운 순서를 계산 - prevTaskOrder 또는 nextTaskOrder가 null일 경우 기본값 반환") + void calculateNewProcessorOrder_WhenNullValues() { + long order1 = policy.calculateNewProcessorOrder(null, 3000L); + long order2 = policy.calculateNewProcessorOrder(1000L, null); + + assertEquals(DEFAULT_PROCESSOR_ORDER_GAP, order1); + assertEquals(DEFAULT_PROCESSOR_ORDER_GAP, order2); + } +} + + + +