From dcb5f364c2c7fde9c2fef37403fecb8a5ef11278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Wed, 20 Jan 2021 17:28:48 +0100 Subject: [PATCH] Add initial Odoo integration (billing address) --- config/dev.exs | 7 ++ lib/recycledcloud/accounts/user.ex | 1 + lib/recycledcloud/billing/partner.ex | 93 +++++++++++++++++++ .../controllers/billing_controller.ex | 33 +++++++ lib/recycledcloud_web/router.ex | 2 + .../templates/billing/index.html.eex | 35 +++++++ .../templates/layout/_sidebar_nav.html.eex | 1 + lib/recycledcloud_web/views/billing_view.ex | 3 + .../20210120141029_add_user_partner_id.exs | 9 ++ priv/static/css/app.css | 4 + 10 files changed, 188 insertions(+) create mode 100644 lib/recycledcloud/billing/partner.ex create mode 100644 lib/recycledcloud_web/controllers/billing_controller.ex create mode 100644 lib/recycledcloud_web/templates/billing/index.html.eex create mode 100644 lib/recycledcloud_web/views/billing_view.ex create mode 100644 priv/repo/migrations/20210120141029_add_user_partner_id.exs diff --git a/config/dev.exs b/config/dev.exs index 1d49686..b41d77d 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -80,3 +80,10 @@ config :recycledcloud, :ldap, bind_dn: "cn=admin,dc=example,dc=org", bind_pw: "admin", default_group_gid: 1000 + +## Odoo API credentials +config :recycledcloud, :odoo, + server: "https://odoo.staging.e-durable.ch", + database: "e-Durable", + user: "user", + secret: "secret" diff --git a/lib/recycledcloud/accounts/user.ex b/lib/recycledcloud/accounts/user.ex index 4da5b3c..8ff984b 100644 --- a/lib/recycledcloud/accounts/user.ex +++ b/lib/recycledcloud/accounts/user.ex @@ -16,6 +16,7 @@ defmodule RecycledCloud.Accounts.User do field :dn, :string, virtual: true field :email, :string, virtual: true field :confirmed_at, :naive_datetime + field :partner_id, :integer has_many :keys, Accounts.Key diff --git a/lib/recycledcloud/billing/partner.ex b/lib/recycledcloud/billing/partner.ex new file mode 100644 index 0000000..2f79634 --- /dev/null +++ b/lib/recycledcloud/billing/partner.ex @@ -0,0 +1,93 @@ +defmodule RecycledCloud.Billing.Partner do + alias RecycledCloud.Accounts.User + alias RecycledCloud.Billing.Partner + alias RecycledCloud.Odoo + alias RecycledCloud.Repo + + import Ecto.Changeset + use Ecto.Schema + + @address_fields [:street, :city, :zip] + + # Note: Odoo stores partner's country under 'country_id'. We do not support + # editing it at the moment. + + embedded_schema do + field :name, :string + field :email, :string + field :street, :string + field :city, :string + field :zip, :string + field :country, :string + end + + def changeset(%Partner{} = partner, attrs) do + partner |> cast(attrs, @address_fields) + end + + def apply(changeset) do + # Check if changeset is valid. + state = changeset |> Ecto.Changeset.apply_action(:update) + case state do + {:ok, partner} -> + changes = partner + |> Map.from_struct + |> Enum.filter(fn {k,_v} -> k in @address_fields end) + |> Enum.map(fn {k, v} -> {to_string(k), v} end) + |> Enum.into(%{}) + + # Propagate to Odoo + Odoo.query([ + "res.partner", + "write", + [[partner.id], changes] + ]) + {:error, _} -> state + end + end + + def get(id) do + result = Odoo.query([ + "res.partner", + "search_read", + [[["id", "=", id]]], + %{"fields" => ["id", "name", "email", "country_id"] ++ @address_fields} + ]) + + format_fields = fn p -> + case p do + {k, false} when k in @address_fields -> {k, ""} + {:country_id, false} -> {:country, ""} + {:country_id, [_id, str]} -> {:country, str} + _ -> p + end + end + + case result do + {:ok, [raw]} -> + args = raw + |> Enum.map(fn {k, v} -> {String.to_atom(k), v} end) + |> Enum.map(format_fields) + struct(Partner, args) + _ -> nil + end + end + + def create(name, email) do + Odoo.query([ + "res.partner", + "create", + [%{"name" => name, "email" => email}] + ]) + end + + def get_or_create(%User{} = user) do + case user.partner_id do + nil -> + {:ok, id} = create(user.username, user.email) + {:ok, updated} = user |> change(%{partner_id: id}) |> Repo.update() + get_or_create(updated) + id -> get(id) + end + end +end diff --git a/lib/recycledcloud_web/controllers/billing_controller.ex b/lib/recycledcloud_web/controllers/billing_controller.ex new file mode 100644 index 0000000..69e7405 --- /dev/null +++ b/lib/recycledcloud_web/controllers/billing_controller.ex @@ -0,0 +1,33 @@ +defmodule RecycledCloudWeb.BillingController do + use RecycledCloudWeb, :controller + + alias RecycledCloud.Billing.Partner + alias RecycledCloud.Repo + + def current_partner(conn) do + user = conn.assigns.current_user + Partner.get_or_create(user) + end + + def index(conn, _params) do + partner = conn |> current_partner + changeset = partner |> Partner.changeset(%{}) + + render(conn, "index.html", partner: partner, partner_changeset: changeset) + end + + def update(conn, %{"partner" => changes} = params) do + partner = conn |> current_partner + changeset = partner |> Partner.changeset(changes) + + case Partner.apply(changeset) do + {:ok, _} -> + conn + |> put_flash(:info, "Billing Partner has been updated") + |> redirect(to: Routes.billing_path(conn, :index)) + {:error, changeset} -> + render(conn, "index.html", partner: partner, partner_changeset: + changeset) + end + end +end diff --git a/lib/recycledcloud_web/router.ex b/lib/recycledcloud_web/router.ex index 4f53843..cfa122b 100644 --- a/lib/recycledcloud_web/router.ex +++ b/lib/recycledcloud_web/router.ex @@ -55,6 +55,8 @@ defmodule RecycledCloudWeb.Router do get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email post "/users/settings/keys/new", UserKeysController, :new get "/users/settings/keys/:key_id/delete", UserKeysController, :delete + get "/billing", BillingController, :index + post "/billing/partner/update", BillingController, :update end scope "/", RecycledCloudWeb do diff --git a/lib/recycledcloud_web/templates/billing/index.html.eex b/lib/recycledcloud_web/templates/billing/index.html.eex new file mode 100644 index 0000000..1a46885 --- /dev/null +++ b/lib/recycledcloud_web/templates/billing/index.html.eex @@ -0,0 +1,35 @@ +

