Parser for EDRPOU completed

This commit is contained in:
2024-12-12 01:32:31 -05:00
parent a7692124e9
commit f9a45fb29d
10 changed files with 449 additions and 38 deletions

View File

@@ -5,8 +5,8 @@ defmodule UkraineTaxidEx.Edrpou.CheckSum do
@typedoc """
Coefficients (weights) for digits to calculate EDRPOU check sum may be two types:
base ([1, 2, 3, 4, 5, 6, 7] for EDRPOU < 30M or EDRPOU > 60M)
or alternative ([7, 1, 2, 3, 4, 5, 6] if EDRPOU between 30M and 60M)
base (`[1, 2, 3, 4, 5, 6, 7] for EDRPOU < 30M or EDRPOU > 60M`)
or alternative (`[7, 1, 2, 3, 4, 5, 6] if EDRPOU between 30M and 60M`)
"""
@type weights_type :: :base | :alternative
@@ -17,7 +17,7 @@ defmodule UkraineTaxidEx.Edrpou.CheckSum do
2. Multiply each digit by its corresponding weight
3. Sum the products
4. Take mod 11 of the sum
5. If mod 11 is greater or equal than 10, repeat steps 2-4 with doubled weights
5. If mod 11 is greater or equal than 10, repeat steps 2-4 with weights +2
"""
@spec check_sum(digits :: C.digits()) :: integer()
def check_sum(digits) do
@@ -29,7 +29,7 @@ defmodule UkraineTaxidEx.Edrpou.CheckSum do
value_digits = value_digits(digits)
case calculate_check_sum(value_digits, weights(type, false)) do
s when s >= 10 -> calculate_check_sum(value_digits, weights(type, true))
s when s >= 10 -> rem(calculate_check_sum(value_digits, weights(type, true)), 10)
s -> s
end
end

View File

@@ -2,23 +2,24 @@ defmodule UkraineTaxidEx.Edrpou.Error do
@type error() ::
:invalid_length
| :invalid_checksum
| :length_to_long
| :length_to_short
| :length_too_long
| :length_too_short
@type errors() :: [error()]
@errors [
:invalid_length,
:invalid_checksum,
:length_to_long,
:length_to_short
:length_too_long,
:length_too_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"
length_too_long: "EDRPOU longer then required length",
length_too_short: "EDRPOU shorter then required length"
]
@spec message(error()) :: String.t()
@spec message({:error, error()} | error()) :: String.t()
def message({:error, error}) when error in @errors, do: @messages[error]
def message(error) when error in @errors, do: @messages[error]
def message(_error), do: "Undefined error"
end

View File

@@ -1,20 +1,84 @@
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.Commons, only: [check_digit: 1, digits: 2, ok: 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
def parse(edrpou_string, incomplete: false) do
digits = digits(edrpou_string, length())
@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_length
| :invalid_checksum}
%{
code: edrpou_string,
check_sum: check_sum(digits),
check_digit: check_digit(digits)
}
@impl BaseParser
@doc """
Parses an EDRPOU code string into a structured format.
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.
Returns {:ok, %Edrpou{}} for valid codes or {:error, reason} for invalid.
## Examples
iex> UkraineTaxidEx.Edrpou.Parser.parse("00032112")
{:ok, %UkraineTaxidEx.Edrpou{code: "00032112", check_digit: 2, check_sum: 2}}
iex> UkraineTaxidEx.Edrpou.Parser.parse({:ok, "00032112"})
{:ok, %UkraineTaxidEx.Edrpou{code: "00032112", check_digit: 2, check_sum: 2}}
iex> UkraineTaxidEx.Edrpou.Parser.parse("32129", normalize?: true)
{:ok, %UkraineTaxidEx.Edrpou{code: "00032129", check_digit: 9, check_sum: 9}}
iex> UkraineTaxidEx.Edrpou.Parser.parse("9 30test62 78", normalize?: true, clean?: true)
{:ok, %UkraineTaxidEx.Edrpou{code: "09306278", check_digit: 8, check_sum: 8}}
iex> UkraineTaxidEx.Edrpou.Parser.parse("123")
{:error, :length_too_short}
iex> UkraineTaxidEx.Edrpou.Parser.parse("123456789")
{:error, :length_too_long}
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)
edrpou_string
|> digits(length, clean?)
|> undigits()
|> validate()
|> generate_edrpou()
end
defp generate_edrpou({:error, error}), do: {:error, error}
defp generate_edrpou({:ok, edrpou_string}) do
digits = digits(edrpou_string)
%{code: edrpou_string, check_sum: check_sum(digits), check_digit: check_digit(digits)}
|> create_struct()
|> ok()
end

View File

@@ -0,0 +1,60 @@
defmodule UkraineTaxidEx.Edrpou.Validator do
@moduledoc """
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:
* `{:ok, edrpou}` 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
"""
@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
@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
end