From 0382fe1858bfdec5befe780328558f2ffdb2a4e0 Mon Sep 17 00:00:00 2001 From: Leandro Regueiro Date: Sun, 23 Jun 2019 15:00:25 +0200 Subject: [PATCH] Add support for Panama TIN Note this only adds support for the legal person's TIN number. Closes #102 --- stdnum/pa/__init__.py | 24 ++++ stdnum/pa/ruc.py | 165 ++++++++++++++++++++++++ tests/test_pa_ruc.doctest | 256 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 445 insertions(+) create mode 100644 stdnum/pa/__init__.py create mode 100644 stdnum/pa/ruc.py create mode 100644 tests/test_pa_ruc.doctest diff --git a/stdnum/pa/__init__.py b/stdnum/pa/__init__.py new file mode 100644 index 00000000..787f7404 --- /dev/null +++ b/stdnum/pa/__init__.py @@ -0,0 +1,24 @@ +# __init__.py - collection of Panamanian numbers +# coding: utf-8 +# +# Copyright (C) 2023 Leandro Regueiro +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +"""Collection of Panamanian numbers.""" + +# provide aliases +from stdnum.pa import ruc as vat # noqa: F401 diff --git a/stdnum/pa/ruc.py b/stdnum/pa/ruc.py new file mode 100644 index 00000000..6e352ccd --- /dev/null +++ b/stdnum/pa/ruc.py @@ -0,0 +1,165 @@ +# ruc.py - functions for handling Panama RUC numbers +# coding: utf-8 +# +# Copyright (C) 2023 Leandro Regueiro +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 USA + +"""RUC (Registro Único del Contribuyente, Panama tax number). + +The Registro Único del Contribuyente (RUC) is an identifier of legal entities +for tax purposes. + +This number has different variants both for natural and legal persons, each +with its own structure, but basically it consists on a number of digits and +letters (only for natural persons) usually separated by hyphens (the number and +position of these varies according to the variant), then followed by a check +number (dígito verificador) consisting in up to two digits. + +More information: + +* https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Panama-TIN.pdf +* https://studylib.es/doc/545131/algoritmo-para-el-calculo-del-digito-verificador-de-la-ru + +>>> validate('253-92-57027 DV 76') +'00000002530092057027DV76' +>>> validate('155587169-2-2014 D.V. 9') +'01555871690002002014DV09' +>>> validate('253-92-57027 DV 23') +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> validate('12345678') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> format('253-92-57027 DV 76') +'0000000253-0092-057027 DV76' +""" # noqa: E501 + +from stdnum.exceptions import * +from stdnum.util import clean, isdigits + + +ARRVAL = { + '00': '00', + '10': '01', + '11': '02', + '12': '03', + '13': '04', + '14': '05', + '15': '06', + '16': '07', + '17': '08', + '18': '09', + '19': '01', + '20': '02', + '21': '03', + '22': '04', + '23': '07', + '24': '08', + '25': '09', + '26': '02', + '27': '03', + '28': '04', + '29': '05', + '30': '06', + '31': '07', + '32': '08', + '33': '09', + '34': '01', + '35': '02', + '36': '03', + '37': '04', + '38': '05', + '39': '06', + '40': '07', + '41': '08', + '42': '09', + '43': '01', + '44': '02', + '45': '03', + '46': '04', + '47': '05', + '48': '06', + '49': '07', +} + + +def compact(number): + """Convert the number to the minimal representation. + + This strips the number of any valid separators and removes surrounding + whitespace. + """ + parts = clean(number, ' .').strip().upper().split('-') + + # We can currently only compact legal person's RUC numbers with check digit. + if len(parts) != 3 or len(parts[0]) in (1, 2) or 'DV' not in parts[2]: + return '' + + parts[2], dv = parts[2].split('DV') + + return ''.join([parts[0].zfill(10), parts[1].zfill(4), + parts[2].strip().zfill(6), 'DV', dv.strip().zfill(2)]) + + +def calc_check_digit(number, is_old_legal_ruc): + """Calculate the check digit.""" + if is_old_legal_ruc: + weights = list(range(2, 12)) + list(range(11, len(number) + 1)) + else: + weights = list(range(2, len(number) + 2)) + total = sum(int(n) * w for w, n in zip(weights, reversed(number))) + r = total % 11 + return str(11 - r) if r > 1 else '0' + + +def validate(number): + """Check if the number is a valid Panama RUC number. + + This checks the length, formatting and check digits. + """ + number = compact(number) + if len(number) != 24: + raise InvalidLength() + if not isdigits(number[:-4]) or not isdigits(number[-2:]): + raise InvalidComponent() + is_old_legal_ruc = number[3:6] in ('000', '001', '002', '003', '004') + if is_old_legal_ruc and number[5:7] in ARRVAL: + number = number[:5] + ARRVAL[number[5:7]] + number[7:] + dv1 = calc_check_digit(number[:-4], is_old_legal_ruc) + if number[-2] != dv1: + raise InvalidChecksum() + dv2 = calc_check_digit(number[:-4] + dv1, is_old_legal_ruc) + if number[-1] != dv2: + raise InvalidChecksum() + return number + + +def is_valid(number): + """Check if the number is a valid Panama RUC number.""" + try: + return bool(validate(number)) + except ValidationError: + return False + + +def format(number): + """Reformat the number to the standard presentation format.""" + number = compact(number) + return ''.join([number[:10], '-', number[10:14], '-', number[14:-4], ' ', + number[-4:]]) diff --git a/tests/test_pa_ruc.doctest b/tests/test_pa_ruc.doctest new file mode 100644 index 00000000..635b90ed --- /dev/null +++ b/tests/test_pa_ruc.doctest @@ -0,0 +1,256 @@ +test_pa_ruc.doctest - more detailed doctests for stdnum.pa.ruc module + +Copyright (C) 2022 Leandro Regueiro + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA + + +This file contains more detailed doctests for the stdnum.pa.ruc module. It +tries to test more corner cases and detailed functionality that is not really +useful as module documentation. + +>>> from stdnum.pa import ruc + + +Tests for some corner cases. + +>>> ruc.validate('253-92-57027 DV 76') +'00000002530092057027DV76' +>>> ruc.validate('155587169-2-2014 D.V. 9') +'01555871690002002014DV09' +>>> ruc.validate('32812-2-249262 D.V. 63') +'00000088120002249262DV63' +>>> ruc.validate('155625946-2-2016 DV00') +'01556259460002002016DV00' +>>> ruc.validate('12345') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> ruc.validate('253-92-57027') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> ruc.validate('253-92-57027 DV') +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> ruc.validate('XXX-92-57027 DV 76') +Traceback (most recent call last): + ... +InvalidComponent: ... +>>> ruc.validate('253-92-57027 DV XX') +Traceback (most recent call last): + ... +InvalidComponent: ... +>>> ruc.validate('253-92-57027 DV 26') +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> ruc.validate('253-92-57027 DV 73') +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> ruc.format('253-92-57027 DV 76') +'0000000253-0092-057027 DV76' +>>> ruc.format('155587169-2-2014 D.V. 9') +'0155587169-0002-002014 DV09' + + +These have been found online and should all be valid numbers. + +>>> numbers = ''' +... +... 130-377-34706 DV 2 +... 58223-2-855 DV 04 +... 356-146-78600 DV 90 +... 1870951-1-1751 DV 18 +... 30746-002-240130 DV 72 +... 2117539-1-759645 DV 39 +... 46004-187-302083 DV 96 +... 994855-1-534959 DV 48 +... 410-0018-001604 DV 26 +... 1002259-1-536336 DV 18 +... 247342-1-402582 DV 38 +... 1644095-1-674293 DV 97 +... 85253-1-376213 DV 18 +... 2110333-1-758424 DV 92 +... 36101-76-262338 DV 11 +... 2251567-0001-781630 DV 00 +... 15419-167-148643 DV 42 +... 702-437-15272 DV 87 +... 2196897-1-37015 DV 80 +... 55462-2-333607 DV 81 +... 16292-152-155203 DV 65 +... 1384907-1-624944 DV 04 +... 2612285-1-835546 DV 17 +... 2456294-1-812932 DV 90 +... 4265-60-58061 DV 00 +... 1897412-1-722027 DV 1 +... 280-134-61098 DV 2 +... 15430-249-148718 DV 09 +... 2492633-1-817803 DV 36 +... 2092063-1-755310 DV 00 +... 2092695-1-755448 DV 00 +... 1407887-1-30747 DV 18 +... 1647770-1-675008 DV 52 +... 2506296-1-819700 DV 19 +... 3724-0103-053659 DV 43 +... 699126-1-468330 DV 17 +... 756-374-135990 DV 70 +... 1200810-1-582064 DV 42 +... 126634-1-382152 DV 92 +... 54920-21-332178 DV 07 +... 6763-10-76640 DV 08 +... 315710-1-412268 DV 59 +... 854254-1-506006 DV 45 +... 1410729-1-629832 DV 92 +... 757127-1-481594 DV 5 +... 1724590-1-691090 DV93 +... 1129184-1-566509 DV26 +... 43085-133-290460 DV20 +... 59838-19-345377 DV 5 +... 59838-2-345376 DV12 +... 1038743-1-544512 DV99 +... 395593-1-423610 DV 4 +... 1423328-1-632401 DV69 +... 1173387-1-576582 DV94 +... 1837876-1-1727 DV62 +... 48192-119-320004 DV42 +... 1620775-1-670190 DV40 +... 56014-2-334942 DV57 +... 48854-14-312652 DV53 +... 19573-102-178376 DV78 +... 2605013-1-834447 DV16 +... 860-116-100257 DV 6 +... 2486668-1-817006 DV77 +... 280-319-61818 DV53 +... 1026161-1-541697 DV41 +... 42860-39-289557 DV42 +... 673-563-133317 DV43 +... 1529223-1-653120 DV85 +... 649-529-117695 DV 1 +... 196-8-47222 DV21 +... 2978-2-46909 DV 9 +... 4360-163-58819 DV75 +... 256211-1-1035 DV89 +... 190229-1-393739 DV80 +... 2486790-1-817030 DV91 +... 9944-73-101955 DV 9 +... 37405-45-267330 DV75 +... 637-262-109021 DV 5 +... 472-520-105024 DV39 +... 358157-1-418546 DV 7 +... 2278610-1-2148 DV20 +... 186-505-46878 DV80 +... 410-468-90986 DV47 +... 210550-1-396820 DV58 +... 644-570-114659 DV12 +... 2451381-1-812308 DV80 +... 515924-1-437913 DV75 +... 2182158-1-770482 DV 9 +... 990445-1-534073 DV 0 +... 2636962-1-838889 DV45 +... 2327456-1-793996 DV96 +... 511078-1-437311 DV94 +... 2424000-1-808259 DV 0 +... 63942-78-356454 DV47 +... 2301555-1-789993 DV19 +... 396-569-88934 DV99 +... 30170-22-237573 DV19 +... 1623537-1-670664 DV72 +... 835607-1-502581 DV22 +... 155630598-2-2016 DV 30 +... 155628291-2-2016 DV32 +... 155650394-2-2017 DV14 +... 155587102-2-2014 DV75 +... 155639020-2-2016 DV49 +... 155589402-2-2014 DV16 +... 155589772-2-2014 DV 8 +... 155668678-2-2018 DV84 +... 155658360-2-2017 DV13 +... 155698228-2-2020 DV68 +... 155671731-2-2018 DV 5 +... 155615059-2-2015 DV62 +... 155660186-2-2018 DV 4 +... 155636329-2-2016 DV96 +... 155614464-2-2015 DV18 +... 155618602-2-2015 DV47 +... 155660844-2-2018 D.V.3 +... 155587169-2-2014 D.V. 9 +... 155660896-2-2018 D.V.6 +... 155604680-2-2015 D.V.0 +... 155593964-2-2015 D.V.09 +... 155599780-2-2015 D.V. 43 +... 155630598-2-2016 DV30 +... 297-154-64691 DV 1 +... 652-446-131292 DV 9 +... 50871-26-319483 DV54 +... 627-549-108161 DV 4 +... 2182171-1-770489 DV 7 +... 342-240-75526 DV50 +... 21094-39-189991 DV48 +... 276745-1-406809 DV 6 +... 39160-2-274372 DV03 +... 1486616-1-1504 DV39 +... 1647259-1-674887 DV 1 +... 12561-16-124551 DV78 +... 853111-1-505797 DV30 +... 1281233-1-600583 DV93 +... 203-438-49939 DV84 +... 38682-54-272473 DV 0 +... 1442235-1-636269 DV20 +... 2122574-1-1987 DV70 +... 1910501-1-724391 DV83 +... 1351606-1-617448 DV76 +... 963942-1-528502 DV19 +... 780179-1-487966 DV40 +... 1570395-1-660585 D.V. 97 +... 3152-26-11756 D.V.60 +... 2413309-1-806698 D.V.89 +... 260141-1-404438 D.V.33 +... 2596686-1-833270 D.V.74 +... 43059-0023-290396 D.V.70 +... 27957-10-229660 D.V.5 +... 42819-60-289401 D.V.81 +... 8075-10-85775 D.V.49 +... 35751-78-261168 D.V.84 +... 811457-1-498110 D.V.88 +... 34932-2-258267 D.V.17 +... 545-496-119585 D.V. 22 +... 128877-1-382443 D.V.62 +... 1610553-1-668171 D.V.22 +... 1066306-1-551009 D.V. 48 +... 356-19-77860 D.V. 12 +... 1848547-1-713252 D.V. 8 +... 521347-1-438253 D.V.70 +... 652-212-129962 D.V. 35 +... 12561-16-124551 D.V.78 +... 32812-2-249262 D.V. 63 +... 4360-163-58819 D.V.75 +... 417-139-92433 D.V. 72 +... 2064-63-38048 D.V. 0 +... 20435-57-185073 D.V. 60 +... 85253 -1-376213 D.V. 18 +... 49511-149-315269 D.V.75 +... 572-395-119290 D.V. 30 +... 105765-1-379278 D.V. 60 +... 822226-1-500338 D.V.30 +... 946321-1-524832 D.V. 04 +... 533-555-116945 D.V. 40 +... +... ''' +>>> [x for x in numbers.splitlines() if x and not ruc.is_valid(x)] +[]