defmodule BexioApiClient.Purchase do
import BexioApiClient.Helpers
@moduledoc """
Purchase Part of the API
"""
alias BexioApiClient.Global.Paging
alias BexioApiClient.Purchase.Bill
alias BexioApiClient.GlobalArguments
import BexioApiClient.GlobalArguments, only: [opts_to_query: 1]
@type api_error_type :: BexioApiClient.Helpers.api_error_type()
@possible_bill_status [
:draft,
:booked,
:partially_created,
:created,
:partially_sent,
:sent,
:partially_downloaded,
:downloaded,
:partially_paid,
:paid,
:partially_failed,
:failed
]
@status_map Enum.map(@possible_bill_status, fn v -> {Atom.to_string(v), v} end)
|> Enum.into(%{})
@type bill_search_args :: [
bill_date_start: Date.t(),
bill_date_end: Date.t(),
due_date_start: Date.t(),
due_date_end: Date.t(),
vendor_ref: String.t(),
title: String.t(),
currency_code: String.t(),
pending_amount_min: float(),
pending_amount_max: float(),
vendor: String.t(),
gross_min: float(),
gross_max: float(),
net_min: float(),
net_max: float(),
document_no: String.t(),
supplier_id: integer(),
status: :drafts | :todo | :paid | :overdue
]
defp bill_search_args_to_query(opts) do
opts
end
@doc """
Endpoint for retrieving Bills
## Arguments:
* `:client` - client to execute the HTTP request with
* `:limit` - limit the number of results (default: 500, max: 2000)
* `:page` - current page
* `:order` - sorting order, example `order=asc&order=desc`
* `:sort` - field to sort by, example `sort=document_no`
* `:search_term` - term for which application will look for (minimum 3 signs, maximum 255 signs), example `search_term=term`
* `:fields` - Items enum, fields for which search will be run (if no fields specified then searching will be done for all allowed fields)
* `:status` - filter for Bill 'status' (DRAFTS: [DRAFT], TODO: [BOOKED, PARTIALLY_CREATED, CREATED, PARTIALLY_SENT, SENT, PARTIALLY_DOWNLOADED, DOWNLOADED, PARTIALLY_PAID, PARTIALLY_FAILED, FAILED], PAID: [PAID], OVERDUE: [BOOKED, PARTIALLY_CREATED, CREATED, PARTIALLY_SENT, SENT, PARTIALLY_DOWNLOADED, DOWNLOADED, PARTIALLY_PAID, PARTIALLY_FAILED, FAILED]) and for 'onlyOverdue' (DRAFTS: [FALSE], TODOS: [FALSE], PAID: [FALSE], OVERDUE: [TRUE]). Choosing OVERDUE means that only Bills with 'due_date' before now will be shown
"""
@spec fetch_bills(
req :: Req.Request.t(),
search_term :: String.t() | nil,
fields :: list(String.t()) | nil,
search_args :: bill_search_args(),
opts :: [GlobalArguments.paging_arg()]
) :: {:ok, {[Bill.t()], Paging.t()}} | api_error_type()
def fetch_bills(
req,
search_term \\ nil,
fields \\ nil,
search_args \\ [],
opts \\ []
) do
bexio_body_handling(
fn ->
Req.get(req,
url: "/4.0/purchase/bills",
params:
Keyword.merge(
[
search_term: search_term,
fields: fields
],
Keyword.merge(
bill_search_args_to_query(search_args),
opts_to_query(opts)
)
)
|> Keyword.filter(fn {_k, v} -> v != nil end)
)
end,
&map_from_paged_bills/2
)
end
defp map_from_paged_bills(
%{
"data" => bills,
"paging" => %{
"page" => page,
"page_size" => page_size,
"page_count" => page_count,
"item_count" => item_count
}
},
_env
) do
{
Enum.map(bills, &map_from_bill/1),
%Paging{
page: page,
page_size: page_size,
page_count: page_count,
item_count: item_count
}
}
end
defp map_from_bill(%{
"id" => id,
"created_at" => created_at,
"document_no" => document_no,
"status" => status,
"vendor_ref" => vendor_ref,
"firstname_suffix" => firstname_suffix,
"lastname_company" => lastname_company,
"vendor" => vendor,
"title" => title,
"currency_code" => currency_code,
"pending_amount" => pending_amount,
"net" => net,
"gross" => gross,
"bill_date" => bill_date,
"due_date" => due_date,
"overdue" => overdue?,
"booking_account_ids" => booking_account_ids,
"attachment_ids" => attachment_ids
}) do
%Bill{
id: id,
created_at: convert_date_time(created_at),
document_no: document_no,
status: map_status(status),
vendor_ref: vendor_ref,
firstname_suffix: firstname_suffix,
lastname_company: lastname_company,
vendor: vendor,
title: title,
currency_code: currency_code,
pending_amount: pending_amount,
net: net,
gross: gross,
bill_date: Date.from_iso8601!(bill_date),
due_date: Date.from_iso8601!(due_date),
overdue?: overdue?,
booking_account_ids: booking_account_ids,
attachment_ids: attachment_ids
}
end
defp map_status(status), do: Map.get(@status_map, String.downcase(status))
defp convert_date_time(date_time) do
case DateTime.from_iso8601(date_time) do
{:ok, dt, _offset} -> dt
{:error, _error} -> raise ArgumentError
end
end
end