Itin parser and validator added

This commit is contained in:
2024-12-12 21:17:30 -05:00
parent 0edc3c3139
commit 695ffc31c5
14 changed files with 513 additions and 108 deletions

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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