defmodule BexioApiClient.SalesOrderManagement do
@moduledoc """
Bexio API for the sales order management part of the API.
"""
import BexioApiClient.Helpers
alias BexioApiClient.GlobalArguments
alias BexioApiClient.SearchCriteria
alias BexioApiClient.SalesOrderManagement.{
Comment,
Order,
Quote,
Invoice,
DocumentSetting,
Delivery,
PositionSubposition,
PositionPagebreak,
PositionDiscount,
PositionItem,
PositionDefault,
PositionText,
PositionSubtotal
}
import BexioApiClient.GlobalArguments, only: [opts_to_query: 1]
@type api_error_type :: BexioApiClient.Helpers.api_error_type()
# Quotes
@doc """
This action fetches a list of all quotes.
"""
@spec fetch_quotes(
req :: Req.Request.t(),
opts :: [GlobalArguments.offset_arg()]
) ::
{:ok, [Quote.t()]} | api_error_type
def fetch_quotes(req, opts \\ []) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_offer", params: opts_to_query(opts))
end,
&map_from_quotes/2
)
end
@doc """
Search quotes via query.
The following search fields are supported:
* id
* kb_item_status
* document_nr
* title
* contact_id
* contact_sub_id
* user_id
* currency_id
* total_gross
* total_net
* total
* is_valid_from
* is_valid_until
* is_valid_to (?)
* updated_at
"""
@spec search_quotes(
req :: Req.Request.t(),
criteria :: list(SearchCriteria.t()),
opts :: [GlobalArguments.offset_arg()]
) ::
{:ok, [Quote.t()]} | api_error_type
def search_quotes(
req,
criteria,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_offer/search", json: criteria, params: opts_to_query(opts))
end,
&map_from_quotes/2
)
end
@doc """
This action fetches a single quote
"""
@spec fetch_quote(
req :: Req.Request.t(),
quote_id :: pos_integer()
) ::
{:ok, Quote.t()} | api_error_type
def fetch_quote(req, quote_id) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_offer/#{quote_id}")
end,
&map_from_quote/2
)
end
@doc """
Create a quote (id in order will be ignored). Be also aware: whether you need or must not send a document number depends on the settings in Bexio. It cannot be controlled by the API
and as such will just send if it exists.
"""
@spec create_quote(
req :: Req.Request.t(),
offer :: Quote.t()
) ::
{:ok, Quote.t()} | api_error_type
def create_quote(req, offer) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_offer", json: remap_quote(offer))
end,
&map_from_quote/2
)
end
@doc """
Edit a quote.
"""
@spec edit_quote(
req :: Req.Request.t(),
offer :: Quote.t()
) :: {:ok, Quote.t()} | api_error_type
def edit_quote(req, offer) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_offer/#{offer.id}", json: remap_edit_quote(offer))
end,
&map_from_quote/2
)
end
@doc """
Delete a quote.
"""
@spec delete_quote(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def delete_quote(req, id) do
bexio_body_handling(
fn ->
Req.delete(req, url: "/2.0/kb_offer/#{id}")
end,
&success_response/2
)
end
@doc """
Issue a quote.
"""
@spec issue_quote(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def issue_quote(req, id) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_offer/#{id}/issue")
end,
&success_response/2
)
end
@doc """
Revert Issue a quote.
"""
@spec revert_issue_quote(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def revert_issue_quote(req, id) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_offer/#{id}/revertIssue")
end,
&success_response/2
)
end
@doc """
Accept a quote.
"""
@spec accept_quote(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def accept_quote(req, id) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_offer/#{id}/accept")
end,
&success_response/2
)
end
@doc """
Decline a quote.
"""
@spec decline_quote(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def decline_quote(req, id) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_offer/#{id}/reject")
end,
&success_response/2
)
end
@doc """
Reissue a quote.
"""
@spec reissue_quote(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def reissue_quote(req, id) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_offer/#{id}/reissue")
end,
&success_response/2
)
end
@doc """
Mark a quote as sent.
"""
@spec mark_quote_as_sent(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | {:error, any()}
def mark_quote_as_sent(req, id) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_offer/#{id}/mark_as_sent")
end,
&success_response/2
)
end
@doc """
This action returns a pdf document of the quote
"""
@spec quote_pdf(
req :: Req.Request.t(),
quote_id :: pos_integer()
) :: {:ok, map()} | api_error_type
def quote_pdf(req, quote_id) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_offer/#{quote_id}/pdf")
end,
&map_from_pdf/2
)
end
defp remap_quote(
%Quote{
positions: positions
} = offer
) do
offer
|> remap_edit_quote()
|> Map.put(:positions, Enum.map(positions, &map_to_post_position/1))
end
defp map_from_pdf(%{"name" => name, "size" => size, "mime" => mime, "content" => content}, _env) do
%{
name: name,
size: size,
mime: mime,
content: content
}
end
defp remap_edit_quote(%Quote{
title: title,
document_nr: document_nr,
contact_id: contact_id,
contact_sub_id: contact_sub_id,
user_id: user_id,
project_id: project_id,
language_id: language_id,
bank_account_id: bank_account_id,
currency_id: curency_id,
payment_type_id: payment_type_id,
header: header,
footer: footer,
mwst_type: mwst_type,
mwst_is_net?: mwst_is_net?,
show_position_taxes?: show_position_taxes?,
is_valid_from: is_valid_from,
is_valid_until: is_valid_until,
delivery_address_type: delivery_address_type,
api_reference: api_reference,
viewed_by_client_at: viewed_by_client_at,
kb_terms_of_payment_template_id: kb_terms_of_payment_template_id,
template_slug: template_slug
}) do
%{
title: title,
document_nr: document_nr,
contact_id: contact_id,
contact_sub_id: contact_sub_id,
user_id: user_id,
pr_project_id: project_id,
language_id: language_id,
bank_account_id: bank_account_id,
currency_id: curency_id,
payment_type_id: payment_type_id,
header: header,
footer: footer,
mwst_type: mwst_type_id(mwst_type),
mwst_is_net: mwst_is_net?,
show_position_taxes: show_position_taxes?,
is_valid_from: to_iso8601(is_valid_from),
is_valid_until: to_iso8601(is_valid_until),
delivery_address_type: delivery_address_type,
api_reference: api_reference,
viewed_by_client_at: to_naive_string(viewed_by_client_at),
kb_terms_of_payment_template_id: kb_terms_of_payment_template_id,
template_slug: template_slug
}
|> remove_document_no_if_nil()
end
defp map_to_post_position(%PositionDefault{
amount: amount,
unit_id: unit_id,
account_id: account_id,
tax_id: tax_id,
text: text,
unit_price: unit_price,
discount_in_percent: discount_in_percent
}),
do: %{
type: "KbPositionCustom",
amount: Decimal.to_string(amount, :normal),
unit_id: unit_id,
account_id: account_id,
tax_id: tax_id,
unit_price: Decimal.to_string(unit_price, :normal),
discount_in_percent: Decimal.to_string(discount_in_percent, :normal),
text: text
}
defp map_to_post_position(%PositionItem{
amount: amount,
unit_id: unit_id,
account_id: account_id,
tax_id: tax_id,
text: text,
unit_price: unit_price,
discount_in_percent: discount_in_percent,
article_id: article_id
}),
do: %{
type: "KbPositionArticle",
amount: Decimal.to_string(amount, :normal),
unit_id: unit_id,
account_id: account_id,
tax_id: tax_id,
unit_price: Decimal.to_string(unit_price, :normal),
discount_in_percent: Decimal.to_string(discount_in_percent, :normal),
text: text,
article_id: article_id
}
defp map_to_post_position(%PositionText{
text: text,
show_pos_nr?: show_pos_nr?
}),
do: %{
type: "KbPositionText",
text: text,
show_pos_nr: show_pos_nr?
}
defp map_to_post_position(%PositionSubtotal{text: text}),
do: %{
type: "KbPositionSubtotal",
text: text
}
defp map_to_post_position(%PositionPagebreak{}),
do: %{
type: "KbPositionPagebreak",
pagebreak: true
}
defp map_to_post_position(%PositionDiscount{
text: text,
percentual?: percentual?,
value: value
}),
do: %{
type: "KbPositionDiscount",
is_percentual: percentual?,
text: text,
value: Decimal.to_string(value, :normal)
}
defp map_from_quotes(quotes, _env), do: Enum.map(quotes, &map_from_quote/1)
defp map_from_quote(
%{
"id" => id,
"document_nr" => document_nr,
"title" => title,
"contact_id" => contact_id,
"contact_sub_id" => contact_sub_id,
"user_id" => user_id,
"project_id" => project_id,
"language_id" => language_id,
"bank_account_id" => bank_account_id,
"currency_id" => currency_id,
"payment_type_id" => payment_type_id,
"header" => header,
"footer" => footer,
"total_gross" => total_gross,
"total_net" => total_net,
"total_taxes" => total_taxes,
"total" => total,
"total_rounding_difference" => total_rounding_difference,
"mwst_type" => mwst_type_id,
"mwst_is_net" => mwst_is_net?,
"show_position_taxes" => show_position_taxes?,
"is_valid_from" => is_valid_from,
"is_valid_until" => is_valid_until,
"contact_address" => contact_address,
"delivery_address_type" => delivery_address_type,
"delivery_address" => delivery_address,
"kb_item_status_id" => kb_item_status_id,
"api_reference" => api_reference,
"viewed_by_client_at" => viewed_by_client_at,
"kb_terms_of_payment_template_id" => kb_terms_of_payment_template_id,
"show_total" => show_total?,
"updated_at" => updated_at,
"template_slug" => template_slug,
"taxs" => taxs,
"network_link" => network_link
} = map,
_env \\ nil
) do
%Quote{
id: id,
document_nr: document_nr,
title: title,
contact_id: contact_id,
contact_sub_id: contact_sub_id,
user_id: user_id,
project_id: project_id,
language_id: language_id,
bank_account_id: bank_account_id,
currency_id: currency_id,
payment_type_id: payment_type_id,
header: header,
footer: footer,
total_gross: to_decimal(total_gross),
total_net: to_decimal(total_net),
total_taxes: to_decimal(total_taxes),
total: to_decimal(total),
total_rounding_difference: total_rounding_difference,
mwst_type: mwst_type(mwst_type_id),
mwst_is_net?: mwst_is_net?,
show_position_taxes?: show_position_taxes?,
is_valid_from: to_date(is_valid_from),
is_valid_until: to_date(is_valid_until),
contact_address: contact_address,
delivery_address_type: delivery_address_type,
delivery_address: delivery_address,
kb_item_status: kb_item_status(kb_item_status_id),
api_reference: api_reference,
viewed_by_client_at: to_datetime(viewed_by_client_at),
kb_terms_of_payment_template_id: kb_terms_of_payment_template_id,
show_total?: show_total?,
updated_at: to_datetime(updated_at),
template_slug: template_slug,
taxs: Enum.map(taxs, &to_tax/1),
network_link: network_link
}
|> map_quote_positions(Map.get(map, "positions"))
end
defp map_quote_positions(q, nil), do: q
defp map_quote_positions(q, positions), do: %{q | positions: remap_positions(positions)}
defp mwst_type(0), do: :including
defp mwst_type(1), do: :excluding
defp mwst_type(2), do: :exempt
defp mwst_type_id(:including), do: 0
defp mwst_type_id(:excluding), do: 1
defp mwst_type_id(:exempt), do: 2
defp kb_item_status(1), do: :draft
defp kb_item_status(2), do: :pending
defp kb_item_status(3), do: :confirmed
defp kb_item_status(4), do: :declined
defp to_tax(%{"percentage" => percentage, "value" => value}),
do: %{percentage: percentage, value: value}
# Orders
@doc """
This action fetches a list of all orders.
"""
@spec fetch_orders(
req :: Req.Request.t(),
opts :: [GlobalArguments.offset_arg()]
) :: {:ok, [Order.t()]} | api_error_type
def fetch_orders(req, opts \\ []) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_order", params: opts_to_query(opts))
end,
&map_from_orders/2
)
end
@doc """
Search orders via query.
The following search fields are supported:
* id
* kb_item_status
* document_nr
* title
* contact_id
* contact_sub_id
* user_id
* currency_id
* total_gross
* total_net
* total
* is_valid_from
* updated_at
"""
@spec search_orders(
req :: Req.Request.t(),
criteria :: list(SearchCriteria.t()),
opts :: [GlobalArguments.offset_arg()]
) :: {:ok, [Order.t()]} | api_error_type
def search_orders(
req,
criteria,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_order/search", json: criteria, params: opts_to_query(opts))
end,
&map_from_orders/2
)
end
@doc """
This action fetches a single order
"""
@spec fetch_order(
req :: Req.Request.t(),
order_id :: pos_integer()
) :: {:ok, Order.t()} | api_error_type
def fetch_order(req, order_id) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_order/#{order_id}")
end,
&map_from_order/2
)
end
@doc """
Create an order (id in order will be ignored). Be also aware: whether you need or must not send a document number depends on the settings in Bexio. It cannot be controlled by the API
and as such will just send if it exists.
"""
@spec create_order(
req :: Req.Request.t(),
order :: Order.t()
) :: {:ok, Order.t()} | api_error_type
def create_order(req, order) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_order", json: remap_order(order))
end,
&map_from_order/2
)
end
@doc """
Edit an order.
"""
@spec edit_order(
req :: Req.Request.t(),
order :: Order.t()
) :: {:ok, Order.t()} | api_error_type
def edit_order(req, order) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_order/#{order.id}", json: remap_edit_order(order))
end,
&map_from_order/2
)
end
@doc """
Delete an order.
"""
@spec delete_order(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def delete_order(req, id) do
bexio_body_handling(
fn ->
Req.delete(req, url: "/2.0/kb_order/#{id}")
end,
&success_response/2
)
end
@doc """
This action returns a pdf document of the order
"""
@spec order_pdf(
req :: Req.Request.t(),
order_id :: pos_integer()
) :: {:ok, map()} | api_error_type
def order_pdf(req, order_id) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_order/#{order_id}/pdf")
end,
&map_from_pdf/2
)
end
defp remap_order(
%Order{
positions: positions
} = order
) do
order
|> remap_edit_order()
|> Map.put(:positions, Enum.map(positions, &map_to_post_position/1))
end
defp remap_edit_order(%Order{
title: title,
document_nr: document_nr,
contact_id: contact_id,
contact_sub_id: contact_sub_id,
user_id: user_id,
project_id: project_id,
language_id: language_id,
bank_account_id: bank_account_id,
currency_id: curency_id,
payment_type_id: payment_type_id,
header: header,
footer: footer,
mwst_type: mwst_type,
mwst_is_net?: mwst_is_net?,
show_position_taxes?: show_position_taxes?,
is_valid_from: is_valid_from,
delivery_address_type: delivery_address_type,
api_reference: api_reference,
template_slug: template_slug
}) do
%{
title: title,
document_nr: document_nr,
contact_id: contact_id,
contact_sub_id: contact_sub_id,
user_id: user_id,
pr_project_id: project_id,
language_id: language_id,
bank_account_id: bank_account_id,
currency_id: curency_id,
payment_type_id: payment_type_id,
header: header,
footer: footer,
mwst_type: mwst_type_id(mwst_type),
mwst_is_net: mwst_is_net?,
show_position_taxes: show_position_taxes?,
is_valid_from: Date.to_iso8601(is_valid_from),
delivery_address_type: delivery_address_type,
api_reference: api_reference,
template_slug: template_slug
}
|> remove_document_no_if_nil()
end
defp map_from_orders(orders, _env), do: Enum.map(orders, &map_from_order/1)
defp map_from_order(
%{
"id" => id,
"document_nr" => document_nr,
"title" => title,
"contact_id" => contact_id,
"contact_sub_id" => contact_sub_id,
"user_id" => user_id,
"project_id" => project_id,
"language_id" => language_id,
"bank_account_id" => bank_account_id,
"currency_id" => currency_id,
"payment_type_id" => payment_type_id,
"header" => header,
"footer" => footer,
"total_gross" => total_gross,
"total_net" => total_net,
"total_taxes" => total_taxes,
"total" => total,
"total_rounding_difference" => total_rounding_difference,
"mwst_type" => mwst_type_id,
"mwst_is_net" => mwst_is_net?,
"show_position_taxes" => show_position_taxes?,
"is_valid_from" => is_valid_from,
"contact_address" => contact_address,
"delivery_address_type" => delivery_address_type,
"delivery_address" => delivery_address,
"kb_item_status_id" => kb_item_status_id,
"is_recurring" => is_recurring?,
"api_reference" => api_reference,
"viewed_by_client_at" => viewed_by_client_at,
"updated_at" => updated_at,
"template_slug" => template_slug,
"taxs" => taxs,
"network_link" => network_link
} = map,
_env \\ nil
) do
%Order{
id: id,
document_nr: document_nr,
title: title,
contact_id: contact_id,
contact_sub_id: contact_sub_id,
user_id: user_id,
project_id: project_id,
language_id: language_id,
bank_account_id: bank_account_id,
currency_id: currency_id,
payment_type_id: payment_type_id,
header: header,
footer: footer,
total_gross: to_decimal(total_gross),
total_net: to_decimal(total_net),
total_taxes: to_decimal(total_taxes),
total: to_decimal(total),
total_rounding_difference: total_rounding_difference,
mwst_type: mwst_type(mwst_type_id),
mwst_is_net?: mwst_is_net?,
show_position_taxes?: show_position_taxes?,
is_valid_from: to_date(is_valid_from),
contact_address: contact_address,
delivery_address_type: delivery_address_type,
delivery_address: delivery_address,
kb_item_status: order_kb_item_status(kb_item_status_id),
api_reference: api_reference,
viewed_by_client_at: to_datetime(viewed_by_client_at),
is_recurring?: is_recurring?,
updated_at: to_datetime(updated_at),
template_slug: template_slug,
taxs: Enum.map(taxs, &to_tax/1),
network_link: network_link
}
|> map_order_positions(Map.get(map, "positions"))
end
defp map_order_positions(q, nil), do: q
defp map_order_positions(q, positions), do: %{q | positions: remap_positions(positions)}
defp order_kb_item_status(5), do: :pending
defp order_kb_item_status(6), do: :done
defp order_kb_item_status(15), do: :partial
defp order_kb_item_status(21), do: :cancelled
# Deliveries
@doc """
This action fetches a list of all deliveries.
"""
@spec fetch_deliveries(
req :: Req.Request.t(),
opts :: [GlobalArguments.offset_arg()]
) :: {:ok, [Delivery.t()]} | api_error_type
def fetch_deliveries(req, opts \\ []) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_delivery", params: opts_to_query(opts))
end,
&map_from_deliveries/2
)
end
@doc """
This action fetches a single delivery
"""
@spec fetch_delivery(
req :: Req.Request.t(),
delivery_id :: pos_integer()
) :: {:ok, Delivery.t()} | api_error_type
def fetch_delivery(req, delivery_id) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_delivery/#{delivery_id}")
end,
&map_from_delivery/2
)
end
@doc """
Issues a delivery (only possible if it's in draft status!). The result whether the issue was successful or not
"""
@spec issue_delivery(
req :: Req.Request.t(),
delivery_id :: integer()
) :: {:ok, boolean()} | api_error_type
def issue_delivery(req, delivery_id) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_delivery/#{delivery_id}/issue")
end,
&success_response/2
)
end
defp map_from_deliveries(deliveries, _env), do: Enum.map(deliveries, &map_from_delivery/1)
defp map_from_delivery(
%{
"id" => id,
"document_nr" => document_nr,
"title" => title,
"contact_id" => contact_id,
"contact_sub_id" => contact_sub_id,
"user_id" => user_id,
"language_id" => language_id,
"bank_account_id" => bank_account_id,
"currency_id" => currency_id,
"header" => header,
"footer" => footer,
"total_gross" => total_gross,
"total_net" => total_net,
"total_taxes" => total_taxes,
"total" => total,
"total_rounding_difference" => total_rounding_difference,
"mwst_type" => mwst_type_id,
"mwst_is_net" => mwst_is_net?,
"is_valid_from" => is_valid_from,
"contact_address" => contact_address,
"delivery_address_type" => delivery_address_type,
"delivery_address" => delivery_address,
"kb_item_status_id" => kb_item_status_id,
"api_reference" => api_reference,
"updated_at" => updated_at,
"taxs" => taxs
} = map,
_env \\ nil
) do
%Delivery{
id: id,
document_nr: document_nr,
title: title,
contact_id: contact_id,
contact_sub_id: contact_sub_id,
user_id: user_id,
language_id: language_id,
bank_account_id: bank_account_id,
currency_id: currency_id,
header: header,
footer: footer,
total_gross: to_decimal(total_gross),
total_net: to_decimal(total_net),
total_taxes: to_decimal(total_taxes),
total: to_decimal(total),
total_rounding_difference: total_rounding_difference,
mwst_type: mwst_type(mwst_type_id),
mwst_is_net?: mwst_is_net?,
is_valid_from: to_date(is_valid_from),
contact_address: contact_address,
delivery_address_type: delivery_address_type,
delivery_address: delivery_address,
kb_item_status: delivery_kb_item_status(kb_item_status_id),
api_reference: api_reference,
updated_at: to_datetime(updated_at),
taxs: Enum.map(taxs, &to_tax/1)
}
|> map_delivery_positions(Map.get(map, "positions"))
end
defp map_delivery_positions(q, nil), do: q
defp map_delivery_positions(q, positions), do: %{q | positions: remap_positions(positions)}
defp delivery_kb_item_status(10), do: :draft
defp delivery_kb_item_status(18), do: :done
defp delivery_kb_item_status(20), do: :cancelled
# Invoices
@doc """
This action fetches a list of all invoices.
"""
@spec fetch_invoices(
req :: Req.Request.t(),
opts :: [GlobalArguments.offset_arg()]
) :: {:ok, [Invoice.t()]} | api_error_type
def fetch_invoices(req, opts \\ []) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_invoice", params: opts_to_query(opts))
end,
&map_from_invoices/2
)
end
@doc """
Search invoices via query.
The following search fields are supported:
* id
* kb_item_status
* document_nr
* title
* contact_id
* contact_sub_id
* user_id
* currency_id
* total_gross
* total_net
* total
* is_valid_from
* is_valid_to
* updated_at
"""
@spec search_invoices(
req :: Req.Request.t(),
criteria :: list(SearchCriteria.t()),
opts :: [GlobalArguments.offset_arg()]
) :: {:ok, [Invoice.t()]} | api_error_type
def search_invoices(
req,
criteria,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_invoice/search", json: criteria, params: opts_to_query(opts))
end,
&map_from_invoices/2
)
end
@doc """
This action fetches a single invoice
"""
@spec fetch_invoice(
req :: Req.Request.t(),
invoice_id :: pos_integer()
) :: {:ok, Quote.t()} | api_error_type
def fetch_invoice(req, invoice_id) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_invoice/#{invoice_id}")
end,
&map_from_invoice/2
)
end
@doc """
Create an invoice (id in order will be ignored). Be also aware: whether you need or must not send a document number depends on the settings in Bexio. It cannot be controlled by the API
and as such will just send if it exists.
"""
@spec create_invoice(
req :: Req.Request.t(),
invoice :: Invoice.t()
) :: {:ok, Invoice.t()} | api_error_type
def create_invoice(req, invoice) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_invoice", json: remap_invoice(invoice))
end,
&map_from_invoice/2
)
end
@doc """
Edit an invoice.
"""
@spec edit_invoice(
req :: Req.Request.t(),
invoice :: Invoice.t()
) :: {:ok, Invoice.t()} | api_error_type
def edit_invoice(req, invoice) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_invoice/#{invoice.id}", json: remap_edit_invoice(invoice))
end,
&map_from_invoice/2
)
end
@doc """
Delete an invoice.
"""
@spec delete_invoice(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def delete_invoice(req, id) do
bexio_body_handling(
fn ->
Req.delete(req, url: "/2.0/kb_invoice/#{id}")
end,
&success_response/2
)
end
@doc """
Issue an invoice.
"""
@spec issue_invoice(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def issue_invoice(req, id) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_invoice/#{id}/issue")
end,
&success_response/2
)
end
@doc """
Revert Issue an invoice.
"""
@spec revert_issue_invoice(
req :: Req.Request.t(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def revert_issue_invoice(req, id) do
bexio_body_handling(
fn ->
Req.post(req, url: "/2.0/kb_invoice/#{id}/revert_issue")
end,
&success_response/2
)
end
@doc """
This action returns a pdf document of the quote
"""
@spec invoice_pdf(
req :: Req.Request.t(),
invoice_id :: pos_integer()
) :: {:ok, map()} | api_error_type
def invoice_pdf(req, invoice_id) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_invoice/#{invoice_id}/pdf")
end,
&map_from_pdf/2
)
end
defp remap_invoice(
%Invoice{
positions: positions
} = invoice
) do
invoice
|> remap_edit_invoice()
|> Map.put(:positions, Enum.map(positions, &map_to_post_position/1))
end
defp remap_edit_invoice(%Invoice{
title: title,
document_nr: document_nr,
contact_id: contact_id,
contact_sub_id: contact_sub_id,
user_id: user_id,
project_id: project_id,
language_id: language_id,
bank_account_id: bank_account_id,
currency_id: curency_id,
payment_type_id: payment_type_id,
header: header,
footer: footer,
mwst_type: mwst_type,
mwst_is_net?: mwst_is_net?,
show_position_taxes?: show_position_taxes?,
is_valid_from: is_valid_from,
is_valid_to: is_valid_to,
api_reference: api_reference,
template_slug: template_slug,
reference: reference
}) do
%{
title: title,
document_nr: document_nr,
contact_id: contact_id,
contact_sub_id: contact_sub_id,
user_id: user_id,
pr_project_id: project_id,
language_id: language_id,
bank_account_id: bank_account_id,
currency_id: curency_id,
payment_type_id: payment_type_id,
header: header,
footer: footer,
mwst_type: mwst_type_id(mwst_type),
mwst_is_net: mwst_is_net?,
show_position_taxes: show_position_taxes?,
is_valid_from: to_iso8601(is_valid_from),
is_valid_to: to_iso8601(is_valid_to),
reference: reference,
api_reference: api_reference,
template_slug: template_slug
}
|> remove_document_no_if_nil()
end
defp map_from_invoices(invoices, _env), do: Enum.map(invoices, &map_from_invoice/1)
defp map_from_invoice(
%{
"id" => id,
"document_nr" => document_nr,
"title" => title,
"contact_id" => contact_id,
"contact_sub_id" => contact_sub_id,
"user_id" => user_id,
"project_id" => project_id,
"language_id" => language_id,
"bank_account_id" => bank_account_id,
"currency_id" => currency_id,
"payment_type_id" => payment_type_id,
"header" => header,
"footer" => footer,
"total_gross" => total_gross,
"total_net" => total_net,
"total_taxes" => total_taxes,
"total" => total,
"total_rounding_difference" => total_rounding_difference,
"mwst_type" => mwst_type_id,
"mwst_is_net" => mwst_is_net?,
"show_position_taxes" => show_position_taxes?,
"is_valid_from" => is_valid_from,
"is_valid_to" => is_valid_to,
"contact_address" => contact_address,
"kb_item_status_id" => kb_item_status_id,
"api_reference" => api_reference,
"viewed_by_client_at" => viewed_by_client_at,
"updated_at" => updated_at,
"template_slug" => template_slug,
"taxs" => taxs,
"network_link" => network_link,
"esr_id" => esr_id,
"qr_invoice_id" => qr_invoice_id,
"reference" => reference,
"total_credit_vouchers" => total_credit_vouchers,
"total_received_payments" => total_received_payments,
"total_remaining_payments" => total_remaining_payments
} = map,
_env \\ nil
) do
%Invoice{
id: id,
document_nr: document_nr,
title: title,
contact_id: contact_id,
contact_sub_id: contact_sub_id,
user_id: user_id,
project_id: project_id,
language_id: language_id,
bank_account_id: bank_account_id,
currency_id: currency_id,
payment_type_id: payment_type_id,
header: header,
footer: footer,
total_gross: to_decimal(total_gross),
total_net: to_decimal(total_net),
total_taxes: to_decimal(total_taxes),
total: to_decimal(total),
total_rounding_difference: total_rounding_difference,
mwst_type: mwst_type(mwst_type_id),
mwst_is_net?: mwst_is_net?,
show_position_taxes?: show_position_taxes?,
is_valid_from: to_date(is_valid_from),
is_valid_to: to_date(is_valid_to),
contact_address: contact_address,
kb_item_status: invoice_kb_item_status(kb_item_status_id),
api_reference: api_reference,
viewed_by_client_at: to_datetime(viewed_by_client_at),
updated_at: to_datetime(updated_at),
template_slug: template_slug,
taxs: Enum.map(taxs, &to_tax/1),
network_link: network_link,
esr_id: esr_id,
qr_invoice_id: qr_invoice_id,
reference: reference,
total_credit_vouchers: to_decimal(total_credit_vouchers),
total_received_payments: to_decimal(total_received_payments),
total_remaining_payments: to_decimal(total_remaining_payments)
}
|> map_invoice_positions(Map.get(map, "positions"))
end
defp map_invoice_positions(q, nil), do: q
defp map_invoice_positions(q, positions), do: %{q | positions: remap_positions(positions)}
defp invoice_kb_item_status(7), do: :draft
defp invoice_kb_item_status(8), do: :pending
defp invoice_kb_item_status(9), do: :paid
defp invoice_kb_item_status(16), do: :partial
defp invoice_kb_item_status(19), do: :cancelled
defp invoice_kb_item_status(31), do: :unpaid
# Document Settings
@doc """
Fetch a list of document settings
"""
@spec fetch_document_settings(
req :: Req.Request.t(),
opts :: [GlobalArguments.offset_arg()]
) ::
{:ok, [DocumentSetting.t()]} | api_error_type
def fetch_document_settings(req, opts \\ []) do
bexio_body_handling(
fn ->
Req.get(req, url: "/2.0/kb_item_setting", params: opts_to_query(opts))
end,
&map_from_document_settings/2
)
end
defp map_from_document_settings(document_settings, _env),
do: Enum.map(document_settings, &map_from_document_setting/1)
defp map_from_document_setting(
%{
"id" => id,
"text" => text,
"kb_item_class" => kb_item_class,
"enumeration_format" => enumeration_format,
"use_automatic_enumeration" => use_automatic_enumeration,
"use_yearly_enumeration" => use_yearly_enumeration,
"next_nr" => next_nr,
"nr_min_length" => nr_min_length,
"default_time_period_in_days" => default_time_period_in_days,
"default_logopaper_id" => default_logopaper_id,
"default_language_id" => default_language_id,
"default_client_bank_account_new_id" => default_client_bank_account_new_id,
"default_currency_id" => default_currency_id,
"default_mwst_type" => default_mwst_type,
"default_mwst_is_net" => default_mwst_is_net,
"default_nb_decimals_amount" => default_nb_decimals_amount,
"default_nb_decimals_price" => default_nb_decimals_price,
"default_show_position_taxes" => default_show_position_taxes,
"default_title" => default_title,
"default_show_esr_on_same_page" => default_show_esr_on_same_page?,
"default_payment_type_id" => default_payment_type_id?,
"kb_terms_of_payment_template_id" => kb_terms_of_payment_template_id,
"default_show_total" => default_show_total
},
_env \\ nil
) do
%DocumentSetting{
id: id,
text: text,
kb_item_class: String.to_atom(kb_item_class),
enumeration_format: enumeration_format,
automatic_enumeration?: use_automatic_enumeration,
yearly_enumeration?: use_yearly_enumeration,
next_nr: next_nr,
nr_min_length: nr_min_length,
default_time_period_in_days: default_time_period_in_days,
default_logopaper_id: default_logopaper_id,
default_language_id: default_language_id,
default_client_bank_account_new_id: default_client_bank_account_new_id,
default_currency_id: default_currency_id,
default_mwst_type: default_mwst_type,
default_mwst_net?: default_mwst_is_net,
default_nb_decimals_amount: default_nb_decimals_amount,
default_nb_decimals_price: default_nb_decimals_price,
default_show_position_taxes?: default_show_position_taxes,
default_title: default_title,
default_show_esr_on_same_page?: default_show_esr_on_same_page?,
default_payment_type_id: default_payment_type_id?,
kb_terms_of_payment_template_id: kb_terms_of_payment_template_id,
default_show_total?: default_show_total
}
end
# Comments
@doc """
This action fetches a list of comments.
"""
@spec fetch_comments(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
opts :: [GlobalArguments.offset_without_order_by_arg()]
) :: {:ok, [Comment.t()]} | api_error_type
def fetch_comments(
req,
document_type,
document_id,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.get(req,
url: "/2.0/kb_#{document_type}/#{document_id}/comment",
params: opts_to_query(opts)
)
end,
&map_from_comments/2
)
end
@doc """
This action fetches a single comment.
"""
@spec fetch_comment(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
comment_id :: pos_integer()
) :: {:ok, Comment.t()} | api_error_type
def fetch_comment(
req,
document_type,
document_id,
comment_id
) do
bexio_body_handling(
fn ->
Req.get(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/comment/#{comment_id}"
)
end,
&map_from_comment/2
)
end
@doc """
Create a comment
"""
@spec create_comment(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
comment :: Comment.t()
) :: {:ok, Comment.t()} | api_error_type
def create_comment(
req,
document_type,
document_id,
comment
) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/comment",
json: mapped_comment(comment)
)
end,
&map_from_comment/2
)
end
defp mapped_comment(%{
text: text,
user_id: user_id,
user_email: user_email,
user_name: user_name,
public?: public?
}) do
%{
text: text,
user_id: user_id,
user_email: user_email,
user_name: user_name,
is_public: public?
}
end
defp map_from_comments(comments, _env), do: Enum.map(comments, &map_from_comment/1)
defp map_from_comment(
%{
"id" => id,
"text" => text,
"user_id" => user_id,
"user_email" => user_email,
"user_name" => user_name,
"date" => date,
"is_public" => public?,
"image" => image,
"image_path" => image_path
},
_env \\ nil
) do
%Comment{
id: id,
text: text,
user_id: user_id,
user_email: user_email,
user_name: user_name,
date: to_datetime(date),
public?: public?,
image: image,
image_path: image_path
}
end
# Remap positions for single orders / quotes
defp remap_positions([]), do: []
defp remap_positions([position | tl]), do: [remap_position(position) | remap_positions(tl)]
defp remap_position(%{"type" => "KbPositionCustom"} = position),
do: map_from_default_position(position)
defp remap_position(%{"type" => "KbPositionArticle"} = position),
do: map_from_item_position(position)
defp remap_position(%{"type" => "KbPositionText"} = position),
do: map_from_text_position(position)
defp remap_position(%{"type" => "KbPositionSubtotal"} = position),
do: map_from_subtotal_position(position)
defp remap_position(%{"type" => "KbPositionPagebreak"} = position),
do: map_from_pagebreak_position(position)
defp remap_position(%{"type" => "KbPositionDiscount"} = position),
do: map_from_discount_position(position)
# Subtotal Positions
@doc """
This action fetches a list of all subtotal positions for a document.
"""
@spec fetch_subtotal_positions(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
opts :: [GlobalArguments.offset_without_order_by_arg()]
) :: {:ok, [PositionSubtotal.t()]} | api_error_type
def fetch_subtotal_positions(
req,
document_type,
document_id,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.get(req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_subtotal",
params: opts_to_query(opts)
)
end,
&map_from_subtotal_positions/2
)
end
@doc """
This action fetches a single subtotal position for a document.
"""
@spec fetch_subtotal_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer()
) :: {:ok, PositionSubtotal.t()} | api_error_type
def fetch_subtotal_position(
req,
document_type,
document_id,
position_id
) do
bexio_body_handling(
fn ->
Req.get(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_subtotal/#{position_id}"
)
end,
&map_from_subtotal_position/2
)
end
@doc """
Create a subtotal position.
"""
@spec create_subtotal_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
text :: String.t()
) :: {:ok, PositionSubtotal.t()} | api_error_type
def create_subtotal_position(req, document_type, document_id, text) do
bexio_body_handling(
fn ->
Req.post(req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_subtotal",
json: %{
text: text
}
)
end,
&map_from_subtotal_position/2
)
end
@doc """
Edit a subtotal position.
"""
@spec edit_subtotal_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer(),
text :: String.t()
) :: {:ok, PositionSubtotal.t()} | api_error_type
def edit_subtotal_position(req, document_type, document_id, position_id, text) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_subtotal/#{position_id}",
json: %{text: text}
)
end,
&map_from_subtotal_position/2
)
end
@doc """
Delete a subtotal position.
"""
@spec delete_subtotal_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def delete_subtotal_position(req, document_type, document_id, id) do
bexio_body_handling(
fn ->
Req.delete(req, url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_subtotal/#{id}")
end,
&success_response/2
)
end
defp map_from_subtotal_positions(subtotal_positions, _env),
do: Enum.map(subtotal_positions, &map_from_subtotal_position/1)
defp map_from_subtotal_position(
%{
"id" => id,
"text" => text,
"value" => value,
"internal_pos" => internal_pos,
"is_optional" => optional?,
"parent_id" => parent_id
},
_env \\ nil
) do
%PositionSubtotal{
id: id,
text: text,
value: decimal_nil_as_zero(value),
internal_pos: internal_pos,
optional?: optional?,
parent_id: parent_id
}
end
### Text Position
@doc """
This action fetches a list of all text positions for a document.
"""
@spec fetch_text_positions(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
opts :: [GlobalArguments.offset_without_order_by_arg()]
) :: {:ok, [PositionText.t()]} | api_error_type
def fetch_text_positions(
req,
document_type,
document_id,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.get(req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_text",
params: opts_to_query(opts)
)
end,
&map_from_text_positions/2
)
end
@doc """
This action fetches a single text position for a document.
"""
@spec fetch_text_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer()
) :: {:ok, PositionText.t()} | api_error_type
def fetch_text_position(
req,
document_type,
document_id,
position_id
) do
bexio_body_handling(
fn ->
Req.get(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_text/#{position_id}"
)
end,
&map_from_text_position/2
)
end
@doc """
Create a text position
"""
@spec create_text_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
text :: String.t(),
show_pos_nr? :: boolean()
) :: {:ok, PositionText.t()} | api_error_type
def create_text_position(req, document_type, document_id, text, show_pos_nr?) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_text",
json: %{text: text, show_pos_nr: show_pos_nr?}
)
end,
&map_from_text_position/2
)
end
@doc """
Edit a text position.
"""
@spec edit_text_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer(),
text :: String.t(),
show_pos_nr? :: boolean()
) :: {:ok, PositionText.t()} | api_error_type
def edit_text_position(req, document_type, document_id, position_id, text, show_pos_nr?) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_text/#{position_id}",
json: %{text: text, show_pos_nr: show_pos_nr?}
)
end,
&map_from_text_position/2
)
end
@doc """
Delete a text position.
"""
@spec delete_text_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def delete_text_position(req, document_type, document_id, id) do
bexio_body_handling(
fn ->
Req.delete(req, url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_text/#{id}")
end,
&success_response/2
)
end
defp map_from_text_positions(text_positions, _env),
do: Enum.map(text_positions, &map_from_text_position/1)
defp map_from_text_position(
%{
"id" => id,
"text" => text,
"show_pos_nr" => show_pos_nr?,
"pos" => pos,
"internal_pos" => internal_pos,
"is_optional" => optional?,
"parent_id" => parent_id
},
_env \\ nil
) do
%PositionText{
id: id,
text: text,
show_pos_nr?: show_pos_nr?,
pos: pos,
internal_pos: internal_pos,
optional?: optional?,
parent_id: parent_id
}
end
### Default Position
@doc """
This action fetches a list of all default positions for a document.
"""
@spec fetch_default_positions(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
opts :: [GlobalArguments.offset_without_order_by_arg()]
) :: {:ok, [PositionDefault.t()]} | api_error_type
def fetch_default_positions(
req,
document_type,
document_id,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.get(req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_custom",
params: opts_to_query(opts)
)
end,
&map_from_default_positions/2
)
end
@doc """
This action fetches a single default position for a document.
"""
@spec fetch_default_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer()
) :: {:ok, PositionDefault.t()} | api_error_type
def fetch_default_position(
req,
document_type,
document_id,
position_id
) do
bexio_body_handling(
fn ->
Req.get(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_custom/#{position_id}"
)
end,
&map_from_default_position/2
)
end
@doc """
Create a default position
"""
@spec create_default_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position :: PositionDefault.t()
) :: {:ok, PositionDefault.t()} | api_error_type
def create_default_position(req, document_type, document_id, position) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_custom",
json: remap_default_position(position)
)
end,
&map_from_default_position/2
)
end
@doc """
Edit a default position.
"""
@spec edit_default_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position :: PositionDefault.t()
) :: {:ok, PositionDefault.t()} | api_error_type
def edit_default_position(req, document_type, document_id, position) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_custom/#{position.id}",
json: remap_default_position(position)
)
end,
&map_from_default_position/2
)
end
@doc """
Delete a default position.
"""
@spec delete_default_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def delete_default_position(req, document_type, document_id, id) do
bexio_body_handling(
fn ->
Req.delete(req, url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_custom/#{id}")
end,
&success_response/2
)
end
defp remap_default_position(%PositionDefault{
amount: amount,
unit_id: unit_id,
account_id: account_id,
tax_id: tax_id,
text: text,
unit_price: unit_price,
discount_in_percent: discount_in_percent
}) do
%{
amount: Decimal.to_string(amount, :normal),
unit_id: unit_id,
account_id: account_id,
tax_id: tax_id,
text: text,
unit_price: Decimal.to_string(unit_price, :normal),
discount_in_percent: Decimal.to_string(discount_in_percent, :normal)
}
end
defp map_from_default_positions(default_positions, _env),
do: Enum.map(default_positions, &map_from_default_position/1)
defp map_from_default_position(
%{
"id" => id,
"amount" => amount,
"unit_id" => unit_id,
"account_id" => account_id,
"unit_name" => unit_name,
"tax_id" => tax_id,
"tax_value" => tax_value,
"text" => text,
"unit_price" => unit_price,
"discount_in_percent" => discount_in_percent,
"position_total" => position_total,
"pos" => pos,
"internal_pos" => internal_pos,
"is_optional" => optional?,
"parent_id" => parent_id
},
_env \\ nil
) do
%PositionDefault{
id: id,
amount: decimal_nil_as_zero(amount),
unit_id: unit_id,
account_id: account_id,
unit_name: unit_name,
tax_id: tax_id,
tax_value: decimal_nil_as_zero(tax_value),
text: text,
unit_price: decimal_nil_as_zero(unit_price),
discount_in_percent: decimal_nil_as_zero(discount_in_percent),
position_total: decimal_nil_as_zero(position_total),
pos: pos,
internal_pos: internal_pos,
optional?: optional?,
parent_id: parent_id
}
end
### Item Position
@doc """
This action fetches a list of all item positions for a document.
"""
@spec fetch_item_positions(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
opts :: [GlobalArguments.offset_without_order_by_arg()]
) :: {:ok, [PositionItem.t()]} | api_error_type
def fetch_item_positions(
req,
document_type,
document_id,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.get(req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_article",
params: opts_to_query(opts)
)
end,
&map_from_item_positions/2
)
end
@doc """
This action fetches a single item position for a document.
"""
@spec fetch_item_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer()
) :: {:ok, PositionItem.t()} | api_error_type
def fetch_item_position(
req,
document_type,
document_id,
position_id
) do
bexio_body_handling(
fn ->
Req.get(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_article/#{position_id}"
)
end,
&map_from_item_position/2
)
end
@doc """
Create an item position
"""
@spec create_item_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position :: PositionItem.t()
) :: {:ok, PositionItem.t()} | api_error_type
def create_item_position(req, document_type, document_id, position) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_article",
json: remap_item_position(position)
)
end,
&map_from_item_position/2
)
end
@doc """
Edit an item.
"""
@spec edit_item_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position :: PositionItem.t()
) :: {:ok, PositionItem.t()} | api_error_type
def edit_item_position(req, document_type, document_id, position) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_article/#{position.id}",
json: remap_item_position(position)
)
end,
&map_from_default_position/2
)
end
@doc """
Delete a default position.
"""
@spec delete_item_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def delete_item_position(req, document_type, document_id, id) do
bexio_body_handling(
fn ->
Req.delete(req, url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_article/#{id}")
end,
&success_response/2
)
end
defp remap_item_position(%PositionItem{
amount: amount,
unit_id: unit_id,
account_id: account_id,
tax_id: tax_id,
text: text,
unit_price: unit_price,
discount_in_percent: discount_in_percent,
article_id: article_id
}) do
%{
amount: Decimal.to_string(amount, :normal),
unit_id: unit_id,
account_id: account_id,
tax_id: tax_id,
text: text,
unit_price: Decimal.to_string(unit_price, :normal),
discount_in_percent: Decimal.to_string(discount_in_percent, :normal),
article_id: article_id
}
end
defp map_from_item_positions(item_positions, _env),
do: Enum.map(item_positions, &map_from_item_position/1)
defp map_from_item_position(
%{
"id" => id,
"amount" => amount,
"unit_id" => unit_id,
"account_id" => account_id,
"unit_name" => unit_name,
"tax_id" => tax_id,
"tax_value" => tax_value,
"text" => text,
"unit_price" => unit_price,
"discount_in_percent" => discount_in_percent,
"position_total" => position_total,
"pos" => pos,
"internal_pos" => internal_pos,
"is_optional" => optional?,
"article_id" => article_id,
"parent_id" => parent_id
},
_env \\ nil
) do
%PositionItem{
id: id,
amount: decimal_nil_as_zero(amount),
unit_id: unit_id,
account_id: account_id,
unit_name: unit_name,
tax_id: tax_id,
tax_value: decimal_nil_as_zero(tax_value),
text: text,
unit_price: decimal_nil_as_zero(unit_price),
discount_in_percent: decimal_nil_as_zero(discount_in_percent),
position_total: decimal_nil_as_zero(position_total),
pos: pos,
internal_pos: internal_pos,
optional?: optional?,
article_id: article_id,
parent_id: parent_id
}
end
### Discount Position
@doc """
This action fetches a list of all discount positions for a document.
"""
@spec fetch_discount_positions(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
opts :: [GlobalArguments.offset_without_order_by_arg()]
) :: {:ok, [PositionDiscount.t()]} | api_error_type
def fetch_discount_positions(
req,
document_type,
document_id,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.get(req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_discount",
params: opts_to_query(opts)
)
end,
&map_from_discount_positions/2
)
end
@doc """
This action fetches a single discount position for a document.
"""
@spec fetch_discount_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer()
) :: {:ok, PositionDiscount.t()} | api_error_type
def fetch_discount_position(
req,
document_type,
document_id,
position_id
) do
bexio_body_handling(
fn ->
Req.get(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_discount/#{position_id}"
)
end,
&map_from_discount_position/2
)
end
@doc """
Create a discount position
"""
@spec create_discount_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position :: PositionDiscount.t()
) :: {:ok, PositionDiscount.t()} | api_error_type
def create_discount_position(req, document_type, document_id, position) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_discount",
json: remap_discount_position(position)
)
end,
&map_from_discount_position/2
)
end
@doc """
Edit a discount position.
"""
@spec edit_discount_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position :: PositionDiscount.t()
) :: {:ok, PositionDiscount.t()} | api_error_type
def edit_discount_position(req, document_type, document_id, position) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_discount/#{position.id}",
json: remap_discount_position(position)
)
end,
&map_from_discount_position/2
)
end
@doc """
Delete a discount position.
"""
@spec delete_discount_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def delete_discount_position(req, document_type, document_id, id) do
bexio_body_handling(
fn ->
Req.delete(req, url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_discount/#{id}")
end,
&success_response/2
)
end
defp remap_discount_position(%PositionDiscount{
text: text,
percentual?: percentual?,
value: value
}) do
%{
text: text,
is_percentual: percentual?,
value: Decimal.to_string(value, :normal)
}
end
defp map_from_discount_positions(discount_positions, _env),
do: Enum.map(discount_positions, &map_from_discount_position/1)
defp map_from_discount_position(
%{
"id" => id,
"text" => text,
"value" => value,
"discount_total" => discount_total,
"is_percentual" => percentual?
},
_env \\ nil
) do
%PositionDiscount{
id: id,
text: text,
value: decimal_nil_as_zero(value),
discount_total: decimal_nil_as_zero(discount_total),
percentual?: percentual?
}
end
### Pagebreak Position
@doc """
This action fetches a list of all pagebreak positions for a document.
"""
@spec fetch_pagebreak_positions(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
opts :: [GlobalArguments.offset_without_order_by_arg()]
) :: {:ok, [PositionPagebreak.t()]} | api_error_type
def fetch_pagebreak_positions(
req,
document_type,
document_id,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.get(req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_pagebreak",
params: opts_to_query(opts)
)
end,
&map_from_pagebreak_positions/2
)
end
@doc """
This action fetches a single pagebreak position for a document.
"""
@spec fetch_pagebreak_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer()
) :: {:ok, PositionPagebreak.t()} | api_error_type
def fetch_pagebreak_position(
req,
document_type,
document_id,
position_id
) do
bexio_body_handling(
fn ->
Req.get(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_pagebreak/#{position_id}"
)
end,
&map_from_pagebreak_position/2
)
end
@doc """
Create a pagebreak position
"""
@spec create_pagebreak_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer()
) :: {:ok, PositionPagebreak.t()} | api_error_type
def create_pagebreak_position(req, document_type, document_id) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_pagebreak"
)
end,
&map_from_pagebreak_position/2
)
end
@doc """
Edit a pagebreak position.
"""
@spec edit_pagebreak_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer(),
pagebreak :: boolean()
) :: {:ok, PositionPagebreak.t()} | api_error_type
def edit_pagebreak_position(req, document_type, document_id, position_id, pagebreak) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_pagebreak/#{position_id}",
json: %{papebreak: pagebreak}
)
end,
&map_from_pagebreak_position/2
)
end
@doc """
Delete a pagebreak position.
"""
@spec delete_pagebreak_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def delete_pagebreak_position(req, document_type, document_id, id) do
bexio_body_handling(
fn ->
Req.delete(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_pagebreak/#{id}"
)
end,
&success_response/2
)
end
defp map_from_pagebreak_positions(pagebreak_positions, _env),
do: Enum.map(pagebreak_positions, &map_from_pagebreak_position/1)
defp map_from_pagebreak_position(
%{
"id" => id,
"internal_pos" => internal_pos,
"is_optional" => optional?,
"parent_id" => parent_id
},
_env \\ nil
) do
%PositionPagebreak{
id: id,
internal_pos: internal_pos,
optional?: optional?,
parent_id: parent_id
}
end
### Subposition Position
@doc """
This action fetches a list of all subposition positions for a document.
"""
@spec fetch_subposition_positions(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
opts :: [GlobalArguments.offset_without_order_by_arg()]
) ::
{:ok, [PositionSubposition.t()]} | api_error_type
def fetch_subposition_positions(
req,
document_type,
document_id,
opts \\ []
) do
bexio_body_handling(
fn ->
Req.get(req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_subposition",
params: opts_to_query(opts)
)
end,
&map_from_subposition_positions/2
)
end
@doc """
This action fetches a single subposition position for a document.
"""
@spec fetch_subposition_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer()
) ::
{:ok, PositionSubposition.t()} | api_error_type
def fetch_subposition_position(
req,
document_type,
document_id,
position_id
) do
bexio_body_handling(
fn ->
Req.get(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_subposition/#{position_id}"
)
end,
&map_from_subposition_position/2
)
end
@doc """
Create a subposition position
"""
@spec create_subposition_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
text :: String.t(),
show_pos_nr? :: boolean()
) :: {:ok, PositionSubposition.t()} | api_error_type
def create_subposition_position(req, document_type, document_id, text, show_pos_nr?) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_subposition",
json: %{text: text, show_pos_nr: show_pos_nr?}
)
end,
&map_from_subposition_position/2
)
end
@doc """
Edit a subposition position.
"""
@spec edit_subposition_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
position_id :: pos_integer(),
text :: String.t(),
show_pos_nr? :: boolean()
) :: {:ok, PositionSubposition.t()} | api_error_type
def edit_subposition_position(
req,
document_type,
document_id,
position_id,
text,
show_pos_nr?
) do
bexio_body_handling(
fn ->
Req.post(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_subposition/#{position_id}",
json: %{text: text, show_pos_nr: show_pos_nr?}
)
end,
&map_from_subposition_position/2
)
end
@doc """
Delete a subposition position.
"""
@spec delete_subposition_position(
req :: Req.Request.t(),
document_type :: :offer | :order | :invoice,
document_id :: pos_integer(),
id :: non_neg_integer()
) :: {:ok, boolean()} | api_error_type
def delete_subposition_position(req, document_type, document_id, id) do
bexio_body_handling(
fn ->
Req.delete(
req,
url: "/2.0/kb_#{document_type}/#{document_id}/kb_position_subposition/#{id}"
)
end,
&success_response/2
)
end
defp map_from_subposition_positions(subposition_positions, _env),
do: Enum.map(subposition_positions, &map_from_subposition_position/1)
defp map_from_subposition_position(
%{
"id" => id,
"text" => text,
"pos" => pos,
"internal_pos" => internal_pos,
"show_pos_nr" => show_pos_nr?,
"is_optional" => optional?,
"total_sum" => total_sum,
"show_pos_prices" => show_pos_prices?,
"parent_id" => parent_id
},
_env \\ nil
) do
%PositionSubposition{
id: id,
text: text,
pos: pos,
internal_pos: internal_pos,
show_pos_prices?: show_pos_prices?,
show_pos_nr?: show_pos_nr?,
total_sum: decimal_nil_as_zero(total_sum),
optional?: optional?,
parent_id: parent_id
}
end
end