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
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 \xa3 ului , Romanian Trade Register identifier).
2323
2424All businesses in Romania have the to register with the National Trade
2525Register 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')
3334Traceback (most recent call last):
3435 ...
3536InvalidComponent: ...
37+ >>> validate('J2012000750528')
38+ 'J2012000750528'
3639"""
3740
3841import datetime
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
5975def 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
7692def 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
95142def is_valid (number ):
0 commit comments