Skip to content

Commit 2907676

Browse files
unhoarthurdejong
authored andcommitted
Add support for Morocco TIN
Closes arthurdejong#226 Closes arthurdejong#312
1 parent d70549a commit 2907676

File tree

3 files changed

+279
-0
lines changed

3 files changed

+279
-0
lines changed

stdnum/ma/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# __init__.py - collection of Moroccan numbers
2+
# coding: utf-8
3+
#
4+
# Copyright (C) 2022 Leandro Regueiro
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+
"""Collection of Moroccan numbers."""
22+
23+
# provide vat as an alias
24+
from stdnum.ma import ice as vat # noqa: F401

stdnum/ma/ice.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# ice.py - functions for handling Morocco ICE numbers
2+
# coding: utf-8
3+
#
4+
# Copyright (C) 2022 Leandro Regueiro
5+
# Copyright (C) 2022 Arthur de Jong
6+
#
7+
# This library is free software; you can redistribute it and/or
8+
# modify it under the terms of the GNU Lesser General Public
9+
# License as published by the Free Software Foundation; either
10+
# version 2.1 of the License, or (at your option) any later version.
11+
#
12+
# This library is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
# Lesser General Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser General Public
18+
# License along with this library; if not, write to the Free Software
19+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20+
# 02110-1301 USA
21+
22+
"""ICE (Identifiant Commun de l’Entreprise, التعريف الموحد للمقاولة, Morocco tax number).
23+
24+
The ICE is a number that identifies the company and its branches in a unique
25+
and uniform way by all Moroccan administrations. It comes in addition to the
26+
other legal identifiers, notably the "identifiant fiscal" (IF), the "numéro de
27+
registre de commerce" (RC) and the CNSS number. The ICE does not replace these
28+
identifiers, which remain mandatory.
29+
30+
The ICE is intended to ease communication among Moroccan administration
31+
branches, therefore simplifying procedures, increasing reliability and speed,
32+
and thefore reducing costs.
33+
34+
The ICE applies to legal entities and their branches, as well as to natural
35+
persons.
36+
37+
The ICE consists of 15 characters, where the first 9 represent the enterprise,
38+
the following 4 represent its establishments, and the last 2 are control
39+
characters.
40+
41+
More information:
42+
43+
* https://www.ice.gov.ma/
44+
* https://www.ice.gov.ma/ICE/Depliant_ICE.pdf
45+
* https://www.ice.gov.ma/ICE/Guide_ICE.pdf
46+
47+
>>> validate('001561191000066')
48+
'001561191000066'
49+
>>> validate('00 21 36 09 30 00 040')
50+
'002136093000040'
51+
>>> validate('12345')
52+
Traceback (most recent call last):
53+
...
54+
InvalidLength: ...
55+
>>> validate('001561191000065')
56+
Traceback (most recent call last):
57+
...
58+
InvalidChecksum: ...
59+
>>> format('00 21 36 09 30 00 040')
60+
'002136093000040'
61+
"""
62+
63+
from stdnum.exceptions import *
64+
from stdnum.iso7064 import mod_97_10
65+
from stdnum.util import clean, isdigits
66+
67+
68+
def compact(number):
69+
"""Convert the number to the minimal representation.
70+
71+
This strips the number of any valid separators, removes surrounding
72+
whitespace.
73+
"""
74+
return clean(number, ' ')
75+
76+
77+
def validate(number):
78+
"""Check if the number is a valid Morocco ICE number.
79+
80+
This checks the length and formatting.
81+
"""
82+
number = compact(number)
83+
if len(number) != 15:
84+
raise InvalidLength()
85+
if not isdigits(number):
86+
raise InvalidFormat()
87+
if mod_97_10.checksum(number) != 0:
88+
raise InvalidChecksum()
89+
return number
90+
91+
92+
def is_valid(number):
93+
"""Check if the number is a valid Morocco ICE number."""
94+
try:
95+
return bool(validate(number))
96+
except ValidationError:
97+
return False
98+
99+
100+
def format(number):
101+
"""Reformat the number to the standard presentation format."""
102+
return compact(number).zfill(15)

