diff --git a/CHANGELOG.md b/CHANGELOG.md index c15c7e4..ba1884b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.5.0 + +* SearchCriteria changed to sigil ~f with separations for reduced name lengths (old functionality is still there for now) +* Payment Types added + ## 0.4.2 * Added Github Action to automatically publish a package version diff --git a/README.md b/README.md index 8ca6624..90dec59 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ by adding `bexio_api_client` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:bexio_api_client, "~> 0.4.3"} + {:bexio_api_client, "~> 0.5.0"} ] end ``` diff --git a/lib/bexio_api_client/others.ex b/lib/bexio_api_client/others.ex index b12e425..71ce554 100644 --- a/lib/bexio_api_client/others.ex +++ b/lib/bexio_api_client/others.ex @@ -902,4 +902,45 @@ defmodule BexioApiClient.Others do &success_response/2 ) end + + @doc """ + Fetch a list of payment types. + """ + @spec fetch_payment_types( + req :: Req.Request.t(), + opts :: [GlobalArguments.offset_without_order_by_arg()] + ) :: {:ok, map()} | api_error_type() + def fetch_payment_types(req, opts \\ []) do + bexio_body_handling( + fn -> + Req.get(req, url: "/2.0/payment_type", params: opts_to_query(opts)) + end, + &body_to_map/2 + ) + end + + @doc """ + Search a payment type + + Following fields are supported: + + * `name` + """ + @spec search_payment_types( + req :: Req.Request.t(), + criteria :: list(SearchCriteria.t()), + opts :: [GlobalArguments.offset_arg()] + ) :: {:ok, map()} | api_error_type() + def search_payment_types(req, criteria, opts \\ []) do + bexio_body_handling( + fn -> + Req.post(req, + url: "/2.0/payment_type/search", + json: criteria, + params: opts_to_query(opts) + ) + end, + &body_to_map/2 + ) + end end diff --git a/lib/bexio_api_client/search_criteria.ex b/lib/bexio_api_client/search_criteria.ex index 66978c1..1ec084b 100644 --- a/lib/bexio_api_client/search_criteria.ex +++ b/lib/bexio_api_client/search_criteria.ex @@ -47,6 +47,8 @@ defmodule BexioApiClient.SearchCriteria do @derive Jason.Encoder defstruct [:field, :criteria, :value] + @regex ~r/^(?\S+)\s*(?!=|=|<=|<|>=|>|in|not in|like|not like|is nil|is not nil)\s*(?.*)$/ + defp new(field, criteria, value) do %__MODULE__{ field: field, @@ -55,6 +57,43 @@ defmodule BexioApiClient.SearchCriteria do } end + @spec sigil_f(String.t(), Keyword.t()) :: t() + def sigil_f(field, opts) + + def sigil_f(expression, _opts) do + case Regex.run(@regex, expression, capture: :all_but_first) do + nil -> raise ArgumentError, "Invalid search criteria: #{expression}" + [field, operator, value] -> handle(field, operator, value) + end + end + + defp handle(field, operator, value) when is_binary(field), + do: handle(String.to_existing_atom(field), operator, value) + + defp handle(field, "=", value) when is_atom(field), do: equal(field, value) + defp handle(field, "!=", value) when is_atom(field), do: not_equal(field, value) + defp handle(field, "<", value) when is_atom(field), do: less_than(field, value) + defp handle(field, "<=", value) when is_atom(field), do: less_equal(field, value) + defp handle(field, ">", value) when is_atom(field), do: greater_than(field, value) + defp handle(field, ">=", value) when is_atom(field), do: greater_equal(field, value) + defp handle(field, "like", value) when is_atom(field), do: like(field, value) + defp handle(field, "not like", value) when is_atom(field), do: not_like(field, value) + defp handle(field, "is nil", _value) when is_atom(field), do: nil?(field) + defp handle(field, "is not nil", _value) when is_atom(field), do: not_nil?(field) + defp handle(field, "in", value) when is_atom(field), do: part_of(field, listify(value)) + defp handle(field, "not in", value) when is_atom(field), do: not_part_of(field, listify(value)) + + defp handle(field, operator, value), + do: + raise(ArgumentError, "Invalid operator: #{operator} for field #{field} and value #{value}") + + defp listify(value) do + case ~r/^\[(?.*)\]$/ |> Regex.named_captures(value) do + %{"value" => value} -> String.split(value, ",") + nil -> [value] + end + end + @doc "Creates a = search criteria" @spec equal(atom(), any()) :: t() def equal(field, value), do: new(field, :=, value) diff --git a/mix.exs b/mix.exs index d5441c8..c78df39 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule BexioApiClient.MixProject do def project do [ app: :bexio_api_client, - version: "0.4.3", + version: "0.5.0", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: deps(), diff --git a/test/bexio_api_client/others_test.exs b/test/bexio_api_client/others_test.exs index c90c0e3..73f1334 100644 --- a/test/bexio_api_client/others_test.exs +++ b/test/bexio_api_client/others_test.exs @@ -1558,4 +1558,63 @@ defmodule BexioApiClient.OthersTest do assert result == true end end + + describe "fetching a list of payment types" do + setup do + mock_request(fn + %{ + method: "GET", + request_path: "/2.0/payment_type" + } = conn -> + json(conn, [ + %{ + "id" => 1, + "name" => "Cash" + } + ]) + end) + + :ok + end + + test "lists valid results" do + client = BexioApiClient.new("123") + + assert {:ok, result} = BexioApiClient.Others.fetch_payment_types(client) + + assert Enum.count(result) == 1 + assert result[1] == "Cash" + end + end + + describe "searching payment types" do + setup do + mock_request(fn + %{ + method: "POST", + request_path: "/2.0/payment_type/search" + } = conn -> + json(conn, [ + %{ + "id" => 1, + "name" => "Cash" + } + ]) + end) + + :ok + end + + test "lists found results" do + client = BexioApiClient.new("123") + + assert {:ok, result} = + BexioApiClient.Others.search_payment_types(client, [ + SearchCriteria.equal(:name, "Cash") + ]) + + assert Enum.count(result) == 1 + assert result[1] == "Cash" + end + end end diff --git a/test/bexio_api_client/sales_order_management_test.exs b/test/bexio_api_client/sales_order_management_test.exs index c30b7e7..71e6af1 100644 --- a/test/bexio_api_client/sales_order_management_test.exs +++ b/test/bexio_api_client/sales_order_management_test.exs @@ -2166,35 +2166,36 @@ defmodule BexioApiClient.SalesOrderManagementTest do describe "fetching a list of document settings" do setup do - json = Jason.decode! """ - [ - { - "id": 1, - "text": "Quote", - "kb_item_class": "KbOffer", - "enumeration_format": "AN-%nummer%", - "use_automatic_enumeration": true, - "use_yearly_enumeration": false, - "next_nr": 1, - "nr_min_length": 5, - "default_time_period_in_days": 14, - "default_logopaper_id": 1, - "default_language_id": 1, - "default_client_bank_account_new_id": 1, - "default_currency_id": 1, - "default_mwst_type": 0, - "default_mwst_is_net": true, - "default_nb_decimals_amount": 2, - "default_nb_decimals_price": 2, - "default_show_position_taxes": false, - "default_title": "Angebot", - "default_show_esr_on_same_page": false, - "default_payment_type_id": 1, - "kb_terms_of_payment_template_id": 1, - "default_show_total": true - } - ] - """ + json = + Jason.decode!(""" + [ + { + "id": 1, + "text": "Quote", + "kb_item_class": "KbOffer", + "enumeration_format": "AN-%nummer%", + "use_automatic_enumeration": true, + "use_yearly_enumeration": false, + "next_nr": 1, + "nr_min_length": 5, + "default_time_period_in_days": 14, + "default_logopaper_id": 1, + "default_language_id": 1, + "default_client_bank_account_new_id": 1, + "default_currency_id": 1, + "default_mwst_type": 0, + "default_mwst_is_net": true, + "default_nb_decimals_amount": 2, + "default_nb_decimals_price": 2, + "default_show_position_taxes": false, + "default_title": "Angebot", + "default_show_esr_on_same_page": false, + "default_payment_type_id": 1, + "kb_terms_of_payment_template_id": 1, + "default_show_total": true + } + ] + """) mock_request(fn %{method: "GET", request_path: "/2.0/kb_item_setting"} = conn -> diff --git a/test/bexio_api_client/search_criteria_test.exs b/test/bexio_api_client/search_criteria_test.exs index 706cd21..934bf8d 100644 --- a/test/bexio_api_client/search_criteria_test.exs +++ b/test/bexio_api_client/search_criteria_test.exs @@ -101,4 +101,122 @@ defmodule BexioApiClient.SearchCriteriaTest do assert sc.value == nil end end + + describe "sigils " do + setup do + %{name: "fred"} + end + + test "equal will generate search criteria" do + sc = ~f(name = fred) + + assert sc.field == :name + assert sc.criteria == := + assert sc.value == "fred" + end + + test "not_equal will generate search criteria" do + sc = ~f(name != fred) + + assert sc.field == :name + assert sc.criteria == :!= + assert sc.value == "fred" + end + + test "greater_than will generate search criteria" do + sc = ~f(name > fred) + + assert sc.field == :name + assert sc.criteria == :> + assert sc.value == "fred" + end + + test "less_than will generate search criteria" do + sc = ~f(name < fred) + + assert sc.field == :name + assert sc.criteria == :< + assert sc.value == "fred" + end + + test "greater_equal will generate search criteria" do + sc = ~f(name >= fred) + + assert sc.field == :name + assert sc.criteria == :>= + assert sc.value == "fred" + end + + test "less_equal will generate search criteria" do + sc = ~f(name <= fred) + + assert sc.field == :name + assert sc.criteria == :<= + assert sc.value == "fred" + end + + test "like will generate search criteria" do + sc = ~f(name like fred) + + assert sc.field == :name + assert sc.criteria == :like + assert sc.value == "fred" + end + + test "not_like will generate search criteria" do + sc = ~f(name not like fred) + + assert sc.field == :name + assert sc.criteria == :not_like + assert sc.value == "fred" + end + + test "part_of will generate search criteria" do + sc = ~f(name in fred,me) + + assert sc.field == :name + assert sc.criteria == :in + assert sc.value == ["fred,me"] + end + + test "part_of will generate search criteria in list" do + sc = ~f(name in [fred,me]) + + assert sc.field == :name + assert sc.criteria == :in + assert sc.value == ["fred", "me"] + end + + test "not_part_of will generate search criteria" do + sc = ~f(name not in fred,me) + + assert sc.field == :name + assert sc.criteria == :not_in + assert sc.value == ["fred,me"] + end + + test "not_part_of will generate search criteria in list" do + sc = ~f(name not in [fred,me]) + + assert sc.field == :name + assert sc.criteria == :not_in + assert sc.value == ["fred", "me"] + end + + test "nil? will generate search criteria" do + sc = ~f(name is nil) + + assert sc.field == :name + assert sc.criteria == :is_null + assert sc.value == nil + end + + test "not_nil? will generate search criteria" do + sc = ~f(name is not nil) + + assert sc.field == :name + assert sc.criteria == :not_null + assert sc.value == nil + end + end end