Skip to content

Commit 6d366e3

Browse files
unhoarthurdejong
authored andcommitted
Add support for Egypt TIN
This also convertis Arabic digits to ASCII digits. Closes #225 Closes #334
1 parent b1dc313 commit 6d366e3

File tree

3 files changed

+307
-0
lines changed

3 files changed

+307
-0
lines changed

stdnum/eg/__init__.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# __init__.py - collection of Egypt 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 Egypt numbers."""
22+
23+
# provide aliases
24+
from stdnum.eg import tn as vat # noqa: F401

stdnum/eg/tn.py

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# tn.py - functions for handling Egypt Tax Number 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+
u"""Tax Registration Number (الرقم الضريبي, Egypt tax number).
22+
23+
This number consists of 9 digits, usually separated into three groups
24+
using hyphens to make it easier to read, like XXX-XXX-XXX.
25+
26+
More information:
27+
28+
* https://emsp.mts.gov.eg:8181/EMDB-web/faces/authoritiesandcompanies/authority/website/SearchAuthority.xhtml?lang=en
29+
30+
>>> validate('100-531-385')
31+
'100531385'
32+
>>> validate(u'٣٣١-١٠٥-٢٦٨')
33+
'331105268'
34+
>>> validate('12345')
35+
Traceback (most recent call last):
36+
...
37+
InvalidLength: ...
38+
>>> validate('VV3456789')
39+
Traceback (most recent call last):
40+
...
41+
InvalidFormat: ...
42+
>>> format('100531385')
43+
'100-531-385'
44+
""" # noqa: E501
45+
46+
from stdnum.exceptions import *
47+
from stdnum.util import clean, isdigits
48+
49+
50+
_ARABIC_NUMBERS_MAP = {
51+
# Arabic-indic digits.
52+
u'٠': '0',
53+
u'١': '1',
54+
u'٢': '2',
55+
u'٣': '3',
56+
u'٤': '4',
57+
u'٥': '5',
58+
u'٦': '6',
59+
u'٧': '7',
60+
u'٨': '8',
61+
u'٩': '9',
62+
# Extended arabic-indic digits.
63+
u'۰': '0',
64+
u'۱': '1',
65+
u'۲': '2',
66+
u'۳': '3',
67+
u'۴': '4',
68+
u'۵': '5',
69+
u'۶': '6',
70+
u'۷': '7',
71+
u'۸': '8',
72+
u'۹': '9',
73+
}
74+
75+
76+
def compact(number):
77+
"""Convert the number to the minimal representation.
78+
79+
This strips the number of any valid separators and removes surrounding
80+
whitespace. It also converts arabic numbers.
81+
"""
82+
try:
83+
return str(''.join((_ARABIC_NUMBERS_MAP.get(c, c) for c in clean(number, ' -/').strip())))
84+
except UnicodeError: # pragma: no cover (Python 2 specific)
85+
raise InvalidFormat()
86+
87+
88+
def validate(number):
89+
"""Check if the number is a valid Egypt Tax Number number.
90+
91+
This checks the length and formatting.
92+
"""
93+
number = compact(number)
94+
if not isdigits(number):
95+
raise InvalidFormat()
96+
if len(number) != 9:
97+
raise InvalidLength()
98+
return number
99+
100+
101+
def is_valid(number):
102+
"""Check if the number is a valid Egypt Tax Number number."""
103+
try:
104+
return bool(validate(number))
105+
except ValidationError:
106+
return False
107+
108+
109+
def format(number):
110+
"""Reformat the number to the standard presentation format."""
111+
number = compact(number)
112+
return '-'.join([number[:3], number[3:-3], number[-3:]])

