Itin parser and validator added
This commit is contained in:
parent
0edc3c3139
commit
695ffc31c5
|
@ -1,9 +1,9 @@
|
|||
defmodule UkraineTaxidEx.Base do
|
||||
@callback length() :: non_neg_integer()
|
||||
@callback parse(data :: {:ok, String.t()} | String.t(), options :: Keyword.t()) ::
|
||||
{:ok, term} | {:error, atom()}
|
||||
@callback to_map(data :: term) :: map()
|
||||
@callback to_string(data :: term) :: String.t()
|
||||
@callback length() :: non_neg_integer()
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
|
@ -11,9 +11,8 @@ defmodule UkraineTaxidEx.Base do
|
|||
|
||||
alias UkraineTaxidEx.{Base, Serialize, Commons}
|
||||
|
||||
@impl Base
|
||||
@spec length() :: non_neg_integer()
|
||||
def length(), do: @length
|
||||
@parse_module (Module.split(__MODULE__) ++ ["Parser"]) |> Module.safe_concat()
|
||||
# def parse_module(), do: @parse_module
|
||||
|
||||
@impl Base
|
||||
@spec to_map(data :: t()) :: map()
|
||||
|
@ -23,7 +22,19 @@ defmodule UkraineTaxidEx.Base do
|
|||
@spec to_string(data :: t()) :: binary()
|
||||
defdelegate to_string(data), to: Serialize
|
||||
|
||||
defoverridable to_string: 1, to_map: 1, length: 0
|
||||
@doc """
|
||||
Returns the length of the code.
|
||||
"""
|
||||
@impl Base
|
||||
@spec length() :: non_neg_integer()
|
||||
def length(), do: @length
|
||||
|
||||
@impl Base
|
||||
@spec parse(data :: {:ok, String.t()} | String.t(), options :: Keyword.t()) ::
|
||||
{:ok, t()} | {:error, atom()}
|
||||
defdelegate parse(data, options \\ [normalize?: false, clean?: false]), to: @parse_module
|
||||
|
||||
defoverridable to_string: 1, to_map: 1, length: 0, parse: 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,33 @@ defmodule UkraineTaxidEx.BaseParser do
|
|||
@behaviour UkraineTaxidEx.BaseParser
|
||||
|
||||
alias UkraineTaxidEx.BaseParser
|
||||
|
||||
@type string_or_ok() :: String.t() | {:ok, String.t()}
|
||||
@type struct_or_error() :: {:ok, term} | {:error, atom()}
|
||||
|
||||
@struct_module Module.split(__MODULE__) |> Enum.slice(0..-2//1) |> Module.safe_concat()
|
||||
# def struct_module(), do: @struct_module
|
||||
|
||||
defp to_struct(map), do: struct(@struct_module, map)
|
||||
|
||||
@impl BaseParser
|
||||
@spec parse(data :: string_or_ok, options :: BaseParser.options()) :: struct_or_error()
|
||||
def parse(data, options \\ [normalize?: false, clean?: false])
|
||||
def parse({:ok, string}, options), do: parse(string, options)
|
||||
def parse({:error, error}, _options), do: {:error, error}
|
||||
|
||||
def parse(string, options) do
|
||||
length = (Keyword.get(options, :normalize?, false) && length()) || 0
|
||||
clean? = Keyword.get(options, :clean?, false)
|
||||
|
||||
string
|
||||
|> digits(length, clean?)
|
||||
|> undigits()
|
||||
|> validate()
|
||||
|> generate()
|
||||
end
|
||||
|
||||
defp generate({:error, error}), do: {:error, error}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
defmodule UkraineTaxidEx.BaseValidator do
|
||||
@callback validate(String.t()) ::
|
||||
{:ok, String.t()}
|
||||
| {:error,
|
||||
:length_too_short | :length_too_long | :invalid_length | :invalid_checksum}
|
||||
@callback violates_length?(String.t()) :: boolean
|
||||
@callback violates_length_too_short?(String.t()) :: boolean
|
||||
@callback violates_length_too_long?(String.t()) :: boolean
|
||||
@callback violates_checksum?(String.t()) :: boolean
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
alias UkraineTaxidEx.BaseValidator
|
||||
|
||||
@behaviour UkraineTaxidEx.BaseValidator
|
||||
import UkraineTaxidEx.Commons, only: [digits: 1, digits_and_check_digit: 1, error: 1, ok: 1]
|
||||
|
||||
@impl BaseValidator
|
||||
@spec validate(String.t()) ::
|
||||
{:ok, String.t()}
|
||||
| {:error,
|
||||
:length_too_short | :length_too_long | :invalid_length | :invalid_checksum}
|
||||
def validate(string) do
|
||||
cond do
|
||||
violates_length_too_short?(string) -> error(:length_too_short)
|
||||
violates_length_too_long?(string) -> error(:length_too_long)
|
||||
violates_checksum?(string) -> error(:invalid_checksum)
|
||||
true -> ok(string)
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Check whether a given EDRPOU violates the required length"
|
||||
@impl BaseValidator
|
||||
@spec violates_length?(String.t()) :: boolean
|
||||
def violates_length?(string),
|
||||
do: String.length(string) != length()
|
||||
|
||||
@doc "Check whether a given EDRPOU too short"
|
||||
@impl BaseValidator
|
||||
@spec violates_length_too_short?(String.t()) :: boolean
|
||||
def violates_length_too_short?(string),
|
||||
do: String.length(string) < length()
|
||||
|
||||
@doc "Check whether a given EDRPOU too long"
|
||||
@impl BaseValidator
|
||||
@spec violates_length_too_long?(String.t()) :: boolean
|
||||
def violates_length_too_long?(string),
|
||||
do: String.length(string) > length()
|
||||
|
||||
@doc "Check whether a given EDRPOU has correct checksum"
|
||||
@impl BaseValidator
|
||||
@spec violates_checksum?(String.t()) :: boolean
|
||||
def violates_checksum?(string) do
|
||||
{digits, check_digit} =
|
||||
string
|
||||
|> digits()
|
||||
|> digits_and_check_digit()
|
||||
|
||||
check_sum = check_sum(digits)
|
||||
|
||||
check_sum != check_digit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,8 +6,6 @@ defmodule UkraineTaxidEx.Edrpou do
|
|||
"""
|
||||
|
||||
@length 8
|
||||
alias UkraineTaxidEx.Edrpou.Parser
|
||||
use UkraineTaxidEx.Base
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
code: String.t(),
|
||||
|
@ -17,8 +15,5 @@ defmodule UkraineTaxidEx.Edrpou do
|
|||
|
||||
defstruct code: nil, check_digit: nil, check_sum: nil
|
||||
|
||||
@impl Base
|
||||
@spec parse(data :: {:ok, String.t()} | String.t(), options :: Keyword.t()) ::
|
||||
{:ok, t()} | {:error, atom()}
|
||||
defdelegate parse(data, options \\ [normalize?: false, clean?: false]), to: Parser
|
||||
use UkraineTaxidEx.Base
|
||||
end
|
||||
|
|
|
@ -2,31 +2,8 @@ defmodule UkraineTaxidEx.Edrpou.Parser do
|
|||
@moduledoc """
|
||||
Parser module for EDRPOU (Unified State Register of Ukrainian Enterprises and Organizations) codes.
|
||||
Handles validation and structure creation for EDRPOU codes with additional options for normalization and cleaning.
|
||||
"""
|
||||
|
||||
alias UkraineTaxidEx.Edrpou
|
||||
|
||||
import UkraineTaxidEx.Edrpou, only: [length: 0]
|
||||
import UkraineTaxidEx.Edrpou.CheckSum, only: [check_sum: 1]
|
||||
import UkraineTaxidEx.Edrpou.Validator, only: [validate: 1]
|
||||
import UkraineTaxidEx.Commons, only: [check_digit: 1, digits: 1, digits: 3, undigits: 1, ok: 1]
|
||||
|
||||
use UkraineTaxidEx.BaseParser
|
||||
|
||||
@type edrpou_string() :: String.t()
|
||||
@type edrpou_string_or_ok() :: edrpou_string() | {:ok, edrpou_string()}
|
||||
@type edrpou() :: Edrpou.t()
|
||||
@type edrpou_or_error() ::
|
||||
{:ok, Edrpou.t()}
|
||||
| {:error,
|
||||
:length_too_short
|
||||
| :length_too_long
|
||||
| :invalid_checksum}
|
||||
|
||||
@impl BaseParser
|
||||
|
||||
@doc """
|
||||
Parses an EDRPOU code string into a structured format (clean and normalize, validate and decompose).
|
||||
Parses an EDRPOU code string into a structured format (clean and normalize, validate and decompo).
|
||||
Options:
|
||||
- normalize?: When true, pads string to full EDRPOU length. Defaults to false.
|
||||
- clean?: When true, removes non-digit characters before processing. Defaults to false.
|
||||
|
@ -34,6 +11,7 @@ defmodule UkraineTaxidEx.Edrpou.Parser do
|
|||
|
||||
## Examples
|
||||
|
||||
```elixir
|
||||
iex> UkraineTaxidEx.Edrpou.Parser.parse("00032112")
|
||||
{:ok, %UkraineTaxidEx.Edrpou{code: "00032112", check_digit: 2, check_sum: 2}}
|
||||
|
||||
|
@ -54,33 +32,34 @@ defmodule UkraineTaxidEx.Edrpou.Parser do
|
|||
|
||||
iex> UkraineTaxidEx.Edrpou.Parser.parse("123", normalize?: true)
|
||||
{:error, :invalid_checksum}
|
||||
```
|
||||
"""
|
||||
@spec parse(data :: edrpou_string_or_ok, options :: BaseParser.options()) ::
|
||||
edrpou_or_error()
|
||||
def parse(data, options \\ [normalize?: false, clean?: false])
|
||||
def parse({:ok, edrpou_string}, options), do: parse(edrpou_string, options)
|
||||
def parse({:error, error}, _options), do: {:error, error}
|
||||
|
||||
def parse(edrpou_string, options) do
|
||||
length = (Keyword.get(options, :normalize?, false) && length()) || 0
|
||||
clean? = Keyword.get(options, :clean?, false)
|
||||
alias UkraineTaxidEx.Edrpou
|
||||
|
||||
edrpou_string
|
||||
|> digits(length, clean?)
|
||||
|> undigits()
|
||||
|> validate()
|
||||
|> generate_edrpou()
|
||||
end
|
||||
import UkraineTaxidEx.Edrpou, only: [length: 0]
|
||||
import UkraineTaxidEx.Edrpou.CheckSum, only: [check_sum: 1]
|
||||
import UkraineTaxidEx.Edrpou.Validator, only: [validate: 1]
|
||||
import UkraineTaxidEx.Commons, only: [check_digit: 1, digits: 1, digits: 3, undigits: 1, ok: 1]
|
||||
|
||||
defp generate_edrpou({:error, error}), do: {:error, error}
|
||||
use UkraineTaxidEx.BaseParser
|
||||
|
||||
defp generate_edrpou({:ok, edrpou_string}) do
|
||||
digits = digits(edrpou_string)
|
||||
@type edrpou_string() :: String.t()
|
||||
@type edrpou() :: Edrpou.t()
|
||||
@type edrpou_or_error() ::
|
||||
{:ok, Edrpou.t()}
|
||||
| {:error,
|
||||
:length_too_short
|
||||
| :length_too_long
|
||||
| :invalid_checksum}
|
||||
|
||||
%{code: edrpou_string, check_sum: check_sum(digits), check_digit: check_digit(digits)}
|
||||
|> create_struct()
|
||||
defp generate({:error, error}), do: {:error, error}
|
||||
|
||||
defp generate({:ok, string}) do
|
||||
digits = digits(string)
|
||||
|
||||
%{code: string, check_sum: check_sum(digits), check_digit: check_digit(digits)}
|
||||
|> to_struct()
|
||||
|> ok()
|
||||
end
|
||||
|
||||
defp create_struct(map), do: struct(Edrpou, map)
|
||||
end
|
||||
|
|
|
@ -3,13 +3,7 @@ defmodule UkraineTaxidEx.Edrpou.Validator do
|
|||
Functions for validating EDRPOU number format and checksum.
|
||||
|
||||
This module provides validation functions to verify if an EDRPOU number meets the standard requirements including length and checksum validation.
|
||||
"""
|
||||
|
||||
import UkraineTaxidEx.Commons, only: [digits: 1, digits_and_check_digit: 1, error: 1, ok: 1]
|
||||
import UkraineTaxidEx.Edrpou, only: [length: 0]
|
||||
import UkraineTaxidEx.Edrpou.CheckSum, only: [check_sum: 1]
|
||||
|
||||
@doc """
|
||||
Validates an EDRPOU number to check if it meets length requirements and has a valid checksum.
|
||||
|
||||
Returns:
|
||||
|
@ -17,44 +11,25 @@ defmodule UkraineTaxidEx.Edrpou.Validator do
|
|||
* `{:error, :length_too_short}` if shorter than required length
|
||||
* `{:error, :length_too_long}` if longer than required length
|
||||
* `{:error, :invalid_checksum}` if checksum is invalid
|
||||
|
||||
## Examples
|
||||
|
||||
```elixir
|
||||
iex> UkraineTaxidEx.Edrpou.Validator.validate("00032112")
|
||||
{:ok, "00032112"}
|
||||
|
||||
iex> UkraineTaxidEx.Edrpou.Validator.validate("0003211")
|
||||
{:error, :length_too_short}
|
||||
|
||||
iex> UkraineTaxidEx.Edrpou.Validator.validate("000321122")
|
||||
{:error, :length_too_long}
|
||||
|
||||
iex> UkraineTaxidEx.Edrpou.Validator.validate("00032113")
|
||||
{:error, :invalid_checksum}
|
||||
```
|
||||
"""
|
||||
@spec validate(String.t()) ::
|
||||
{:ok, String.t()}
|
||||
| {:error, :length_too_short | :length_too_long | :invalid_length | :invalid_checksum}
|
||||
def validate(edrpou) do
|
||||
cond do
|
||||
violates_length_too_short?(edrpou) -> error(:length_too_short)
|
||||
violates_length_too_long?(edrpou) -> error(:length_too_long)
|
||||
violates_checksum?(edrpou) -> error(:invalid_checksum)
|
||||
true -> ok(edrpou)
|
||||
end
|
||||
end
|
||||
import UkraineTaxidEx.Edrpou, only: [length: 0]
|
||||
import UkraineTaxidEx.Edrpou.CheckSum, only: [check_sum: 1]
|
||||
|
||||
@doc "Check whether a given EDRPOU violates the required length"
|
||||
@spec violates_length?(String.t()) :: boolean
|
||||
def violates_length?(edrpou),
|
||||
do: String.length(edrpou) != length()
|
||||
|
||||
@doc "Check whether a given EDRPOU too short"
|
||||
@spec violates_length_too_short?(String.t()) :: boolean
|
||||
def violates_length_too_short?(edrpou),
|
||||
do: String.length(edrpou) < length()
|
||||
|
||||
@doc "Check whether a given EDRPOU too long"
|
||||
@spec violates_length_too_long?(String.t()) :: boolean
|
||||
def violates_length_too_long?(edrpou),
|
||||
do: String.length(edrpou) > length()
|
||||
|
||||
@doc "Check whether a given EDRPOU has correct checksum"
|
||||
@spec violates_checksum?(String.t()) :: boolean
|
||||
def violates_checksum?(edrpou) do
|
||||
{digits, check_digit} =
|
||||
edrpou
|
||||
|> digits()
|
||||
|> digits_and_check_digit()
|
||||
|
||||
check_sum = check_sum(digits)
|
||||
|
||||
check_sum != check_digit
|
||||
end
|
||||
use UkraineTaxidEx.BaseValidator
|
||||
end
|
||||
|
|
|
@ -5,20 +5,19 @@ defmodule UkraineTaxidEx.Itin do
|
|||
The ITIN is a unique 10-digit number that identifies individuals in Ukraine. It is issued to all individuals when they are registered in the official tax register. The Individual Taxpayer Identification Number (ITIN) is an automated system that collects, stores, and processes data on individuals in Ukraine.
|
||||
"""
|
||||
|
||||
@base_date Date.new!(1899, 12, 31)
|
||||
@length 10
|
||||
use UkraineTaxidEx.Base
|
||||
|
||||
@type gender :: 0 | 1
|
||||
@type t :: %__MODULE__{
|
||||
code: String.t(),
|
||||
birth_date: Date.t(),
|
||||
number: integer(),
|
||||
gender: gender,
|
||||
check_digit: C.digit(),
|
||||
check_sum: C.digit()
|
||||
}
|
||||
|
||||
defstruct code: nil, birth_date: nil, gender: nil, check_digit: nil, check_sum: nil
|
||||
defstruct code: nil, birth_date: nil, number: nil, gender: nil, check_digit: nil, check_sum: nil
|
||||
|
||||
def base_date(), do: @base_date
|
||||
use UkraineTaxidEx.Base
|
||||
end
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
defmodule UkraineTaxidEx.Itin.CheckSum do
|
||||
@moduledoc """
|
||||
Module for calculating the checksum of Ukrainian Individual Tax Identification Numbers (ITIN).
|
||||
Provides functions for checksum calculation based on weighted digits and helper functions
|
||||
for working with ITIN weights and dividers. Uses specified numerical weights to multiply
|
||||
each digit of the ITIN and validate its authenticity.
|
||||
"""
|
||||
|
||||
alias UkraineTaxidEx.Commons, as: C
|
||||
|
||||
import UkraineTaxidEx.Commons, only: [value_digits: 1]
|
||||
|
||||
@weights [-1, 5, 7, 9, 4, 6, 10, 5, 7]
|
||||
|
||||
@doc """
|
||||
Returns the list of numerical weights used to calculate the ITIN checksum.
|
||||
Each digit in the ITIN is multiplied by its corresponding weight.
|
||||
"""
|
||||
@spec weights() :: C.digits()
|
||||
def weights(), do: @weights
|
||||
|
||||
@spec divider() :: non_neg_integer()
|
||||
defp divider(), do: 11
|
||||
|
||||
@doc """
|
||||
Calculate checksum for ITIN number.
|
||||
The checksum for ITIN is calculated in several steps:
|
||||
1. Multiply each digit by its corresponding weight
|
||||
2. Sum the products
|
||||
3. Take mod 11 of the sum
|
||||
4. If mod 11 is greater or equal than 10, repeat steps 2-4 with weights +2
|
||||
"""
|
||||
@spec check_sum(digits :: C.digits(), weights :: C.digits()) :: integer()
|
||||
def check_sum(digits, weights \\ @weights) do
|
||||
digits
|
||||
|> value_digits()
|
||||
|> Enum.zip(weights)
|
||||
|> Enum.reduce(0, fn {digit, weight}, acc -> digit * weight + acc end)
|
||||
|> rem(divider())
|
||||
|> rem(10)
|
||||
end
|
||||
end
|
|
@ -12,10 +12,10 @@ defmodule UkraineTaxidEx.Itin.Error do
|
|||
:length_to_short
|
||||
]
|
||||
@messages [
|
||||
invalid_length: "EDRPOU violates the required length",
|
||||
invalid_checksum: "EDRPOU checksum is invalid",
|
||||
length_to_long: "EDRPOU longer then required length",
|
||||
length_to_short: "EDRPOU shorter then required length"
|
||||
invalid_length: "ITIN violates the required length",
|
||||
invalid_checksum: "ITIN checksum is invalid",
|
||||
length_to_long: "ITIN longer then required length",
|
||||
length_to_short: "ITIN shorter then required length"
|
||||
]
|
||||
|
||||
@spec message(error()) :: String.t()
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
defmodule UkraineTaxidEx.Itin.Parser do
|
||||
@moduledoc """
|
||||
This module provides parsing functionality for Ukrainian Individual Taxpayer Identification Numbers (ITIN).
|
||||
|
||||
ITINs (also known as РНОКПП/ІПН) are unique identifiers assigned to individuals in Ukraine for tax purposes.
|
||||
The parser validates the number format and extracts meaningful components like the checksum according to
|
||||
official requirements.
|
||||
|
||||
Key features:
|
||||
- Validates ITIN format and length
|
||||
- Calculates and verifies checksum
|
||||
- Parses components into a structured format
|
||||
- Handles both raw strings and pre-validated input
|
||||
|
||||
Examples of successful ITIN parsing:
|
||||
|
||||
```elixir
|
||||
iex> UkraineTaxidEx.Itin.Parser.parse("2222222222")
|
||||
{:ok, %UkraineTaxidEx.Itin{code: "2222222222", birth_date: ~D[1960-12-17], number: 2222, gender: 0, check_sum: 2, check_digit: 2}}
|
||||
|
||||
iex> UkraineTaxidEx.Itin.Parser.parse("3333333333")
|
||||
{:ok, %UkraineTaxidEx.Itin{code: "3333333333", birth_date: ~D[1991-03-05], number: 3333, gender: 1, check_sum: 3, check_digit: 3}}
|
||||
```
|
||||
|
||||
Examples of unsuccessful ITIN parsing:
|
||||
|
||||
```elixir
|
||||
iex> UkraineTaxidEx.Itin.Parser.parse("123456")
|
||||
{:error, :length_too_short}
|
||||
|
||||
iex> UkraineTaxidEx.Itin.Parser.parse("12345678901")
|
||||
{:error, :length_too_long}
|
||||
|
||||
iex> UkraineTaxidEx.Itin.Parser.parse("1234567890")
|
||||
{:error, :invalid_checksum}
|
||||
```
|
||||
"""
|
||||
alias UkraineTaxidEx.Itin
|
||||
alias UkraineTaxidEx.Commons, as: C
|
||||
|
||||
import UkraineTaxidEx.Itin, only: [length: 0]
|
||||
import UkraineTaxidEx.Itin.CheckSum, only: [check_sum: 1]
|
||||
import UkraineTaxidEx.Itin.Validator, only: [validate: 1]
|
||||
import UkraineTaxidEx.Commons, only: [check_digit: 1, digits: 1, digits: 3, undigits: 1, ok: 1]
|
||||
|
||||
require Integer
|
||||
|
||||
@type itin_string() :: String.t()
|
||||
@type itin() :: Itin.t()
|
||||
@type itin_or_error() ::
|
||||
{:ok, Itin.t()}
|
||||
| {:error,
|
||||
:length_too_short
|
||||
| :length_too_long
|
||||
| :invalid_checksum}
|
||||
|
||||
@base_date Date.new!(1899, 12, 31)
|
||||
def base_date(), do: @base_date
|
||||
|
||||
use UkraineTaxidEx.BaseParser
|
||||
|
||||
defp generate({:ok, string}) do
|
||||
digits = digits(string)
|
||||
|
||||
%{
|
||||
code: string,
|
||||
birth_date: birth_date(digits),
|
||||
number: number(digits),
|
||||
gender: gender(digits),
|
||||
check_sum: check_sum(digits),
|
||||
check_digit: check_digit(digits)
|
||||
}
|
||||
|> to_struct()
|
||||
|> ok()
|
||||
end
|
||||
|
||||
@spec slice(digits :: C.digits(), Range.t()) :: Date.t()
|
||||
defp slice(digits, range) do
|
||||
digits
|
||||
|> Enum.slice(range)
|
||||
|> undigits()
|
||||
|> String.to_integer()
|
||||
end
|
||||
|
||||
@spec birth_date(digits :: C.digits()) :: Date.t()
|
||||
def birth_date(digits), do: Date.add(base_date(), slice(digits, 0..4))
|
||||
|
||||
@spec number(digits :: C.digits()) :: integer()
|
||||
def number(digits), do: slice(digits, 5..8)
|
||||
|
||||
@spec gender(digits :: C.digits()) :: Itin.gender()
|
||||
def gender(digits), do: (Integer.is_odd(slice(digits, -2..-2//1)) && 1) || 0
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
defmodule UkraineTaxidEx.Itin.Validator do
|
||||
@moduledoc """
|
||||
Validator module for Ukrainian Individual Taxpayer Identification Number (ITIN/IPN).
|
||||
Handles validation of ITIN numbers according to Ukrainian tax authority requirements.
|
||||
|
||||
Validates an ITIN number to check if it meets length requirements and has a valid checksum.
|
||||
|
||||
Returns:
|
||||
* `{:ok, itin}` if validation successful
|
||||
* `{:error, :length_too_short}` if shorter than required length
|
||||
* `{:error, :length_too_long}` if longer than required length
|
||||
* `{:error, :invalid_checksum}` if checksum is invalid
|
||||
|
||||
Examples:
|
||||
|
||||
```elixir
|
||||
iex> UkraineTaxidEx.Itin.Validator.validate("3184710691")
|
||||
{:ok, "3184710691"}
|
||||
|
||||
iex> UkraineTaxidEx.Itin.Validator.validate("123456")
|
||||
{:error, :length_too_short}
|
||||
|
||||
iex> UkraineTaxidEx.Itin.Validator.validate("12345678901")
|
||||
{:error, :length_too_long}
|
||||
|
||||
iex> UkraineTaxidEx.Itin.Validator.validate("3184710692")
|
||||
{:error, :invalid_checksum}
|
||||
```
|
||||
"""
|
||||
|
||||
import UkraineTaxidEx.Itin, only: [length: 0]
|
||||
import UkraineTaxidEx.Itin.CheckSum, only: [check_sum: 1]
|
||||
|
||||
use UkraineTaxidEx.BaseValidator
|
||||
end
|
|
@ -1,3 +1,5 @@
|
|||
defmodule UkraineTaxidEx.EdrpouTest do
|
||||
use ExUnit.Case
|
||||
doctest UkraineTaxidEx.Edrpou.Validator
|
||||
doctest UkraineTaxidEx.Itin.Validator
|
||||
end
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
defmodule UkraineTaxidEx.Itin.CheckSumTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias UkraineTaxidEx.Itin.CheckSum
|
||||
|
||||
describe "weights/0" do
|
||||
test "returns the correct list of weights" do
|
||||
expected_weights = [-1, 5, 7, 9, 4, 6, 10, 5, 7]
|
||||
assert CheckSum.weights() == expected_weights
|
||||
end
|
||||
|
||||
test "returns a list of 9 weights" do
|
||||
assert length(CheckSum.weights()) == 9
|
||||
end
|
||||
end
|
||||
|
||||
describe "check_sum/2" do
|
||||
test "calculates correct checksum for valid ITIN (3334513284)" do
|
||||
# Test case with known ITIN and its checksum
|
||||
digits = [3, 3, 3, 4, 5, 1, 3, 2, 8, 4]
|
||||
assert CheckSum.check_sum(digits) == 4
|
||||
end
|
||||
|
||||
test "calculates correct checksum for valid ITIN (2038817850)" do
|
||||
# Test case with known ITIN and its checksum
|
||||
digits = [2, 0, 3, 8, 8, 1, 7, 8, 5, 0]
|
||||
assert CheckSum.check_sum(digits) == 0
|
||||
end
|
||||
|
||||
test "calculates correct checksum for valid ITIN (3486110380)" do
|
||||
# Test case with known ITIN and its checksum
|
||||
digits = [3, 4, 8, 6, 1, 1, 0, 3, 8, 0]
|
||||
assert CheckSum.check_sum(digits) == 0
|
||||
end
|
||||
|
||||
test "calculates correct checksum for valid ITIN (3402109517)" do
|
||||
# Test case with known ITIN and its checksum
|
||||
digits = [3, 4, 0, 2, 1, 0, 9, 5, 1, 7]
|
||||
assert CheckSum.check_sum(digits) == 7
|
||||
end
|
||||
|
||||
test "calculates correct checksum for valid ITIN (2598917292)" do
|
||||
# Test case with known ITIN and its checksum
|
||||
digits = [2, 5, 9, 8, 9, 1, 7, 2, 9, 2]
|
||||
assert CheckSum.check_sum(digits) == 2
|
||||
end
|
||||
|
||||
test "calculates correct checksum for valid ITIN (3505804094)" do
|
||||
# Test case with known ITIN and its checksum
|
||||
digits = [3, 5, 0, 5, 8, 0, 4, 0, 9, 4]
|
||||
assert CheckSum.check_sum(digits) == 4
|
||||
end
|
||||
|
||||
test "calculates correct checksum for valid ITIN (2971306745)" do
|
||||
# Test case with known ITIN and its checksum
|
||||
digits = [2, 9, 7, 1, 3, 0, 6, 7, 4, 5]
|
||||
assert CheckSum.check_sum(digits) == 5
|
||||
end
|
||||
|
||||
test "calculates correct checksum for ITIN with invalid check digit (2598917291)" do
|
||||
# Test case with known ITIN and its checksum
|
||||
digits = [2, 5, 9, 8, 9, 1, 7, 2, 9, 1]
|
||||
assert CheckSum.check_sum(digits) == 2
|
||||
end
|
||||
|
||||
test "calculates correct checksum for ITIN with invalid check digit (3505804099)" do
|
||||
# Test case with known ITIN and its checksum
|
||||
digits = [3, 5, 0, 5, 8, 0, 4, 0, 9, 9]
|
||||
assert CheckSum.check_sum(digits) == 4
|
||||
end
|
||||
|
||||
test "calculates correct checksum for ITIN with invalid check digit (2971306747)" do
|
||||
# Test case with known ITIN and its checksum
|
||||
digits = [2, 9, 7, 1, 3, 0, 6, 7, 4, 7]
|
||||
assert CheckSum.check_sum(digits) == 5
|
||||
end
|
||||
|
||||
test "calculates checksum with custom weights" do
|
||||
digits = [1, 2, 3]
|
||||
weights = [1, 2, 3]
|
||||
assert CheckSum.check_sum(digits, weights) == 5
|
||||
end
|
||||
|
||||
test "handles zero digits" do
|
||||
digits = [0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
assert CheckSum.check_sum(digits) == 0
|
||||
end
|
||||
|
||||
test "handles single digit input" do
|
||||
digits = [5]
|
||||
weights = [2]
|
||||
# (5*2) = 10 % 11 % 10 = 0
|
||||
assert CheckSum.check_sum(digits, weights) == 0
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,87 @@
|
|||
defmodule UkraineTaxidEx.Itin.ParserTest do
|
||||
use ExUnit.Case, async: true
|
||||
alias UkraineTaxidEx.Itin
|
||||
alias UkraineTaxidEx.Itin.Parser
|
||||
|
||||
describe "parse/1" do
|
||||
test "successfully parses valid ITIN" do
|
||||
assert {:ok, %Itin{} = itin} = Parser.parse("2847211760")
|
||||
assert itin.code == "2847211760"
|
||||
assert itin.birth_date == ~D[1977-12-14]
|
||||
assert itin.number == 1176
|
||||
assert itin.gender == 0
|
||||
assert itin.check_sum == 0
|
||||
assert itin.check_digit == 0
|
||||
|
||||
assert {:ok, %Itin{} = itin} = Parser.parse("3176422893")
|
||||
assert itin.code == "3176422893"
|
||||
assert itin.birth_date == ~D[1986-12-19]
|
||||
assert itin.number == 2289
|
||||
assert itin.gender == 1
|
||||
assert itin.check_sum == 3
|
||||
assert itin.check_digit == 3
|
||||
end
|
||||
|
||||
test "returns error for string shorter than required length" do
|
||||
assert {:error, :length_too_short} = Parser.parse("123456")
|
||||
end
|
||||
|
||||
test "returns error for string longer than required length" do
|
||||
assert {:error, :length_too_long} = Parser.parse("12345678901")
|
||||
end
|
||||
|
||||
test "returns error for invalid checksum" do
|
||||
assert {:error, :invalid_checksum} = Parser.parse("1234567890")
|
||||
end
|
||||
end
|
||||
|
||||
describe "birth_date/1" do
|
||||
test "calculates correct birth date from digits" do
|
||||
digits = [3, 7, 5, 8, 0, 0, 5, 3, 6, 4]
|
||||
assert Parser.birth_date(digits) == ~D[2002-11-21]
|
||||
|
||||
digits = [3, 0, 4, 3, 2, 1, 6, 8, 2, 4]
|
||||
assert Parser.birth_date(digits) == ~D[1983-04-27]
|
||||
|
||||
digits = [2, 8, 8, 1, 2, 2, 0, 4, 5, 2]
|
||||
assert Parser.birth_date(digits) == ~D[1978-11-19]
|
||||
end
|
||||
end
|
||||
|
||||
describe "number/1" do
|
||||
test "extracts correct number from digits" do
|
||||
digits = [2, 8, 8, 1, 2, 2, 0, 4, 5, 2]
|
||||
assert Parser.number(digits) == 2045
|
||||
|
||||
digits = [3, 5, 8, 9, 4, 1, 2, 3, 7, 1]
|
||||
assert Parser.number(digits) == 1237
|
||||
|
||||
digits = [3, 2, 1, 5, 6, 0, 5, 0, 5, 3]
|
||||
assert Parser.number(digits) == 505
|
||||
end
|
||||
end
|
||||
|
||||
describe "gender/1" do
|
||||
test "determines correct gender from digits" do
|
||||
# Even number in the second-to-last position (gender 1)
|
||||
digits = [2, 5, 8, 5, 1, 1, 9, 1, 1, 5]
|
||||
assert Parser.gender(digits) == 1
|
||||
|
||||
digits = [2, 6, 0, 3, 8, 2, 1, 5, 3, 1]
|
||||
assert Parser.gender(digits) == 1
|
||||
|
||||
# Odd number in the second-to-last position (gender 0)
|
||||
digits = [2, 2, 0, 7, 3, 1, 5, 4, 2, 8]
|
||||
assert Parser.gender(digits) == 0
|
||||
|
||||
digits = [3, 1, 2, 6, 7, 0, 7, 0, 2, 6]
|
||||
assert Parser.gender(digits) == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "base_date/0" do
|
||||
test "returns the correct base date" do
|
||||
assert Parser.base_date() == ~D[1899-12-31]
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue