From 227ca344bacdee0a2aa7c95972ed83536337412a Mon Sep 17 00:00:00 2001 From: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> Date: Sun, 2 Apr 2023 11:48:56 +0800 Subject: [PATCH 1/9] refactor: updated specs Signed-off-by: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> --- lib/aba_file_validator.ex | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/aba_file_validator.ex b/lib/aba_file_validator.ex index d862f24..0ff02d8 100644 --- a/lib/aba_file_validator.ex +++ b/lib/aba_file_validator.ex @@ -5,9 +5,9 @@ defmodule AbaFileValidator do Documentation for `AbaFileValidator`. """ - @spec get_descriptive_record(binary) :: + @spec get_descriptive_record(String.t()) :: {:error, :incorrect_length | :incorrect_starting_code | :invalid_format} - | {:ok, binary(), binary(), binary(), binary(), binary(), binary()} + | {:ok, String.t(), String.t(), String.t(), String.t(), String.t(), String.t()} @doc """ Get the entries as part of the descriptiive record @@ -99,6 +99,22 @@ defmodule AbaFileValidator do end end + @spec get_file_total_record(String.t(), integer()) :: + {:error, :incorrect_length | :incorrect_starting_code | :invalid_input} + | {:error, :invalid_format, + [ + :bsb_filler + | :first_blank + | :last_blank + | :mid_blank + | :net_total + | :net_total_mismatch + | :record_count + | :records_mismatch + | :total_credit + | :total_debit + ]} + | {:ok, integer(), integer(), integer(), integer()} @doc """ Get the entries as part of the file total record @@ -207,7 +223,7 @@ defmodule AbaFileValidator do end end - @spec get_transaction_code_description(any) :: :error | binary() + @spec get_transaction_code_description(String.t()) :: :error | String.t() @doc """ Get a description for a given transaction code From 712c0cce8035286af181bef81a41ade73bb43466 Mon Sep 17 00:00:00 2001 From: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> Date: Sun, 2 Apr 2023 12:53:41 +0800 Subject: [PATCH 2/9] refactor: updated method to get description based on atom Signed-off-by: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> --- lib/aba_file_validator.ex | 54 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/aba_file_validator.ex b/lib/aba_file_validator.ex index 0ff02d8..d8be2a0 100644 --- a/lib/aba_file_validator.ex +++ b/lib/aba_file_validator.ex @@ -226,38 +226,86 @@ defmodule AbaFileValidator do @spec get_transaction_code_description(String.t()) :: :error | String.t() @doc """ Get a description for a given transaction code + defp get_transaction_code("13"), do: :externally_initiated_debit; + + defp get_transaction_code("50"), + do: :externally_initiated_credit + + defp get_transaction_code("51"), do: :australian_government_security_interest + defp get_transaction_code("52"), do: :family_allowance + defp get_transaction_code("53"), do: :pay + defp get_transaction_code("54"), do: :pension + defp get_transaction_code("55"), do: :allotment + defp get_transaction_code("56"), do: :dividend + defp get_transaction_code("57"), do: :debenture_note_interest + defp get_transaction_code(_), do: :error + + @spec get_transaction_code_description(String.t()| integer() | atom()) :: :error | String.t() + @doc """ + Get a description for a given transaction code. See [here](https://www.cemtexaba.com/aba-format/cemtex-aba-file-format-details) for the possible transaction code + + The following atoms are valid inputs: + - :allotment + - :australian_government_security_interest + - :debenture_note_interest + - :dividend + - :error + - :externally_initiated_credit + - :externally_initiated_debit + - :family_allowance + - :pay + - :pension ## Examples iex> AbaFileValidator.get_transaction_code_description("11") :error - iex> AbaFileValidator.get_transaction_code_description(13) - "Externally initiated debit items" + iex> AbaFileValidator.get_transaction_code_description(53) + "Pay" + + iex> AbaFileValidator.get_transaction_code_description(:australian_government_security_interest) + "Australian Government Security Interest" """ def get_transaction_code_description(13), do: "Externally initiated debit items" def get_transaction_code_description("13"), do: "Externally initiated debit items" + def get_transaction_code_description(:externally_initiated_debit), do: "Externally initiated debit items" def get_transaction_code_description(50), do: "Externally initiated credit items with the exception of those bearing Transaction Codes" - def get_transaction_code_description("50"), do: "Externally initiated credit items with the exception of those bearing Transaction Codes" + def get_transaction_code_description(:externally_initiated_credit), + do: "Externally initiated credit items with the exception of those bearing Transaction Codes" def get_transaction_code_description(51), do: "Australian Government Security Interest" def get_transaction_code_description("51"), do: "Australian Government Security Interest" + def get_transaction_code_description(:australian_government_security_interest), do: "Australian Government Security Interest" + def get_transaction_code_description(52), do: "Family Allowance" def get_transaction_code_description("52"), do: "Family Allowance" + def get_transaction_code_description(:family_allowance), do: "Family Allowance" + def get_transaction_code_description(53), do: "Pay" def get_transaction_code_description("53"), do: "Pay" + def get_transaction_code_description(:pay), do: "Pay" + def get_transaction_code_description(54), do: "Pension" def get_transaction_code_description("54"), do: "Pension" + def get_transaction_code_description(:pension), do: "Pension" + def get_transaction_code_description(55), do: "Allotment" def get_transaction_code_description("55"), do: "Allotment" + def get_transaction_code_description(:allotment), do: "Allotment" + def get_transaction_code_description(56), do: "Dividend" def get_transaction_code_description("56"), do: "Dividend" + def get_transaction_code_description(:dividend), do: "Dividend" + def get_transaction_code_description(57), do: "Debenture/Note Interest" def get_transaction_code_description("57"), do: "Debenture/Note Interest" + def get_transaction_code_description(:debenture_note_interest), do: "Debenture/Note Interest" + def get_transaction_code_description(_), do: :error end From d617ddc031976dc804e70e7a077e702da80bfd7d Mon Sep 17 00:00:00 2001 From: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> Date: Sun, 2 Apr 2023 13:17:05 +0800 Subject: [PATCH 3/9] feature: added new method to validate bsb Signed-off-by: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> --- lib/util.ex | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/lib/util.ex b/lib/util.ex index d660b88..bc15fa7 100644 --- a/lib/util.ex +++ b/lib/util.ex @@ -1,20 +1,42 @@ defmodule AbaFileValidator.Utils do def correct_length?(entry, n) when is_binary(entry), do: String.length(entry) == n + def string_empty?(entry) when is_binary(entry), do: String.trim(entry) |> String.length() == 0 + @spec valid_date?(String.t()) :: boolean def valid_date?(<>) do if string_empty?(dd) or - string_empty?(mm) or - string_empty?(yy) do - false - else - [yy, mm, dd] = for i <- [yy, mm, dd], do: String.to_integer(i) - - NaiveDateTime.new(2000 + yy, mm, dd, 0, 0, 0) - |> case do - {:ok, _} -> true - {:error, _} -> false - end + string_empty?(mm) or + string_empty?(yy) do + false + else + [yy, mm, dd] = for i <- [yy, mm, dd], do: String.to_integer(i) + + NaiveDateTime.new(2000 + yy, mm, dd, 0, 0, 0) + |> case do + {:ok, _} -> true + {:error, _} -> false end + end + end + + @spec valid_bsb?(String.t()) :: boolean + def valid_bsb?(<>, last::binary-3>>) do + cond do + string_empty?(first) or + string_empty?(last) -> + false + + Integer.parse(first) === :error -> + false + + Integer.parse(last) === :error -> + false + + true -> + true + end end + + def valid_bsb?(_bsb), do: false end From 200832d94c751d9acd0f27f59701d650fcd3cdc1 Mon Sep 17 00:00:00 2001 From: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> Date: Sun, 2 Apr 2023 13:17:25 +0800 Subject: [PATCH 4/9] test: updated tests Signed-off-by: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> --- test/aba_file_validator_test.exs | 65 +++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/test/aba_file_validator_test.exs b/test/aba_file_validator_test.exs index 1bbf871..a2d0d56 100644 --- a/test/aba_file_validator_test.exs +++ b/test/aba_file_validator_test.exs @@ -88,7 +88,7 @@ defmodule AbaFileValidatorTest do end end - describe "AbaFileValidator.get_file_total_record/1" do + describe "AbaFileValidator.get_file_total_record/2" do test "validates succesfully" do entry = "7999-999 000000000000000353890000035389 000000 " @@ -127,7 +127,7 @@ defmodule AbaFileValidatorTest do assert AbaFileValidator.get_file_total_record(entry) == {:error, :invalid_format, - [:bsb_filler, :net_total, :total_credit, :total_debit, :record_count]} + [:bsb_filler, :net_total, :total_credit, :total_debit, :record_count]} end test "returns an error if balance don't match" do @@ -146,4 +146,65 @@ defmodule AbaFileValidatorTest do {:error, :invalid_format, [:bsb_filler, :records_mismatch]} end end + + describe "AbaFileValidator.get_detail_record/1" do + test "validates succesfully" do + entry = + "1032-898 12345678 130000035389 money Batch payment 040-404 12345678 test 00000000" + + assert AbaFileValidator.get_detail_record(entry) == + {:ok, "032-898", "12345678", :blank, :externally_initiated_debit, 35389, " money", + " Batch payment", "040-404", "12345678", " test", 0} + + entry = + "1032-8980-2345678N130000035389money Batch payment 040-404 12345678test 00000000" + + assert AbaFileValidator.get_detail_record(entry) == + {:ok, "032-898", "0-2345678", :new_bank, :externally_initiated_debit, 35389, + "money", "Batch payment", "040-404", "12345678", "test", 0} + end + + test "returns an error if incorrect length with correct starting code" do + assert AbaFileValidator.get_detail_record("1") == {:error, :incorrect_length} + end + + test "returns an error if incorrect length with incorrect starting code" do + assert AbaFileValidator.get_detail_record("7") == {:error, :incorrect_length} + end + + test "returns an error if incorrect starting code" do + entry = + "7032-898 12345678 130000035389money Batch payment 040-404 12345678test 00000000" + + assert AbaFileValidator.get_detail_record(entry) == + {:error, :incorrect_starting_code} + end + + test "returns an error if invalid string" do + entry = + "1032 898 12345678 130000035389money Batch payment 040 404 12345678test 00000000" + + assert AbaFileValidator.get_detail_record(entry) == + {:error, :invalid_format, [:bsb, :trace_record]} + end + + test "returns an error if empty string" do + entry = + "1 " + + assert AbaFileValidator.get_detail_record(entry) == + {:error, :invalid_format, + [ + :bsb, + :account_number, + :indicator, + :amount, + :account_name, + :reference, + :trace_record, + :remitter, + :withheld_tax + ]} + end + end end From 0dfe65f2215fe4c607bd46363d1fd3c18b21b08b Mon Sep 17 00:00:00 2001 From: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> Date: Sun, 2 Apr 2023 13:40:27 +0800 Subject: [PATCH 5/9] refactor: added logic to validate detail Signed-off-by: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> --- lib/aba_file_validator.ex | 168 +++++++++++++++++++++++++++++-- test/aba_file_validator_test.exs | 3 +- 2 files changed, 163 insertions(+), 8 deletions(-) diff --git a/lib/aba_file_validator.ex b/lib/aba_file_validator.ex index d8be2a0..92c74e5 100644 --- a/lib/aba_file_validator.ex +++ b/lib/aba_file_validator.ex @@ -1,9 +1,10 @@ defmodule AbaFileValidator do - import __MODULE__.Utils, only: [correct_length?: 2, string_empty?: 1, valid_date?: 1] + import __MODULE__.Utils @moduledoc """ Documentation for `AbaFileValidator`. """ + alias Mix.Tasks.Compile.Yecc @spec get_descriptive_record(String.t()) :: {:error, :incorrect_length | :incorrect_starting_code | :invalid_format} @@ -179,6 +180,7 @@ defmodule AbaFileValidator do errors = if not correct_length?(mid_blank, 24), do: errors ++ [:mid_blank], else: errors + # TODO Validate amount is positve errors = if string_empty?(net_total), do: errors ++ [:net_total], else: errors errors = if string_empty?(total_credit), do: errors ++ [:total_credit], else: errors @@ -204,6 +206,7 @@ defmodule AbaFileValidator do errors end + # TODO Validate total amount from detail match debit/credit errors = unless string_empty?(record_count) do if records !== String.to_integer(record_count), @@ -223,10 +226,148 @@ defmodule AbaFileValidator do end end - @spec get_transaction_code_description(String.t()) :: :error | String.t() @doc """ - Get a description for a given transaction code - defp get_transaction_code("13"), do: :externally_initiated_debit; + Get the entries as part of the detail record + + ## Examples + + iex> AbaFileValidator.get_detail_record(1) + {:error, :invalid_input} + + iex> AbaFileValidator.get_detail_record("11") + {:error, :incorrect_length} + + iex> AbaFileValidator.get_detail_record("01") + {:error, :incorrect_length} + + iex> AbaFileValidator.get_detail_record("1032 898 12345678 130000035389money Batch payment 040 404 12345678test 00000000") + {:error, :invalid_format, [:bsb,:trace_record]} + + iex> AbaFileValidator.get_detail_record("1 ") + {:error, :invalid_format, + [:bsb, :account_number, :transasction_code, :amount, :account_name, :reference, :trace_record, :trace_account_number, :remitter, :withheld_tax]} + + iex> AbaFileValidator.get_detail_record("7999 999 000000000000000353890000035388 000002 ") + {:error, :incorrect_starting_code} + + iex> AbaFileValidator.get_detail_record("1032-898 12345678 130000035389 money Batch payment 040-404 12345678 test 00000000") + {:ok, "032-898", "12345678", :blank, :externally_initiated_debit, 35389, " money", " Batch payment","040-404", "12345678", " test", 0} + + iex> AbaFileValidator.get_detail_record("1032-8980-2345678N130000035389money Batch payment 040-404 12345678test 00000000") + {:ok, "032-898", "0-2345678", :new_bank, :externally_initiated_debit, 35389, "money", "Batch payment","040-404", "12345678", "test", 0} + + """ + def get_detail_record(entry) when not is_binary(entry) do + {:error, :invalid_input} + end + + def get_detail_record(entry) do + if not correct_length?(entry, 120) do + {:error, :incorrect_length} + else + {code, entry} = String.split_at(entry, 1) + + if code != "1" do + {:error, :incorrect_starting_code} + else + {bsb, entry} = String.split_at(entry, 7) + {account_number, entry} = String.split_at(entry, 9) + {indicator, entry} = String.split_at(entry, 1) + {transasction_code, entry} = String.split_at(entry, 2) + {amount, entry} = String.split_at(entry, 10) + {account_name, entry} = String.split_at(entry, 32) + {reference, entry} = String.split_at(entry, 18) + {trace_record, entry} = String.split_at(entry, 7) + {trace_account_number, entry} = String.split_at(entry, 9) + {remitter_name, withheld_tax} = String.split_at(entry, 16) + + reference = String.trim_trailing(reference) + trace_account_number = String.trim_leading(trace_account_number) + remitter_name = String.trim_trailing(remitter_name) + account_number = String.trim_leading(account_number) + account_name = String.trim_trailing(account_name) + errors = [] + + errors = if not valid_bsb?(bsb), do: errors ++ [:bsb], else: errors + errors = if string_empty?(account_number), do: errors ++ [:account_number], else: errors + + errors = + if get_indicator_code(indicator) == :error, do: errors ++ [:indicator], else: errors + + errors = + if get_transaction_code(transasction_code) == :error, + do: errors ++ [:transasction_code], + else: errors + + errors = if Integer.parse(amount) == :error, do: errors ++ [:amount], else: errors + errors = if string_empty?(account_name), do: errors ++ [:account_name], else: errors + errors = if string_empty?(reference), do: errors ++ [:reference], else: errors + errors = if not valid_bsb?(trace_record), do: errors ++ [:trace_record], else: errors + + errors = + if string_empty?(trace_account_number), + do: errors ++ [:trace_account_number], + else: errors + + errors = if string_empty?(remitter_name), do: errors ++ [:remitter], else: errors + + errors = + if Integer.parse(withheld_tax) == :error, do: errors ++ [:withheld_tax], else: errors + + # errors = + # if not correct_length?(first_blank, 12), do: errors ++ [:first_blank], else: errors + + # errors = if not correct_length?(last_blank, 40), do: errors ++ [:last_blank], else: errors + + # errors = if not correct_length?(mid_blank, 24), do: errors ++ [:mid_blank], else: errors + + # errors = if string_empty?(net_total), do: errors ++ [:net_total], else: errors + + # errors = if string_empty?(total_credit), do: errors ++ [:total_credit], else: errors + + # errors = + # if string_empty?(total_debit), + # do: errors ++ [:total_debit], + # else: errors + + # errors = if string_empty?(record_count), do: errors ++ [:record_count], else: errors + + # errors = + # unless string_empty?(net_total) and string_empty?(total_credit) and + # string_empty?(total_debit) do + # net_amount = String.to_integer(net_total) + # credit_amount = String.to_integer(total_credit) + # debit_amount = String.to_integer(total_debit) + + # if net_amount !== credit_amount - debit_amount, + # do: errors ++ [:net_total_mismatch], + # else: errors + # else + # errors + # end + + # errors = + # unless string_empty?(record_count) do + # if records !== String.to_integer(record_count), + # do: errors ++ [:records_mismatch], + # else: errors + # else + # errors + # end + + if(length(errors) > 0) do + {:error, :invalid_format, errors} + else + {:ok, bsb, account_number, get_indicator_code(indicator), + get_transaction_code(transasction_code), String.to_integer(amount), account_name, + reference, trace_record, trace_account_number, remitter_name, + String.to_integer(withheld_tax)} + end + end + end + end + + defp get_transaction_code("13"), do: :externally_initiated_debit defp get_transaction_code("50"), do: :externally_initiated_credit @@ -240,7 +381,14 @@ defmodule AbaFileValidator do defp get_transaction_code("57"), do: :debenture_note_interest defp get_transaction_code(_), do: :error - @spec get_transaction_code_description(String.t()| integer() | atom()) :: :error | String.t() + defp get_indicator_code("N"), do: :new_bank + defp get_indicator_code("W"), do: :dividend_resident_country_double_tax + defp get_indicator_code("X"), do: :dividend_non_resident + defp get_indicator_code("Y"), do: :interest_non_residents + defp get_indicator_code(" "), do: :blank + defp get_indicator_code(_), do: :error + + @spec get_transaction_code_description(String.t() | integer() | atom()) :: :error | String.t() @doc """ Get a description for a given transaction code. See [here](https://www.cemtexaba.com/aba-format/cemtex-aba-file-format-details) for the possible transaction code @@ -270,18 +418,24 @@ defmodule AbaFileValidator do """ def get_transaction_code_description(13), do: "Externally initiated debit items" def get_transaction_code_description("13"), do: "Externally initiated debit items" - def get_transaction_code_description(:externally_initiated_debit), do: "Externally initiated debit items" + + def get_transaction_code_description(:externally_initiated_debit), + do: "Externally initiated debit items" def get_transaction_code_description(50), do: "Externally initiated credit items with the exception of those bearing Transaction Codes" + def get_transaction_code_description("50"), do: "Externally initiated credit items with the exception of those bearing Transaction Codes" + def get_transaction_code_description(:externally_initiated_credit), do: "Externally initiated credit items with the exception of those bearing Transaction Codes" def get_transaction_code_description(51), do: "Australian Government Security Interest" def get_transaction_code_description("51"), do: "Australian Government Security Interest" - def get_transaction_code_description(:australian_government_security_interest), do: "Australian Government Security Interest" + + def get_transaction_code_description(:australian_government_security_interest), + do: "Australian Government Security Interest" def get_transaction_code_description(52), do: "Family Allowance" def get_transaction_code_description("52"), do: "Family Allowance" diff --git a/test/aba_file_validator_test.exs b/test/aba_file_validator_test.exs index a2d0d56..b87fe56 100644 --- a/test/aba_file_validator_test.exs +++ b/test/aba_file_validator_test.exs @@ -197,11 +197,12 @@ defmodule AbaFileValidatorTest do [ :bsb, :account_number, - :indicator, + :transasction_code, :amount, :account_name, :reference, :trace_record, + :trace_account_number, :remitter, :withheld_tax ]} From e312af6d536eab78bcf0e3198f6c77511a9372bd Mon Sep 17 00:00:00 2001 From: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> Date: Sun, 2 Apr 2023 13:45:21 +0800 Subject: [PATCH 6/9] chore: updated module name Signed-off-by: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> --- lib/aba_file_validator.ex | 464 +----------------- lib/aba_validator.ex | 464 ++++++++++++++++++ lib/util.ex | 2 +- mix.exs | 4 +- ...idator_test.exs => aba_validator_test.exs} | 92 ++-- 5 files changed, 514 insertions(+), 512 deletions(-) create mode 100644 lib/aba_validator.ex rename test/{aba_file_validator_test.exs => aba_validator_test.exs} (64%) diff --git a/lib/aba_file_validator.ex b/lib/aba_file_validator.ex index 92c74e5..11a73f7 100644 --- a/lib/aba_file_validator.ex +++ b/lib/aba_file_validator.ex @@ -1,465 +1,3 @@ -defmodule AbaFileValidator do - import __MODULE__.Utils +defmodule AbaValidator.File do - @moduledoc """ - Documentation for `AbaFileValidator`. - """ - alias Mix.Tasks.Compile.Yecc - - @spec get_descriptive_record(String.t()) :: - {:error, :incorrect_length | :incorrect_starting_code | :invalid_format} - | {:ok, String.t(), String.t(), String.t(), String.t(), String.t(), String.t()} - @doc """ - Get the entries as part of the descriptiive record - - ## Examples - - iex> AbaFileValidator.get_descriptive_record(1) - {:error, :invalid_input} - - iex> AbaFileValidator.get_descriptive_record("11") - {:error, :incorrect_length} - - iex> AbaFileValidator.get_descriptive_record("01") - {:error, :incorrect_length} - - iex> AbaFileValidator.get_descriptive_record("1 01CBA test 301500221212121227121222 ") - {:error, :incorrect_starting_code} - - iex> AbaFileValidator.get_descriptive_record("0 CBA test 301500221212121227121222 ") - {:error, :invalid_format, [:reel_sequence_number]} - - iex> AbaFileValidator.get_descriptive_record("0 01CBA test 301500221212121227121222 ") - {:ok, "01", "CBA", "test ", "301500", "221212121227", "121222"} - - """ - def get_descriptive_record(entry) when not is_binary(entry) do - {:error, :invalid_input} - end - - def get_descriptive_record(entry) do - if not correct_length?(entry, 120) do - {:error, :incorrect_length} - else - {code, entry} = String.split_at(entry, 1) - - if code != "0" do - {:error, :incorrect_starting_code} - else - {first_blank, entry} = String.split_at(entry, 17) - {reel_sequence_number, entry} = String.split_at(entry, 2) - {bank_abbreviation, entry} = String.split_at(entry, 3) - {mid_blank, entry} = String.split_at(entry, 7) - {user_preferred_specification, entry} = String.split_at(entry, 26) - {user_id_number, entry} = String.split_at(entry, 6) - {description, entry} = String.split_at(entry, 12) - {date, last_blank} = String.split_at(entry, 6) - - with correct_first_blanks <- correct_length?(first_blank, 17), - correct_mid_blanks <- correct_length?(mid_blank, 7), - correct_last_blanks <- correct_length?(last_blank, 40), - reel_sequence_number_empty? <- string_empty?(reel_sequence_number), - bank_abbreviation_empty? <- string_empty?(bank_abbreviation), - user_preferred_specification_empty? <- string_empty?(user_preferred_specification), - user_id_number_empty? <- string_empty?(user_id_number), - description_empty? <- string_empty?(description), - valid_date <- valid_date?(date), - date_empty? <- string_empty?(date) do - errors = [] - - errors = if not correct_first_blanks, do: errors ++ [:first_blank], else: errors - - errors = if not correct_last_blanks, do: errors ++ [:last_blank], else: errors - - errors = if not correct_mid_blanks, do: errors ++ [:mid_blank], else: errors - - errors = - if reel_sequence_number_empty?, do: errors ++ [:reel_sequence_number], else: errors - - errors = if bank_abbreviation_empty?, do: errors ++ [:bank_abbreviation], else: errors - - errors = - if user_preferred_specification_empty?, - do: errors ++ [:user_preferred_specification], - else: errors - - errors = if user_id_number_empty?, do: errors ++ [:user_id_number], else: errors - - errors = if description_empty?, do: errors ++ [:description], else: errors - - errors = if not valid_date or date_empty?, do: errors ++ [:date], else: errors - - if(length(errors) > 0) do - {:error, :invalid_format, errors} - else - {:ok, reel_sequence_number, bank_abbreviation, user_preferred_specification, - user_id_number, description, date} - end - end - end - end - end - - @spec get_file_total_record(String.t(), integer()) :: - {:error, :incorrect_length | :incorrect_starting_code | :invalid_input} - | {:error, :invalid_format, - [ - :bsb_filler - | :first_blank - | :last_blank - | :mid_blank - | :net_total - | :net_total_mismatch - | :record_count - | :records_mismatch - | :total_credit - | :total_debit - ]} - | {:ok, integer(), integer(), integer(), integer()} - @doc """ - Get the entries as part of the file total record - - ## Examples - - iex> AbaFileValidator.get_file_total_record(1) - {:error, :invalid_input} - - iex> AbaFileValidator.get_file_total_record("11") - {:error, :incorrect_length} - - iex> AbaFileValidator.get_file_total_record("01") - {:error, :incorrect_length} - - iex> AbaFileValidator.get_file_total_record("1 01CBA test 301500221212121227121222 ") - {:error, :incorrect_starting_code} - - iex> AbaFileValidator.get_file_total_record("7999 999 000000000000000353890000035388 000000 ") - {:error, :invalid_format, [:bsb_filler, :net_total_mismatch ]} - - iex> AbaFileValidator.get_file_total_record("7 ") - {:error, :invalid_format, [:bsb_filler, :net_total, :total_credit, :total_debit, :record_count]} - - iex> AbaFileValidator.get_file_total_record("7999 999 000000000000000353890000035388 000002 ") - {:error, :invalid_format, [:bsb_filler, :net_total_mismatch, :records_mismatch]} - - iex> AbaFileValidator.get_file_total_record("7999-999 000000000000000353890000035389 000000 ") - {:ok, 0, 35389, 35389, 0} - - """ - def get_file_total_record(entry, records \\ 0) - - def get_file_total_record(entry, _records) when not is_binary(entry) do - {:error, :invalid_input} - end - - def get_file_total_record(entry, records) when is_number(records) do - if not correct_length?(entry, 120) do - {:error, :incorrect_length} - else - {code, entry} = String.split_at(entry, 1) - - if code != "7" do - {:error, :incorrect_starting_code} - else - {bsb_filler, entry} = String.split_at(entry, 7) - {first_blank, entry} = String.split_at(entry, 12) - {net_total, entry} = String.split_at(entry, 10) - {total_credit, entry} = String.split_at(entry, 10) - {total_debit, entry} = String.split_at(entry, 10) - {mid_blank, entry} = String.split_at(entry, 24) - {record_count, last_blank} = String.split_at(entry, 6) - - errors = [] - - errors = if bsb_filler !== "999-999", do: errors ++ [:bsb_filler], else: errors - - errors = - if not correct_length?(first_blank, 12), do: errors ++ [:first_blank], else: errors - - errors = if not correct_length?(last_blank, 40), do: errors ++ [:last_blank], else: errors - - errors = if not correct_length?(mid_blank, 24), do: errors ++ [:mid_blank], else: errors - - # TODO Validate amount is positve - errors = if string_empty?(net_total), do: errors ++ [:net_total], else: errors - - errors = if string_empty?(total_credit), do: errors ++ [:total_credit], else: errors - - errors = - if string_empty?(total_debit), - do: errors ++ [:total_debit], - else: errors - - errors = if string_empty?(record_count), do: errors ++ [:record_count], else: errors - - errors = - unless string_empty?(net_total) and string_empty?(total_credit) and - string_empty?(total_debit) do - net_amount = String.to_integer(net_total) - credit_amount = String.to_integer(total_credit) - debit_amount = String.to_integer(total_debit) - - if net_amount !== credit_amount - debit_amount, - do: errors ++ [:net_total_mismatch], - else: errors - else - errors - end - - # TODO Validate total amount from detail match debit/credit - errors = - unless string_empty?(record_count) do - if records !== String.to_integer(record_count), - do: errors ++ [:records_mismatch], - else: errors - else - errors - end - - if(length(errors) > 0) do - {:error, :invalid_format, errors} - else - {:ok, String.to_integer(net_total), String.to_integer(total_credit), - String.to_integer(total_debit), String.to_integer(record_count)} - end - end - end - end - - @doc """ - Get the entries as part of the detail record - - ## Examples - - iex> AbaFileValidator.get_detail_record(1) - {:error, :invalid_input} - - iex> AbaFileValidator.get_detail_record("11") - {:error, :incorrect_length} - - iex> AbaFileValidator.get_detail_record("01") - {:error, :incorrect_length} - - iex> AbaFileValidator.get_detail_record("1032 898 12345678 130000035389money Batch payment 040 404 12345678test 00000000") - {:error, :invalid_format, [:bsb,:trace_record]} - - iex> AbaFileValidator.get_detail_record("1 ") - {:error, :invalid_format, - [:bsb, :account_number, :transasction_code, :amount, :account_name, :reference, :trace_record, :trace_account_number, :remitter, :withheld_tax]} - - iex> AbaFileValidator.get_detail_record("7999 999 000000000000000353890000035388 000002 ") - {:error, :incorrect_starting_code} - - iex> AbaFileValidator.get_detail_record("1032-898 12345678 130000035389 money Batch payment 040-404 12345678 test 00000000") - {:ok, "032-898", "12345678", :blank, :externally_initiated_debit, 35389, " money", " Batch payment","040-404", "12345678", " test", 0} - - iex> AbaFileValidator.get_detail_record("1032-8980-2345678N130000035389money Batch payment 040-404 12345678test 00000000") - {:ok, "032-898", "0-2345678", :new_bank, :externally_initiated_debit, 35389, "money", "Batch payment","040-404", "12345678", "test", 0} - - """ - def get_detail_record(entry) when not is_binary(entry) do - {:error, :invalid_input} - end - - def get_detail_record(entry) do - if not correct_length?(entry, 120) do - {:error, :incorrect_length} - else - {code, entry} = String.split_at(entry, 1) - - if code != "1" do - {:error, :incorrect_starting_code} - else - {bsb, entry} = String.split_at(entry, 7) - {account_number, entry} = String.split_at(entry, 9) - {indicator, entry} = String.split_at(entry, 1) - {transasction_code, entry} = String.split_at(entry, 2) - {amount, entry} = String.split_at(entry, 10) - {account_name, entry} = String.split_at(entry, 32) - {reference, entry} = String.split_at(entry, 18) - {trace_record, entry} = String.split_at(entry, 7) - {trace_account_number, entry} = String.split_at(entry, 9) - {remitter_name, withheld_tax} = String.split_at(entry, 16) - - reference = String.trim_trailing(reference) - trace_account_number = String.trim_leading(trace_account_number) - remitter_name = String.trim_trailing(remitter_name) - account_number = String.trim_leading(account_number) - account_name = String.trim_trailing(account_name) - errors = [] - - errors = if not valid_bsb?(bsb), do: errors ++ [:bsb], else: errors - errors = if string_empty?(account_number), do: errors ++ [:account_number], else: errors - - errors = - if get_indicator_code(indicator) == :error, do: errors ++ [:indicator], else: errors - - errors = - if get_transaction_code(transasction_code) == :error, - do: errors ++ [:transasction_code], - else: errors - - errors = if Integer.parse(amount) == :error, do: errors ++ [:amount], else: errors - errors = if string_empty?(account_name), do: errors ++ [:account_name], else: errors - errors = if string_empty?(reference), do: errors ++ [:reference], else: errors - errors = if not valid_bsb?(trace_record), do: errors ++ [:trace_record], else: errors - - errors = - if string_empty?(trace_account_number), - do: errors ++ [:trace_account_number], - else: errors - - errors = if string_empty?(remitter_name), do: errors ++ [:remitter], else: errors - - errors = - if Integer.parse(withheld_tax) == :error, do: errors ++ [:withheld_tax], else: errors - - # errors = - # if not correct_length?(first_blank, 12), do: errors ++ [:first_blank], else: errors - - # errors = if not correct_length?(last_blank, 40), do: errors ++ [:last_blank], else: errors - - # errors = if not correct_length?(mid_blank, 24), do: errors ++ [:mid_blank], else: errors - - # errors = if string_empty?(net_total), do: errors ++ [:net_total], else: errors - - # errors = if string_empty?(total_credit), do: errors ++ [:total_credit], else: errors - - # errors = - # if string_empty?(total_debit), - # do: errors ++ [:total_debit], - # else: errors - - # errors = if string_empty?(record_count), do: errors ++ [:record_count], else: errors - - # errors = - # unless string_empty?(net_total) and string_empty?(total_credit) and - # string_empty?(total_debit) do - # net_amount = String.to_integer(net_total) - # credit_amount = String.to_integer(total_credit) - # debit_amount = String.to_integer(total_debit) - - # if net_amount !== credit_amount - debit_amount, - # do: errors ++ [:net_total_mismatch], - # else: errors - # else - # errors - # end - - # errors = - # unless string_empty?(record_count) do - # if records !== String.to_integer(record_count), - # do: errors ++ [:records_mismatch], - # else: errors - # else - # errors - # end - - if(length(errors) > 0) do - {:error, :invalid_format, errors} - else - {:ok, bsb, account_number, get_indicator_code(indicator), - get_transaction_code(transasction_code), String.to_integer(amount), account_name, - reference, trace_record, trace_account_number, remitter_name, - String.to_integer(withheld_tax)} - end - end - end - end - - defp get_transaction_code("13"), do: :externally_initiated_debit - - defp get_transaction_code("50"), - do: :externally_initiated_credit - - defp get_transaction_code("51"), do: :australian_government_security_interest - defp get_transaction_code("52"), do: :family_allowance - defp get_transaction_code("53"), do: :pay - defp get_transaction_code("54"), do: :pension - defp get_transaction_code("55"), do: :allotment - defp get_transaction_code("56"), do: :dividend - defp get_transaction_code("57"), do: :debenture_note_interest - defp get_transaction_code(_), do: :error - - defp get_indicator_code("N"), do: :new_bank - defp get_indicator_code("W"), do: :dividend_resident_country_double_tax - defp get_indicator_code("X"), do: :dividend_non_resident - defp get_indicator_code("Y"), do: :interest_non_residents - defp get_indicator_code(" "), do: :blank - defp get_indicator_code(_), do: :error - - @spec get_transaction_code_description(String.t() | integer() | atom()) :: :error | String.t() - @doc """ - Get a description for a given transaction code. See [here](https://www.cemtexaba.com/aba-format/cemtex-aba-file-format-details) for the possible transaction code - - The following atoms are valid inputs: - - :allotment - - :australian_government_security_interest - - :debenture_note_interest - - :dividend - - :error - - :externally_initiated_credit - - :externally_initiated_debit - - :family_allowance - - :pay - - :pension - - ## Examples - - iex> AbaFileValidator.get_transaction_code_description("11") - :error - - iex> AbaFileValidator.get_transaction_code_description(53) - "Pay" - - iex> AbaFileValidator.get_transaction_code_description(:australian_government_security_interest) - "Australian Government Security Interest" - - """ - def get_transaction_code_description(13), do: "Externally initiated debit items" - def get_transaction_code_description("13"), do: "Externally initiated debit items" - - def get_transaction_code_description(:externally_initiated_debit), - do: "Externally initiated debit items" - - def get_transaction_code_description(50), - do: "Externally initiated credit items with the exception of those bearing Transaction Codes" - - def get_transaction_code_description("50"), - do: "Externally initiated credit items with the exception of those bearing Transaction Codes" - - def get_transaction_code_description(:externally_initiated_credit), - do: "Externally initiated credit items with the exception of those bearing Transaction Codes" - - def get_transaction_code_description(51), do: "Australian Government Security Interest" - def get_transaction_code_description("51"), do: "Australian Government Security Interest" - - def get_transaction_code_description(:australian_government_security_interest), - do: "Australian Government Security Interest" - - def get_transaction_code_description(52), do: "Family Allowance" - def get_transaction_code_description("52"), do: "Family Allowance" - def get_transaction_code_description(:family_allowance), do: "Family Allowance" - - def get_transaction_code_description(53), do: "Pay" - def get_transaction_code_description("53"), do: "Pay" - def get_transaction_code_description(:pay), do: "Pay" - - def get_transaction_code_description(54), do: "Pension" - def get_transaction_code_description("54"), do: "Pension" - def get_transaction_code_description(:pension), do: "Pension" - - def get_transaction_code_description(55), do: "Allotment" - def get_transaction_code_description("55"), do: "Allotment" - def get_transaction_code_description(:allotment), do: "Allotment" - - def get_transaction_code_description(56), do: "Dividend" - def get_transaction_code_description("56"), do: "Dividend" - def get_transaction_code_description(:dividend), do: "Dividend" - - def get_transaction_code_description(57), do: "Debenture/Note Interest" - def get_transaction_code_description("57"), do: "Debenture/Note Interest" - def get_transaction_code_description(:debenture_note_interest), do: "Debenture/Note Interest" - - def get_transaction_code_description(_), do: :error end diff --git a/lib/aba_validator.ex b/lib/aba_validator.ex new file mode 100644 index 0000000..b0cb901 --- /dev/null +++ b/lib/aba_validator.ex @@ -0,0 +1,464 @@ +defmodule AbaValidator do + import __MODULE__.Utils + + @moduledoc """ + Documentation for `AbaValidator`. + """ + + @spec get_descriptive_record(String.t()) :: + {:error, :incorrect_length | :incorrect_starting_code | :invalid_format} + | {:ok, String.t(), String.t(), String.t(), String.t(), String.t(), String.t()} + @doc """ + Get the entries as part of the descriptiive record + + ## Examples + + iex> AbaValidator.get_descriptive_record(1) + {:error, :invalid_input} + + iex> AbaValidator.get_descriptive_record("11") + {:error, :incorrect_length} + + iex> AbaValidator.get_descriptive_record("01") + {:error, :incorrect_length} + + iex> AbaValidator.get_descriptive_record("1 01CBA test 301500221212121227121222 ") + {:error, :incorrect_starting_code} + + iex> AbaValidator.get_descriptive_record("0 CBA test 301500221212121227121222 ") + {:error, :invalid_format, [:reel_sequence_number]} + + iex> AbaValidator.get_descriptive_record("0 01CBA test 301500221212121227121222 ") + {:ok, "01", "CBA", "test ", "301500", "221212121227", "121222"} + + """ + def get_descriptive_record(entry) when not is_binary(entry) do + {:error, :invalid_input} + end + + def get_descriptive_record(entry) do + if not correct_length?(entry, 120) do + {:error, :incorrect_length} + else + {code, entry} = String.split_at(entry, 1) + + if code != "0" do + {:error, :incorrect_starting_code} + else + {first_blank, entry} = String.split_at(entry, 17) + {reel_sequence_number, entry} = String.split_at(entry, 2) + {bank_abbreviation, entry} = String.split_at(entry, 3) + {mid_blank, entry} = String.split_at(entry, 7) + {user_preferred_specification, entry} = String.split_at(entry, 26) + {user_id_number, entry} = String.split_at(entry, 6) + {description, entry} = String.split_at(entry, 12) + {date, last_blank} = String.split_at(entry, 6) + + with correct_first_blanks <- correct_length?(first_blank, 17), + correct_mid_blanks <- correct_length?(mid_blank, 7), + correct_last_blanks <- correct_length?(last_blank, 40), + reel_sequence_number_empty? <- string_empty?(reel_sequence_number), + bank_abbreviation_empty? <- string_empty?(bank_abbreviation), + user_preferred_specification_empty? <- string_empty?(user_preferred_specification), + user_id_number_empty? <- string_empty?(user_id_number), + description_empty? <- string_empty?(description), + valid_date <- valid_date?(date), + date_empty? <- string_empty?(date) do + errors = [] + + errors = if not correct_first_blanks, do: errors ++ [:first_blank], else: errors + + errors = if not correct_last_blanks, do: errors ++ [:last_blank], else: errors + + errors = if not correct_mid_blanks, do: errors ++ [:mid_blank], else: errors + + errors = + if reel_sequence_number_empty?, do: errors ++ [:reel_sequence_number], else: errors + + errors = if bank_abbreviation_empty?, do: errors ++ [:bank_abbreviation], else: errors + + errors = + if user_preferred_specification_empty?, + do: errors ++ [:user_preferred_specification], + else: errors + + errors = if user_id_number_empty?, do: errors ++ [:user_id_number], else: errors + + errors = if description_empty?, do: errors ++ [:description], else: errors + + errors = if not valid_date or date_empty?, do: errors ++ [:date], else: errors + + if(length(errors) > 0) do + {:error, :invalid_format, errors} + else + {:ok, reel_sequence_number, bank_abbreviation, user_preferred_specification, + user_id_number, description, date} + end + end + end + end + end + + @spec get_file_total_record(String.t(), integer()) :: + {:error, :incorrect_length | :incorrect_starting_code | :invalid_input} + | {:error, :invalid_format, + [ + :bsb_filler + | :first_blank + | :last_blank + | :mid_blank + | :net_total + | :net_total_mismatch + | :record_count + | :records_mismatch + | :total_credit + | :total_debit + ]} + | {:ok, integer(), integer(), integer(), integer()} + @doc """ + Get the entries as part of the file total record + + ## Examples + + iex> AbaValidator.get_file_total_record(1) + {:error, :invalid_input} + + iex> AbaValidator.get_file_total_record("11") + {:error, :incorrect_length} + + iex> AbaValidator.get_file_total_record("01") + {:error, :incorrect_length} + + iex> AbaValidator.get_file_total_record("1 01CBA test 301500221212121227121222 ") + {:error, :incorrect_starting_code} + + iex> AbaValidator.get_file_total_record("7999 999 000000000000000353890000035388 000000 ") + {:error, :invalid_format, [:bsb_filler, :net_total_mismatch ]} + + iex> AbaValidator.get_file_total_record("7 ") + {:error, :invalid_format, [:bsb_filler, :net_total, :total_credit, :total_debit, :record_count]} + + iex> AbaValidator.get_file_total_record("7999 999 000000000000000353890000035388 000002 ") + {:error, :invalid_format, [:bsb_filler, :net_total_mismatch, :records_mismatch]} + + iex> AbaValidator.get_file_total_record("7999-999 000000000000000353890000035389 000000 ") + {:ok, 0, 35389, 35389, 0} + + """ + def get_file_total_record(entry, records \\ 0) + + def get_file_total_record(entry, _records) when not is_binary(entry) do + {:error, :invalid_input} + end + + def get_file_total_record(entry, records) when is_number(records) do + if not correct_length?(entry, 120) do + {:error, :incorrect_length} + else + {code, entry} = String.split_at(entry, 1) + + if code != "7" do + {:error, :incorrect_starting_code} + else + {bsb_filler, entry} = String.split_at(entry, 7) + {first_blank, entry} = String.split_at(entry, 12) + {net_total, entry} = String.split_at(entry, 10) + {total_credit, entry} = String.split_at(entry, 10) + {total_debit, entry} = String.split_at(entry, 10) + {mid_blank, entry} = String.split_at(entry, 24) + {record_count, last_blank} = String.split_at(entry, 6) + + errors = [] + + errors = if bsb_filler !== "999-999", do: errors ++ [:bsb_filler], else: errors + + errors = + if not correct_length?(first_blank, 12), do: errors ++ [:first_blank], else: errors + + errors = if not correct_length?(last_blank, 40), do: errors ++ [:last_blank], else: errors + + errors = if not correct_length?(mid_blank, 24), do: errors ++ [:mid_blank], else: errors + + # TODO Validate amount is positve + errors = if string_empty?(net_total), do: errors ++ [:net_total], else: errors + + errors = if string_empty?(total_credit), do: errors ++ [:total_credit], else: errors + + errors = + if string_empty?(total_debit), + do: errors ++ [:total_debit], + else: errors + + errors = if string_empty?(record_count), do: errors ++ [:record_count], else: errors + + errors = + unless string_empty?(net_total) and string_empty?(total_credit) and + string_empty?(total_debit) do + net_amount = String.to_integer(net_total) + credit_amount = String.to_integer(total_credit) + debit_amount = String.to_integer(total_debit) + + if net_amount !== credit_amount - debit_amount, + do: errors ++ [:net_total_mismatch], + else: errors + else + errors + end + + # TODO Validate total amount from detail match debit/credit + errors = + unless string_empty?(record_count) do + if records !== String.to_integer(record_count), + do: errors ++ [:records_mismatch], + else: errors + else + errors + end + + if(length(errors) > 0) do + {:error, :invalid_format, errors} + else + {:ok, String.to_integer(net_total), String.to_integer(total_credit), + String.to_integer(total_debit), String.to_integer(record_count)} + end + end + end + end + + @doc """ + Get the entries as part of the detail record + + ## Examples + + iex> AbaValidator.get_detail_record(1) + {:error, :invalid_input} + + iex> AbaValidator.get_detail_record("11") + {:error, :incorrect_length} + + iex> AbaValidator.get_detail_record("01") + {:error, :incorrect_length} + + iex> AbaValidator.get_detail_record("1032 898 12345678 130000035389money Batch payment 040 404 12345678test 00000000") + {:error, :invalid_format, [:bsb,:trace_record]} + + iex> AbaValidator.get_detail_record("1 ") + {:error, :invalid_format, + [:bsb, :account_number, :transasction_code, :amount, :account_name, :reference, :trace_record, :trace_account_number, :remitter, :withheld_tax]} + + iex> AbaValidator.get_detail_record("7999 999 000000000000000353890000035388 000002 ") + {:error, :incorrect_starting_code} + + iex> AbaValidator.get_detail_record("1032-898 12345678 130000035389 money Batch payment 040-404 12345678 test 00000000") + {:ok, "032-898", "12345678", :blank, :externally_initiated_debit, 35389, " money", " Batch payment","040-404", "12345678", " test", 0} + + iex> AbaValidator.get_detail_record("1032-8980-2345678N130000035389money Batch payment 040-404 12345678test 00000000") + {:ok, "032-898", "0-2345678", :new_bank, :externally_initiated_debit, 35389, "money", "Batch payment","040-404", "12345678", "test", 0} + + """ + def get_detail_record(entry) when not is_binary(entry) do + {:error, :invalid_input} + end + + def get_detail_record(entry) do + if not correct_length?(entry, 120) do + {:error, :incorrect_length} + else + {code, entry} = String.split_at(entry, 1) + + if code != "1" do + {:error, :incorrect_starting_code} + else + {bsb, entry} = String.split_at(entry, 7) + {account_number, entry} = String.split_at(entry, 9) + {indicator, entry} = String.split_at(entry, 1) + {transasction_code, entry} = String.split_at(entry, 2) + {amount, entry} = String.split_at(entry, 10) + {account_name, entry} = String.split_at(entry, 32) + {reference, entry} = String.split_at(entry, 18) + {trace_record, entry} = String.split_at(entry, 7) + {trace_account_number, entry} = String.split_at(entry, 9) + {remitter_name, withheld_tax} = String.split_at(entry, 16) + + reference = String.trim_trailing(reference) + trace_account_number = String.trim_leading(trace_account_number) + remitter_name = String.trim_trailing(remitter_name) + account_number = String.trim_leading(account_number) + account_name = String.trim_trailing(account_name) + errors = [] + + errors = if not valid_bsb?(bsb), do: errors ++ [:bsb], else: errors + errors = if string_empty?(account_number), do: errors ++ [:account_number], else: errors + + errors = + if get_indicator_code(indicator) == :error, do: errors ++ [:indicator], else: errors + + errors = + if get_transaction_code(transasction_code) == :error, + do: errors ++ [:transasction_code], + else: errors + + errors = if Integer.parse(amount) == :error, do: errors ++ [:amount], else: errors + errors = if string_empty?(account_name), do: errors ++ [:account_name], else: errors + errors = if string_empty?(reference), do: errors ++ [:reference], else: errors + errors = if not valid_bsb?(trace_record), do: errors ++ [:trace_record], else: errors + + errors = + if string_empty?(trace_account_number), + do: errors ++ [:trace_account_number], + else: errors + + errors = if string_empty?(remitter_name), do: errors ++ [:remitter], else: errors + + errors = + if Integer.parse(withheld_tax) == :error, do: errors ++ [:withheld_tax], else: errors + + # errors = + # if not correct_length?(first_blank, 12), do: errors ++ [:first_blank], else: errors + + # errors = if not correct_length?(last_blank, 40), do: errors ++ [:last_blank], else: errors + + # errors = if not correct_length?(mid_blank, 24), do: errors ++ [:mid_blank], else: errors + + # errors = if string_empty?(net_total), do: errors ++ [:net_total], else: errors + + # errors = if string_empty?(total_credit), do: errors ++ [:total_credit], else: errors + + # errors = + # if string_empty?(total_debit), + # do: errors ++ [:total_debit], + # else: errors + + # errors = if string_empty?(record_count), do: errors ++ [:record_count], else: errors + + # errors = + # unless string_empty?(net_total) and string_empty?(total_credit) and + # string_empty?(total_debit) do + # net_amount = String.to_integer(net_total) + # credit_amount = String.to_integer(total_credit) + # debit_amount = String.to_integer(total_debit) + + # if net_amount !== credit_amount - debit_amount, + # do: errors ++ [:net_total_mismatch], + # else: errors + # else + # errors + # end + + # errors = + # unless string_empty?(record_count) do + # if records !== String.to_integer(record_count), + # do: errors ++ [:records_mismatch], + # else: errors + # else + # errors + # end + + if(length(errors) > 0) do + {:error, :invalid_format, errors} + else + {:ok, bsb, account_number, get_indicator_code(indicator), + get_transaction_code(transasction_code), String.to_integer(amount), account_name, + reference, trace_record, trace_account_number, remitter_name, + String.to_integer(withheld_tax)} + end + end + end + end + + defp get_transaction_code("13"), do: :externally_initiated_debit + + defp get_transaction_code("50"), + do: :externally_initiated_credit + + defp get_transaction_code("51"), do: :australian_government_security_interest + defp get_transaction_code("52"), do: :family_allowance + defp get_transaction_code("53"), do: :pay + defp get_transaction_code("54"), do: :pension + defp get_transaction_code("55"), do: :allotment + defp get_transaction_code("56"), do: :dividend + defp get_transaction_code("57"), do: :debenture_note_interest + defp get_transaction_code(_), do: :error + + defp get_indicator_code("N"), do: :new_bank + defp get_indicator_code("W"), do: :dividend_resident_country_double_tax + defp get_indicator_code("X"), do: :dividend_non_resident + defp get_indicator_code("Y"), do: :interest_non_residents + defp get_indicator_code(" "), do: :blank + defp get_indicator_code(_), do: :error + + @spec get_transaction_code_description(String.t() | integer() | atom()) :: :error | String.t() + @doc """ + Get a description for a given transaction code. See [here](https://www.cemtexaba.com/aba-format/cemtex-aba-file-format-details) for the possible transaction code + + The following atoms are valid inputs: + - :allotment + - :australian_government_security_interest + - :debenture_note_interest + - :dividend + - :error + - :externally_initiated_credit + - :externally_initiated_debit + - :family_allowance + - :pay + - :pension + + ## Examples + + iex> AbaValidator.get_transaction_code_description("11") + :error + + iex> AbaValidator.get_transaction_code_description(53) + "Pay" + + iex> AbaValidator.get_transaction_code_description(:australian_government_security_interest) + "Australian Government Security Interest" + + """ + def get_transaction_code_description(13), do: "Externally initiated debit items" + def get_transaction_code_description("13"), do: "Externally initiated debit items" + + def get_transaction_code_description(:externally_initiated_debit), + do: "Externally initiated debit items" + + def get_transaction_code_description(50), + do: "Externally initiated credit items with the exception of those bearing Transaction Codes" + + def get_transaction_code_description("50"), + do: "Externally initiated credit items with the exception of those bearing Transaction Codes" + + def get_transaction_code_description(:externally_initiated_credit), + do: "Externally initiated credit items with the exception of those bearing Transaction Codes" + + def get_transaction_code_description(51), do: "Australian Government Security Interest" + def get_transaction_code_description("51"), do: "Australian Government Security Interest" + + def get_transaction_code_description(:australian_government_security_interest), + do: "Australian Government Security Interest" + + def get_transaction_code_description(52), do: "Family Allowance" + def get_transaction_code_description("52"), do: "Family Allowance" + def get_transaction_code_description(:family_allowance), do: "Family Allowance" + + def get_transaction_code_description(53), do: "Pay" + def get_transaction_code_description("53"), do: "Pay" + def get_transaction_code_description(:pay), do: "Pay" + + def get_transaction_code_description(54), do: "Pension" + def get_transaction_code_description("54"), do: "Pension" + def get_transaction_code_description(:pension), do: "Pension" + + def get_transaction_code_description(55), do: "Allotment" + def get_transaction_code_description("55"), do: "Allotment" + def get_transaction_code_description(:allotment), do: "Allotment" + + def get_transaction_code_description(56), do: "Dividend" + def get_transaction_code_description("56"), do: "Dividend" + def get_transaction_code_description(:dividend), do: "Dividend" + + def get_transaction_code_description(57), do: "Debenture/Note Interest" + def get_transaction_code_description("57"), do: "Debenture/Note Interest" + def get_transaction_code_description(:debenture_note_interest), do: "Debenture/Note Interest" + + def get_transaction_code_description(_), do: :error +end diff --git a/lib/util.ex b/lib/util.ex index bc15fa7..d4be9ad 100644 --- a/lib/util.ex +++ b/lib/util.ex @@ -1,4 +1,4 @@ -defmodule AbaFileValidator.Utils do +defmodule AbaValidator.Utils do def correct_length?(entry, n) when is_binary(entry), do: String.length(entry) == n def string_empty?(entry) when is_binary(entry), do: String.trim(entry) |> String.length() == 0 diff --git a/mix.exs b/mix.exs index 18b6d2f..e11b21a 100644 --- a/mix.exs +++ b/mix.exs @@ -1,9 +1,9 @@ -defmodule AbaFileValidator.MixProject do +defmodule AbaValidator.MixProject do use Mix.Project def project do [ - app: :aba_file_validator, + app: :aba_validator, version: "0.1.0", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, diff --git a/test/aba_file_validator_test.exs b/test/aba_validator_test.exs similarity index 64% rename from test/aba_file_validator_test.exs rename to test/aba_validator_test.exs index b87fe56..651416b 100644 --- a/test/aba_file_validator_test.exs +++ b/test/aba_validator_test.exs @@ -1,65 +1,65 @@ -defmodule AbaFileValidatorTest do +defmodule AbaValidatorTest do use ExUnit.Case - doctest AbaFileValidator + doctest AbaValidator - test "AbaFileValidator.get_transaction_code_description/1" do - assert AbaFileValidator.get_transaction_code_description(11) == :error + test "AbaValidator.get_transaction_code_description/1" do + assert AbaValidator.get_transaction_code_description(11) == :error - assert AbaFileValidator.get_transaction_code_description(13) == + assert AbaValidator.get_transaction_code_description(13) == "Externally initiated debit items" - assert AbaFileValidator.get_transaction_code_description("13") == + assert AbaValidator.get_transaction_code_description("13") == "Externally initiated debit items" - assert AbaFileValidator.get_transaction_code_description(50) == + assert AbaValidator.get_transaction_code_description(50) == "Externally initiated credit items with the exception of those bearing Transaction Codes" - assert AbaFileValidator.get_transaction_code_description("50") == + assert AbaValidator.get_transaction_code_description("50") == "Externally initiated credit items with the exception of those bearing Transaction Codes" - assert AbaFileValidator.get_transaction_code_description(51) == + assert AbaValidator.get_transaction_code_description(51) == "Australian Government Security Interest" - assert AbaFileValidator.get_transaction_code_description("51") == + assert AbaValidator.get_transaction_code_description("51") == "Australian Government Security Interest" - assert AbaFileValidator.get_transaction_code_description(52) == "Family Allowance" - assert AbaFileValidator.get_transaction_code_description("52") == "Family Allowance" - assert AbaFileValidator.get_transaction_code_description(53) == "Pay" - assert AbaFileValidator.get_transaction_code_description("53") == "Pay" - assert AbaFileValidator.get_transaction_code_description(54) == "Pension" - assert AbaFileValidator.get_transaction_code_description("54") == "Pension" - assert AbaFileValidator.get_transaction_code_description(55) == "Allotment" - assert AbaFileValidator.get_transaction_code_description("55") == "Allotment" - assert AbaFileValidator.get_transaction_code_description(56) == "Dividend" - assert AbaFileValidator.get_transaction_code_description("56") == "Dividend" - assert AbaFileValidator.get_transaction_code_description(57) == "Debenture/Note Interest" - assert AbaFileValidator.get_transaction_code_description("57") == "Debenture/Note Interest" + assert AbaValidator.get_transaction_code_description(52) == "Family Allowance" + assert AbaValidator.get_transaction_code_description("52") == "Family Allowance" + assert AbaValidator.get_transaction_code_description(53) == "Pay" + assert AbaValidator.get_transaction_code_description("53") == "Pay" + assert AbaValidator.get_transaction_code_description(54) == "Pension" + assert AbaValidator.get_transaction_code_description("54") == "Pension" + assert AbaValidator.get_transaction_code_description(55) == "Allotment" + assert AbaValidator.get_transaction_code_description("55") == "Allotment" + assert AbaValidator.get_transaction_code_description(56) == "Dividend" + assert AbaValidator.get_transaction_code_description("56") == "Dividend" + assert AbaValidator.get_transaction_code_description(57) == "Debenture/Note Interest" + assert AbaValidator.get_transaction_code_description("57") == "Debenture/Note Interest" end - describe "AbaFileValidator.get_descriptive_record/1" do + describe "AbaValidator.get_descriptive_record/1" do test "validates succesfully" do entry = "0 01CBA test 301500221212121227121222 " - assert AbaFileValidator.get_descriptive_record(entry) == + assert AbaValidator.get_descriptive_record(entry) == {:ok, "01", "CBA", "test ", "301500", "221212121227", "121222"} end test "returns an error if incorrect length with correct starting code" do - assert AbaFileValidator.get_descriptive_record("0") == {:error, :incorrect_length} + assert AbaValidator.get_descriptive_record("0") == {:error, :incorrect_length} end test "returns an error if incorrect length with incorrect starting code" do - assert AbaFileValidator.get_descriptive_record("1") == {:error, :incorrect_length} + assert AbaValidator.get_descriptive_record("1") == {:error, :incorrect_length} end test "returns an error if incorrect starting code" do entry = "1 01CBA test 301500221212121227121222 " - assert AbaFileValidator.get_descriptive_record(entry) == + assert AbaValidator.get_descriptive_record(entry) == {:error, :incorrect_starting_code} end @@ -67,7 +67,7 @@ defmodule AbaFileValidatorTest do entry = "0 CBA test 301500221212121227121222 " - assert AbaFileValidator.get_descriptive_record(entry) == + assert AbaValidator.get_descriptive_record(entry) == {:error, :invalid_format, [:reel_sequence_number]} end @@ -75,7 +75,7 @@ defmodule AbaFileValidatorTest do entry = "0 " - assert AbaFileValidator.get_descriptive_record(entry) == + assert AbaValidator.get_descriptive_record(entry) == {:error, :invalid_format, [ :reel_sequence_number, @@ -88,28 +88,28 @@ defmodule AbaFileValidatorTest do end end - describe "AbaFileValidator.get_file_total_record/2" do + describe "AbaValidator.get_file_total_record/2" do test "validates succesfully" do entry = "7999-999 000000000000000353890000035389 000000 " - assert AbaFileValidator.get_file_total_record(entry) == + assert AbaValidator.get_file_total_record(entry) == {:ok, 0, 35389, 35389, 0} end test "returns an error if incorrect length with correct starting code" do - assert AbaFileValidator.get_file_total_record("7") == {:error, :incorrect_length} + assert AbaValidator.get_file_total_record("7") == {:error, :incorrect_length} end test "returns an error if incorrect length with incorrect starting code" do - assert AbaFileValidator.get_file_total_record("1") == {:error, :incorrect_length} + assert AbaValidator.get_file_total_record("1") == {:error, :incorrect_length} end test "returns an error if incorrect starting code" do entry = "1 01CBA test 301500221212121227121222 " - assert AbaFileValidator.get_file_total_record(entry) == + assert AbaValidator.get_file_total_record(entry) == {:error, :incorrect_starting_code} end @@ -117,7 +117,7 @@ defmodule AbaFileValidatorTest do entry = "7999 999 000000000000000353890000035389 000000 " - assert AbaFileValidator.get_file_total_record(entry) == + assert AbaValidator.get_file_total_record(entry) == {:error, :invalid_format, [:bsb_filler]} end @@ -125,7 +125,7 @@ defmodule AbaFileValidatorTest do entry = "7 " - assert AbaFileValidator.get_file_total_record(entry) == + assert AbaValidator.get_file_total_record(entry) == {:error, :invalid_format, [:bsb_filler, :net_total, :total_credit, :total_debit, :record_count]} end @@ -134,7 +134,7 @@ defmodule AbaFileValidatorTest do entry = "7999 999 000000000000000353890000035388 000000 " - assert AbaFileValidator.get_file_total_record(entry) == + assert AbaValidator.get_file_total_record(entry) == {:error, :invalid_format, [:bsb_filler, :net_total_mismatch]} end @@ -142,41 +142,41 @@ defmodule AbaFileValidatorTest do entry = "7999 999 000000000000000353890000035389 000002 " - assert AbaFileValidator.get_file_total_record(entry) == + assert AbaValidator.get_file_total_record(entry) == {:error, :invalid_format, [:bsb_filler, :records_mismatch]} end end - describe "AbaFileValidator.get_detail_record/1" do + describe "AbaValidator.get_detail_record/1" do test "validates succesfully" do entry = "1032-898 12345678 130000035389 money Batch payment 040-404 12345678 test 00000000" - assert AbaFileValidator.get_detail_record(entry) == + assert AbaValidator.get_detail_record(entry) == {:ok, "032-898", "12345678", :blank, :externally_initiated_debit, 35389, " money", " Batch payment", "040-404", "12345678", " test", 0} entry = "1032-8980-2345678N130000035389money Batch payment 040-404 12345678test 00000000" - assert AbaFileValidator.get_detail_record(entry) == + assert AbaValidator.get_detail_record(entry) == {:ok, "032-898", "0-2345678", :new_bank, :externally_initiated_debit, 35389, "money", "Batch payment", "040-404", "12345678", "test", 0} end test "returns an error if incorrect length with correct starting code" do - assert AbaFileValidator.get_detail_record("1") == {:error, :incorrect_length} + assert AbaValidator.get_detail_record("1") == {:error, :incorrect_length} end test "returns an error if incorrect length with incorrect starting code" do - assert AbaFileValidator.get_detail_record("7") == {:error, :incorrect_length} + assert AbaValidator.get_detail_record("7") == {:error, :incorrect_length} end test "returns an error if incorrect starting code" do entry = "7032-898 12345678 130000035389money Batch payment 040-404 12345678test 00000000" - assert AbaFileValidator.get_detail_record(entry) == + assert AbaValidator.get_detail_record(entry) == {:error, :incorrect_starting_code} end @@ -184,7 +184,7 @@ defmodule AbaFileValidatorTest do entry = "1032 898 12345678 130000035389money Batch payment 040 404 12345678test 00000000" - assert AbaFileValidator.get_detail_record(entry) == + assert AbaValidator.get_detail_record(entry) == {:error, :invalid_format, [:bsb, :trace_record]} end @@ -192,7 +192,7 @@ defmodule AbaFileValidatorTest do entry = "1 " - assert AbaFileValidator.get_detail_record(entry) == + assert AbaValidator.get_detail_record(entry) == {:error, :invalid_format, [ :bsb, From 6e78abc3373f20d4ca6c13b2c1d2c22180f903ec Mon Sep 17 00:00:00 2001 From: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> Date: Sun, 2 Apr 2023 14:08:01 +0800 Subject: [PATCH 7/9] chore: added tests for utils Signed-off-by: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> --- lib/util.ex | 15 +++++++++++++-- test/aba_file_validator_test.exs | 5 +++++ test/aba_utils_test.exs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 test/aba_file_validator_test.exs create mode 100644 test/aba_utils_test.exs diff --git a/lib/util.ex b/lib/util.ex index d4be9ad..2a58635 100644 --- a/lib/util.ex +++ b/lib/util.ex @@ -1,9 +1,12 @@ defmodule AbaValidator.Utils do + def correct_length?(entry, n \\ 0) + def correct_length?(entry, _n) when not is_binary(entry), do: :error def correct_length?(entry, n) when is_binary(entry), do: String.length(entry) == n + def string_empty?(entry) when not is_binary(entry), do: :error def string_empty?(entry) when is_binary(entry), do: String.trim(entry) |> String.length() == 0 - @spec valid_date?(String.t()) :: boolean + @spec valid_date?(String.t()) :: boolean() | :error def valid_date?(<>) do if string_empty?(dd) or string_empty?(mm) or @@ -20,7 +23,9 @@ defmodule AbaValidator.Utils do end end - @spec valid_bsb?(String.t()) :: boolean + def valid_date?(_bsb), do: :error + + @spec valid_bsb?(String.t()) :: boolean() | :error def valid_bsb?(<>, last::binary-3>>) do cond do string_empty?(first) or @@ -30,9 +35,15 @@ defmodule AbaValidator.Utils do Integer.parse(first) === :error -> false + String.match?(first, ~r/\d{3}/) === false -> + false + Integer.parse(last) === :error -> false + String.match?(last, ~r/\d{3}/) === false -> + false + true -> true end diff --git a/test/aba_file_validator_test.exs b/test/aba_file_validator_test.exs new file mode 100644 index 0000000..147e2d8 --- /dev/null +++ b/test/aba_file_validator_test.exs @@ -0,0 +1,5 @@ +defmodule AbaValidatorFileTest do + use ExUnit.Case + doctest AbaValidator.File + +end diff --git a/test/aba_utils_test.exs b/test/aba_utils_test.exs new file mode 100644 index 0000000..a05b62e --- /dev/null +++ b/test/aba_utils_test.exs @@ -0,0 +1,31 @@ +defmodule AbaValidatorUtilsTest do + use ExUnit.Case + doctest AbaValidator.Utils + import AbaValidator.Utils + + test "correct_length?/2" do + assert correct_length?(11) == :error + assert correct_length?("13") == false + assert correct_length?("13", 2) == true + assert correct_length?(" ", 2) == true + assert correct_length?("") == true + end + + test "string_empty?/1" do + assert string_empty?(11) == :error + assert string_empty?("13") == false + assert string_empty?(" ") == true + end + + test "valid_bsb?/1" do + assert valid_bsb?(11) == false + assert valid_bsb?("13") == false + assert valid_bsb?(" ") == false + assert valid_bsb?("123-231") == true + assert valid_bsb?("123 231") == false + assert valid_bsb?("1a3-231") == false + assert valid_bsb?("123 231") == false + assert valid_bsb?("123-1s1") == false + assert valid_bsb?("a13-231") == false + end +end From 518db479042e65f7c330eb041b20dfaacc5e3d6c Mon Sep 17 00:00:00 2001 From: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> Date: Sun, 2 Apr 2023 14:20:27 +0800 Subject: [PATCH 8/9] refactor: updated docs Signed-off-by: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> --- lib/util.ex | 69 +++++++++++++++++++++++++++++++++++++++-- test/aba_utils_test.exs | 1 + 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/lib/util.ex b/lib/util.ex index 2a58635..0fed2bd 100644 --- a/lib/util.ex +++ b/lib/util.ex @@ -1,8 +1,52 @@ defmodule AbaValidator.Utils do + + @moduledoc """ + Documentation for `AbaValidator.Utils`. + """ + + @spec correct_length?(String.t(), integer()) :: :error | boolean() + @doc """ + Checks if the length of the string matches to the defined 'n' value + + ## Examples + + iex> AbaValidator.Utils.correct_length?(1) + :error + + iex> AbaValidator.Utils.correct_length?("", false) + :error + + iex> AbaValidator.Utils.correct_length?("11") + false + + iex> AbaValidator.Utils.correct_length?("01",2) + true + + iex> AbaValidator.Utils.correct_length?(" " ,1 ) + true + + """ def correct_length?(entry, n \\ 0) - def correct_length?(entry, _n) when not is_binary(entry), do: :error + def correct_length?(entry, n) when not is_binary(entry) or not is_integer(n), do: :error def correct_length?(entry, n) when is_binary(entry), do: String.length(entry) == n + + @spec string_empty?(String.t()) :: :error | boolean() + @doc """ + Checks if the string is empty + + ## Examples + + iex> AbaValidator.Utils.string_empty?(1) + :error + + iex> AbaValidator.Utils.string_empty?("11") + false + + iex> AbaValidator.Utils.string_empty?(" " ) + true + + """ def string_empty?(entry) when not is_binary(entry), do: :error def string_empty?(entry) when is_binary(entry), do: String.trim(entry) |> String.length() == 0 @@ -25,7 +69,28 @@ defmodule AbaValidator.Utils do def valid_date?(_bsb), do: :error - @spec valid_bsb?(String.t()) :: boolean() | :error + @spec valid_bsb?(String.t()) :: boolean() + @doc """ + Checks if the string is a valid bsb format + + ## Examples + + iex> AbaValidator.Utils.valid_bsb?(1) + false + + iex> AbaValidator.Utils.valid_bsb?("123 231") + false + + iex> AbaValidator.Utils.valid_bsb?("123-2a1") + false + + iex> AbaValidator.Utils.valid_bsb?("11") + false + + iex> AbaValidator.Utils.valid_bsb?("123-312" ) + true + + """ def valid_bsb?(<>, last::binary-3>>) do cond do string_empty?(first) or diff --git a/test/aba_utils_test.exs b/test/aba_utils_test.exs index a05b62e..49e5c06 100644 --- a/test/aba_utils_test.exs +++ b/test/aba_utils_test.exs @@ -8,6 +8,7 @@ defmodule AbaValidatorUtilsTest do assert correct_length?("13") == false assert correct_length?("13", 2) == true assert correct_length?(" ", 2) == true + assert correct_length?("", false) == :error assert correct_length?("") == true end From 41bc90dc740d69d8eac6802b08e766565ca25a5f Mon Sep 17 00:00:00 2001 From: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> Date: Sun, 2 Apr 2023 14:41:53 +0800 Subject: [PATCH 9/9] refactor: updated test for valid date Signed-off-by: alt-ctrl-dev <1557519+alt-ctrl-dev@users.noreply.github.com> --- lib/util.ex | 42 +++++++++++++++++++++++++++++++++++------ test/aba_utils_test.exs | 8 ++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/lib/util.ex b/lib/util.ex index 0fed2bd..28dbfc6 100644 --- a/lib/util.ex +++ b/lib/util.ex @@ -1,5 +1,4 @@ defmodule AbaValidator.Utils do - @moduledoc """ Documentation for `AbaValidator.Utils`. """ @@ -30,7 +29,6 @@ defmodule AbaValidator.Utils do def correct_length?(entry, n) when not is_binary(entry) or not is_integer(n), do: :error def correct_length?(entry, n) when is_binary(entry), do: String.length(entry) == n - @spec string_empty?(String.t()) :: :error | boolean() @doc """ Checks if the string is empty @@ -50,14 +48,46 @@ defmodule AbaValidator.Utils do def string_empty?(entry) when not is_binary(entry), do: :error def string_empty?(entry) when is_binary(entry), do: String.trim(entry) |> String.length() == 0 - @spec valid_date?(String.t()) :: boolean() | :error - def valid_date?(<>) do + + @spec valid_date?(String.t(), String.t()) :: :error | boolean() + @doc """ + Checks if the date string is valid. If a year prefix is not provided then it will consider it as first two digits of the current year + + ## Examples + + iex> AbaValidator.Utils.valid_date?(1) + :error + + iex> AbaValidator.Utils.valid_date?("11") + :error + + iex> AbaValidator.Utils.valid_date?(" ") + :error + + iex> AbaValidator.Utils.valid_date?("") + :error + + iex> AbaValidator.Utils.valid_date?("123231") + false + + iex> AbaValidator.Utils.valid_date?("120423") + true + """ + def valid_date?(date, year_prefix\\"") + def valid_date?(<> = date, year_prefix) when is_binary(date) and is_binary(year_prefix) do if string_empty?(dd) or string_empty?(mm) or string_empty?(yy) do false else - [yy, mm, dd] = for i <- [yy, mm, dd], do: String.to_integer(i) + year_prefix = if String.trim(year_prefix)|>String.length() === 0 do + <> =Integer.to_string(NaiveDateTime.utc_now().year) + prefix + else + year_prefix + end + + [yy, mm, dd] = for i <- [year_prefix<>yy, mm, dd], do: String.to_integer(i) NaiveDateTime.new(2000 + yy, mm, dd, 0, 0, 0) |> case do @@ -67,7 +97,7 @@ defmodule AbaValidator.Utils do end end - def valid_date?(_bsb), do: :error + def valid_date?(_, _), do: :error @spec valid_bsb?(String.t()) :: boolean() @doc """ diff --git a/test/aba_utils_test.exs b/test/aba_utils_test.exs index 49e5c06..2244ce4 100644 --- a/test/aba_utils_test.exs +++ b/test/aba_utils_test.exs @@ -29,4 +29,12 @@ defmodule AbaValidatorUtilsTest do assert valid_bsb?("123-1s1") == false assert valid_bsb?("a13-231") == false end + + test "valid_date?/2" do + assert valid_date?(11) == :error + assert valid_date?("13") == :error + assert valid_date?(" ") == :error + assert valid_date?("123231") == false + assert valid_date?("120423") == true + end end