defmodule WorkloadServiceWeb.TaskController do use WorkloadServiceWeb, :controller use OpenApiSpex.ControllerSpecs alias WorkloadService.CommandedApp alias WorkloadService.Workload.Queries alias WorkloadService.Aggregates.TaskId alias WorkloadServiceWeb.Schemas.Task, as: S alias WorkloadServiceWeb.QueryHelpers tags(["Tasks"]) operation(:list, summary: "List tasks", parameters: QueryHelpers.flop( [:status, :application_id, :policy_type], [:created_at, :updated_at, :status] ), responses: [ ok: {"Task list", "application/json", S.TaskListResponse}, bad_request: {"Invalid params", "application/json", S.ErrorResponse} ] ) def list(conn, params) do case Queries.list_tasks(params) do {:ok, {tasks, meta}} -> conn |> put_status(:ok) |> json(%{ data: Enum.map(tasks, &task_summary/1), meta: meta_json(meta) }) {:error, _} -> conn |> put_status(:bad_request) |> json(%{error: "invalid parameters"}) end end operation(:show, summary: "Get task by ID", parameters: [ id: [in: :path, type: :string, required: true] ], responses: [ ok: {"Task detail", "application/json", S.TaskDetailResponse}, not_found: {"Not found", "application/json", S.ErrorResponse} ] ) def show(conn, %{"id" => id}) do case Queries.get_task_by_id(id) do {:error, :not_found} -> conn |> put_status(:not_found) |> json(%{error: "task not found"}) {:ok, task} -> conn |> put_status(:ok) |> json(%{data: task_detail(task)}) end end operation(:submit, summary: "Submit task data (quote response or confirm delivery)", parameters: [ id: [in: :path, type: :string, required: true] ], request_body: {"Task submission", "application/json", S.SubmitRequest, required: true}, responses: [ ok: {"Task submitted", "application/json", S.TaskDetailResponse}, not_found: {"Not found", "application/json", S.ErrorResponse}, unprocessable_entity: {"Error", "application/json", S.ErrorResponse} ] ) def submit(conn, %{"id" => id} = params) do task_type = get_task_type(id) case task_type do "quote" -> handle_quote_submit(conn, id, params) "solicitation" -> handle_solicitation_submit(conn, id, params) _ -> conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid task type"}) end end defp handle_quote_submit(conn, id, params) do case Queries.get_task_by_id(id) do {:error, :not_found} -> conn |> put_status(:not_found) |> json(%{error: "task not found"}) {:ok, %{status: "created"} = _task} -> task_id = TaskId.parse!(id) command = %WorkloadService.Commands.QuoteTask.SubmitResponse{ id: task_id, submission: %{ "quote_id" => params["quote_id"], "plans" => params["plans"], "valid_until" => params["valid_until"], "recorded_by" => "system", "document_data" => params["document_data"] }, attachments: params["document_urls"] || [] } dispatch_and_respond(conn, id, command) {:ok, _task} -> conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid state for submit"}) end end defp handle_solicitation_submit(conn, id, params) do case Queries.get_task_by_id(id) do {:error, :not_found} -> conn |> put_status(:not_found) |> json(%{error: "task not found"}) {:ok, %{status: "created"} = _task} -> task_id = TaskId.parse!(id) command = %WorkloadService.Commands.SolicitationTask.SubmitResponse{ id: task_id, submission: %{ "provider_policy_number" => params["provider_policy_number"], "effective_date" => params["effective_date"], "expiry_date" => params["expiry_date"], "recorded_by" => "system" }, attachments: params["document_urls"] || [] } dispatch_and_respond(conn, id, command) {:ok, _task} -> conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid state for submit"}) end end operation(:approve, summary: "Approve submission", parameters: [ id: [in: :path, type: :string, required: true] ], responses: [ ok: {"Task approved", "application/json", S.TaskDetailResponse}, not_found: {"Not found", "application/json", S.ErrorResponse}, unprocessable_entity: {"Error", "application/json", S.ErrorResponse} ] ) def approve(conn, %{"id" => id}) do task_type = get_task_type(id) case task_type do "quote" -> handle_approve(conn, id, WorkloadService.Commands.QuoteTask.ApproveSubmission) "solicitation" -> handle_approve(conn, id, WorkloadService.Commands.SolicitationTask.ApproveSubmission) _ -> conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid task type"}) end end defp handle_approve(conn, id, command_module) do case Queries.get_task_by_id(id) do {:error, :not_found} -> conn |> put_status(:not_found) |> json(%{error: "task not found"}) {:ok, %{status: "draft"} = _task} -> task_id = TaskId.parse!(id) command = struct(command_module, id: task_id) dispatch_and_respond(conn, id, command) {:ok, _task} -> conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid state for approve"}) end end operation(:complete, summary: "Complete task", parameters: [ id: [in: :path, type: :string, required: true] ], request_body: {"Complete request", "application/json", S.CompleteRequest, required: false}, responses: [ ok: {"Task completed", "application/json", S.TaskDetailResponse}, not_found: {"Not found", "application/json", S.ErrorResponse}, unprocessable_entity: {"Error", "application/json", S.ErrorResponse} ] ) def complete(conn, %{"id" => id} = params) do task_type = get_task_type(id) completed_by = params["completed_by"] || "system" case task_type do "quote" -> handle_complete(conn, id, completed_by, WorkloadService.Commands.QuoteTask.CompleteTask) "solicitation" -> handle_complete( conn, id, completed_by, WorkloadService.Commands.SolicitationTask.CompleteTask ) _ -> conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid task type"}) end end defp handle_complete(conn, id, completed_by, command_module) do case Queries.get_task_by_id(id) do {:error, :not_found} -> conn |> put_status(:not_found) |> json(%{error: "task not found"}) {:ok, %{status: "approved"} = _task} -> task_id = TaskId.parse!(id) command = struct(command_module, id: task_id, completed_by: completed_by) dispatch_and_respond(conn, id, command) {:ok, _task} -> conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid state for complete"}) end end defp dispatch_and_respond(conn, id, command) do case CommandedApp.dispatch(command) do :ok -> {:ok, task} = Queries.get_task_by_id(id) conn |> put_status(:ok) |> json(%{data: task_detail(task)}) {:error, reason} -> conn |> put_status(:unprocessable_entity) |> json(%{error: inspect(reason)}) end end defp get_task_type(id) do case WorkloadService.Aggregates.TaskId.parse(id) do {:ok, %{type: type}} -> type _ -> "unknown" end end defp task_summary(t) do %{ id: t.id, org_id: t.org_id, application_id: t.application_id, policy_type: t.policy_type, task_info: t.task_info, status: t.status, created_at: t.inserted_at } end defp task_detail(t) do %{ id: t.id, org_id: t.org_id, application_id: t.application_id, policy_type: t.policy_type, task_info: t.task_info, submission: t.submission, attachments: t.attachments, status: t.status, created_at: t.inserted_at, updated_at: t.updated_at } end defp meta_json(meta) do %{ total_count: meta.total_count, total_pages: meta.total_pages, current_page: meta.current_page, page_size: meta.page_size, has_next: meta.has_next_page?, has_prev: meta.has_previous_page? } end end