From 01ad2270bcc436326b76b3bddc93ec66c63a9ea3 Mon Sep 17 00:00:00 2001 From: HaimKortovich Date: Fri, 17 Apr 2026 11:42:19 -0500 Subject: [PATCH] simplify task api --- lib/workload_service/aggregates/task.ex | 12 +- lib/workload_service/application.ex | 2 +- lib/workload_service/commands/quote_task.ex | 2 +- .../commands/solicitation_task.ex | 2 +- .../consumers/quote_requested_consumer.ex | 20 +- .../solicitation_requested_consumer.ex | 218 ++++++++-------- lib/workload_service/events/task.ex | 12 +- lib/workload_service/projections/task.ex | 8 +- .../projectors/task_projector.ex | 2 - .../controllers/task_controller.ex | 244 ++++++++++-------- lib/workload_service_web/router.ex | 7 +- lib/workload_service_web/schemas/task.ex | 63 +++-- .../20240101000001_create_tasks.exs | 29 +-- 13 files changed, 320 insertions(+), 301 deletions(-) diff --git a/lib/workload_service/aggregates/task.ex b/lib/workload_service/aggregates/task.ex index b5491ac..6dd3c86 100644 --- a/lib/workload_service/aggregates/task.ex +++ b/lib/workload_service/aggregates/task.ex @@ -30,8 +30,6 @@ defmodule WorkloadService.Aggregates.Task do defstruct [ :id, :application_id, - :provider_id, - :provider_name, :task_info, :submission, :attachments, @@ -44,9 +42,7 @@ defmodule WorkloadService.Aggregates.Task do %WorkloadService.Events.TaskCreated{ id: cmd.id, application_id: cmd.application_id, - provider_id: cmd.provider_id, - provider_name: cmd.provider_name, - task_info: cmd.task_info || %{}, + task_info: cmd.task_info, attachments: cmd.attachments || [] } end @@ -71,9 +67,9 @@ defmodule WorkloadService.Aggregates.Task do end @impl Aggregate - def execute(%__MODULE__{status: "draft"}, %ApproveSubmission{}) do + def execute(%__MODULE__{id: id, status: "draft"}, %ApproveSubmission{}) do %WorkloadService.Events.SubmissionApproved{ - id: nil + id: id } end @@ -101,8 +97,6 @@ defmodule WorkloadService.Aggregates.Task do agg | id: e.id, application_id: e.application_id, - provider_id: e.provider_id, - provider_name: e.provider_name, task_info: e.task_info, attachments: e.attachments, status: "created", diff --git a/lib/workload_service/application.ex b/lib/workload_service/application.ex index 043c6e9..687999a 100644 --- a/lib/workload_service/application.ex +++ b/lib/workload_service/application.ex @@ -8,7 +8,7 @@ defmodule WorkloadService.Application do children = [ WorkloadService.CommandedApp, WorkloadService.Consumers.QuoteRequestedConsumer, - WorkloadService.Consumers.SolicitationRequestedConsumer, + # WorkloadService.Consumers.SolicitationRequestedConsumer, WorkloadService.Projectors.TaskProjector, WorkloadService.Repo, WorkloadServiceWeb.Telemetry, diff --git a/lib/workload_service/commands/quote_task.ex b/lib/workload_service/commands/quote_task.ex index 5f0c3cd..32caf69 100644 --- a/lib/workload_service/commands/quote_task.ex +++ b/lib/workload_service/commands/quote_task.ex @@ -8,7 +8,7 @@ defmodule WorkloadService.Commands.QuoteTask do Command to create a new quote task. """ @derive Jason.Encoder - defstruct [:id, :application_id, :provider_id, :provider_name, :task_info, :attachments] + defstruct [:id, :application_id, :task_info, :attachments] def new(attrs) do struct(__MODULE__, attrs) diff --git a/lib/workload_service/commands/solicitation_task.ex b/lib/workload_service/commands/solicitation_task.ex index bec9233..201ff97 100644 --- a/lib/workload_service/commands/solicitation_task.ex +++ b/lib/workload_service/commands/solicitation_task.ex @@ -8,7 +8,7 @@ defmodule WorkloadService.Commands.SolicitationTask do Command to create a new solicitation task. """ @derive Jason.Encoder - defstruct [:id, :application_id, :provider_id, :provider_name, :task_info, :attachments] + defstruct [:id, :application_id, :task_info, :attachments] def new(attrs) do struct(__MODULE__, attrs) diff --git a/lib/workload_service/consumers/quote_requested_consumer.ex b/lib/workload_service/consumers/quote_requested_consumer.ex index b53ebd3..e2cb658 100644 --- a/lib/workload_service/consumers/quote_requested_consumer.ex +++ b/lib/workload_service/consumers/quote_requested_consumer.ex @@ -52,26 +52,22 @@ defmodule WorkloadService.Consumers.QuoteRequestedConsumer do defp handle_event( %{ - "id" => application_id, - "org_id" => org_id, + "id" => %{"org_id" => org_id} = application_id, "provider_id" => provider_id, - "policy_type" => policy_type + "policy_details" => policy_details, + "applicant_info" => applicant_info } = event ) do task_id = WorkloadService.Aggregates.TaskId.new(org_id, "quote", Ecto.UUID.generate()) command = %QuoteTask.CreateTask{ id: task_id, - application_id: id, - provider_id: provider_id, - provider_name: Map.get(event, "provider_name", ""), + application_id: application_id, task_info: %{ - "policy_type" => policy_type, - "provider_email" => Map.get(event, "provider_email"), - "applicant_info" => Map.get(event, "applicant_info", %{}), - "car_details" => Map.get(event, "car_details", %{}), - "building_details" => Map.get(event, "building_details", %{}), - "life_details" => Map.get(event, "life_details", %{}) + "provider_id" => provider_id, + "applicant_info" => applicant_info, + "policy_details" => policy_details, + "provider_email" => Map.get(event, "provider_email") } } diff --git a/lib/workload_service/consumers/solicitation_requested_consumer.ex b/lib/workload_service/consumers/solicitation_requested_consumer.ex index a395baa..de3a4db 100644 --- a/lib/workload_service/consumers/solicitation_requested_consumer.ex +++ b/lib/workload_service/consumers/solicitation_requested_consumer.ex @@ -1,137 +1,137 @@ -defmodule WorkloadService.Consumers.SolicitationRequestedConsumer do - use GenServer - require Logger +# defmodule WorkloadService.Consumers.SolicitationRequestedConsumer do +# use GenServer +# require Logger - alias WorkloadService.CommandedApp - alias WorkloadService.Commands.SolicitationTask +# alias WorkloadService.CommandedApp +# alias WorkloadService.Commands.SolicitationTask - @exchange "workload_service.events.solicitation_requested" - @queue "workload_service.solicitation_requested" - @routing_key "solicitation.requested" +# @exchange "workload_service.events.solicitation_requested" +# @queue "workload_service.solicitation_requested" +# @routing_key "solicitation.requested" - @provider_service_url "http://localhost:4002" - @solicitation_service_url "http://localhost:8081" +# @provider_service_url "http://localhost:4002" +# @solicitation_service_url "http://localhost:8081" - def start_link(opts \\ []) do - GenServer.start_link(__MODULE__, opts, name: __MODULE__) - end +# def start_link(opts \\ []) do +# GenServer.start_link(__MODULE__, opts, name: __MODULE__) +# end - def init(_opts) do - {:ok, conn} = AMQP.Connection.open(amqp_url()) - {:ok, channel} = AMQP.Channel.open(conn) +# def init(_opts) do +# {:ok, conn} = AMQP.Connection.open(amqp_url()) +# {:ok, channel} = AMQP.Channel.open(conn) - AMQP.Queue.declare(channel, @queue, durable: true) - AMQP.Queue.bind(channel, @queue, @exchange, routing_key: @routing_key) - {:ok, _tag} = AMQP.Basic.consume(channel, @queue) +# AMQP.Queue.declare(channel, @queue, durable: true) +# AMQP.Queue.bind(channel, @queue, @exchange, routing_key: @routing_key) +# {:ok, _tag} = AMQP.Basic.consume(channel, @queue) - Logger.info("SolicitationRequestedConsumer started, listening on #{@queue}") +# Logger.info("SolicitationRequestedConsumer started, listening on #{@queue}") - {:ok, %{channel: channel}} - end +# {:ok, %{channel: channel}} +# end - def handle_info({:basic_consume_ok, _}, state), do: {:noreply, state} - def handle_info({:basic_cancel, _}, state), do: {:stop, :normal, state} - def handle_info({:basic_cancel_ok, _}, state), do: {:noreply, state} +# def handle_info({:basic_consume_ok, _}, state), do: {:noreply, state} +# def handle_info({:basic_cancel, _}, state), do: {:stop, :normal, state} +# def handle_info({:basic_cancel_ok, _}, state), do: {:noreply, state} - def handle_info({:basic_deliver, payload, meta}, state) do - case process(payload) do - :ok -> - AMQP.Basic.ack(state.channel, meta.delivery_tag) +# def handle_info({:basic_deliver, payload, meta}, state) do +# case process(payload) do +# :ok -> +# AMQP.Basic.ack(state.channel, meta.delivery_tag) - {:error, reason} -> - Logger.error("SolicitationRequestedConsumer: failed to process: #{inspect(reason)}") - AMQP.Basic.reject(state.channel, meta.delivery_tag, requeue: false) - end +# {:error, reason} -> +# Logger.error("SolicitationRequestedConsumer: failed to process: #{inspect(reason)}") +# AMQP.Basic.reject(state.channel, meta.delivery_tag, requeue: false) +# end - {:noreply, state} - end +# {:noreply, state} +# end - defp process(payload) do - with {:ok, event} <- Jason.decode(payload), - {:ok, provider} <- get_provider(event["provider_id"]), - {:ok, template} <- get_active_template(provider, event["policy_type"]), - {:ok, result} <- generate_solicitation(event, template), - :ok <- create_task(event, result) do - :ok - end - end +# defp process(payload) do +# with {:ok, event} <- Jason.decode(payload), +# {:ok, provider} <- get_provider(event["provider_id"]), +# {:ok, template} <- get_active_template(provider, event["policy_type"]), +# {:ok, result} <- generate_solicitation(event, template), +# :ok <- create_task(event, result) do +# :ok +# end +# end - defp get_provider(provider_id) do - url = "#{@provider_service_url}/api/v1/providers/#{provider_id}" +# defp get_provider(provider_id) do +# url = "#{@provider_service_url}/api/v1/providers/#{provider_id}" - case Req.get(url) do - {:ok, %{status: 200, body: %{"data" => provider}}} -> {:ok, provider} - {:ok, %{status: 404}} -> {:error, :provider_not_found} - {:error, reason} -> {:error, reason} - end - end +# case Req.get(url) do +# {:ok, %{status: 200, body: %{"data" => provider}}} -> {:ok, provider} +# {:ok, %{status: 404}} -> {:error, :provider_not_found} +# {:error, reason} -> {:error, reason} +# end +# end - defp get_active_template(provider, policy_type) do - templates = get_in(provider, ["templates", policy_type]) || [] - default_id = get_in(provider, ["default_templates", policy_type]) +# defp get_active_template(provider, policy_type) do +# templates = get_in(provider, ["templates", policy_type]) || [] +# default_id = get_in(provider, ["default_templates", policy_type]) - template = - if default_id do - Enum.find(templates, &(&1["template_id"] == default_id)) - else - Enum.find(templates, &(&1["active"] == true)) - end +# template = +# if default_id do +# Enum.find(templates, &(&1["template_id"] == default_id)) +# else +# Enum.find(templates, &(&1["active"] == true)) +# end - case template do - nil -> {:error, :no_active_template} - t -> {:ok, t} - end - end +# case template do +# nil -> {:error, :no_active_template} +# t -> {:ok, t} +# end +# end - defp generate_solicitation(event, template) do - url = "#{@solicitation_service_url}/api/solicitations/generate" +# defp generate_solicitation(event, template) do +# url = "#{@solicitation_service_url}/api/solicitations/generate" - body = %{ - org_id: event["org_id"], - id: event["id"], - template_document_url: template["document_url"], - fields: event["fields"] || %{} - } +# body = %{ +# org_id: event["org_id"], +# id: event["id"], +# template_document_url: template["document_url"], +# fields: event["fields"] || %{} +# } - case Req.post(url, json: body) do - {:ok, %{status: 200, body: result}} -> - {:ok, result} +# case Req.post(url, json: body) do +# {:ok, %{status: 200, body: result}} -> +# {:ok, result} - {:ok, %{status: status, body: body}} -> - {:error, "solicitation_service returned #{status}: #{inspect(body)}"} +# {:ok, %{status: status, body: body}} -> +# {:error, "solicitation_service returned #{status}: #{inspect(body)}"} - {:error, reason} -> - {:error, reason} - end - end +# {:error, reason} -> +# {:error, reason} +# end +# end - defp create_task(event, result) do - org_id = event["org_id"] - task_id = WorkloadService.Aggregates.TaskId.new(org_id, "solicitation", Ecto.UUID.generate()) +# defp create_task(event, result) do +# org_id = event["org_id"] +# task_id = WorkloadService.Aggregates.TaskId.new(org_id, "solicitation", Ecto.UUID.generate()) - command = %SolicitationTask.CreateTask{ - id: task_id, - application_id: event["id"], - provider_id: event["provider_id"], - provider_name: event["provider_name"] || "", - task_info: %{ - "quote" => event["quote"], - "plan" => event["plan"] - }, - attachments: [result["document_url"]] |> Enum.filter(& &1) - } +# command = %SolicitationTask.CreateTask{ +# id: task_id, +# application_id: event["id"], +# provider_id: event["provider_id"], +# provider_name: event["provider_name"] || "", +# task_info: %{ +# "quote" => event["quote"], +# "plan" => event["plan"] +# }, +# attachments: [result["document_url"]] |> Enum.filter(& &1) +# } - case CommandedApp.dispatch(command) do - :ok -> - Logger.info("SolicitationRequestedConsumer: created task #{task_id}") - :ok +# case CommandedApp.dispatch(command) do +# :ok -> +# Logger.info("SolicitationRequestedConsumer: created task #{task_id}") +# :ok - {:error, reason} -> - {:error, reason} - end - end +# {:error, reason} -> +# {:error, reason} +# end +# end - defp amqp_url do - Application.get_env(:workload_service, :amqp_url, "amqp://guest:guest@localhost:5672") - end -end +# defp amqp_url do +# Application.get_env(:workload_service, :amqp_url, "amqp://guest:guest@localhost:5672") +# end +# end diff --git a/lib/workload_service/events/task.ex b/lib/workload_service/events/task.ex index c01a709..72a3ecf 100644 --- a/lib/workload_service/events/task.ex +++ b/lib/workload_service/events/task.ex @@ -9,7 +9,7 @@ defmodule WorkloadService.Events do ID format: "org_id:type:task_id" (e.g., "test:quote:uuid") - TaskId struct """ @derive Jason.Encoder - defstruct [:id, :application_id, :provider_id, :provider_name, :task_info, :attachments] + defstruct [:id, :application_id, :task_info, :attachments] end defmodule SubmissionUpdated do @@ -36,12 +36,4 @@ defmodule WorkloadService.Events do @derive Jason.Encoder defstruct [:id, :completed_by] end - - defmodule QuoteAccepted do - @moduledoc """ - Emitted when a quote is accepted - triggers solicitation generation. - """ - @derive Jason.Encoder - defstruct [:id, :provider_id, :quote_id, :plan_id] - end -end \ No newline at end of file +end diff --git a/lib/workload_service/projections/task.ex b/lib/workload_service/projections/task.ex index 4fda04d..b87eed1 100644 --- a/lib/workload_service/projections/task.ex +++ b/lib/workload_service/projections/task.ex @@ -8,15 +8,13 @@ defmodule WorkloadService.Projections.Task do import Ecto.Changeset @derive {Flop.Schema, - filterable: [:status, :org_id, :application_id, :provider_id], + filterable: [:status, :org_id, :application_id], sortable: [:inserted_at, :updated_at, :status]} @primary_key {:id, :string, []} schema "tasks" do field(:org_id, :string) field(:application_id, :string) - field(:provider_id, :string) - field(:provider_name, :string) field(:task_info, :map) field(:submission, :map) field(:attachments, {:array, :string}) @@ -40,8 +38,6 @@ defmodule WorkloadService.Projections.Task do :id, :org_id, :application_id, - :provider_id, - :provider_name, :task_info, :submission, :attachments, @@ -52,8 +48,6 @@ defmodule WorkloadService.Projections.Task do :id, :org_id, :application_id, - :provider_id, - :provider_name, :status ]) |> validate_inclusion(:status, @statuses) diff --git a/lib/workload_service/projectors/task_projector.ex b/lib/workload_service/projectors/task_projector.ex index 1f288bc..118422d 100644 --- a/lib/workload_service/projectors/task_projector.ex +++ b/lib/workload_service/projectors/task_projector.ex @@ -17,8 +17,6 @@ defmodule WorkloadService.Projectors.TaskProjector do id: to_string(e.id), org_id: org_id, application_id: e.application_id, - provider_id: e.provider_id, - provider_name: e.provider_name, task_info: e.task_info, attachments: e.attachments || [], status: "created" diff --git a/lib/workload_service_web/controllers/task_controller.ex b/lib/workload_service_web/controllers/task_controller.ex index 0126dbc..179a582 100644 --- a/lib/workload_service_web/controllers/task_controller.ex +++ b/lib/workload_service_web/controllers/task_controller.ex @@ -14,10 +14,8 @@ defmodule WorkloadServiceWeb.TaskController do page: [in: :query, type: :integer, required: false, example: 1], page_size: [in: :query, type: :integer, required: false, example: 20], status: [in: :query, type: :string, required: false], - policy_type: [in: :query, type: :string, required: false], org_id: [in: :query, type: :string, required: false], - application_id: [in: :query, type: :string, required: false], - provider_id: [in: :query, type: :string, required: false] + application_id: [in: :query, type: :string, required: false] ], responses: [ ok: {"Task list", "application/json", S.TaskListResponse}, @@ -61,20 +59,35 @@ defmodule WorkloadServiceWeb.TaskController do end end - operation(:respond_to_quote, - summary: "Record quote response", + operation(:submit, + summary: "Submit task data (quote response or confirm delivery)", parameters: [ id: [in: :path, type: :string, required: true] ], - request_body: {"Quote response", "application/json", S.QuoteResponseRequest, required: true}, + request_body: {"Task submission", "application/json", S.SubmitRequest, required: true}, responses: [ - ok: {"Quote response recorded", "application/json", S.TaskDetailResponse}, + ok: {"Task submitted", "application/json", S.TaskDetailResponse}, not_found: {"Not found", "application/json", S.ErrorResponse}, unprocessable_entity: {"Error", "application/json", S.ErrorResponse} ] ) - def respond_to_quote(conn, %{"id" => id} = params) do + 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"}) @@ -86,70 +99,20 @@ defmodule WorkloadServiceWeb.TaskController do "quote_id" => params["quote_id"], "plans" => params["plans"], "valid_until" => params["valid_until"], - "responded_by" => params["responded_by"], + "recorded_by" => params["recorded_by"], "document_data" => params["document_data"] }, - attachments: [params["document_url"]] |> Enum.filter(& &1) + attachments: List.wrap(params["document_url"]) } - 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 - - {:ok, %{status: "draft"} = _task} -> - command = %WorkloadService.Commands.QuoteTask.ApproveSubmission{ - id: id - } - - 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 - - {:ok, %{status: "approved"} = _task} -> - command = %WorkloadService.Commands.QuoteTask.CompleteTask{ - id: id, - completed_by: params["completed_by"] || "system" - } - - 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 + dispatch_and_respond(conn, id, command) {:ok, _task} -> - conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid state"}) + conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid state for submit"}) end end - operation(:confirm_delivery, - summary: "Confirm solicitation delivery", - parameters: [ - id: [in: :path, type: :string, required: true] - ], - request_body: - {"Delivery confirmation", "application/json", S.ConfirmDeliveryRequest, required: false}, - responses: [ - ok: {"Delivery confirmed", "application/json", S.TaskDetailResponse}, - not_found: {"Not found", "application/json", S.ErrorResponse}, - unprocessable_entity: {"Error", "application/json", S.ErrorResponse} - ] - ) - - def confirm_delivery(conn, %{"id" => id} = params) do + 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"}) @@ -158,51 +121,122 @@ defmodule WorkloadServiceWeb.TaskController do command = %WorkloadService.Commands.SolicitationTask.SubmitResponse{ id: id, submission: %{ - "delivery_confirmed_by" => params["delivery_confirmed_by"] || "system" + "recorded_by" => params["recorded_by"] || "system" }, attachments: [] } - 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 - - {:ok, %{status: "draft"} = _task} -> - command = %WorkloadService.Commands.SolicitationTask.ApproveSubmission{ - id: id - } - - 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 - - {:ok, %{status: "approved"} = _task} -> - command = %WorkloadService.Commands.SolicitationTask.CompleteTask{ - id: id, - completed_by: params["completed_by"] || "system" - } - - 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 + dispatch_and_respond(conn, id, command) {:ok, _task} -> - conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid state"}) + 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} -> + command = struct(command_module, id: 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} -> + command = struct(command_module, id: 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 @@ -211,8 +245,6 @@ defmodule WorkloadServiceWeb.TaskController do id: t.id, org_id: t.org_id, application_id: t.application_id, - provider_id: t.provider_id, - provider_name: t.provider_name, task_info: t.task_info, status: t.status, created_at: t.inserted_at @@ -224,8 +256,6 @@ defmodule WorkloadServiceWeb.TaskController do id: t.id, org_id: t.org_id, application_id: t.application_id, - provider_id: t.provider_id, - provider_name: t.provider_name, task_info: t.task_info, submission: t.submission, attachments: t.attachments, @@ -246,4 +276,4 @@ defmodule WorkloadServiceWeb.TaskController do has_prev: meta.has_previous_page? } end -end \ No newline at end of file +end diff --git a/lib/workload_service_web/router.ex b/lib/workload_service_web/router.ex index eb002c6..3eb4844 100644 --- a/lib/workload_service_web/router.ex +++ b/lib/workload_service_web/router.ex @@ -19,8 +19,9 @@ defmodule WorkloadServiceWeb.Router do scope "/v1" do get "/tasks", TaskController, :list get "/tasks/:id", TaskController, :show - post "/tasks/:id/respond-quote", TaskController, :respond_to_quote - post "/tasks/:id/confirm-delivery", TaskController, :confirm_delivery + post "/tasks/:id/submit", TaskController, :submit + post "/tasks/:id/approve", TaskController, :approve + post "/tasks/:id/complete", TaskController, :complete end end @@ -29,4 +30,4 @@ defmodule WorkloadServiceWeb.Router do get "/", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi" end end -end +end \ No newline at end of file diff --git a/lib/workload_service_web/schemas/task.ex b/lib/workload_service_web/schemas/task.ex index f98db2d..3fa33da 100644 --- a/lib/workload_service_web/schemas/task.ex +++ b/lib/workload_service_web/schemas/task.ex @@ -26,11 +26,40 @@ defmodule WorkloadServiceWeb.Schemas.Task do type: :object, properties: %{ plan_id: %Schema{type: :string}, - plan_name: %Schema{type: :string}, + name: %Schema{type: :string}, premium: %Schema{type: :number}, - coverage_details: %Schema{type: :string}, - deductible: %Schema{type: :number, nullable: true}, - coverage_limit: %Schema{type: :number, nullable: true} + coverage_details: %Schema{type: :object, additionalProperties: true} + } + }) + end + + defmodule QuoteSubmission do + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "QuoteSubmission", + type: :object, + required: [:recorded_by, :quote_id], + properties: %{ + recorded_by: %Schema{type: :string}, + quote_id: %Schema{type: :string}, + valid_until: %Schema{type: :string, format: :date}, + plans: %Schema{type: :array, items: Plan}, + document_url: %Schema{type: :string}, + document_data: %Schema{type: :object, additionalProperties: true} + } + }) + end + + defmodule SolicitationSubmission do + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "SolicitationSubmission", + type: :object, + required: [:recorded_by], + properties: %{ + recorded_by: %Schema{type: :string} } }) end @@ -45,8 +74,6 @@ defmodule WorkloadServiceWeb.Schemas.Task do id: %Schema{type: :string}, org_id: %Schema{type: :string}, application_id: %Schema{type: :string}, - provider_id: %Schema{type: :string}, - provider_name: %Schema{type: :string}, task_info: %Schema{type: :object}, status: %Schema{type: :string, enum: ["created", "draft", "approved", "completed"]}, created_at: %Schema{type: :string, format: :"date-time"} @@ -64,8 +91,6 @@ defmodule WorkloadServiceWeb.Schemas.Task do id: %Schema{type: :string}, org_id: %Schema{type: :string}, application_id: %Schema{type: :string}, - provider_id: %Schema{type: :string}, - provider_name: %Schema{type: :string}, task_info: %Schema{type: :object}, submission: %Schema{type: :object, nullable: true}, attachments: %Schema{type: :array, items: %Schema{type: :string}}, @@ -77,32 +102,24 @@ defmodule WorkloadServiceWeb.Schemas.Task do }) end - defmodule QuoteResponseRequest do + defmodule SubmitRequest do require OpenApiSpex OpenApiSpex.schema(%{ - title: "QuoteResponseRequest", + title: "SubmitRequest", type: :object, - required: [:quote_id, :document_url], - properties: %{ - quote_id: %Schema{type: :string}, - plans: %Schema{type: :array, items: Plan}, - valid_until: %Schema{type: :string, format: :date}, - responded_by: %Schema{type: :string}, - document_url: %Schema{type: :string}, - document_data: %Schema{type: :object, additionalProperties: true} - } + anyOf: [QuoteSubmission, SolicitationSubmission] }) end - defmodule ConfirmDeliveryRequest do + defmodule CompleteRequest do require OpenApiSpex OpenApiSpex.schema(%{ - title: "ConfirmDeliveryRequest", + title: "CompleteRequest", type: :object, properties: %{ - delivery_confirmed_by: %Schema{type: :string} + completed_by: %Schema{type: :string} } }) end @@ -143,4 +160,4 @@ defmodule WorkloadServiceWeb.Schemas.Task do } }) end -end \ No newline at end of file +end diff --git a/priv/repo/migrations/20240101000001_create_tasks.exs b/priv/repo/migrations/20240101000001_create_tasks.exs index 46c0c23..3935424 100644 --- a/priv/repo/migrations/20240101000001_create_tasks.exs +++ b/priv/repo/migrations/20240101000001_create_tasks.exs @@ -3,23 +3,20 @@ defmodule WorkloadService.Repo.Migrations.CreateTasks do def change do create table(:tasks, primary_key: false) do - add :id, :string, primary_key: true - add :org_id, :string, null: false - add :application_id, :string, null: false - add :provider_id, :string, null: false - add :provider_name, :string - add :task_info, :map, default: %{} - add :submission, :map - add :attachments, {:array, :string}, default: [] - add :status, :string, null: false, default: "created" - add :version, :integer, default: 1 + add(:id, :string, primary_key: true) + add(:org_id, :string, null: false) + add(:application_id, :string, null: false) + add(:task_info, :map, default: %{}) + add(:submission, :map) + add(:attachments, {:array, :string}, default: []) + add(:status, :string, null: false, default: "created") + add(:version, :integer, default: 1) - timestamps type: :utc_datetime + timestamps(type: :utc_datetime) end - create index(:tasks, [:application_id]) - create index(:tasks, [:org_id]) - create index(:tasks, [:provider_id]) - create index(:tasks, [:status]) + create(index(:tasks, [:application_id])) + create(index(:tasks, [:org_id])) + create(index(:tasks, [:status])) end -end \ No newline at end of file +end