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