tests/test_ma_ice.doctest

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
test_ma_ice.doctest - more detailed doctests for stdnum.ma.ice module
2+
3+
Copyright (C) 2022 Leandro Regueiro
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.ma.ice module. It
22+
tries to test more corner cases and detailed functionality that is not really
23+
useful as module documentation.
24+
25+
>>> from stdnum.ma import ice
26+
27+
28+
Tests for some corner cases.
29+
30+
>>> ice.validate('001561191000066')
31+
'001561191000066'
32+
>>> ice.validate('12345')
33+
Traceback (most recent call last):
34+
...
35+
InvalidLength: ...
36+
>>> ice.validate('X0156119100006Z')
37+
Traceback (most recent call last):
38+
...
39+
InvalidFormat: ...
40+
>>> ice.format('00 21 36 09 30 00 040')
41+
'002136093000040'
42+
>>> ice.format('36794000036')
43+
'000036794000036'
44+
45+
46+
These have been found online and should all be valid numbers.
47+
48+
>>> numbers = '''
49+
...
50+
... 000030205000041
51+
... 000039557000028
52+
... 000084803000004
53+
... 000106304000022
54+
... 000121494000008
55+
... 000206981000071
56+
... 00 21 36 09 30 00 040
57+
... 001512572000078
58+
... 001561191000066
59+
... 001565457000023
60+
... 001604356000066
61+
... 001629924000079
62+
... 001680801000017
63+
... 001731355000043
64+
... 001744856000042
65+
... 001747979000014
66+
... 001748885000093
67+
... 001753989000025
68+
... 001757319000034
69+
... 001827913000046
70+
... 001867807000093
71+
... 001883389000068
72+
... 001887940000090
73+
... 001906225000028
74+
... 001911196000059
75+
... 001965640000009
76+
... 001974718000022
77+
... 001976463000049
78+
... 001985336000068
79+
... 001996344000060
80+
... 002007999000043
81+
... 002020847000019
82+
... 002023181000051
83+
... 002033432000015
84+
... 002034945000001
85+
... 002037826000008
86+
... 002058647000053
87+
... 002063738000045
88+
... 002064672000047
89+
... 002076600000031
90+
... 002085583000087
91+
... 002105112000096
92+
... 002117772000007
93+
... 002143491000017
94+
... 002153925000084
95+
... 002171831000070
96+
... 002175561000046
97+
... 002183254000012
98+
... 002196660000054
99+
... 002200638000027
100+
... 002214705000070
101+
... 002223396000056
102+
... 002229460000064
103+
... 002270907000084
104+
... 002276099000065
105+
... 002284350000097
106+
... 002289496000059
107+
... 002295895000043
108+
... 002316916000023
109+
... 002329576000031
110+
... 002330015000012
111+
... 002332839000006
112+
... 002333592000045
113+
... 002335910000024
114+
... 002336083000009
115+
... 002356463000030
116+
... 002362254000037
117+
... 002364621000051
118+
... 002397991000094
119+
... 002398966000056
120+
... 002404272000063
121+
... 002410367000010
122+
... 002411901000011
123+
... 002428800000026
124+
... 002434341000090
125+
... 002443493000045
126+
... 002533410000002
127+
... 002539687000079
128+
... 002548326000014
129+
... 002553650000020
130+
... 002567289000076
131+
... 002581462000070
132+
... 002586051000036
133+
... 002591358000016
134+
... 002604451000070
135+
... 002614910000044
136+
... 002627640000005
137+
... 002630734000081
138+
... 002667689000038
139+
... 002672505000083
140+
... 002673962000029
141+
... 002701712000007
142+
... 002702346000058
143+
... 002738846000078
144+
... 002753855000004
145+
... 002848452000089
146+
... 002855545000056
147+
... 002904997000057
148+
... 003004995000009
149+
... 003102867000036
150+
...
151+
... '''
152+
>>> [x for x in numbers.splitlines() if x and not ice.is_valid(x)]
153+
[]

0 commit comments

Comments
 (0)