diff --git a/stdnum/gh/__init__.py b/stdnum/gh/__init__.py new file mode 100644 index 00000000..41922b43 --- /dev/null +++ b/stdnum/gh/__init__.py @@ -0,0 +1,24 @@ +# __init__.py - collection of Ghana numbers +# coding: utf-8 +# +# 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 + +"""Collection of Ghana numbers.""" + +# provide aliases +from stdnum.gh import tin as vat # noqa: F401 diff --git a/stdnum/gh/tin.py b/stdnum/gh/tin.py new file mode 100644 index 00000000..62fabcb0 --- /dev/null +++ b/stdnum/gh/tin.py @@ -0,0 +1,98 @@ +# tin.py - functions for handling Ghana TIN numbers +# coding: utf-8 +# +# 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 + +"""TIN (Taxpayer Identification Number, Ghana tax number). + +This number is issued by the Ghana Revenue Authority (GRA) to individuals who +are not eligible for the Ghanacard PIN and other entities. + +This number consists of 11 alpha-numeric characters. It begins with one of the +following prefixes: + + P00 For Individuals. + C00 For Companies limited by guarantee, shares, Unlimited (i.e organisation + required to register with the RGD). + G00 Government Agencies, MDAs. + Q00 Foreign Missions, Employees of foreign missions. + V00 Public Institutions, Trusts, Co-operatives, Foreign Shareholder + (Offshore), (Entities not registered by RGD). + +More information: + +* https://www.oecd.org/tax/automatic-exchange/crs-implementation-and-assistance/tax-identification-numbers/Ghana-TIN.pdf +* https://gra.gov.gh/tin/ +* https://gra.gov.gh/tin/tin-faq/ + +>>> validate('C0000803561') +'C0000803561' +>>> validate('12345') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> validate('X0000803561') +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> format('C0000803561') +'C0000803561' +""" # noqa: E501 + +import re + +from stdnum.exceptions import * +from stdnum.util import clean + + +_gh_tin_re = re.compile(r'^[PCGQV]{1}00[A-Z0-9]{8}$') + + +def compact(number): + """Convert the number to the minimal representation. + + This strips the number of any valid separators and removes surrounding + whitespace. + """ + return clean(number, ' ').upper() + + +def validate(number): + """Check if the number is a valid Ghana TIN number. + + This checks the length and formatting. + """ + number = compact(number) + if len(number) != 11: + raise InvalidLength() + if not _gh_tin_re.match(number): + raise InvalidFormat() + return number + + +def is_valid(number): + """Check if the number is a valid Ghana TIN number.""" + try: + return bool(validate(number)) + except ValidationError: + return False + + +def format(number): + """Reformat the number to the standard presentation format.""" + return compact(number) diff --git a/tests/test_gh_tin.doctest b/tests/test_gh_tin.doctest new file mode 100644 index 00000000..2d6e889d --- /dev/null +++ b/tests/test_gh_tin.doctest @@ -0,0 +1,106 @@ +test_gh_tin.doctest - more detailed doctests for stdnum.gh.tin 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.gh.tin module. It +tries to test more corner cases and detailed functionality that is not really +useful as module documentation. + +>>> from stdnum.gh import tin + + +Tests for some corner cases. + +>>> tin.validate('C0000803561') +'C0000803561' +>>> tin.validate('P0008816751') +'P0008816751' +>>> tin.validate('V0022862404') +'V0022862404' +>>> tin.validate('G0005513405') +'G0005513405' +>>> tin.format('C0000803561') +'C0000803561' +>>> tin.validate('12345') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> tin.validate('X0000803561') +Traceback (most recent call last): + ... +InvalidFormat: ... + + +These have been found online and should all be valid numbers. + +>>> numbers = ''' +... +... C000261992X +... C0000803561 +... C0005015774 +... C0009705228 +... C0004894162 +... C0004656830 +... C0003268071 +... C0003257630 +... C000327828X +... C0004056450 +... C000366497X +... C0002442477 +... C0003257673 +... C0003137007 +... C0003278263 +... C0003278484 +... C0003165493 +... C0002862646 +... C0003278271 +... C0004524764 +... C0003136973 +... P0008816751 +... P0009487379 +... P0001525662 +... P0009329250 +... P0009329188 +... C0029454158 +... C0003417425 +... C0002147866 +... C0010952330 +... C001095242X +... C0005203333 +... P0004697499 +... V0022862404 +... C0013225812 +... G0061140708 +... C0003442500 +... C0006570275 +... P0039937100 +... P0006187994 +... V0003107108 +... C0002551888 +... C0003168417 +... C0003831426 +... C0003664996 +... C0004743520 +... C0021485674 +... P0012631833 +... G0005513405 +... +... ''' +>>> [x for x in numbers.splitlines() if x and not tin.is_valid(x)] +[]