diff --git a/src/main/java/com/fowoco/server/task/api/TaskController.java b/src/main/java/com/fowoco/server/task/api/TaskController.java index fb32af6..723bd97 100644 --- a/src/main/java/com/fowoco/server/task/api/TaskController.java +++ b/src/main/java/com/fowoco/server/task/api/TaskController.java @@ -8,6 +8,7 @@ import com.fowoco.server.task.domain.TaskStatus; import com.fowoco.server.task.domain.TaskType; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -62,6 +63,8 @@ public TaskPageResponse findAll( @RequestParam(required = false) TaskStatus status, @RequestParam(required = false) TaskType taskType, @RequestParam(required = false) UUID workerId, + @Parameter(description = "Case ID 필터") + @RequestParam(name = "case_id", required = false) UUID caseId, @RequestParam(required = false) LocalDate dueFrom, @RequestParam(required = false) LocalDate dueTo, @RequestParam(required = false) String keyword, @@ -72,6 +75,7 @@ public TaskPageResponse findAll( status, taskType, workerId, + caseId, dueFrom, dueTo, keyword, diff --git a/src/main/java/com/fowoco/server/task/application/TaskWorkflowService.java b/src/main/java/com/fowoco/server/task/application/TaskWorkflowService.java index d5108a6..ff239cf 100644 --- a/src/main/java/com/fowoco/server/task/application/TaskWorkflowService.java +++ b/src/main/java/com/fowoco/server/task/application/TaskWorkflowService.java @@ -178,6 +178,7 @@ public TaskPageResult findAll( TaskStatus status, TaskType taskType, UUID workerId, + UUID caseId, LocalDate dueFrom, LocalDate dueTo, String keyword, @@ -195,6 +196,7 @@ public TaskPageResult findAll( status, taskType, workerId, + caseId, dueFrom, dueTo, keyword, diff --git a/src/main/java/com/fowoco/server/task/application/port/TaskRepository.java b/src/main/java/com/fowoco/server/task/application/port/TaskRepository.java index 6dec314..c74a7d9 100644 --- a/src/main/java/com/fowoco/server/task/application/port/TaskRepository.java +++ b/src/main/java/com/fowoco/server/task/application/port/TaskRepository.java @@ -21,6 +21,7 @@ record TaskSearchCriteria( TaskStatus status, TaskType taskType, UUID workerId, + UUID caseId, LocalDate dueFrom, LocalDate dueTo, String keyword, diff --git a/src/main/java/com/fowoco/server/task/infrastructure/persistence/JpaTaskRepository.java b/src/main/java/com/fowoco/server/task/infrastructure/persistence/JpaTaskRepository.java index 4bbe400..663b16e 100644 --- a/src/main/java/com/fowoco/server/task/infrastructure/persistence/JpaTaskRepository.java +++ b/src/main/java/com/fowoco/server/task/infrastructure/persistence/JpaTaskRepository.java @@ -30,6 +30,7 @@ public TaskPage findAll(TaskSearchCriteria criteria) { criteria.status(), criteria.taskType(), criteria.workerId(), + criteria.caseId(), criteria.dueFrom(), criteria.dueTo(), normalizeKeyword(criteria.keyword()), diff --git a/src/main/java/com/fowoco/server/task/infrastructure/persistence/SpringDataTaskJpaRepository.java b/src/main/java/com/fowoco/server/task/infrastructure/persistence/SpringDataTaskJpaRepository.java index 029471b..f5f1442 100644 --- a/src/main/java/com/fowoco/server/task/infrastructure/persistence/SpringDataTaskJpaRepository.java +++ b/src/main/java/com/fowoco/server/task/infrastructure/persistence/SpringDataTaskJpaRepository.java @@ -22,6 +22,7 @@ interface SpringDataTaskJpaRepository extends JpaRepository AND (:status IS NULL OR task.status = :status) AND (:taskType IS NULL OR task.taskType = :taskType) AND (:workerId IS NULL OR task.workerId = :workerId) + AND (:caseId IS NULL OR task.caseId = :caseId) AND (:dueFrom IS NULL OR task.dueDate >= :dueFrom) AND (:dueTo IS NULL OR task.dueDate <= :dueTo) AND ( @@ -35,6 +36,7 @@ Page search( @Param("status") TaskStatus status, @Param("taskType") TaskType taskType, @Param("workerId") UUID workerId, + @Param("caseId") UUID caseId, @Param("dueFrom") LocalDate dueFrom, @Param("dueTo") LocalDate dueTo, @Param("keyword") String keyword, diff --git a/src/test/java/com/fowoco/server/task/TaskWorkflowIntegrationTest.java b/src/test/java/com/fowoco/server/task/TaskWorkflowIntegrationTest.java index 792c9d4..0214a76 100644 --- a/src/test/java/com/fowoco/server/task/TaskWorkflowIntegrationTest.java +++ b/src/test/java/com/fowoco/server/task/TaskWorkflowIntegrationTest.java @@ -222,6 +222,44 @@ void viewerCannotWriteAndAnotherCompanyCannotDiscoverTask() throws Exception { .isEqualTo(404); } + @Test + void caseIdFilterReturnsOnlyTheRequestedCompanyCase() throws Exception { + String hrToken = login(HR_A_EMAIL); + UUID selectedCaseId = UUID.randomUUID(); + UUID otherCaseId = UUID.randomUUID(); + UUID selectedTaskId = UUID.fromString(JsonPath.read( + post( + "/api/v1/tasks", + createBody(selectedCaseId, "선택 Case 업무"), + hrToken + ).body(), + "$.task_id" + )); + post( + "/api/v1/tasks", + createBody(otherCaseId, "다른 Case 업무"), + hrToken + ); + + HttpResponse filtered = get( + "/api/v1/tasks?case_id=" + selectedCaseId, + hrToken + ); + HttpResponse unfiltered = get("/api/v1/tasks", hrToken); + HttpResponse otherCompany = get( + "/api/v1/tasks?case_id=" + selectedCaseId, + login(HR_B_EMAIL) + ); + + assertThat(filtered.statusCode()).isEqualTo(200); + assertThat(JsonPath.>read(filtered.body(), "$.items[*].task_id")) + .containsExactly(selectedTaskId.toString()); + assertThat(JsonPath.>read(filtered.body(), "$.items[*].case_id")) + .containsExactly(selectedCaseId.toString()); + assertThat(JsonPath.>read(unfiltered.body(), "$.items")).hasSize(2); + assertThat(JsonPath.>read(otherCompany.body(), "$.items")).isEmpty(); + } + @Test void resignedAndTerminatedWorkersCannotReceiveNewTasks() throws Exception { String token = login(HR_A_EMAIL); @@ -422,6 +460,21 @@ private String validCreateBody() { """.formatted(WORKER_A); } + private String createBody(UUID caseId, String title) { + return """ + { + "worker_id":"%s", + "case_id":"%s", + "task_type":"RECONTRACT", + "workflow_id":"WF-CON-001", + "title":"%s", + "description":"기존 조건 확인", + "due_date":"2026-08-20", + "business_data":{"monthly_wage":2500000} + } + """.formatted(WORKER_A, caseId, title); + } + private String login(String email) throws Exception { HttpResponse response = post( "/api/v1/auth/login", diff --git a/src/test/java/com/fowoco/server/task/api/TaskOpenApiContractTest.java b/src/test/java/com/fowoco/server/task/api/TaskOpenApiContractTest.java index 14714dd..05c9662 100644 --- a/src/test/java/com/fowoco/server/task/api/TaskOpenApiContractTest.java +++ b/src/test/java/com/fowoco/server/task/api/TaskOpenApiContractTest.java @@ -112,6 +112,19 @@ void responseAndCatalogSchemasExposePinnedVersionsInSnakeCase() { assertThat(workflow.has("source_ids")).isTrue(); } + @Test + void taskListPublishesTheOptionalCaseIdFilter() { + JsonNode parameters = openApi.at("/paths/~1api~1v1~1tasks/get/parameters"); + + assertThat(parameters) + .anySatisfy(parameter -> { + assertThat(parameter.path("name").asText()).isEqualTo("case_id"); + assertThat(parameter.path("in").asText()).isEqualTo("query"); + assertThat(parameter.path("required").asBoolean()).isFalse(); + assertThat(parameter.at("/schema/format").asText()).isEqualTo("uuid"); + }); + } + private String operationId(String pointer) { return openApi.at(pointer).path("operationId").asText(); }