2024-12-13 02:17:30 +00:00
|
|
|
|
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
|
|
|
|
|
|
2024-12-13 05:34:25 +00:00
|
|
|
|
defp generate(string) do
|
2024-12-13 02:17:30 +00:00
|
|
|
|
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
|