simplify task api
All checks were successful
Build and Publish / build-release (push) Successful in 1m23s

This commit is contained in:
2026-04-17 11:42:19 -05:00
parent 202538e844
commit 01ad2270bc
13 changed files with 320 additions and 301 deletions

View File

@@ -30,8 +30,6 @@ defmodule WorkloadService.Aggregates.Task do
defstruct [ defstruct [
:id, :id,
:application_id, :application_id,
:provider_id,
:provider_name,
:task_info, :task_info,
:submission, :submission,
:attachments, :attachments,
@@ -44,9 +42,7 @@ defmodule WorkloadService.Aggregates.Task do
%WorkloadService.Events.TaskCreated{ %WorkloadService.Events.TaskCreated{
id: cmd.id, id: cmd.id,
application_id: cmd.application_id, application_id: cmd.application_id,
provider_id: cmd.provider_id, task_info: cmd.task_info,
provider_name: cmd.provider_name,
task_info: cmd.task_info || %{},
attachments: cmd.attachments || [] attachments: cmd.attachments || []
} }
end end
@@ -71,9 +67,9 @@ defmodule WorkloadService.Aggregates.Task do
end end
@impl Aggregate @impl Aggregate
def execute(%__MODULE__{status: "draft"}, %ApproveSubmission{}) do def execute(%__MODULE__{id: id, status: "draft"}, %ApproveSubmission{}) do
%WorkloadService.Events.SubmissionApproved{ %WorkloadService.Events.SubmissionApproved{
id: nil id: id
} }
end end
@@ -101,8 +97,6 @@ defmodule WorkloadService.Aggregates.Task do
agg agg
| id: e.id, | id: e.id,
application_id: e.application_id, application_id: e.application_id,
provider_id: e.provider_id,
provider_name: e.provider_name,
task_info: e.task_info, task_info: e.task_info,
attachments: e.attachments, attachments: e.attachments,
status: "created", status: "created",

View File

@@ -8,7 +8,7 @@ defmodule WorkloadService.Application do
children = [ children = [
WorkloadService.CommandedApp, WorkloadService.CommandedApp,
WorkloadService.Consumers.QuoteRequestedConsumer, WorkloadService.Consumers.QuoteRequestedConsumer,
WorkloadService.Consumers.SolicitationRequestedConsumer, # WorkloadService.Consumers.SolicitationRequestedConsumer,
WorkloadService.Projectors.TaskProjector, WorkloadService.Projectors.TaskProjector,
WorkloadService.Repo, WorkloadService.Repo,
WorkloadServiceWeb.Telemetry, WorkloadServiceWeb.Telemetry,

View File

@@ -8,7 +8,7 @@ defmodule WorkloadService.Commands.QuoteTask do
Command to create a new quote task. Command to create a new quote task.
""" """
@derive Jason.Encoder @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 def new(attrs) do
struct(__MODULE__, attrs) struct(__MODULE__, attrs)

View File

@@ -8,7 +8,7 @@ defmodule WorkloadService.Commands.SolicitationTask do
Command to create a new solicitation task. Command to create a new solicitation task.
""" """
@derive Jason.Encoder @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 def new(attrs) do
struct(__MODULE__, attrs) struct(__MODULE__, attrs)

View File

@@ -52,26 +52,22 @@ defmodule WorkloadService.Consumers.QuoteRequestedConsumer do
defp handle_event( defp handle_event(
%{ %{
"id" => application_id, "id" => %{"org_id" => org_id} = application_id,
"org_id" => org_id,
"provider_id" => provider_id, "provider_id" => provider_id,
"policy_type" => policy_type "policy_details" => policy_details,
"applicant_info" => applicant_info
} = event } = event
) do ) do
task_id = WorkloadService.Aggregates.TaskId.new(org_id, "quote", Ecto.UUID.generate()) task_id = WorkloadService.Aggregates.TaskId.new(org_id, "quote", Ecto.UUID.generate())
command = %QuoteTask.CreateTask{ command = %QuoteTask.CreateTask{
id: task_id, id: task_id,
application_id: id, application_id: application_id,
provider_id: provider_id,
provider_name: Map.get(event, "provider_name", ""),
task_info: %{ task_info: %{
"policy_type" => policy_type, "provider_id" => provider_id,
"provider_email" => Map.get(event, "provider_email"), "applicant_info" => applicant_info,
"applicant_info" => Map.get(event, "applicant_info", %{}), "policy_details" => policy_details,
"car_details" => Map.get(event, "car_details", %{}), "provider_email" => Map.get(event, "provider_email")
"building_details" => Map.get(event, "building_details", %{}),
"life_details" => Map.get(event, "life_details", %{})
} }
} }

View File

@@ -1,137 +1,137 @@
defmodule WorkloadService.Consumers.SolicitationRequestedConsumer do # defmodule WorkloadService.Consumers.SolicitationRequestedConsumer do
use GenServer # use GenServer
require Logger # require Logger
alias WorkloadService.CommandedApp # alias WorkloadService.CommandedApp
alias WorkloadService.Commands.SolicitationTask # alias WorkloadService.Commands.SolicitationTask
@exchange "workload_service.events.solicitation_requested" # @exchange "workload_service.events.solicitation_requested"
@queue "workload_service.solicitation_requested" # @queue "workload_service.solicitation_requested"
@routing_key "solicitation.requested" # @routing_key "solicitation.requested"
@provider_service_url "http://localhost:4002" # @provider_service_url "http://localhost:4002"
@solicitation_service_url "http://localhost:8081" # @solicitation_service_url "http://localhost:8081"
def start_link(opts \\ []) do # def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__) # GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end # end
def init(_opts) do # def init(_opts) do
{:ok, conn} = AMQP.Connection.open(amqp_url()) # {:ok, conn} = AMQP.Connection.open(amqp_url())
{:ok, channel} = AMQP.Channel.open(conn) # {:ok, channel} = AMQP.Channel.open(conn)
AMQP.Queue.declare(channel, @queue, durable: true) # AMQP.Queue.declare(channel, @queue, durable: true)
AMQP.Queue.bind(channel, @queue, @exchange, routing_key: @routing_key) # AMQP.Queue.bind(channel, @queue, @exchange, routing_key: @routing_key)
{:ok, _tag} = AMQP.Basic.consume(channel, @queue) # {:ok, _tag} = AMQP.Basic.consume(channel, @queue)
Logger.info("SolicitationRequestedConsumer started, listening on #{@queue}") # Logger.info("SolicitationRequestedConsumer started, listening on #{@queue}")
{:ok, %{channel: channel}} # {:ok, %{channel: channel}}
end # end
def handle_info({:basic_consume_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, _}, state), do: {:stop, :normal, state}
def handle_info({:basic_cancel_ok, _}, state), do: {:noreply, state} # def handle_info({:basic_cancel_ok, _}, state), do: {:noreply, state}
def handle_info({:basic_deliver, payload, meta}, state) do # def handle_info({:basic_deliver, payload, meta}, state) do
case process(payload) do # case process(payload) do
:ok -> # :ok ->
AMQP.Basic.ack(state.channel, meta.delivery_tag) # AMQP.Basic.ack(state.channel, meta.delivery_tag)
{:error, reason} -> # {:error, reason} ->
Logger.error("SolicitationRequestedConsumer: failed to process: #{inspect(reason)}") # Logger.error("SolicitationRequestedConsumer: failed to process: #{inspect(reason)}")
AMQP.Basic.reject(state.channel, meta.delivery_tag, requeue: false) # AMQP.Basic.reject(state.channel, meta.delivery_tag, requeue: false)
end # end
{:noreply, state} # {:noreply, state}
end # end
defp process(payload) do # defp process(payload) do
with {:ok, event} <- Jason.decode(payload), # with {:ok, event} <- Jason.decode(payload),
{:ok, provider} <- get_provider(event["provider_id"]), # {:ok, provider} <- get_provider(event["provider_id"]),
{:ok, template} <- get_active_template(provider, event["policy_type"]), # {:ok, template} <- get_active_template(provider, event["policy_type"]),
{:ok, result} <- generate_solicitation(event, template), # {:ok, result} <- generate_solicitation(event, template),
:ok <- create_task(event, result) do # :ok <- create_task(event, result) do
:ok # :ok
end # end
end # end
defp get_provider(provider_id) do # defp get_provider(provider_id) do
url = "#{@provider_service_url}/api/v1/providers/#{provider_id}" # url = "#{@provider_service_url}/api/v1/providers/#{provider_id}"
case Req.get(url) do # case Req.get(url) do
{:ok, %{status: 200, body: %{"data" => provider}}} -> {:ok, provider} # {:ok, %{status: 200, body: %{"data" => provider}}} -> {:ok, provider}
{:ok, %{status: 404}} -> {:error, :provider_not_found} # {:ok, %{status: 404}} -> {:error, :provider_not_found}
{:error, reason} -> {:error, reason} # {:error, reason} -> {:error, reason}
end # end
end # end
defp get_active_template(provider, policy_type) do # defp get_active_template(provider, policy_type) do
templates = get_in(provider, ["templates", policy_type]) || [] # templates = get_in(provider, ["templates", policy_type]) || []
default_id = get_in(provider, ["default_templates", policy_type]) # default_id = get_in(provider, ["default_templates", policy_type])
template = # template =
if default_id do # if default_id do
Enum.find(templates, &(&1["template_id"] == default_id)) # Enum.find(templates, &(&1["template_id"] == default_id))
else # else
Enum.find(templates, &(&1["active"] == true)) # Enum.find(templates, &(&1["active"] == true))
end # end
case template do # case template do
nil -> {:error, :no_active_template} # nil -> {:error, :no_active_template}
t -> {:ok, t} # t -> {:ok, t}
end # end
end # end
defp generate_solicitation(event, template) do # defp generate_solicitation(event, template) do
url = "#{@solicitation_service_url}/api/solicitations/generate" # url = "#{@solicitation_service_url}/api/solicitations/generate"
body = %{ # body = %{
org_id: event["org_id"], # org_id: event["org_id"],
id: event["id"], # id: event["id"],
template_document_url: template["document_url"], # template_document_url: template["document_url"],
fields: event["fields"] || %{} # fields: event["fields"] || %{}
} # }
case Req.post(url, json: body) do # case Req.post(url, json: body) do
{:ok, %{status: 200, body: result}} -> # {:ok, %{status: 200, body: result}} ->
{:ok, result} # {:ok, result}
{:ok, %{status: status, body: body}} -> # {:ok, %{status: status, body: body}} ->
{:error, "solicitation_service returned #{status}: #{inspect(body)}"} # {:error, "solicitation_service returned #{status}: #{inspect(body)}"}
{:error, reason} -> # {:error, reason} ->
{:error, reason} # {:error, reason}
end # end
end # end
defp create_task(event, result) do # defp create_task(event, result) do
org_id = event["org_id"] # org_id = event["org_id"]
task_id = WorkloadService.Aggregates.TaskId.new(org_id, "solicitation", Ecto.UUID.generate()) # task_id = WorkloadService.Aggregates.TaskId.new(org_id, "solicitation", Ecto.UUID.generate())
command = %SolicitationTask.CreateTask{ # command = %SolicitationTask.CreateTask{
id: task_id, # id: task_id,
application_id: event["id"], # application_id: event["id"],
provider_id: event["provider_id"], # provider_id: event["provider_id"],
provider_name: event["provider_name"] || "", # provider_name: event["provider_name"] || "",
task_info: %{ # task_info: %{
"quote" => event["quote"], # "quote" => event["quote"],
"plan" => event["plan"] # "plan" => event["plan"]
}, # },
attachments: [result["document_url"]] |> Enum.filter(& &1) # attachments: [result["document_url"]] |> Enum.filter(& &1)
} # }
case CommandedApp.dispatch(command) do # case CommandedApp.dispatch(command) do
:ok -> # :ok ->
Logger.info("SolicitationRequestedConsumer: created task #{task_id}") # Logger.info("SolicitationRequestedConsumer: created task #{task_id}")
:ok # :ok
{:error, reason} -> # {:error, reason} ->
{:error, reason} # {:error, reason}
end # end
end # end
defp amqp_url do # defp amqp_url do
Application.get_env(:workload_service, :amqp_url, "amqp://guest:guest@localhost:5672") # Application.get_env(:workload_service, :amqp_url, "amqp://guest:guest@localhost:5672")
end # end
end # end

View File

@@ -9,7 +9,7 @@ defmodule WorkloadService.Events do
ID format: "org_id:type:task_id" (e.g., "test:quote:uuid") - TaskId struct ID format: "org_id:type:task_id" (e.g., "test:quote:uuid") - TaskId struct
""" """
@derive Jason.Encoder @derive Jason.Encoder
defstruct [:id, :application_id, :provider_id, :provider_name, :task_info, :attachments] defstruct [:id, :application_id, :task_info, :attachments]
end end
defmodule SubmissionUpdated do defmodule SubmissionUpdated do
@@ -36,12 +36,4 @@ defmodule WorkloadService.Events do
@derive Jason.Encoder @derive Jason.Encoder
defstruct [:id, :completed_by] defstruct [:id, :completed_by]
end end
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

View File

@@ -8,15 +8,13 @@ defmodule WorkloadService.Projections.Task do
import Ecto.Changeset import Ecto.Changeset
@derive {Flop.Schema, @derive {Flop.Schema,
filterable: [:status, :org_id, :application_id, :provider_id], filterable: [:status, :org_id, :application_id],
sortable: [:inserted_at, :updated_at, :status]} sortable: [:inserted_at, :updated_at, :status]}
@primary_key {:id, :string, []} @primary_key {:id, :string, []}
schema "tasks" do schema "tasks" do
field(:org_id, :string) field(:org_id, :string)
field(:application_id, :string) field(:application_id, :string)
field(:provider_id, :string)
field(:provider_name, :string)
field(:task_info, :map) field(:task_info, :map)
field(:submission, :map) field(:submission, :map)
field(:attachments, {:array, :string}) field(:attachments, {:array, :string})
@@ -40,8 +38,6 @@ defmodule WorkloadService.Projections.Task do
:id, :id,
:org_id, :org_id,
:application_id, :application_id,
:provider_id,
:provider_name,
:task_info, :task_info,
:submission, :submission,
:attachments, :attachments,
@@ -52,8 +48,6 @@ defmodule WorkloadService.Projections.Task do
:id, :id,
:org_id, :org_id,
:application_id, :application_id,
:provider_id,
:provider_name,
:status :status
]) ])
|> validate_inclusion(:status, @statuses) |> validate_inclusion(:status, @statuses)

