Skip to content

Commit 5f02b11

Browse files
committed
Add support for Panama TIN
Note this only adds support for the legal person's TIN number. Closes arthurdejong#102
1 parent 6d366e3 commit 5f02b11

File tree

3 files changed

+447
-0
lines changed

3 files changed

+447
-0
lines changed

Diff for: stdnum/pa/__init__.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# __init__.py - collection of Panamanian numbers
2+
# coding: utf-8
3+
#
4+
# Copyright (C) 2023 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 Panamanian numbers."""
22+
23+
# provide aliases
24+
from stdnum.pa import ruc as vat # noqa: F401

Diff for: stdnum/pa/ruc.py

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# ruc.py - functions for handling Panama RUC numbers
2+
# coding: utf-8
3+
#
4+
# Copyright (C) 2023 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+
"""RUC (Registro Único del Contribuyente, Panama tax number).
22+
23+
The Registro Único del Contribuyente (RUC) is an identifier of legal entities
24+
for tax purposes.
25+
26+
This number has different variants both for natural and legal persons, each
27+
with its own structure, but basically it consists on a number of digits and
28+
letters (only for natural persons) usually separated by hyphens (the number and
29+
position of these varies according to the variant), then followed by a check
30+
number (dígito verificador) consisting in up to two digits.
31+
32+
More information:
33+
34+
* https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Panama-TIN.pdf
35+
* https://studylib.es/doc/545131/algoritmo-para-el-calculo-del-digito-verificador-de-la-ru
36+
37+
>>> validate('253-92-57027 DV 76')
38+
'00000002530092057027DV76'
39+
>>> validate('155587169-2-2014 D.V. 9')
40+
'01555871690002002014DV09'
41+
>>> validate('253-92-57027 DV 23')
42+
Traceback (most recent call last):
43+
...
44+
InvalidChecksum: ...
45+
>>> validate('12345678')
46+
Traceback (most recent call last):
47+
...
48+
InvalidLength: ...
49+
>>> format('253-92-57027 DV 76')
50+
'0000000253-0092-057027 DV76'
51+
""" # noqa: E501
52+
53+
from stdnum.exceptions import *
54+
from stdnum.util import clean, isdigits
55+
56+
57+
ARRVAL = {
58+
'00': '00',
59+
'10': '01',
60+
'11': '02',
61+
'12': '03',
62+
'13': '04',
63+
'14': '05',
64+
'15': '06',
65+
'16': '07',
66+
'17': '08',
67+
'18': '09',
68+
'19': '01',
69+
'20': '02',
70+
'21': '03',
71+
'22': '04',
72+
'23': '07',
73+
'24': '08',
74+
'25': '09',
75+
'26': '02',
76+
'27': '03',
77+
'28': '04',
78+
'29': '05',
79+
'30': '06',
80+
'31': '07',
81+
'32': '08',
82+
'33': '09',
83+
'34': '01',
84+
'35': '02',
85+
'36': '03',
86+
'37': '04',
87+
'38': '05',
88+
'39': '06',
89+
'40': '07',
90+
'41': '08',
91+
'42': '09',
92+
'43': '01',
93+
'44': '02',
94+
'45': '03',
95+
'46': '04',
96+
'47': '05',
97+
'48': '06',
98+
'49': '07',
99+
}
100+
101+
102+
def compact(number):
103+
"""Convert the number to the minimal representation.
104+
105+
This strips the number of any valid separators and removes surrounding
106+
whitespace.
107+
"""
108+
parts = clean(number, ' .').strip().upper().split('-')
109+
110+
# We can currently only compact legal person's RUC numbers with check digit.
111+
if len(parts) != 3 or len(parts[0]) in (1, 2) or 'DV' not in parts[2]:
112+
return ''
113+
114+
parts[2], dv = parts[2].split('DV')
115+
116+
return ''.join([parts[0].zfill(10), parts[1].zfill(4),
117+
parts[2].strip().zfill(6), 'DV', dv.strip().zfill(2)])
118+
119+
120+
def calc_check_digit(number, is_old_legal_ruc):
121+
"""Calculate the check digit."""
122+
if is_old_legal_ruc:
123+
weights = list(range(2, 12)) + list(range(11, len(number) + 1))
124+
else:
125+
weights = list(range(2, len(number) + 2))
126+
total = sum(int(n) * w for w, n in zip(weights, reversed(number)))
127+
r = total % 11
128+
return str(11 - r) if r > 1 else '0'
129+
130+
131+
def validate(number):
132+
"""Check if the number is a valid Panama RUC number.
133+
134+
This checks the length, formatting and check digits.
135+
"""
136+
number = compact(number)
137+
if len(number) != 24:
138+
raise InvalidLength()
139+
if not isdigits(number[:-4]) or not isdigits(number[-2:]):
140+
raise InvalidComponent()
141+
if number[-4:-2] != 'DV':
142+
raise InvalidComponent()
143+
is_old_legal_ruc = number[3:6] in ('000', '001', '002', '003', '004')
144+
if is_old_legal_ruc and number[5:7] in ARRVAL:
145+
number = number[:5] + ARRVAL[number[5:7]] + number[7:]
146+
dv1 = calc_check_digit(number[:-4], is_old_legal_ruc)
147+
if number[-2] != dv1:
148+
raise InvalidChecksum()
149+
dv2 = calc_check_digit(number[:-4] + dv1, is_old_legal_ruc)
150+
if number[-1] != dv2:
151+
raise InvalidChecksum()
152+
return number
153+
154+
155+
def is_valid(number):
156+
"""Check if the number is a valid Panama RUC number."""
157+
try:
158+
return bool(validate(number))
159+
except ValidationError:
160+
return False
161+
162+
163+
def format(number):
164+
"""Reformat the number to the standard presentation format."""
165+
number = compact(number)
166+
return ''.join([number[:10], '-', number[10:14], '-', number[14:-4], ' ',
167+
number[-4:]])

0 commit comments

Comments
 (0)