File: onrc.py

package info (click to toggle)
python-stdnum 2.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,960 kB
  • sloc: python: 10,600; javascript: 6,995; sh: 13; makefile: 10
file content (160 lines) | stat: -rw-r--r-- 5,427 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# onrc.py - functions for handling Romanian ONRC numbers
# coding: utf-8
#
# Copyright (C) 2020-2024 Dimitrios Josef Moustos
# Copyright (C) 2020-2025 Arthur de Jong
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301 USA

"""ONRC (Ordine din Registrul Comerţului, Romanian Trade Register identifier).

All businesses in Romania have the to register with the National Trade
Register Office to receive a registration number. The number contains
information about the type of company and registration year.

On 2024-07-26 a new format was introduced and for a while both old and new
formats need to be valid.

More information:

* https://targetare.ro/blog/schimbari-importante-la-registrul-comertului-ce-trebuie-sa-stii-despre-noul-format-al-numarului-de-ordine

>>> validate('J52/750/2012')
'J52/750/2012'
>>> validate('X52/750/2012')
Traceback (most recent call last):
    ...
InvalidComponent: ...
>>> validate('J2012000750528')
'J2012000750528'
>>> validate('J2012000750529')
Traceback (most recent call last):
    ...
InvalidChecksum: ...
"""  # noqa: E501

from __future__ import annotations

import datetime
import re

from stdnum.exceptions import *
from stdnum.util import clean, isdigits


# These characters should all be replaced by slashes
_cleanup_re = re.compile(r'[ /\\-]+')

# This pattern should match numbers that for some reason have a full date
# as last field for the old format
_old_onrc_fulldate_re = re.compile(r'^([A-Z][0-9]+/[0-9]+/)\d{2}[.]\d{2}[.](\d{4})$')

# This pattern should match all valid numbers in the old format
_old_onrc_re = re.compile(r'^[A-Z][0-9]+/[0-9]+/[0-9]+$')

# List of valid counties
_counties = set(list(range(1, 41)) + [51, 52])


def compact(number: str) -> str:
    """Convert the number to the minimal representation. This strips the
    number of any valid separators and removes surrounding whitespace."""
    number = _cleanup_re.sub('/', clean(number).upper().strip())
    # remove optional slash between first letter and county digits
    if number[1:2] == '/':
        number = number[:1] + number[2:]
    # normalise county number to two digits
    if number[2:3] == '/':
        number = number[:1] + '0' + number[1:]
    # convert trailing full date to year only
    m = _old_onrc_fulldate_re.match(number)
    if m:
        number = ''.join(m.groups())
    return number


def _validate_old_format(number: str) -> None:
    # old YJJ/XXXX/AAAA format
    if not _old_onrc_re.match(number):
        raise InvalidFormat()
    county, serial, year = number[1:].split('/')
    if len(serial) > 5:
        raise InvalidLength()
    if len(county) not in (1, 2) or int(county) not in _counties:
        raise InvalidComponent()
    if len(year) != 4:
        raise InvalidLength()
    # old format numbers will not be issued after 2024
    if int(year) < 1990 or int(year) > 2024:
        raise InvalidComponent()


def _calc_check_digit(number: str) -> str:
    """Calculate the check digit for the new ONRC format."""
    # replace letters with digits
    number = str(ord(number[0]) % 10) + number[1:]
    return str(sum(int(n) for n in number[:-1]) % 10)


def _validate_new_format(number: str) -> None:
    # new YAAAAXXXXXXJJC format, no slashes
    if not isdigits(number[1:]):
        raise InvalidFormat()
    if len(number) != 14:
        raise InvalidLength()
    year = int(number[1:5])
    if year < 1990 or year > datetime.date.today().year:
        raise InvalidComponent()
    # the registration year determines which counties are allowed
    # companies registered after 2024-07-26 have 00 as county code
    county = int(number[11:13])
    if year < 2024:
        if county not in _counties:
            raise InvalidComponent()
    elif year == 2024:
        if county not in _counties.union([0]):
            raise InvalidComponent()
    else:
        if county != 0:
            raise InvalidComponent()
    if number[-1] != _calc_check_digit(number):
        raise InvalidChecksum


def validate(number: str) -> str:
    """Check if the number is a valid ONRC."""
    number = compact(number)
    # J: legal entities (e.g., LLC, SA, etc.)
    # F: sole proprietorships, individual and family businesses
    # C: cooperative societies.
    if number[:1] not in 'JFC':
        raise InvalidComponent()
    if '/' in number:
        # old YJJ/XXXX/AAAA format, still supported but will be phased out, companies
        # will get a new number
        _validate_old_format(number)
    else:
        # new YAAAAXXXXXXJJC format, no slashes
        _validate_new_format(number)
    return number


def is_valid(number: str) -> bool:
    """Check if the number is a valid ONRC."""
    try:
        return bool(validate(number))
    except ValidationError:
        return False