diff --git a/src/main/java/clap/server/adapter/inbound/web/dto/task/request/FilterTeamStatusRequest.java b/src/main/java/clap/server/adapter/inbound/web/dto/task/request/FilterTeamStatusRequest.java index ba517760..6202642e 100644 --- a/src/main/java/clap/server/adapter/inbound/web/dto/task/request/FilterTeamStatusRequest.java +++ b/src/main/java/clap/server/adapter/inbound/web/dto/task/request/FilterTeamStatusRequest.java @@ -1,6 +1,7 @@ package clap.server.adapter.inbound.web.dto.task.request; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import java.util.List; diff --git a/src/main/java/clap/server/adapter/inbound/web/task/CancelTaskController.java b/src/main/java/clap/server/adapter/inbound/web/task/CancelTaskController.java new file mode 100644 index 00000000..74d476ca --- /dev/null +++ b/src/main/java/clap/server/adapter/inbound/web/task/CancelTaskController.java @@ -0,0 +1,26 @@ +package clap.server.adapter.inbound.web.task; + +import clap.server.application.port.inbound.task.CancelTaskUsecase; +import clap.server.common.annotation.architecture.WebAdapter; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.annotation.Secured; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +@Tag(name = "02. Task [거부 & 종료]") +@RequestMapping("/api/tasks") +@RequiredArgsConstructor +@WebAdapter +public class CancelTaskController { + private final CancelTaskUsecase cancelTaskUsecase; + + @Operation(summary = "작업 취소") + @Secured("ROLE_USER") + @PatchMapping("/{taskId}/cancle") + public void cancelTask(@PathVariable Long taskId) { + cancelTaskUsecase.cancleTask(taskId); + } +} diff --git a/src/main/java/clap/server/adapter/inbound/web/task/ManagerController.java b/src/main/java/clap/server/adapter/inbound/web/task/ManagerController.java index f0c387e9..108ed27d 100644 --- a/src/main/java/clap/server/adapter/inbound/web/task/ManagerController.java +++ b/src/main/java/clap/server/adapter/inbound/web/task/ManagerController.java @@ -3,18 +3,24 @@ import clap.server.application.port.inbound.task.FindManagersUsecase; import clap.server.adapter.inbound.web.dto.task.response.FindManagersResponse; import clap.server.common.annotation.architecture.WebAdapter; +import io.swagger.v3.oas.annotations.Operation; + +import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import java.util.List; + +@Tag(name = "02. Task [조회]") @WebAdapter @RequestMapping("/api/managers") @RequiredArgsConstructor public class ManagerController { private final FindManagersUsecase findManagersUsecase; + @Operation(summary = "담당자 조회 API") @GetMapping public ResponseEntity> findManagers() { return ResponseEntity.ok(findManagersUsecase.findManagers()); diff --git a/src/main/java/clap/server/adapter/inbound/web/task/TeamStatusController.java b/src/main/java/clap/server/adapter/inbound/web/task/TeamStatusController.java index 705f7e31..7781eff2 100644 --- a/src/main/java/clap/server/adapter/inbound/web/task/TeamStatusController.java +++ b/src/main/java/clap/server/adapter/inbound/web/task/TeamStatusController.java @@ -9,18 +9,18 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; - @Tag(name = "02. Task [담당자]") -@WebAdapter @RestController -@RequiredArgsConstructor @RequestMapping("/api/team-status") - public class TeamStatusController { - +@RequiredArgsConstructor +@WebAdapter +public class TeamStatusController { + private final TeamStatusService teamStatusService; @Operation(summary = "팀 현황 필터링 조회 API") @GetMapping("/filter") @@ -28,4 +28,5 @@ public ResponseEntity filterTeamStatus(@ModelAttribute Filte TeamStatusResponse response = teamStatusService.filterTeamStatus(filter); return ResponseEntity.ok(response != null ? response : new TeamStatusResponse(List.of(), 0, 0, 0)); } -} + +} \ No newline at end of file diff --git a/src/main/java/clap/server/adapter/outbound/persistense/TaskPersistenceAdapter.java b/src/main/java/clap/server/adapter/outbound/persistense/TaskPersistenceAdapter.java index 1db21131..bed7a99b 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/TaskPersistenceAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/TaskPersistenceAdapter.java @@ -1,7 +1,7 @@ package clap.server.adapter.outbound.persistense; -import clap.server.adapter.inbound.web.dto.task.request.FilterTaskBoardRequest; import clap.server.adapter.inbound.web.dto.task.request.FilterTaskListRequest; +import clap.server.adapter.inbound.web.dto.task.request.FilterTaskBoardRequest; import clap.server.adapter.inbound.web.dto.task.request.FilterTeamStatusRequest; import clap.server.adapter.inbound.web.dto.task.response.TeamTaskResponse; import clap.server.adapter.outbound.persistense.entity.task.TaskEntity; @@ -23,7 +23,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.SliceImpl; -import org.springframework.http.ResponseEntity; import java.time.LocalDateTime; import java.util.List; diff --git a/src/main/java/clap/server/adapter/outbound/persistense/entity/task/constant/TaskStatus.java b/src/main/java/clap/server/adapter/outbound/persistense/entity/task/constant/TaskStatus.java index 297909a7..33900952 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/entity/task/constant/TaskStatus.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/entity/task/constant/TaskStatus.java @@ -8,7 +8,7 @@ public enum TaskStatus { REQUESTED("요청"), IN_PROGRESS("진행 중"), - IN_REVIEWING("완료 대기"), + IN_REVIEWING("검토중"), COMPLETED("완료"), TERMINATED("종료"); diff --git a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java index 88e2bd82..b14c3079 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/repository/task/TaskCustomRepositoryImpl.java @@ -21,6 +21,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; +import java.util.Optional; import static clap.server.adapter.outbound.persistense.entity.task.QTaskEntity.taskEntity; import static com.querydsl.core.types.Order.ASC; @@ -102,6 +103,11 @@ public List findTeamStatus(Long memberId, FilterTeamStatusRequ .orderBy(orderBy) .fetch(); + // null 또는 빈 리스트 처리 + if (taskEntities == null || taskEntities.isEmpty()) { + return List.of(); // 빈 리스트 반환 + } + return taskEntities.stream() .collect(Collectors.groupingBy(t -> t.getProcessor().getMemberId())) .entrySet().stream() 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 c1df73a3..39e3bdab 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 @@ -6,15 +6,12 @@ import clap.server.adapter.outbound.persistense.entity.task.TaskEntity; import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; import io.lettuce.core.dynamic.annotation.Param; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; - import org.springframework.stereotype.Repository; import java.time.LocalDateTime; import java.util.Collection; - import java.util.List; import java.util.Optional; @@ -52,7 +49,7 @@ List findTasksWithTaskStatusAndCompletedAt( Optional findTopByProcessor_MemberIdAndTaskStatusAndProcessorOrderAfterOrderByProcessorOrderAsc( Long processorId, TaskStatus taskStatus, Long processorOrder); - @Query("SELECT t FROM TaskEntity t JOIN FETCH t.processor p WHERE (:memberId IS NULL OR p.memberId = :memberId) ") +// @Query("SELECT t FROM TaskEntity t JOIN FETCH t.processor p WHERE (:memberId IS NULL OR p.memberId = :memberId) ") List findTeamStatus(@Param("memberId") Long memberId, FilterTeamStatusRequest filter); Optional findTopByProcessor_MemberIdAndTaskStatusAndTaskIdLessThanOrderByTaskIdDesc(Long processorId, TaskStatus taskStatus, Long taskId); diff --git a/src/main/java/clap/server/application/mapper/TaskResponseMapper.java b/src/main/java/clap/server/application/mapper/TaskResponseMapper.java index 84379ddf..1d3effdb 100644 --- a/src/main/java/clap/server/application/mapper/TaskResponseMapper.java +++ b/src/main/java/clap/server/application/mapper/TaskResponseMapper.java @@ -2,6 +2,8 @@ import clap.server.adapter.inbound.web.dto.task.response.*; +import clap.server.adapter.outbound.persistense.entity.task.LabelEntity; +import clap.server.adapter.outbound.persistense.entity.task.TaskEntity; import clap.server.adapter.outbound.persistense.entity.task.constant.TaskStatus; import clap.server.domain.model.member.Member; import clap.server.domain.model.task.Attachment; @@ -193,4 +195,61 @@ public static FindManagersResponse toFindManagersResponse(Member manager, int re remainingTasks ); } + + public static TeamStatusResponse toTeamStatusResponse(List taskEntities) { + // 담당자별로 그룹화 + Map> tasksByProcessor = taskEntities.stream() + .collect(Collectors.groupingBy(taskEntity -> taskEntity.getProcessor().getMemberId())); + + List memberResponses = tasksByProcessor.entrySet().stream() + .map(entry -> { + List teamtaskItemResponses = entry.getValue().stream() + .map(TaskResponseMapper::toTeamTaskItemResponse) + .collect(Collectors.toList()); + + return new TeamTaskResponse( + entry.getKey(), + entry.getValue().get(0).getProcessor().getNickname(), + entry.getValue().get(0).getProcessor().getImageUrl(), + entry.getValue().get(0).getProcessor().getDepartment().getName(), + (int) entry.getValue().stream().filter(t -> t.getTaskStatus() == TaskStatus.IN_PROGRESS).count(), + (int) entry.getValue().stream().filter(t -> t.getTaskStatus() == TaskStatus.IN_REVIEWING).count(), + entry.getValue().size(), + teamtaskItemResponses + ); + }) + .collect(Collectors.toList()); + + return new TeamStatusResponse(memberResponses); + } + + public static TeamTaskItemResponse toTeamTaskItemResponse(TaskEntity taskEntity) { + return new TeamTaskItemResponse( + taskEntity.getTaskId(), + taskEntity.getTaskCode(), + taskEntity.getTitle(), + taskEntity.getCategory().getMainCategory().getName(), + taskEntity.getCategory().getName(), + taskEntity.getLabel() != null ? toLabelInfo(taskEntity.getLabel()) : null, + taskEntity.getRequester().getNickname(), + taskEntity.getRequester().getImageUrl(), + taskEntity.getRequester().getDepartment().getName(), + taskEntity.getProcessorOrder(), + taskEntity.getTaskStatus(), + taskEntity.getCreatedAt() + ); + } + + public static TeamTaskItemResponse.LabelInfo toLabelInfo(LabelEntity label) { // Label → LabelEntity로 변경 + return new TeamTaskItemResponse.LabelInfo( + label.getLabelName(), + label.getLabelColor() + ); + } + + + + + + } diff --git a/src/main/java/clap/server/application/port/inbound/task/CancelTaskUsecase.java b/src/main/java/clap/server/application/port/inbound/task/CancelTaskUsecase.java new file mode 100644 index 00000000..de1597b1 --- /dev/null +++ b/src/main/java/clap/server/application/port/inbound/task/CancelTaskUsecase.java @@ -0,0 +1,5 @@ +package clap.server.application.port.inbound.task; + +public interface CancelTaskUsecase { + void cancleTask(Long taskId); +} diff --git a/src/main/java/clap/server/application/service/task/CancelTaskService.java b/src/main/java/clap/server/application/service/task/CancelTaskService.java new file mode 100644 index 00000000..9fefe764 --- /dev/null +++ b/src/main/java/clap/server/application/service/task/CancelTaskService.java @@ -0,0 +1,21 @@ +package clap.server.application.service.task; + +import clap.server.application.port.inbound.domain.TaskService; +import clap.server.application.port.inbound.task.CancelTaskUsecase; +import clap.server.common.annotation.architecture.ApplicationService; +import clap.server.domain.model.task.Task; +import lombok.RequiredArgsConstructor; + + +@ApplicationService +@RequiredArgsConstructor +public class CancelTaskService implements CancelTaskUsecase { + private final TaskService taskService; + + @Override + public void cancleTask(Long taskId) { + Task task = taskService.findById(taskId); + task.cancelTask(); + taskService.upsert(task); + } +} diff --git a/src/main/java/clap/server/application/service/task/TeamStatusService.java b/src/main/java/clap/server/application/service/task/TeamStatusService.java index 5b6e5ac8..6fc2be25 100644 --- a/src/main/java/clap/server/application/service/task/TeamStatusService.java +++ b/src/main/java/clap/server/application/service/task/TeamStatusService.java @@ -1,12 +1,13 @@ package clap.server.application.service.task; import clap.server.adapter.inbound.web.dto.task.request.FilterTeamStatusRequest; -import clap.server.adapter.inbound.web.dto.task.response.TeamTaskResponse; import clap.server.adapter.inbound.web.dto.task.response.TeamStatusResponse; +import clap.server.adapter.inbound.web.dto.task.response.TeamTaskResponse; import clap.server.application.port.inbound.task.FilterTeamStatusUsecase; import clap.server.application.port.inbound.task.LoadTeamStatusUsecase; import clap.server.application.port.outbound.task.LoadTaskPort; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -22,12 +23,19 @@ public TeamStatusService(LoadTaskPort loadTaskPort) { @Override public TeamStatusResponse getTeamStatus(Long memberId, FilterTeamStatusRequest filter) { List members = loadTaskPort.findTeamStatus(memberId, filter); + if (members == null) { + members = List.of(); + } return new TeamStatusResponse(members); } @Override + @Transactional(readOnly = true) public TeamStatusResponse filterTeamStatus(FilterTeamStatusRequest filter) { List members = loadTaskPort.findTeamStatus(null, filter); + if (members == null) { + members = List.of(); + } return new TeamStatusResponse(members); } diff --git a/src/main/java/clap/server/domain/model/task/Task.java b/src/main/java/clap/server/domain/model/task/Task.java index 62b44f68..1a8985b3 100644 --- a/src/main/java/clap/server/domain/model/task/Task.java +++ b/src/main/java/clap/server/domain/model/task/Task.java @@ -102,4 +102,9 @@ private static String toTaskCode(Category category) { public void updateProcessorOrder(long newProcessorOrder) { this.processorOrder = newProcessorOrder; } + + public void cancelTask() { + this.taskStatus = TaskStatus.TERMINATED; + this.finishedAt = LocalDateTime.now(); + } }