Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
41 changes: 41 additions & 0 deletions lib/bexio_api_client/others.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
39 changes: 39 additions & 0 deletions lib/bexio_api_client/search_criteria.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ defmodule BexioApiClient.SearchCriteria do
@derive Jason.Encoder
defstruct [:field, :criteria, :value]

@regex ~r/^(?<field>\S+)\s*(?<operator>!=|=|<=|<|>=|>|in|not in|like|not like|is nil|is not nil)\s*(?<value>.*)$/

defp new(field, criteria, value) do
%__MODULE__{
field: field,
Expand All @@ -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/^\[(?<value>.*)\]$/ |> 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)
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
59 changes: 59 additions & 0 deletions test/bexio_api_client/others_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
59 changes: 30 additions & 29 deletions test/bexio_api_client/sales_order_management_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
118 changes: 118 additions & 0 deletions test/bexio_api_client/search_criteria_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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