11# onrc.py - functions for handling Romanian ONRC numbers
22# coding: utf-8
33#
4- # Copyright (C) 2020 Dimitrios Josef Moustos
5- # Copyright (C) 2020 Arthur de Jong
4+ # Copyright (C) 2020-2024 Dimitrios Josef Moustos
5+ # Copyright (C) 2020-2025 Arthur de Jong
66#
77# This library is free software; you can redistribute it and/or
88# modify it under the terms of the GNU Lesser General Public
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 and registration year.
27+
28+ On 2024-07-26 a new format was introduced and for a while both old and new
29+ formats need to be valid.
30+
31+ More information:
32+
33+ * https://targetare.ro/blog/schimbari-importante-la-registrul-comertului-ce-trebuie-sa-stii-despre-noul-format-al-numarului-de-ordine
2934
3035>>> validate('J52/750/2012')
3136'J52/750/2012'
3237>>> validate('X52/750/2012')
3338Traceback (most recent call last):
3439 ...
3540InvalidComponent: ...
36- """
41+ >>> validate('J2012000750528')
42+ 'J2012000750528'
43+ >>> validate('J2012000750529')
44+ Traceback (most recent call last):
45+ ...
46+ InvalidChecksum: ...
47+ """ # noqa: E501
3748
3849from __future__ import annotations
3950
4051import datetime
4152import re
4253
4354from stdnum .exceptions import *
44- from stdnum .util import clean
55+ from stdnum .util import clean , isdigits
4556
4657
4758# These characters should all be replaced by slashes
4859_cleanup_re = re .compile (r'[ /\\-]+' )
4960
5061# This pattern should match numbers that for some reason have a full date
51- # as last field
52- _onrc_fulldate_re = re .compile (r'^([A-Z][0-9]+/[0-9]+/)\d{2}[.]\d{2}[.](\d{4})$' )
62+ # as last field for the old format
63+ _old_onrc_fulldate_re = re .compile (r'^([A-Z][0-9]+/[0-9]+/)\d{2}[.]\d{2}[.](\d{4})$' )
5364
54- # This pattern should match all valid numbers
55- _onrc_re = re .compile (r'^[A-Z][0-9]+/[0-9]+/[0-9]+$' )
65+ # This pattern should match all valid numbers in the old format
66+ _old_onrc_re = re .compile (r'^[A-Z][0-9]+/[0-9]+/[0-9]+$' )
5667
5768# List of valid counties
5869_counties = set (list (range (1 , 41 )) + [51 , 52 ])
@@ -69,28 +80,75 @@ def compact(number: str) -> str:
6980 if number [2 :3 ] == '/' :
7081 number = number [:1 ] + '0' + number [1 :]
7182 # convert trailing full date to year only
72- m = _onrc_fulldate_re .match (number )
83+ m = _old_onrc_fulldate_re .match (number )
7384 if m :
7485 number = '' .join (m .groups ())
7586 return number
7687
7788
78- def validate (number : str ) -> str :
79- """Check if the number is a valid ONRC."""
80- number = compact (number )
81- if not _onrc_re .match (number ):
89+ def _validate_old_format (number : str ) -> None :
90+ # old YJJ/XXXX/AAAA format
91+ if not _old_onrc_re .match (number ):
8292 raise InvalidFormat ()
83- if number [:1 ] not in 'JFC' :
84- raise InvalidComponent ()
8593 county , serial , year = number [1 :].split ('/' )
8694 if len (serial ) > 5 :
8795 raise InvalidLength ()
8896 if len (county ) not in (1 , 2 ) or int (county ) not in _counties :
8997 raise InvalidComponent ()
9098 if len (year ) != 4 :
9199 raise InvalidLength ()
92- if int (year ) < 1990 or int (year ) > datetime .date .today ().year :
100+ # old format numbers will not be issued after 2024
101+ if int (year ) < 1990 or int (year ) > 2024 :
102+ raise InvalidComponent ()
103+
104+
105+ def _calc_check_digit (number : str ) -> str :
106+ """Calculate the check digit for the new ONRC format."""
107+ # replace letters with digits
108+ number = str (ord (number [0 ]) % 10 ) + number [1 :]
109+ return str (sum (int (n ) for n in number [:- 1 ]) % 10 )
110+
111+
112+ def _validate_new_format (number : str ) -> None :
113+ # new YAAAAXXXXXXJJC format, no slashes
114+ if not isdigits (number [1 :]):
115+ raise InvalidFormat ()
116+ if len (number ) != 14 :
117+ raise InvalidLength ()
118+ year = int (number [1 :5 ])
119+ if year < 1990 or year > datetime .date .today ().year :
120+ raise InvalidComponent ()
121+ # the registration year determines which counties are allowed
122+ # companies registered after 2024-07-26 have 00 as county code
123+ county = int (number [11 :13 ])
124+ if year < 2024 :
125+ if county not in _counties :
126+ raise InvalidComponent ()
127+ elif year == 2024 :
128+ if county not in _counties .union ([0 ]):
129+ raise InvalidComponent ()
130+ else :
131+ if county != 0 :
132+ raise InvalidComponent ()
133+ if number [- 1 ] != _calc_check_digit (number ):
134+ raise InvalidChecksum
135+
136+
137+ def validate (number : str ) -> str :
138+ """Check if the number is a valid ONRC."""
139+ number = compact (number )
140+ # J: legal entities (e.g., LLC, SA, etc.)
141+ # F: sole proprietorships, individual and family businesses
142+ # C: cooperative societies.
143+ if number [:1 ] not in 'JFC' :
93144 raise InvalidComponent ()
145+ if '/' in number :
146+ # old YJJ/XXXX/AAAA format, still supported but will be phased out, companies
147+ # will get a new number
148+ _validate_old_format (number )
149+ else :
150+ # new YAAAAXXXXXXJJC format, no slashes
151+ _validate_new_format (number )
94152 return number
95153
96154
0 commit comments