Skip to content

Commit a906a1e

Browse files
cedkarthurdejong
authored andcommitted
Add accise - the French number for excise
Closes #424
1 parent 1a254d9 commit a906a1e

File tree

4 files changed

+135
-2
lines changed

4 files changed

+135
-2
lines changed

stdnum/eu/excise.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def validate(number: str) -> str:
7070
if len(number) != 13:
7171
raise InvalidLength()
7272
module = _get_cc_module(number[:2])
73-
if module: # pragma: no cover (no implementation yet)
73+
if module:
7474
module.validate(number)
7575
return number
7676

stdnum/fr/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@
2222

2323
from __future__ import annotations
2424

25-
# provide vat as an alias
25+
# provide excise and vat as an alias
26+
from stdnum.fr import accise as excise # noqa: F401
2627
from stdnum.fr import tva as vat # noqa: F401

stdnum/fr/accise.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# accise.py - functions for handling French Accise numbers
2+
# coding: utf-8
3+
#
4+
# Copyright (C) 2023 Cédric Krier
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+
"""n° d'accise (French number to identify taxpayers of excise taxes).
22+
23+
The n° d'accise always start by FR0 following by the 2 ending digits of the
24+
year, 3 number of customs office, one letter for the type and an ordering
25+
number of 4 digits.
26+
27+
>>> validate('FR023004N9448')
28+
'FR023004N9448'
29+
>>> validate('FR0XX907E0820')
30+
Traceback (most recent call last):
31+
...
32+
InvalidFormat: ...
33+
>>> validate('FR012907A0820')
34+
Traceback (most recent call last):
35+
...
36+
InvalidComponent: ...
37+
"""
38+
39+
from __future__ import annotations
40+
41+
from stdnum.exceptions import *
42+
from stdnum.util import clean, isdigits
43+
44+
45+
OPERATORS = set(['E', 'N', 'C', 'B'])
46+
47+
48+
def compact(number: str) -> str:
49+
"""Convert the number to the minimal representation. This strips the number
50+
of any valid separators and removes surrounding whitespace."""
51+
return clean(number, ' ').upper().strip()
52+
53+
54+
def validate(number: str) -> str:
55+
"""Check if the number is a valid accise number. This checks the length,
56+
formatting."""
57+
number = compact(number)
58+
code = number[:3]
59+
if code != 'FR0':
60+
raise InvalidFormat()
61+
if len(number) != 13:
62+
raise InvalidLength()
63+
if not isdigits(number[3:5]):
64+
raise InvalidFormat()
65+
if not isdigits(number[5:8]):
66+
raise InvalidFormat()
67+
if number[8] not in OPERATORS:
68+
raise InvalidComponent()
69+
if not isdigits(number[9:12]):
70+
raise InvalidFormat()
71+
return number
72+
73+
74+
def is_valid(number: str) -> bool:
75+
"""Check if the number is a valid accise number."""
76+
try:
77+
return bool(validate(number))
78+
except ValidationError:
79+
return False

tests/test_fr_accise.doctest

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
test_fr_accise.doctest - more detailed doctests for the stdnum.fr.accise module
2+
3+
Copyright (C) 2023 Cédric Krier
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+
This file contains more detailed doctests for the stdnum.fr.accise module. It
21+
tries to validate a number of Excise numbers that have been found online.
22+
23+
>>> from stdnum.fr import accise
24+
>>> from stdnum.exceptions import *
25+
26+
27+
>>> accise.compact('FR0 23 004 N 9448')
28+
'FR023004N9448'
29+
>>> accise.validate('FR023004N9448')
30+
'FR023004N9448'
31+
>>> accise.validate('FR012907E0820')
32+
'FR012907E0820'
33+
34+
>>> accise.validate('FR012345')
35+
Traceback (most recent call last):
36+
...
37+
InvalidLength: ...
38+
>>> accise.validate('FR0XX907E0820')
39+
Traceback (most recent call last):
40+
...
41+
InvalidFormat: ...
42+
>>> accise.validate('FR012XXXE0820')
43+
Traceback (most recent call last):
44+
...
45+
InvalidFormat: ...
46+
>>> accise.validate('FR012907A0820')
47+
Traceback (most recent call last):
48+
...
49+
InvalidComponent: ...
50+
>>> accise.validate('FR012907EXXXX')
51+
Traceback (most recent call last):
52+
...
53+
InvalidFormat: ...

0 commit comments

Comments
 (0)