tests/test_eg_tn.doctest

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
test_eg_tn.doctest - more detailed doctests for stdnum.eg.tn module
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+
22+
This file contains more detailed doctests for the stdnum.eg.tn module. It
23+
tries to test more corner cases and detailed functionality that is not really
24+
useful as module documentation.
25+
26+
>>> from stdnum.eg import tn
27+
28+
29+
Tests for some corner cases.
30+
31+
>>> tn.validate('100-531-385')
32+
'100531385'
33+
>>> tn.validate('100531385')
34+
'100531385'
35+
>>> tn.validate('421 – 159 – 723')
36+
'421159723'
37+
>>> tn.validate('347/404/847')
38+
'347404847'
39+
>>> tn.validate(u'٣٣١-١٠٥-٢٦٨')
40+
'331105268'
41+
>>> tn.validate(u'۹٤۹-۸۹۱-۲۰٤')
42+
'949891204'
43+
>>> tn.format('100531385')
44+
'100-531-385'
45+
>>> tn.format(u'٣٣١-١٠٥-٢٦٨')
46+
'331-105-268'
47+
>>> tn.format(u'۹٤۹-۸۹۱-۲۰٤')
48+
'949-891-204'
49+
>>> tn.validate('12345')
50+
Traceback (most recent call last):
51+
...
52+
InvalidLength: ...
53+
>>> tn.validate('VV3456789')
54+
Traceback (most recent call last):
55+
...
56+
InvalidFormat: ...
57+
58+
59+
These have been found online and should all be valid numbers.
60+
61+
>>> numbers = u'''
62+
...
63+
... 039-528-313
64+
... 100-131-778
65+
... 100-223-508
66+
... 100-294-197
67+
... 100-534-287
68+
... 200-018-728
69+
... 200-131-052
70+
... 200-138-480
71+
... 200-139-649
72+
... 200-140-884
73+
... 200-237-446
74+
... 200-239-090
75+
... 202-458-482
76+
... 202-460-738
77+
... 202-466-566
78+
... 202-468-828
79+
... 202-469-077
80+
... 202-478-696
81+
... 202-483-827
82+
... 202-484-173
83+
... 202-484-327
84+
... 202-486-400
85+
... 202-487-598
86+
... 202-487-938
87+
... 202-490-483
88+
... 202-494-802
89+
... 202-494-985
90+
... 204-829-305
91+
... 204-830-109
92+
... 204-944-252
93+
... 204-946-786
94+
... 204-962-862
95+
... 205-044-816
96+
... 205-047-297
97+
... 215-559-053
98+
... 220-682-836
99+
... 235-005-266
100+
... 239-772-660
101+
... 247-604-224
102+
... 250-067-498
103+
... 254 299-954
104+
... 254-228-992
105+
... 257-149-295
106+
... 280-948-565
107+
... 288-953-452
108+
... 297-923-900
109+
... 303-428-007
110+
... 305-310-313
111+
... 306-006-014
112+
... 308-523-229
113+
... 310-286-719
114+
... 332-673-553
115+
... 337-703-027
116+
... 347/404/847
117+
... 372-076-416
118+
... 374-106-290
119+
... 374-201-099
120+
... 374-380-139
121+
... 376978082
122+
... 383-438-845
123+
... 383-521-815
124+
... 383-556-848
125+
... 383-612-055
126+
... 383-856-183
127+
... 403-965-896
128+
... 408-994-509
129+
... 412-700-212
130+
... 412-907-399
131+
... 413-370-712
132+
... 415-211-468
133+
... 421 – 159 – 723
134+
... 431-134-189
135+
... 432-600-132
136+
... 455-466-138
137+
... 455273677
138+
... 479-738-440
139+
... 499-149-246
140+
... 506-247-368
141+
... 508-717-388
142+
... 513-992-693
143+
... 516-346-997
144+
... 518-934-489
145+
... 518-944-155
146+
... 521-338-514
147+
... 525-915-540
148+
... 528-495-275
149+
... 540-497-754
150+
... 542-251-337
151+
... 554-685-442
152+
... 555-407-284
153+
... 570-165-725
154+
... 589-269-666
155+
... 608-769-347
156+
... 619-395-100
157+
... 626-381-738
158+
... 646-687-799
159+
... 724-125-078
160+
... 728-620-480
161+
... 728-755-645
162+
... 735-739-447
163+
... 825-885-383
164+
... 910-921-383
165+
... ٣٣١-١٠٥-٢٦٨
166+
... ٩٤٦-١٤٩-٢٠٠
167+
... ۹٤۹-۸۹۱-۲۰٤
168+
...
169+
... '''
170+
>>> [x for x in numbers.splitlines() if x and not tn.is_valid(x)]
171+
[]

0 commit comments

Comments
 (0)