Skip to content

Commit e741318

Browse files
fmichel-adistaarthurdejong
authored andcommitted
Add French RCS number
Closes #481
1 parent ca80cc4 commit e741318

File tree

2 files changed

+173
-0
lines changed

2 files changed

+173
-0
lines changed

stdnum/fr/rcs.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# rcs.py - functions for handling French NIR numbers
2+
# coding: utf-8
3+
#
4+
# Copyright (C) 2025 Fabien Michel
5+
#
6+
# This library is free software; you can redistribute it and/or
7+
# modify it under the terms of the GNU Lesser General Public
8+
# License as published by the Free Software Foundation; either
9+
# version 2.1 of the License, or (at your option) any later version.
10+
#
11+
# This library is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
# Lesser General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU Lesser General Public
17+
# License along with this library; if not, write to the Free Software
18+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19+
# 02110-1301 USA
20+
21+
"""RCS (French trade registration number for commercial companies).
22+
23+
The RCS number (Registre du commerce et des sociétés) is given by INSEE to a
24+
company with commercial activity when created. It is required for most of
25+
administrative procedures.
26+
27+
The number consists of "RCS" letters followed by name of the city where the company was registered
28+
followed by letter A for a retailer or B for society
29+
followed by the SIREN number
30+
31+
More information:
32+
33+
* https://entreprendre.service-public.fr/vosdroits/F31190
34+
* https://fr.wikipedia.org/wiki/Registre_du_commerce_et_des_sociétés_(France)
35+
36+
>>> validate('RCS Nancy B 323 159 715')
37+
'RCS Nancy B 323159715'
38+
>>> validate('RCS Nancy B 323 159 716')
39+
Traceback (most recent call last):
40+
...
41+
InvalidChecksum: ...
42+
>>> validate('RCSNancy B 323 159 716')
43+
Traceback (most recent call last):
44+
...
45+
InvalidFormat: ...
46+
>>> format('RCS Nancy B323159715')
47+
'RCS Nancy B 323 159 715'
48+
>>> to_siren('RCS Nancy B 323159 715')
49+
'323159715'
50+
"""
51+
52+
from __future__ import annotations
53+
54+
import re
55+
56+
from stdnum.exceptions import *
57+
from stdnum.fr import siren
58+
from stdnum.util import clean
59+
60+
61+
_rcs_validation_regex = re.compile(
62+
r'^ *(?P<tag>RCS|rcs) +(?P<city>.*?) +(?P<letter>[AB]) *(?P<siren>(?:\d *){9})\b *$',
63+
)
64+
65+
66+
def compact(number: str) -> str:
67+
"""Convert the number to the minimal representation."""
68+
parts = clean(number).strip().split()
69+
rest = ''.join(parts[2:])
70+
return ' '.join(parts[:2] + [rest[:1], siren.compact(rest[1:])])
71+
72+
73+
def validate(number: str) -> str:
74+
"""Validate number is a valid French RCS number."""
75+
number = compact(number)
76+
match = _rcs_validation_regex.match(number)
77+
if not match:
78+
raise InvalidFormat()
79+
siren_number = siren.validate(match.group('siren'))
80+
siren_number = siren.format(siren_number)
81+
return number
82+
83+
84+
def is_valid(number: str) -> bool:
85+
"""Check if the number provided is valid."""
86+
try:
87+
return bool(validate(number))
88+
except ValidationError:
89+
return False
90+
91+
92+
def format(number: str) -> str:
93+
"""Reformat the number to the standard presentation format."""
94+
number = compact(number)
95+
match = _rcs_validation_regex.match(number)
96+
if not match:
97+
raise InvalidFormat()
98+
return ' '.join(
99+
(
100+
'RCS',
101+
match.group('city'),
102+
match.group('letter'),
103+
siren.format(match.group('siren')),
104+
),
105+
)
106+
107+
108+
def to_siren(number: str) -> str:
109+
"""Extract SIREN number from the RCS number.
110+
111+
The SIREN number is the 9 last digits of the RCS number.
112+
"""
113+
number = compact(number)
114+
match = _rcs_validation_regex.match(clean(number))
115+
if not match:
116+
raise InvalidFormat()
117+
return match.group('siren')

tests/test_fr_rcs.doctest

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
test_fr_rcs.doctest - more detailed doctests for the stdnum.fr.rcs module
2+
3+
Copyright (C) 2025 Fabien Michel
4+
5+
This library is free software; you can redistribute it and/or
6+
modify it under the terms of the GNU Lesser General Public
7+
License as published by the Free Software Foundation; either
8+
version 2.1 of the License, or (at your option) any later version.
9+
10+
This library is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
Lesser General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public
16+
License along with this library; if not, write to the Free Software
17+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18+
02110-1301 USA
19+
20+
21+
This file contains more detailed doctests for the stdnum.fr.rcs module.
22+
23+
>>> from stdnum.fr import rcs
24+
>>> from stdnum.exceptions import *
25+
26+
27+
>>> rcs.validate('RCS Nancy B 323 159 715')
28+
'RCS Nancy B 323159715'
29+
>>> rcs.validate('RCS Nancy B 323159715')
30+
'RCS Nancy B 323159715'
31+
>>> rcs.validate('RCS Nancy B323 159715')
32+
'RCS Nancy B 323159715'
33+
>>> rcs.validate('RCS Nancy B 323 159 716')
34+
Traceback (most recent call last):
35+
...
36+
InvalidChecksum: ...
37+
>>> rcs.validate('RCSNancy B 323 159 716')
38+
Traceback (most recent call last):
39+
...
40+
InvalidFormat: ...
41+
>>> rcs.validate('RCS NancyB 323 159 716')
42+
Traceback (most recent call last):
43+
...
44+
InvalidFormat: ...
45+
>>> rcs.format('RCS Nancy B323159715')
46+
'RCS Nancy B 323 159 715'
47+
>>> rcs.format('invalid')
48+
Traceback (most recent call last):
49+
...
50+
InvalidFormat: ...
51+
>>> rcs.to_siren('RCS Nancy B 323159 715')
52+
'323159715'
53+
>>> rcs.to_siren('invalid')
54+
Traceback (most recent call last):
55+
...
56+
InvalidFormat: ...

0 commit comments

Comments
 (0)