Billing

+ +

You currently are registered as billing entity #<%= @partner.id %>.

+ +

Billing address

+ +<%= form_for @partner_changeset, Routes.billing_path(@conn, :update), fn f -> %> + <%= if @partner_changeset.action do %> +
+

Oops, something went wrong! Please check the errors below.

+
+ <% end %> + + <%= text_input f, :street, placeholder: "Street" %> + <%= error_tag f, :street %> + +
+ + <%= text_input f, :city, placeholder: "City" %> + <%= error_tag f, :city %> + +
+ + <%= text_input f, :zip, placeholder: "Zip" %> + <%= error_tag f, :zip %> + +
+ + <%= text_input f, :country, placeholder: "Country", readonly: true %> + <%= error_tag f, :country %> + +
+ <%= submit "Change address" %> +
+<% end %> diff --git a/lib/recycledcloud_web/templates/layout/_sidebar_nav.html.eex b/lib/recycledcloud_web/templates/layout/_sidebar_nav.html.eex index edb3670..5bcb884 100644 --- a/lib/recycledcloud_web/templates/layout/_sidebar_nav.html.eex +++ b/lib/recycledcloud_web/templates/layout/_sidebar_nav.html.eex @@ -4,6 +4,7 @@ <%= if @current_user do %>
  • Logged in as <%= @current_user.username %>
  • <%= link "Settings", to: Routes.user_settings_path(@conn, :edit) %>
  • +
  • <%= link "Billing", to: Routes.billing_path(@conn, :index) %>
  • <%= link "Log out", to: Routes.user_session_path(@conn, :delete), method: :delete %>
  • <% else %>
  • <%= link "Log in", to: Routes.user_session_path(@conn, :new) %>
  • diff --git a/lib/recycledcloud_web/views/billing_view.ex b/lib/recycledcloud_web/views/billing_view.ex new file mode 100644 index 0000000..1cc24a8 --- /dev/null +++ b/lib/recycledcloud_web/views/billing_view.ex @@ -0,0 +1,3 @@ +defmodule RecycledCloudWeb.BillingView do + use RecycledCloudWeb, :view +end diff --git a/priv/repo/migrations/20210120141029_add_user_partner_id.exs b/priv/repo/migrations/20210120141029_add_user_partner_id.exs new file mode 100644 index 0000000..6be46e4 --- /dev/null +++ b/priv/repo/migrations/20210120141029_add_user_partner_id.exs @@ -0,0 +1,9 @@ +defmodule RecycledCloud.Repo.Migrations.AddUserPartnerId do + use Ecto.Migration + + def change do + alter table(:users) do + add :partner_id, :integer + end + end +end diff --git a/priv/static/css/app.css b/priv/static/css/app.css index 5699d00..1688273 100644 --- a/priv/static/css/app.css +++ b/priv/static/css/app.css @@ -20,6 +20,10 @@ input, textarea { color: black; } +input[readonly] { + opacity: 0.5; +} + button { color: #000; padding: 5px;