Skip to content

Commit cd94bf1

Browse files
dotbit1arthurdejong
authored andcommitted
Support the new Romanian ONRC format
Closes #465
1 parent 293136b commit cd94bf1

File tree

2 files changed

+145
-113
lines changed

2 files changed

+145
-113
lines changed

stdnum/ro/onrc.py

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
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
@@ -23,36 +23,47 @@
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 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')
3338
Traceback (most recent call last):
3439
...
3540
InvalidComponent: ...
36-
"""
41+
>>> validate('J2012000750528')
42+
'J2012000750528'
43+
>>> validate('J2012000750529')
44+
Traceback (most recent call last):
45+
...
46+
InvalidChecksum: ...
47+
""" # noqa: E501
3748

3849
from __future__ import annotations
3950

4051
import datetime
4152
import re
4253

4354
from 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

Comments
 (0)