diff --git a/stdnum/ao/__init__.py b/stdnum/ao/__init__.py new file mode 100644 index 00000000..6064f5bf --- /dev/null +++ b/stdnum/ao/__init__.py @@ -0,0 +1,24 @@ +# __init__.py - collection of Angola 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 Angola numbers.""" + +# provide aliases +from stdnum.ao import nif as vat # noqa: F401 diff --git a/stdnum/ao/nif.py b/stdnum/ao/nif.py new file mode 100644 index 00000000..45a82a8b --- /dev/null +++ b/stdnum/ao/nif.py @@ -0,0 +1,92 @@ +# nif.py - functions for handling Angola NIF 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 + +"""NIF (Número de Identificação Fiscal, Angola tax number). + +This number has two variants, one for singular persons and another one for +collective persons. + +The collective person's number consists of 10 digits. + +The singular person's number consists of 14 characters: the first nine are +digits, followed by two letters and ending with three digits. + + +More information: + +* https://portaldocontribuinte.minfin.gov.ao/perguntas-frequentes/cadastro-contribuinte +* https://tin-check.com/en/tin-check-angola/ + +>>> validate('5000767115') +'5000767115' +>>> validate('000238553CA017') +'000238553CA017' +>>> validate('12345') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> format('500 002 85 50') +'5000028550' +""" # noqa: E501 + +import re + +from stdnum.exceptions import * +from stdnum.util import clean, isdigits + + +_nif_singular_re = re.compile(r'^[0-9]{9}[A-Z]{2}[0-9]{3}$') + + +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().strip() + + +def validate(number): + """Check if the number is a valid Angola NIF number. + + This checks the length and formatting. + """ + number = compact(number) + if len(number) not in (10, 14): + raise InvalidLength() + if len(number) == 10 and not isdigits(number): + raise InvalidFormat() + if len(number) == 14 and not _nif_singular_re.search(number): + raise InvalidFormat() + return number + + +def is_valid(number): + """Check if the number is a valid Angola NIF 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_ao_nif.doctest b/tests/test_ao_nif.doctest new file mode 100644 index 00000000..f74864be --- /dev/null +++ b/tests/test_ao_nif.doctest @@ -0,0 +1,117 @@ +test_ao_nif.doctest - more detailed doctests for stdnum.ao.nif module + +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 + + +This file contains more detailed doctests for the stdnum.ao.nif module. It +tries to test more corner cases and detailed functionality that is not really +useful as module documentation. + +>>> from stdnum.ao import nif + + +Tests for some corner cases. + +>>> nif.validate('5000767115') +'5000767115' +>>> nif.validate('500 002 85 50') +'5000028550' +>>> nif.validate('541.745.0855') +'5417450855' +>>> nif.validate('000238553CA017') +'000238553CA017' +>>> nif.validate('12345') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> nif.validate('V234567890') +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> nif.validate('V23456789XX234') +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> nif.validate('12345678901234') +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> nif.validate('123456789XX23V') +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> nif.format('500 002 85 50') +'5000028550' + + +These have been found online and should all be valid numbers. + +>>> numbers = ''' +... +... 5000767115 +... 7403008227 +... 541.745.0855 +... 5000392413 +... 5000817880 +... 5403088601 +... 5000856070 +... 5417061590 +... 5410000510 +... 5417166103 +... 5000505870 +... 540 111 34 20 +... 5000354643 +... 5417423610 +... 5000429660 +... 500 002 85 50 +... 5001078976 +... 5000292605 +... 5000436577 +... 5401061072 +... 5403011362 +... 5401040580 +... 5000636983 +... 5410001095 +... 5417251674 +... 5417407844 +... 5480010743 +... 5410003144 +... 5417017027 +... 5000638170 +... 5417166103 +... 5000392413 +... 5005005000 +... 5417144096 +... 7419001367 +... 5000466123 +... 5000816311 +... 5000740896 +... 5410003691 +... 5401111380 +... 5000952583 +... 5000562386 +... 5410003705 +... 5000430820 +... 5000816310 +... 000238553CA017 +... 541 734 128 2 +... 5417655872 +... +... ''' +>>> [x for x in numbers.splitlines() if x and not nif.is_valid(x)] +[]