defmodule CustomerServiceWeb.CustomerController do use CustomerServiceWeb, :controller use OpenApiSpex.ControllerSpecs alias CustomerService.Commands.{ CreateCustomer, CreateCorporateCustomer, UpdateCustomer, UpdateCorporateCustomer } alias CustomerService.Customer.Queries, as: CustomerQueries alias CustomerServiceWeb.Schemas.Customer, as: CustomerSchemas alias CustomerServiceWeb.QueryHelpers alias CustomerService.Aggregates.CustomerId operation(:index, summary: "List customers", parameters: QueryHelpers.flop([:customer_type, :email, :phone, :document_id, :ruc, :search], [ :last_name, :legal_name, :inserted_at ]), responses: [ ok: {"Customer list", "application/json", CustomerSchemas.CustomerListResponse} ] ) def index(conn, params) do org_id = conn.private[CustomerServiceWeb.Plugs.ExtractOrganizationId] case CustomerQueries.list_by_org(org_id, params) do {:ok, {customers, meta}} -> conn |> put_status(:ok) |> json(%{ data: Enum.map(customers, &customer_json/1), meta: %{ 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? } }) {:error, _meta} -> conn |> put_status(:bad_request) |> json(%{error: "invalid parameters"}) end end operation(:show, summary: "Get customer", parameters: [ id: [in: :path, type: :string, required: true, description: "Customer ID"] ], responses: [ ok: {"Customer", "application/json", CustomerSchemas.CustomerResponse}, not_found: {"Not found", "application/json", %OpenApiSpex.Schema{type: :object}} ] ) def show(conn, %{"id" => id}) do org_id = conn.private[CustomerServiceWeb.Plugs.ExtractOrganizationId] with {:ok, %CustomerId{customer_id: local_id}} <- CustomerId.parse(id), {:ok, customer} <- CustomerQueries.get_by_org(org_id, local_id) do conn |> put_status(:ok) |> json(%{data: customer_json(customer)}) else {:error, :not_found} -> conn |> put_status(:not_found) |> json(%{error: "not found"}) :error -> conn |> put_status(:bad_request) |> json(%{error: "invalid customer id"}) end end operation(:create, summary: "Create individual customer", request_body: {"Customer data", "application/json", CustomerSchemas.CreateCustomer, required: true}, responses: [ ok: {"Customer created", "application/json", CustomerSchemas.CustomerResponse} ] ) def create(conn, params) do org_id = conn.private[CustomerServiceWeb.Plugs.ExtractOrganizationId] customer_type = "individual" customer_uuid = Ecto.UUID.generate() customer_id = CustomerId.new(org_id, customer_type, customer_uuid) command = %CreateCustomer{ id: customer_id, first_name: params["first_name"], last_name: params["last_name"], birth_date: parse_date(params["birth_date"]), gender: params["gender"], email: params["email"], phone: params["phone"], document_id: params["document_id"] } dispatch_and_return(conn, command, customer_id) end operation(:create_corporate, summary: "Create corporate customer", request_body: {"Corporate customer data", "application/json", CustomerSchemas.CreateCorporateCustomer, required: true}, responses: [ ok: {"Corporate customer created", "application/json", CustomerSchemas.CustomerResponse} ] ) def create_corporate(conn, params) do org_id = conn.private[CustomerServiceWeb.Plugs.ExtractOrganizationId] customer_type = "corporate" customer_uuid = Ecto.UUID.generate() customer_id = CustomerId.new(org_id, customer_type, customer_uuid) command = %CreateCorporateCustomer{ id: customer_id, legal_name: params["legal_name"], commercial_name: params["commercial_name"], ruc: params["ruc"], legal_rep_name: params["legal_rep_name"], legal_rep_document_id: params["legal_rep_document_id"], email: params["email"], phone: params["phone"], address: params["address"] } dispatch_and_return(conn, command, customer_id) end operation(:update, summary: "Update individual customer", parameters: [ id: [in: :path, type: :string, required: true, description: "Customer ID"] ], request_body: {"Customer data", "application/json", CustomerSchemas.UpdateCustomer, required: true}, responses: [ ok: {"Customer updated", "application/json", CustomerSchemas.CustomerResponse} ] ) def update(conn, %{"id" => id} = params) do org_id = conn.private[CustomerServiceWeb.Plugs.ExtractOrganizationId] with {:ok, %CustomerId{customer_id: local_id}} <- CustomerId.parse(id), {:ok, _customer} <- CustomerQueries.get_by_org(org_id, local_id) do command = %UpdateCustomer{ id: CustomerId.new(org_id, "individual", local_id), first_name: params["first_name"], last_name: params["last_name"], birth_date: parse_date(params["birth_date"]), gender: params["gender"], email: params["email"], phone: params["phone"], document_id: params["document_id"], address: params["address"] } dispatch_and_return(conn, command, CustomerId.new(org_id, "individual", local_id)) else {:error, :not_found} -> conn |> put_status(:not_found) |> json(%{error: "not found"}) :error -> conn |> put_status(:bad_request) |> json(%{error: "invalid customer id"}) end end operation(:update_corporate, summary: "Update corporate customer", parameters: [ id: [in: :path, type: :string, required: true, description: "Customer ID"] ], request_body: {"Corporate customer data", "application/json", CustomerSchemas.UpdateCorporateCustomer, required: true}, responses: [ ok: {"Corporate customer updated", "application/json", CustomerSchemas.CustomerResponse} ] ) def update_corporate(conn, %{"id" => id} = params) do org_id = conn.private[CustomerServiceWeb.Plugs.ExtractOrganizationId] with {:ok, %CustomerId{customer_id: local_id}} <- CustomerId.parse(id), {:ok, _customer} <- CustomerQueries.get_by_org(org_id, local_id) do command = %UpdateCorporateCustomer{ id: CustomerId.new(org_id, "corporate", local_id), legal_name: params["legal_name"], commercial_name: params["commercial_name"], ruc: params["ruc"], legal_rep_name: params["legal_rep_name"], legal_rep_document_id: params["legal_rep_document_id"], email: params["email"], phone: params["phone"], address: params["address"] } dispatch_and_return(conn, command, CustomerId.new(org_id, "corporate", local_id)) else {:error, :not_found} -> conn |> put_status(:not_found) |> json(%{error: "not found"}) :error -> conn |> put_status(:bad_request) |> json(%{error: "invalid customer id"}) end end defp dispatch_and_return(conn, command, %CustomerId{org_id: org_id} = customer_id) do case CustomerService.CommandedApp.dispatch(command, consistency: :strong) do :ok -> case CustomerQueries.get_by_org(org_id, to_string(customer_id)) do {:ok, customer} -> conn |> put_status(:ok) |> json(%{data: customer_json(customer)}) {:error, :not_found} -> conn |> put_status(:internal_server_error) |> json(%{error: "customer created but not found in projection"}) end {:error, reason} -> conn |> put_status(:unprocessable_entity) |> json(%{error: inspect(reason)}) end end defp parse_date(nil), do: nil defp parse_date(str) do case Date.from_iso8601(str) do {:ok, date} -> date _ -> nil end end defp customer_json(%{customer_type: "corporate"} = c) do %{ id: c.id, customer_type: "corporate", legal_name: c.legal_name, commercial_name: c.commercial_name, ruc: c.ruc, legal_rep_name: c.legal_rep_name, legal_rep_document_id: c.legal_rep_document_id, email: c.email, phone: c.phone, address: c.address } end defp customer_json(c) do %{ id: c.id, customer_type: "individual", first_name: c.first_name, last_name: c.last_name, email: c.email, phone: c.phone, birth_date: c.birth_date, gender: c.gender, document_id: c.document_id } end end