View File

@@ -17,8 +17,6 @@ defmodule WorkloadService.Projectors.TaskProjector do
id: to_string(e.id), id: to_string(e.id),
org_id: org_id, org_id: org_id,
application_id: e.application_id, application_id: e.application_id,
provider_id: e.provider_id,
provider_name: e.provider_name,
task_info: e.task_info, task_info: e.task_info,
attachments: e.attachments || [], attachments: e.attachments || [],
status: "created" status: "created"

View File

@@ -14,10 +14,8 @@ defmodule WorkloadServiceWeb.TaskController do
page: [in: :query, type: :integer, required: false, example: 1], page: [in: :query, type: :integer, required: false, example: 1],
page_size: [in: :query, type: :integer, required: false, example: 20], page_size: [in: :query, type: :integer, required: false, example: 20],
status: [in: :query, type: :string, required: false], status: [in: :query, type: :string, required: false],
policy_type: [in: :query, type: :string, required: false],
org_id: [in: :query, type: :string, required: false], org_id: [in: :query, type: :string, required: false],
application_id: [in: :query, type: :string, required: false], application_id: [in: :query, type: :string, required: false]
provider_id: [in: :query, type: :string, required: false]
], ],
responses: [ responses: [
ok: {"Task list", "application/json", S.TaskListResponse}, ok: {"Task list", "application/json", S.TaskListResponse},
@@ -61,20 +59,35 @@ defmodule WorkloadServiceWeb.TaskController do
end end
end end
operation(:respond_to_quote, operation(:submit,
summary: "Record quote response", summary: "Submit task data (quote response or confirm delivery)",
parameters: [ parameters: [
id: [in: :path, type: :string, required: true] 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: [ responses: [
ok: {"Quote response recorded", "application/json", S.TaskDetailResponse}, ok: {"Task submitted", "application/json", S.TaskDetailResponse},
not_found: {"Not found", "application/json", S.ErrorResponse}, not_found: {"Not found", "application/json", S.ErrorResponse},
unprocessable_entity: {"Error", "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 case Queries.get_task_by_id(id) do
{:error, :not_found} -> {:error, :not_found} ->
conn |> put_status(:not_found) |> json(%{error: "task 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"], "quote_id" => params["quote_id"],
"plans" => params["plans"], "plans" => params["plans"],
"valid_until" => params["valid_until"], "valid_until" => params["valid_until"],
"responded_by" => params["responded_by"], "recorded_by" => params["recorded_by"],
"document_data" => params["document_data"] "document_data" => params["document_data"]
}, },
attachments: [params["document_url"]] |> Enum.filter(& &1) attachments: List.wrap(params["document_url"])
} }
case CommandedApp.dispatch(command) do dispatch_and_respond(conn, id, command)
: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
{:ok, _task} -> {:ok, _task} ->
conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid state"}) conn |> put_status(:unprocessable_entity) |> json(%{error: "invalid state for submit"})
end end
end end
operation(:confirm_delivery, defp handle_solicitation_submit(conn, id, params) do
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
case Queries.get_task_by_id(id) do case Queries.get_task_by_id(id) do
{:error, :not_found} -> {:error, :not_found} ->
conn |> put_status(:not_found) |> json(%{error: "task 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{ command = %WorkloadService.Commands.SolicitationTask.SubmitResponse{
id: id, id: id,
submission: %{ submission: %{
"delivery_confirmed_by" => params["delivery_confirmed_by"] || "system" "recorded_by" => params["recorded_by"] || "system"
}, },
attachments: [] attachments: []
} }
case CommandedApp.dispatch(command) do dispatch_and_respond(conn, id, command)
: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
{:ok, _task} -> {: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
end end
@@ -211,8 +245,6 @@ defmodule WorkloadServiceWeb.TaskController do
id: t.id, id: t.id,
org_id: t.org_id, org_id: t.org_id,
application_id: t.application_id, application_id: t.application_id,
provider_id: t.provider_id,
provider_name: t.provider_name,
task_info: t.task_info, task_info: t.task_info,
status: t.status, status: t.status,
created_at: t.inserted_at created_at: t.inserted_at
@@ -224,8 +256,6 @@ defmodule WorkloadServiceWeb.TaskController do
id: t.id, id: t.id,
org_id: t.org_id, org_id: t.org_id,
application_id: t.application_id, application_id: t.application_id,
provider_id: t.provider_id,
provider_name: t.provider_name,
task_info: t.task_info, task_info: t.task_info,
submission: t.submission, submission: t.submission,
attachments: t.attachments, attachments: t.attachments,
@@ -246,4 +276,4 @@ defmodule WorkloadServiceWeb.TaskController do
has_prev: meta.has_previous_page? has_prev: meta.has_previous_page?
} }
end end
end end

View File

@@ -19,8 +19,9 @@ defmodule WorkloadServiceWeb.Router do
scope "/v1" do scope "/v1" do
get "/tasks", TaskController, :list get "/tasks", TaskController, :list
get "/tasks/:id", TaskController, :show get "/tasks/:id", TaskController, :show
post "/tasks/:id/respond-quote", TaskController, :respond_to_quote post "/tasks/:id/submit", TaskController, :submit
post "/tasks/:id/confirm-delivery", TaskController, :confirm_delivery post "/tasks/:id/approve", TaskController, :approve
post "/tasks/:id/complete", TaskController, :complete
end end
end end
@@ -29,4 +30,4 @@ defmodule WorkloadServiceWeb.Router do
get "/", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi" get "/", OpenApiSpex.Plug.SwaggerUI, path: "/api/openapi"
end end
end end
end end

View File

@@ -26,11 +26,40 @@ defmodule WorkloadServiceWeb.Schemas.Task do
type: :object, type: :object,
properties: %{ properties: %{
plan_id: %Schema{type: :string}, plan_id: %Schema{type: :string},
plan_name: %Schema{type: :string}, name: %Schema{type: :string},
premium: %Schema{type: :number}, premium: %Schema{type: :number},
coverage_details: %Schema{type: :string}, coverage_details: %Schema{type: :object, additionalProperties: true}
deductible: %Schema{type: :number, nullable: true}, }
coverage_limit: %Schema{type: :number, nullable: 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 end
@@ -45,8 +74,6 @@ defmodule WorkloadServiceWeb.Schemas.Task do
id: %Schema{type: :string}, id: %Schema{type: :string},
org_id: %Schema{type: :string}, org_id: %Schema{type: :string},
application_id: %Schema{type: :string}, application_id: %Schema{type: :string},
provider_id: %Schema{type: :string},
provider_name: %Schema{type: :string},
task_info: %Schema{type: :object}, task_info: %Schema{type: :object},
status: %Schema{type: :string, enum: ["created", "draft", "approved", "completed"]}, status: %Schema{type: :string, enum: ["created", "draft", "approved", "completed"]},
created_at: %Schema{type: :string, format: :"date-time"} created_at: %Schema{type: :string, format: :"date-time"}
@@ -64,8 +91,6 @@ defmodule WorkloadServiceWeb.Schemas.Task do
id: %Schema{type: :string}, id: %Schema{type: :string},
org_id: %Schema{type: :string}, org_id: %Schema{type: :string},
application_id: %Schema{type: :string}, application_id: %Schema{type: :string},
provider_id: %Schema{type: :string},
provider_name: %Schema{type: :string},
task_info: %Schema{type: :object}, task_info: %Schema{type: :object},
submission: %Schema{type: :object, nullable: true}, submission: %Schema{type: :object, nullable: true},
attachments: %Schema{type: :array, items: %Schema{type: :string}}, attachments: %Schema{type: :array, items: %Schema{type: :string}},
@@ -77,32 +102,24 @@ defmodule WorkloadServiceWeb.Schemas.Task do
}) })
end end
defmodule QuoteResponseRequest do defmodule SubmitRequest do
require OpenApiSpex require OpenApiSpex
OpenApiSpex.schema(%{ OpenApiSpex.schema(%{
title: "QuoteResponseRequest", title: "SubmitRequest",
type: :object, type: :object,
required: [:quote_id, :document_url], anyOf: [QuoteSubmission, SolicitationSubmission]
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}
}
}) })
end end
defmodule ConfirmDeliveryRequest do defmodule CompleteRequest do
require OpenApiSpex require OpenApiSpex
OpenApiSpex.schema(%{ OpenApiSpex.schema(%{
title: "ConfirmDeliveryRequest", title: "CompleteRequest",
type: :object, type: :object,
properties: %{ properties: %{
delivery_confirmed_by: %Schema{type: :string} completed_by: %Schema{type: :string}
} }
}) })
end end
@@ -143,4 +160,4 @@ defmodule WorkloadServiceWeb.Schemas.Task do
} }
}) })
end end
end end

View File

@@ -3,23 +3,20 @@ defmodule WorkloadService.Repo.Migrations.CreateTasks do
def change do def change do
create table(:tasks, primary_key: false) do create table(:tasks, primary_key: false) do
add :id, :string, primary_key: true add(:id, :string, primary_key: true)
add :org_id, :string, null: false add(:org_id, :string, null: false)
add :application_id, :string, null: false add(:application_id, :string, null: false)
add :provider_id, :string, null: false add(:task_info, :map, default: %{})
add :provider_name, :string add(:submission, :map)
add :task_info, :map, default: %{} add(:attachments, {:array, :string}, default: [])
add :submission, :map add(:status, :string, null: false, default: "created")
add :attachments, {:array, :string}, default: [] add(:version, :integer, default: 1)
add :status, :string, null: false, default: "created"
add :version, :integer, default: 1
timestamps type: :utc_datetime timestamps(type: :utc_datetime)
end end
create index(:tasks, [:application_id]) create(index(:tasks, [:application_id]))
create index(:tasks, [:org_id]) create(index(:tasks, [:org_id]))
create index(:tasks, [:provider_id]) create(index(:tasks, [:status]))
create index(:tasks, [:status])
end end
end end