Skip to content

Commit e8aeba2

Browse files
authored
Implement the new ro.onrc identifier
1 parent fc766bc commit e8aeba2

File tree

1 file changed

+71
-24
lines changed

1 file changed

+71
-24
lines changed

stdnum/ro/onrc.py

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# onrc.py - functions for handling Romanian ONRC numbers
22
# coding: utf-8
33
#
4-
# Copyright (C) 2020 Dimitrios Josef Moustos
4+
# Copyright (C) 2024 Dimitrios Josef Moustos
55
# Copyright (C) 2020 Arthur de Jong
66
#
77
# This library is free software; you can redistribute it and/or
@@ -19,20 +19,23 @@
1919
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
2020
# 02110-1301 USA
2121

22-
"""ONRC (Ordine din Registrul Comerţului, Romanian Trade Register identifier).
22+
"""ONRC (Ordine din Registrul Comer\xc5\xa3ului, Romanian Trade Register identifier).
2323
2424
All businesses in Romania have the to register with the National Trade
2525
Register Office to receive a registration number. The number contains
26-
information about the type of company, county, a sequence number and
27-
registration year. This number can change when registration information
28-
changes.
26+
information about the type of company, registration year, a sequence number,
27+
county and a control sum.
28+
On 2024-07-26 a new format was introduced and for a while both old and new
29+
formats need to be valid.
2930
3031
>>> validate('J52/750/2012')
3132
'J52/750/2012'
3233
>>> validate('X52/750/2012')
3334
Traceback (most recent call last):
3435
...
3536
InvalidComponent: ...
37+
>>> validate('J2012000750528')
38+
'J2012000750528'
3639
"""
3740

3841
import datetime
@@ -46,16 +49,29 @@
4649
_cleanup_re = re.compile(r'[ /\\-]+')
4750

4851
# This pattern should match numbers that for some reason have a full date
49-
# as last field
50-
_onrc_fulldate_re = re.compile(r'^([A-Z][0-9]+/[0-9]+/)\d{2}[.]\d{2}[.](\d{4})$')
52+
# as last field for the old format
53+
_old_onrc_fulldate_re = re.compile(r'^([A-Z][0-9]+/[0-9]+/)\d{2}[.]\d{2}[.](\d{4})$')
54+
55+
# This pattern should match all valid old format numbers
56+
_old_onrc_re = re.compile(r'^[A-Z][0-9]+/[0-9]+/[0-9]+$')
57+
58+
# This pattern should match all valid new format numbers
59+
_onrc_re = re.compile(r'^[A-Z]\d{4}\d{6}\d{2}\d$')
5160

52-
# This pattern should match all valid numbers
53-
_onrc_re = re.compile(r'^[A-Z][0-9]+/[0-9]+/[0-9]+$')
5461

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

65+
# Calculate the control digit which is used in the last position of the new format
66+
def _calculate_control_digit(number):
67+
"""Calculate the control digit for the new ONRC format."""
68+
values = {'J': 10, 'F': 6, 'C': 3}
69+
num = number[:-1] # Exclude control digit
70+
total = values.get(num[0], 0) # Map letter to value
71+
total += sum(int(d) for d in num[1:] if d.isdigit())
72+
return (total + 4) % 10
5873

74+
# This function is only necessary for the old format
5975
def compact(number):
6076
"""Convert the number to the minimal representation. This strips the
6177
number of any valid separators and removes surrounding whitespace."""
@@ -67,29 +83,60 @@ def compact(number):
6783
if number[2:3] == '/':
6884
number = number[:1] + '0' + number[1:]
6985
# convert trailing full date to year only
70-
m = _onrc_fulldate_re.match(number)
86+
m = _old_onrc_fulldate_re.match(number)
7187
if m:
7288
number = ''.join(m.groups())
7389
return number
7490

7591

7692
def validate(number):
7793
"""Check if the number is a valid ONRC."""
78-
number = compact(number)
79-
if not _onrc_re.match(number):
94+
if _onrc_re.match(number):
95+
if number[0] not in 'JFC':
96+
raise InvalidComponent("Invalid register type. Must be J, F, or C.")
97+
year = int(number[1:5])
98+
sequence = number[5:11]
99+
county = int(number[11:13])
100+
control_digit = int(number[13])
101+
102+
# Validate year
103+
if year < 1990 or year > datetime.date.today().year:
104+
raise InvalidComponent("Year out of valid range.")
105+
106+
# Validate sequence number (6 digits)
107+
if len(sequence) != 6 or not sequence.isdigit():
108+
raise InvalidLength("Sequence number must be exactly 6 digits.")
109+
110+
# Companies registered before 2024-07-26 have the county code
111+
if (year <= 2024) and (county not in _counties):
112+
raise InvalidComponent("Invalid county code.")
113+
# Companies registered after 2024-07-26 have 00 as county code.
114+
if (year >= 2024) and (county == 0):
115+
raise InvalidComponent("Invalid county code.")
116+
117+
# Validate control digit
118+
expected_control = _calculate_control_digit(number)
119+
if control_digit != expected_control:
120+
raise InvalidChecksum(f"Control digit {control_digit} does not match expected {expected_control}.")
121+
122+
return number
123+
124+
elif _old_onrc_re.match(number):
125+
number = compact(number)
126+
if number[:1] not in 'JFC':
127+
raise InvalidComponent()
128+
county, serial, year = number[1:].split('/')
129+
if len(serial) > 5:
130+
raise InvalidLength()
131+
if len(county) not in (1, 2) or int(county) not in _counties:
132+
raise InvalidComponent()
133+
if len(year) != 4:
134+
raise InvalidLength()
135+
if int(year) < 1990 or int(year) > 2024:
136+
raise InvalidComponent()
137+
return number
138+
else:
80139
raise InvalidFormat()
81-
if number[:1] not in 'JFC':
82-
raise InvalidComponent()
83-
county, serial, year = number[1:].split('/')
84-
if len(serial) > 5:
85-
raise InvalidLength()
86-
if len(county) not in (1, 2) or int(county) not in _counties:
87-
raise InvalidComponent()
88-
if len(year) != 4:
89-
raise InvalidLength()
90-
if int(year) < 1990 or int(year) > datetime.date.today().year:
91-
raise InvalidComponent()
92-
return number
93140

94141

95142
def is_valid(number):

0 commit comments

Comments
 (0)