defmodule PTAX.Quotation do
@moduledoc "Define a quotation structure for a currency"
use TypedStruct
alias PTAX.{Error, Money, Requests}
@typep currency :: Money.currency()
@typep date :: Date.t()
@typep period :: Date.Range.t()
@typep bulletin :: :opening | :intermediary | :closing
typedstruct enforce: true do
field :pair, Money.Pair.t()
field :quoted_in, DateTime.t()
field :bulletin, bulletin
end
@doc """
Returns the quotation of a currency for the date
## Examples
iex> PTAX.Quotation.get(:USD, ~D[2021-12-24])
{
:ok,
%PTAX.Quotation{
pair: PTAX.Money.Pair.new("1.0", "1.0", :USD, :USD),
quoted_in: DateTime.from_naive!(~N[2021-12-24 11:04:02.178], "America/Sao_Paulo"),
bulletin: :closing
}
}
"""
@spec get(currency, date, bulletin | nil) :: {:ok, t()} | {:error, Error.t()}
def get(currency, date, bulletin \\ :closing) do
period = Date.range(date, date)
case list(currency, period, bulletin) do
{:ok, [quotation]} ->
{:ok, quotation}
{:ok, quotations} ->
error =
Error.new(
code: :not_found,
message: "Expected at most one result but got #{length(quotations)}"
)
{:error, error}
{:error, error} ->
{:error, error}
end
end
@doc """
Returns a quotation list of a currency for a period
## Examples
iex> PTAX.Quotation.list(:GBP, Date.range(~D[2021-12-24], ~D[2021-12-24]))
{
:ok,
[
%PTAX.Quotation{
pair: PTAX.Money.Pair.new("1.3417", "1.3421", :GBP, :USD),
quoted_in: DateTime.from_naive!(~N[2021-12-24 10:08:31.922], "America/Sao_Paulo"),
bulletin: :opening
},
%PTAX.Quotation{
pair: PTAX.Money.Pair.new("1.3402", "1.3406", :GBP, :USD),
quoted_in: DateTime.from_naive!(~N[2021-12-24 11:04:02.173], "America/Sao_Paulo"),
bulletin: :intermediary
},
%PTAX.Quotation{
pair: PTAX.Money.Pair.new("1.3402", "1.3406", :GBP, :USD),
quoted_in: DateTime.from_naive!(~N[2021-12-24 11:04:02.178], "America/Sao_Paulo"),
bulletin: :closing
}
]
}
"""
@spec list(
currency,
period,
bulletin | list(bulletin) | nil
) :: {:ok, list(t)} | {:error, Error.t()}
def list(currency, period, bulletin \\ nil) do
params = [
{"moeda", if(currency == :BRL, do: :USD, else: currency)},
{"dataInicial", Timex.format!(period.first, "%m-%d-%Y", :strftime)},
{"dataFinalCotacao", Timex.format!(period.last, "%m-%d-%Y", :strftime)}
]
quotation_request = Requests.get("/CotacaoMoedaPeriodo", opts: [odata_params: params])
with {:ok, quotation} <- Requests.response(quotation_request) do
result =
quotation
|> Enum.map(&(&1 |> prepare(currency) |> parse()))
|> filter(bulletin)
{:ok, result}
end
end
defp prepare(quotation, :BRL) do
Map.merge(quotation, %{
"simbolo" => "BRL",
"tipo_moeda" => "A",
"paridade_compra" => quotation["cotacao_compra"],
"paridade_venda" => quotation["cotacao_venda"]
})
end
defp prepare(quotation, currency_symbol) do
"/Moedas?$filter=simbolo%20eq%20':currency'"
|> Requests.get(opts: [path_params: [currency: currency_symbol]])
|> Requests.response()
|> elem(1)
|> hd()
|> Map.merge(quotation)
end
defp parse(value) do
currency_symbol = String.to_existing_atom(value["simbolo"])
{base_currency, quoted_currency} =
case value["tipo_moeda"] do
"A" -> {:USD, currency_symbol}
"B" -> {currency_symbol, :USD}
end
bulletin =
case value["tipo_boletim"] do
"Abertura" -> :opening
"Intermediário" -> :intermediary
"Fechamento" -> :closing
end
params = %{
pair:
Money.Pair.new(
value["paridade_compra"],
value["paridade_venda"],
base_currency,
quoted_currency
),
quoted_in:
value
|> Map.get("data_hora_cotacao")
|> Timex.parse!("{ISO:Extended}")
|> Timex.Timezone.convert("America/Sao_Paulo"),
bulletin: bulletin
}
struct!(__MODULE__, params)
end
defp filter(quotations, nil) do
quotations
end
defp filter(quotations, bulletins) when is_list(bulletins) do
Enum.filter(quotations, &(&1.bulletin in bulletins))
end
defp filter(quotations, bulletin) do
Enum.filter(quotations, &(&1.bulletin == bulletin))
end
end