1
1
# itn.py - functions for handling ITN numbers
2
2
#
3
3
# Copyright (C) 2020 Sergi Almacellas Abellana
4
+ # Copyright (C) 2023 Leandro Regueiro
4
5
#
5
6
# This library is free software; you can redistribute it and/or
6
7
# modify it under the terms of the GNU Lesser General Public
21
22
22
23
The number is assigned by The Inland Revenue Board of Malaysia (IRBM) and it
23
24
is required to report the income. This unique number is known as
24
- "Nombor CukaiPendapatan " or Income Tax Number.
25
+ "Nombor Cukai Pendapatan " or Income Tax Number (ITN) .
25
26
26
- The number consist of 11 or 12 digits. It is structured by two types, normally
27
- separated by an space. The first one consists of 1 or 2 leters and represents
28
- the type of the file number. The second one is always ten digits an represents
29
- the tax number.
27
+ For individuals the ITN consists on the 2 letters Type of File Number (SG for
28
+ individual resident or OG for individual non-resident) followed by a space, and
29
+ ending with the Income Tax Number (maximum 11 digits).
30
30
31
- >>> validate('C2584563202')
31
+ For Non-Individuals the ITN consists on the Type of File Number (1 or 2 letters)
32
+ followed by a space, and ending with the Income Tax Number (maximum 10 digits).
33
+ The Type of File Number for Non-Individuals can be one of the following:
34
+
35
+ * C: Company, Pte. Ltd. Company, Limited Company or Non-Resident Company.
36
+ * CS: Cooperative Society.
37
+ * D: Partnership.
38
+ * E: Employer.
39
+ * F: Association.
40
+ * FA: Non-Resident Public Entertainer.
41
+ * PT: Limited Liability Partnership.
42
+ * TA: Trust Body.
43
+ * TC: Unit Trust/ Property Trust.
44
+ * TN: Business Trust.
45
+ * TR: Real Estate Investment Trust/ Property Trust Fund.
46
+ * TP: Deceased Person's Estate.
47
+ * TJ: Hindu Joint Family.
48
+ * LE: Labuan Entity.
49
+
50
+ >>> validate('SG 10234567090')
51
+ 'SG10234567090'
52
+ >>> validate('OG 25845632021')
53
+ 'OG25845632021'
54
+ >>> validate('C 2584563202')
32
55
'C2584563202'
33
- >>> validate('CDB2584563202') # Should contain the prefix
56
+ >>> validate('1')
57
+ Traceback (most recent call last):
58
+ ...
59
+ InvalidComponent: ...
60
+ >>> validate('12345678901234')
34
61
Traceback (most recent call last):
35
62
...
36
- InvalidLength : ...
37
- >>> validate('CD12346789012') # Should contain the prefix
63
+ InvalidComponent : ...
64
+ >>> validate('12345')
38
65
Traceback (most recent call last):
39
66
...
40
- InvalidLength: ...
41
- >>> validate('C258456320B') # number should only contain digits
67
+ InvalidComponent: ...
68
+ >>> validate('X 12345')
69
+ Traceback (most recent call last):
70
+ ...
71
+ InvalidComponent: ...
72
+ >>> validate('C 12345X')
42
73
Traceback (most recent call last):
43
74
...
44
75
InvalidFormat: ...
45
76
>>> format('C2584563202')
46
77
'C 2584563202'
78
+ >>> format('SG10234567090')
79
+ 'SG 10234567090'
47
80
"""
48
81
49
82
from stdnum .exceptions import *
50
83
from stdnum .util import clean , isdigits
51
84
52
85
53
- def compact ( number ):
54
- """Convert the number to the minimal representation. This strips the
55
- number of any valid separators and removes surrounding whitespace."""
56
- return clean ( number , ' -*' ). strip ()
86
+ PREFIXES_11_DIGITS = ( 'SG' , 'OG' )
87
+ PREFIXES_10_DIGITS = ( 'C' , 'CS' , 'D' , 'E' , 'F' , 'FA' , 'PT' , 'TA' , 'TC' , 'TN' ,
88
+ 'TR' , 'TP' , 'TJ' , 'LE' )
89
+ VALID_PREFIXES = PREFIXES_11_DIGITS + PREFIXES_10_DIGITS
57
90
58
91
59
- def split (number ):
60
- number = compact (number )
61
- index = 10
62
- if len (number ) > 12 :
63
- index += 11
64
- return number [:- index ], number [- index :]
92
+ def _get_prefix_and_number (number ):
93
+ """Return the number separated in prefix and numerical part.
94
+
95
+ This assumes the number has been previously compacted.
96
+ """
97
+ for i , c in enumerate (number ):
98
+ if c .isdigit ():
99
+ return number [:i ], number [i :]
100
+ return number , ''
101
+
102
+
103
+ def compact (number ):
104
+ """Convert the number to the minimal representation.
105
+
106
+ This strips the number of any valid separators and removes surrounding
107
+ whitespace.
108
+ """
109
+ return clean (number , ' -*' ).strip ().upper ()
65
110
66
111
67
112
def validate (number ):
68
- """Check if the number is a valid NRIC number. This checks the length,
69
- formatting and birth date and place."""
113
+ """Check if the number is a valid ITN number.
114
+
115
+ This checks the length and formatting.
116
+ """
70
117
number = compact (number )
71
- if len (number ) > 13 or len (number ) <= 10 :
72
- raise InvalidLength ()
73
- prefix , digits = split (number )
74
- if not prefix or len (prefix ) > 2 :
75
- raise InvalidLength ()
118
+ prefix , digits = _get_prefix_and_number (number )
119
+ if prefix not in VALID_PREFIXES :
120
+ raise InvalidComponent ()
121
+ if not digits :
122
+ raise InvalidComponent ()
123
+ if prefix in PREFIXES_11_DIGITS and len (digits ) > 11 :
124
+ raise InvalidComponent ()
125
+ if prefix in PREFIXES_10_DIGITS and len (digits ) > 10 :
126
+ raise InvalidComponent ()
76
127
if not isdigits (digits ):
77
128
raise InvalidFormat ()
78
129
return number
79
130
80
131
81
132
def is_valid (number ):
82
- """Check if the number is a valid NRIC number."""
133
+ """Check if the number is a valid ITN number."""
83
134
try :
84
135
return bool (validate (number ))
85
136
except ValidationError :
@@ -88,4 +139,4 @@ def is_valid(number):
88
139
89
140
def format (number ):
90
141
"""Reformat the number to the standard presentation format."""
91
- return ' ' .join (split ( number ))
142
+ return ' ' .join (_get_prefix_and_number ( compact ( number ) ))
0 commit comments