92 lines
2.7 KiB
Elixir
92 lines
2.7 KiB
Elixir
defmodule UkraineTaxidEx.Edrpou.CheckSum do
|
|
alias UkraineTaxidEx.Commons, as: C
|
|
|
|
import UkraineTaxidEx.Commons, only: [value_digits: 1]
|
|
|
|
@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`)
|
|
"""
|
|
@type weights_type :: :base | :alternative
|
|
|
|
@doc """
|
|
Calculate checksum for EDRPOU number.
|
|
The checksum for EDRPOU is calculated in several steps:
|
|
1. Define the type of weights (base or alternative) as described in `weights_type/1`
|
|
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 weights +2
|
|
"""
|
|
@spec check_sum(digits :: C.digits()) :: integer()
|
|
def check_sum(digits) do
|
|
type =
|
|
digits
|
|
|> Integer.undigits()
|
|
|> weights_type()
|
|
|
|
value_digits = value_digits(digits)
|
|
|
|
case calculate_check_sum(value_digits, weights(type, false)) do
|
|
s when s >= 10 -> rem(calculate_check_sum(value_digits, weights(type, true)), 10)
|
|
s -> s
|
|
end
|
|
end
|
|
|
|
defguardp is_base_weights(value) when value < 30_000_000 or value > 60_000_000
|
|
|
|
@doc """
|
|
Returns a list of weights used for EDRPOU check sum calculation.
|
|
When double_added is true, adds 2 to each weight in the list.
|
|
|
|
## Parameters
|
|
- type: `:base` or `:alternative` weights pattern
|
|
- double_added: when true, adds 2 to each weight
|
|
|
|
## Examples
|
|
```elixir
|
|
iex> weights(:base)
|
|
[1, 2, 3, 4, 5, 6, 7]
|
|
|
|
iex> weights(:alternative)
|
|
[7, 1, 2, 3, 4, 5, 6]
|
|
|
|
iex> weights(:base, true)
|
|
[3, 4, 5, 6, 7, 8, 9]
|
|
|
|
iex> weights(:alternative, true)
|
|
[9, 3, 4, 5, 6, 7, 8]
|
|
```
|
|
"""
|
|
@spec weights(type :: weights_type, double_added? :: boolean()) :: C.digits()
|
|
def weights(type \\ :base, double_added \\ false)
|
|
def weights(:base, false), do: Enum.to_list(1..7)
|
|
|
|
def weights(:alternative, false) do
|
|
base = weights()
|
|
[List.last(base) | Enum.take(base, length(base) - 1)]
|
|
end
|
|
|
|
def weights(type, true) do
|
|
type
|
|
|> weights(false)
|
|
|> Enum.map(&(&1 + 2))
|
|
end
|
|
|
|
@spec divider() :: non_neg_integer()
|
|
defp divider(), do: 11
|
|
|
|
@spec weights_type(value :: pos_integer()) :: C.digits()
|
|
defp weights_type(value) when is_base_weights(value), do: :base
|
|
defp weights_type(_value), do: :alternative
|
|
|
|
@spec calculate_check_sum(digits :: C.digits(), weights :: C.digits()) :: non_neg_integer()
|
|
defp calculate_check_sum(digits, weights) do
|
|
digits
|
|
|> Enum.zip(weights)
|
|
|> Enum.reduce(0, fn {digit, weight}, acc -> digit * weight + acc end)
|
|
|> rem(divider())
|
|
end
|
|
end
|