From fc50897a4a7b28541756cd64dc403cbfd723bfd5 Mon Sep 17 00:00:00 2001 From: Jonathan Finlay Date: Thu, 31 Jul 2014 17:21:42 -0500 Subject: [PATCH 1/3] Add the module for Ecuadorian Identification Card (CI - Cedula de identidad) and Fiscal Numbers (RUC - Registro Unico de Contribuyentes) --- stdnum/ec/__init__.py | 24 +++++++++ stdnum/ec/ci.py | 68 ++++++++++++++++++++++++ stdnum/ec/ruc.py | 117 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 209 insertions(+) create mode 100644 stdnum/ec/__init__.py create mode 100644 stdnum/ec/ci.py create mode 100644 stdnum/ec/ruc.py diff --git a/stdnum/ec/__init__.py b/stdnum/ec/__init__.py new file mode 100644 index 00000000..18d02a88 --- /dev/null +++ b/stdnum/ec/__init__.py @@ -0,0 +1,24 @@ +# __init__.py - collection of Ecuadorian numbers +# coding: utf-8 +# +# Copyright (C) 2014 Jonathan Finlay +# +# 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 Ecuadorian numbers.""" + +# provide vat as an alias +from stdnum.ec import ruc as vat diff --git a/stdnum/ec/ci.py b/stdnum/ec/ci.py new file mode 100644 index 00000000..a12de56a --- /dev/null +++ b/stdnum/ec/ci.py @@ -0,0 +1,68 @@ +# dni.py - functions for handling Ecuadorian personal identity codes +# coding: utf-8 +# +# Copyright (C) 2014 Jonathan Finlay +# +# 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 + +"""CI (Cédula de identidad, Ecuadorian personal identity codes). + +The CI is a 10 digit number used to identify Ecuadorian citizens. + +>>> validate('1714307103') +'1714307103' +>>> validate('171430710-3') +'1714307103' +>>> validate('1714307104') # invalid document +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> validate('171430710') # digit missing +Traceback (most recent call last): + ... +InvalidLength: ... +""" + +from stdnum.exceptions import * +from stdnum.util import clean + + +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): + """Checks to see if the number provided is a valid DNI number. This + checks the length, formatting and check digit.""" + number = compact(number) + if len(number) != 10: + raise InvalidLength() + value = [int(number[x]) * (2 - x % 2) for x in range(9)] + total = sum(map(lambda x: x > 9 and x - 9 or x, value)) + if int(int(number[9] if int(number[9]) != 0 else 10)) != (10 - int(str(total)[-1:])): + raise InvalidChecksum() + return number + + +def is_valid(number): + """Checks to see if the number provided is a valid DNI number. This + checks the length, formatting and check digit.""" + try: + return bool(validate(number)) + except ValidationError: + return False diff --git a/stdnum/ec/ruc.py b/stdnum/ec/ruc.py new file mode 100644 index 00000000..3c9eb06b --- /dev/null +++ b/stdnum/ec/ruc.py @@ -0,0 +1,117 @@ +# cif.py - functions for handling Ecuadorian fiscal numbers +# coding: utf-8 +# +# Copyright (C) 2014 Jonathan Finlay +# +# 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 de Contribuyentes, Ecuadorian company tax number). + +The RUC is a tax identification number for legal entities. It has 13 digits +where the third digit is a letter (denoting the type of entity). + +>>> validate('1714307103001') # Natural entity +'1714307103001' +>>> validate('1768152130001') # Public entity +'1763154690001' +>>> validate('1792060346001') # Juridical entity +'1792060346001' +>>> validate('1763154690001') # Public entity +Traceback (most recent call last): + ... +InvalidChecksum: ... +>>> validate('179206034601') # too short +Traceback (most recent call last): + ... +InvalidLength: ... +>>> validate('1792060346-001') +'1763154690001' +""" + +from stdnum.ec import ci +from stdnum.exceptions import * + + +__all__ = ['compact', 'validate', 'is_valid'] + + +# use the same compact function as CI +compact = ci.compact + + +def validate(number): + """Checks to see if the number provided is a valid RUC number. This + checks the length, formatting, check digit and check sum.""" + number = compact(number) + if len(number) != 13: + raise InvalidLength() + + result = 0 + residue = 0 + + if int(number[2]) == 6: + # 6 = Public RUC + coefficient = "32765432" + checker = int(number[8]) + for i in range(8): + result += (int(number[i]) * int(coefficient[i])) + residue = result % 11 + if residue == 0: + result = residue + else: + result = 11 - residue + elif int(number[2]) == 9: + # 9 = Juridical RUC + coefficient = "432765432" + checker = int(number[9]) + for i in range(9): + result += (int(number[i]) * int(coefficient[i])) + residue = result % 11 + if residue == 0: + result = residue + else: + result = 11 - residue + elif int(number[2]) < 6: + # less than 6 = Natural RUC + coefficient = "212121212" + checker = int(number[9]) + for i in range(9): + sum = (int(number[i]) * int(coefficient[i])) + if sum > 10: + str_sum = str(sum) + sum = int(str_sum[0]) + int(str_sum[1]) + result += sum + residue = result % 10 + if residue == 0: + result = residue + else: + result = 10 - residue + else: + raise InvalidFormat() + + if result != checker: + raise InvalidChecksum() + + return number + + +def is_valid(number): + """Checks to see if the number provided is a valid DNI number. This + checks the length, formatting and check digit.""" + try: + return bool(validate(number)) + except ValidationError: + return False From cf1681c7500e8d9307a66d7ca1cb92b0c7fe92b5 Mon Sep 17 00:00:00 2001 From: Jonathan Finlay Date: Sat, 11 Oct 2014 21:07:32 -0500 Subject: [PATCH 2/3] Fix Arthur issues, and add rst docs --- docs/stdnum.ec.ci.rst | 5 ++++ docs/stdnum.ec.ruc.rst | 5 ++++ stdnum/ec/ci.py | 19 ++++++++++---- stdnum/ec/ruc.py | 58 ++++++++++++++++++++---------------------- 4 files changed, 51 insertions(+), 36 deletions(-) create mode 100644 docs/stdnum.ec.ci.rst create mode 100644 docs/stdnum.ec.ruc.rst diff --git a/docs/stdnum.ec.ci.rst b/docs/stdnum.ec.ci.rst new file mode 100644 index 00000000..d28b9d1f --- /dev/null +++ b/docs/stdnum.ec.ci.rst @@ -0,0 +1,5 @@ +stdnum.ec.ci +============ + +.. automodule:: stdnum.ec.ci + :members: diff --git a/docs/stdnum.ec.ruc.rst b/docs/stdnum.ec.ruc.rst new file mode 100644 index 00000000..8b9442c7 --- /dev/null +++ b/docs/stdnum.ec.ruc.rst @@ -0,0 +1,5 @@ +stdnum.ec.ruc +============= + +.. automodule:: stdnum.ec.ruc + :members: diff --git a/stdnum/ec/ci.py b/stdnum/ec/ci.py index a12de56a..4834d587 100644 --- a/stdnum/ec/ci.py +++ b/stdnum/ec/ci.py @@ -46,21 +46,30 @@ def compact(number): return clean(number, ' -').upper().strip() +def checksum(number): + """Calculate the check digit.""" + value = [int(number[x]) * (2 - x % 2) for x in range(9)] + total = sum(map(lambda x: x > 9 and x - 9 or x, value)) + if int(int(number[9] if int(number[9]) != 0 else 10)) != (10 - int(str(total)[-1:])): + return False + return True + + def validate(number): - """Checks to see if the number provided is a valid DNI number. This + """Checks to see if the number provided is a valid CI number. This checks the length, formatting and check digit.""" number = compact(number) if len(number) != 10: raise InvalidLength() - value = [int(number[x]) * (2 - x % 2) for x in range(9)] - total = sum(map(lambda x: x > 9 and x - 9 or x, value)) - if int(int(number[9] if int(number[9]) != 0 else 10)) != (10 - int(str(total)[-1:])): + if not number.isdigit(): + raise InvalidFormat() + if not checksum(number): raise InvalidChecksum() return number def is_valid(number): - """Checks to see if the number provided is a valid DNI number. This + """Checks to see if the number provided is a valid CI number. This checks the length, formatting and check digit.""" try: return bool(validate(number)) diff --git a/stdnum/ec/ruc.py b/stdnum/ec/ruc.py index 3c9eb06b..d7faec8b 100644 --- a/stdnum/ec/ruc.py +++ b/stdnum/ec/ruc.py @@ -21,7 +21,7 @@ """RUC (Registro Único de Contribuyentes, Ecuadorian company tax number). The RUC is a tax identification number for legal entities. It has 13 digits -where the third digit is a letter (denoting the type of entity). +where the third digit is a number who denoting the type of entity. >>> validate('1714307103001') # Natural entity '1714307103001' @@ -29,7 +29,9 @@ '1763154690001' >>> validate('1792060346001') # Juridical entity '1792060346001' ->>> validate('1763154690001') # Public entity +>>> validate('1792060346-001') +'1763154690001' +>>> validate('1763154690001') # Invalid Traceback (most recent call last): ... InvalidChecksum: ... @@ -37,8 +39,6 @@ Traceback (most recent call last): ... InvalidLength: ... ->>> validate('1792060346-001') -'1763154690001' """ from stdnum.ec import ci @@ -51,24 +51,13 @@ # use the same compact function as CI compact = ci.compact - -def validate(number): - """Checks to see if the number provided is a valid RUC number. This - checks the length, formatting, check digit and check sum.""" - number = compact(number) - if len(number) != 13: - raise InvalidLength() - +def calc_check_sum(number): result = 0 - residue = 0 - if int(number[2]) == 6: # 6 = Public RUC coefficient = "32765432" - checker = int(number[8]) - for i in range(8): - result += (int(number[i]) * int(coefficient[i])) - residue = result % 11 + result = sum(int(number[i]) * int(coefficient[i]) for i in range(8)) + residue = result % 11 if residue == 0: result = residue else: @@ -76,10 +65,8 @@ def validate(number): elif int(number[2]) == 9: # 9 = Juridical RUC coefficient = "432765432" - checker = int(number[9]) - for i in range(9): - result += (int(number[i]) * int(coefficient[i])) - residue = result % 11 + result = sum(int(number[i]) * int(coefficient[i]) for i in range(9)) + residue = result % 11 if residue == 0: result = residue else: @@ -87,13 +74,12 @@ def validate(number): elif int(number[2]) < 6: # less than 6 = Natural RUC coefficient = "212121212" - checker = int(number[9]) for i in range(9): - sum = (int(number[i]) * int(coefficient[i])) - if sum > 10: - str_sum = str(sum) - sum = int(str_sum[0]) + int(str_sum[1]) - result += sum + suma = int(number[i]) * int(coefficient[i]) + if suma > 10: + str_sum = str(suma) + suma = int(str_sum[0]) + int(str_sum[1]) + result += suma residue = result % 10 if residue == 0: result = residue @@ -101,15 +87,25 @@ def validate(number): result = 10 - residue else: raise InvalidFormat() + return result - if result != checker: - raise InvalidChecksum() +def validate(number): + """Checks to see if the number provided is a valid RUC number. This + checks the length, formatting, check digit and check sum.""" + number = compact(number) + if len(number) != 13: + raise InvalidLength() + if not number.isdigit(): + raise InvalidFormat() + checker = int(number[8]) if int(number[2]) == 6 else int(number[9]) + if calc_check_sum(number) != checker: + raise InvalidChecksum() return number def is_valid(number): - """Checks to see if the number provided is a valid DNI number. This + """Checks to see if the number provided is a valid RUC number. This checks the length, formatting and check digit.""" try: return bool(validate(number)) From 315cfd59ab0e2c1fbd16b637009a71d2aeb7986e Mon Sep 17 00:00:00 2001 From: Jonathan Finlay Date: Sat, 11 Oct 2014 21:23:12 -0500 Subject: [PATCH 3/3] Fix nosetests response strings --- stdnum/ec/ruc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdnum/ec/ruc.py b/stdnum/ec/ruc.py index d7faec8b..f0114bc4 100644 --- a/stdnum/ec/ruc.py +++ b/stdnum/ec/ruc.py @@ -26,11 +26,11 @@ >>> validate('1714307103001') # Natural entity '1714307103001' >>> validate('1768152130001') # Public entity -'1763154690001' +'1768152130001' >>> validate('1792060346001') # Juridical entity '1792060346001' >>> validate('1792060346-001') -'1763154690001' +'1792060346001' >>> validate('1763154690001') # Invalid Traceback (most recent call last): ...