defmodule ExCnab.Cnab240.Services.Decode do
@moduledoc """
Service to read and generate info about the CNAB 240 file.
"""
alias ExCnab.Cnab240.Services.BuildDetails
alias ExCnab.Cnab240.Templates.FileHeader
alias ExCnab.Cnab240.Templates.FileFooter
alias ExCnab.Cnab240.Services.GetFileInfo
alias ExCnab.Cnab240.Services.HasIdenticalInformations
@item_type %{
"0" => :file_header,
"1" => :chunk_header,
"2" => :chunk_first_register,
"3" => :details,
"5" => :chunk_footer,
"9" => :file_footer
}
@spec run(String.t(), Map.t()) :: {:ok, Map.t()} | {:error, String.t()}
def run(file, attrs \\ %{}) do
with list <- read_file(file),
{:ok, content} <- generate_content(file, attrs, list) do
build_response(
content.file_header,
content.details,
content.footer,
content.filename_info,
:ok
)
end
end
@spec run!(String.t(), Map.t()) :: Map.t() | {:error, String.t()}
def run!(file, attrs) do
with list <- read_file(file),
{:ok, content} <- generate_content(file, attrs, list) do
build_response(
content.file_header,
content.details,
content.footer,
content.filename_info,
:no
)
end
end
defp generate_content(file, attrs, list) do
with :ok <- HasIdenticalInformations.run(list),
{:ok, map} <- classify_by_type(list),
{:ok, file_header} <- FileHeader.generate(map.file_header, attrs),
{:ok, details} <- BuildDetails.run(map.chunks, attrs),
{:ok, footer} <- FileFooter.generate(map.file_footer, attrs),
{:ok, filename_info} <- GetFileInfo.run(file, attrs) do
{:ok,
%{
file_header: file_header,
details: details,
footer: footer,
filename_info: filename_info
}}
else
{:error, error, line_error} ->
line =
list
|> Enum.find_index(&(&1 == line_error))
|> Kernel.+(1)
{:error, error, "Falha na linha #{line}"}
{:error, error} ->
{:error, error}
end
end
defp read_file(file) do
with {:ok, content} <- File.read(file),
{:ok, %{size: size}} <- File.stat(file),
true <- size <= 524_288_000 do
String.split(content, "\r\n")
else
false ->
{:error, "File size is bigger than 500MB"}
{:error, reason} ->
{:error, "Can't read file: #{inspect(reason)}"}
end
end
defp classify_by_type(array) do
file_header = Enum.at(array, 0)
file_footer = Enum.at(array, -2)
shorted_array =
array
|> Enum.drop(1)
|> Enum.drop(-2)
chunks =
shorted_array
|> get_footers_index()
|> build_details_object(shorted_array)
{:ok, %{file_header: file_header, file_footer: file_footer, chunks: chunks}}
end
defp verify_type(raw_string) do
type = String.slice(raw_string, 7..7)
@item_type[type]
end
defp get_footers_index(shorted_array) do
Enum.reduce(shorted_array, [], fn item, acc ->
case verify_type(item) do
:chunk_footer ->
index = Enum.find_index(shorted_array, &(&1 == item))
acc ++ [index]
_ ->
acc
end
end)
end
defp build_details_object(list_footer_index, shorted_array) do
{_, details} =
Enum.reduce(list_footer_index, {shorted_array, %{chunks: []}}, fn index,
{list_acc, map_acc} ->
footer = Enum.at(shorted_array, index)
footer_index =
list_acc
|> get_footers_index()
|> hd()
{[header | details], shorted_array} = Enum.split(list_acc, footer_index + 1)
details = Enum.drop(details, -1)
key_id = Enum.find_index(list_footer_index, &(&1 == index)) + 1
new_map = %{
"chunk_register_#{key_id}": %{
header: header,
detail: details,
footer: footer
}
}
{shorted_array, Map.update(map_acc, :chunks, [new_map], &(&1 ++ [new_map]))}
end)
details
end
defp build_response(file_header, details, footer, filename_info, ok?) do
response = %{
cnab240: %{
header_arquivo: file_header,
detalhes: details,
trailer: footer
},
informacoes_extras: filename_info
}
case ok? do
:ok ->
{:ok, response}
_ ->
response
end
end
end