From a43f857423cc79322f91cae1f031b3135fb103c1 Mon Sep 17 00:00:00 2001 From: Hardik Prajapati Date: Tue, 3 Sep 2019 11:20:14 +0000 Subject: [PATCH] [IMP] base_vat: replace vatnumver by stdnum library. Python module vatnumber doesn't seem maintained anymore. Therefore, we should: - call directly stdnum (which is maintained and mostly used everywhere in vatnumber) Also improve stdnum import, vat fix method and vat expected formats task-1915371 closes odoo/odoo#36978 Signed-off-by: Quentin De Paoli (qdp) --- addons/base_vat/models/res_partner.py | 175 +++++++++--------- .../etc/init_posbox_image.sh | 2 +- debian/control | 2 +- requirements.txt | 2 +- setup.cfg | 1 - setup.py | 2 +- setup/package.dfdebian | 2 +- setup/package.dffedora | 2 +- setup/win32/winpy_requirements.txt | 2 +- 9 files changed, 98 insertions(+), 92 deletions(-) diff --git a/addons/base_vat/models/res_partner.py b/addons/base_vat/models/res_partner.py index a6604db44fc..d51b1e6a72f 100644 --- a/addons/base_vat/models/res_partner.py +++ b/addons/base_vat/models/res_partner.py @@ -2,63 +2,13 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import datetime -import logging import string import re - -_logger = logging.getLogger(__name__) -try: - import vatnumber -except ImportError: - _logger.warning("VAT validation partially unavailable because the `vatnumber` Python library cannot be found. " - "Install it to support more countries, for example with `easy_install vatnumber`.") - vatnumber = None - -# Although stdnum is a dependency of vatnumber, the import of the latter is surrounded by a try/except -# if it is not installed. Therefore, we cannot be sure stdnum is installed in all cases. -try: - import stdnum -except ImportError: - stdnum = None +import stdnum from odoo import api, models, tools, _ from odoo.tools.misc import ustr from odoo.exceptions import ValidationError -from stdnum.at.uid import compact as compact_at -from stdnum.be.vat import compact as compact_be -from stdnum.bg.vat import compact as compact_bg -from stdnum.ch.vat import compact as compact_ch -from stdnum.cy.vat import compact as compact_cy -from stdnum.cz.dic import compact as compact_cz -from stdnum.de.vat import compact as compact_de -from stdnum.ee.kmkr import compact as compact_ee -# el not in stdnum -from stdnum.es.nif import compact as compact_es -from stdnum.fi.alv import compact as compact_fi -from stdnum.fr.tva import compact as compact_fr -from stdnum.gb.vat import compact as compact_gb -from stdnum.gr.vat import compact as compact_gr -from stdnum.hu.anum import compact as compact_hu -from stdnum.hr.oib import compact as compact_hr -from stdnum.ie.vat import compact as compact_ie -from stdnum.it.iva import compact as compact_it -from stdnum.lt.pvm import compact as compact_lt -from stdnum.lu.tva import compact as compact_lu -from stdnum.lv.pvn import compact as compact_lv -from stdnum.mt.vat import compact as compact_mt -from stdnum.mx.rfc import compact as compact_mx -from stdnum.nl.btw import compact as compact_nl -from stdnum.no.mva import compact as compact_no -# pe is not in stdnum -from stdnum.pl.nip import compact as compact_pl -from stdnum.pt.nif import compact as compact_pt -from stdnum.ro.cf import compact as compact_ro -from stdnum.se.vat import compact as compact_se -from stdnum.si.ddv import compact as compact_si -from stdnum.sk.dph import compact as compact_sk -from stdnum.ar.cuit import compact as compact_ar -# tr compact vat is not in stdnum - _eu_country_vat = { 'GR': 'EL' @@ -67,42 +17,51 @@ _eu_country_vat = { _eu_country_vat_inverse = {v: k for k, v in _eu_country_vat.items()} _ref_vat = { + 'al': 'ALJ91402501L', + 'ar': 'AR200-5536168-2 or 20055361682', 'at': 'ATU12345675', 'be': 'BE0477472701', 'bg': 'BG1234567892', 'ch': 'CHE-123.456.788 TVA or CH TVA 123456', # Swiss by Yannick Vaucher @ Camptocamp 'cl': 'CL76086428-5', 'co': 'CO213123432-1 or CO213.123.432-1', - 'cy': 'CY12345678F', + 'cy': 'CY10259033P', 'cz': 'CZ12345679', 'de': 'DE123456788', 'dk': 'DK12345674', + 'do': 'DO1-01-85004-3 or 101850043', + 'ec': 'EC1792060346-001', 'ee': 'EE123456780', 'el': 'EL12345670', 'es': 'ESA12345674', 'fi': 'FI12345671', - 'fr': 'FR32123456789', + 'fr': 'FR23334175221', 'gb': 'GB123456782', 'gr': 'GR12345670', 'hu': 'HU12345676', 'hr': 'HR01234567896', # Croatia, contributed by Milan Tribuson 'ie': 'IE1234567FA', + 'is': 'IS062199', 'it': 'IT12345670017', 'lt': 'LT123456715', 'lu': 'LU12345613', 'lv': 'LV41234567891', + 'mc': 'FR53000004605', 'mt': 'MT12345634', - 'mx': 'ABC123456T1B', + 'mx': 'MXGODE561231GR8 or GODE561231GR8', 'nl': 'NL123456782B90', 'no': 'NO123456785', 'pe': '10XXXXXXXXY or 20XXXXXXXXY or 15XXXXXXXXY or 16XXXXXXXXY or 17XXXXXXXXY', 'pl': 'PL1234567883', 'pt': 'PT123456789', 'ro': 'RO1234567897', + 'rs': 'RS101134702', + 'ru': 'RU123456789047', 'se': 'SE123456789701', 'si': 'SI12345679', - 'sk': 'SK0012345675', - 'tr': 'TR1234567890 (VERGINO) veya TR12345678901 (TCKIMLIKNO)' # Levent Karakas @ Eska Yazilim A.S. + 'sk': 'SK2022749619', + 'sm': 'SM24165', + 'tr': 'TR1234567890 (VERGINO) or TR17291716060 (TCKIMLIKNO)' # Levent Karakas @ Eska Yazilim A.S. } @@ -122,7 +81,7 @@ class ResPartner(models.Model): if not ustr(country_code).encode('utf-8').isalpha(): return False check_func_name = 'check_vat_' + country_code - check_func = getattr(self, check_func_name, None) or getattr(vatnumber, check_func_name, None) + check_func = getattr(self, check_func_name, None) or getattr(stdnum.util.get_cc_module(country_code, 'vat'), 'is_valid', None) if not check_func: # No VAT validation available, default to check that the country code exists if country_code.upper() == 'EU': @@ -138,7 +97,7 @@ class ResPartner(models.Model): def _check_vies(self, vat): # Store the VIES result in the cache. In case an exception is raised during the request # (e.g. service unavailable), the fallback on simple_vat_check is not kept in cache. - return vatnumber.check_vies(vat) + return stdnum.eu.vat.check_vies(vat) @api.model def vies_vat_check(self, country_code, vat_number): @@ -252,6 +211,26 @@ class ResPartner(models.Model): checksum = extra + sum((8-i) * int(x) for i, x in enumerate(vat[:7])) return 'WABCDEFGHIJKLMNOPQRSTUV'[checksum % 23] + def check_vat_co(self, rut): + ''' + Check Colombian RUT number. + Method copied from vatnumber 1.2 lib https://code.google.com/archive/p/vatnumber/ + ''' + if len(rut) != 10: + return False + try: + int(rut) + except ValueError: + return False + nums = [3, 7, 13, 17, 19, 23, 29, 37, 41, 43, 47, 53, 59, 67, 71] + sum = 0 + for i in range(len(rut) - 2, -1, -1): + sum += int(rut[i]) * nums[len(rut) - 2 - i] + if sum % 11 > 1: + return int(rut[-1]) == 11 - (sum % 11) + else: + return int(rut[-1]) == sum % 11 + def check_vat_ie(self, vat): """ Temporary Ireland VAT validation to support the new format introduced in January 2013 in Ireland, until upstream is fixed. @@ -396,6 +375,43 @@ class ResPartner(models.Model): dig_check = 1 return int(vat[10]) == dig_check + def check_vat_ru(self, vat): + ''' + Check Russia VAT number. + Method copied from vatnumber 1.2 lib https://code.google.com/archive/p/vatnumber/ + ''' + if len(vat) != 10 and len(vat) != 12: + return False + try: + int(vat) + except ValueError: + return False + + if len(vat) == 10: + check_sum = 2 * int(vat[0]) + 4 * int(vat[1]) + 10 * int(vat[2]) + \ + 3 * int(vat[3]) + 5 * int(vat[4]) + 9 * int(vat[5]) + \ + 4 * int(vat[6]) + 6 * int(vat[7]) + 8 * int(vat[8]) + check = check_sum % 11 + if check % 10 != int(vat[9]): + return False + else: + check_sum1 = 7 * int(vat[0]) + 2 * int(vat[1]) + 4 * int(vat[2]) + \ + 10 * int(vat[3]) + 3 * int(vat[4]) + 5 * int(vat[5]) + \ + 9 * int(vat[6]) + 4 * int(vat[7]) + 6 * int(vat[8]) + \ + 8 * int(vat[9]) + check = check_sum1 % 11 + + if check != int(vat[10]): + return False + check_sum2 = 3 * int(vat[0]) + 7 * int(vat[1]) + 2 * int(vat[2]) + \ + 4 * int(vat[3]) + 10 * int(vat[4]) + 3 * int(vat[5]) + \ + 5 * int(vat[6]) + 9 * int(vat[7]) + 4 * int(vat[8]) + \ + 6 * int(vat[9]) + 8 * int(vat[10]) + check = check_sum2 % 11 + if check != int(vat[11]): + return False + return True + # VAT validation in Turkey, contributed by # Levent Karakas @ Eska Yazilim A.S. def check_vat_tr(self, vat): @@ -439,35 +455,27 @@ class ResPartner(models.Model): return False - def check_vat_al(self, vat): + def check_vat_ua(self, vat): + ''' + Check Ukraine VAT number. + Method copied from vatnumber 1.2 lib https://code.google.com/archive/p/vatnumber/ + ''' + if len(vat) != 8: + return False try: - import stdnum.al - return stdnum.al.vat.is_valid(vat) - except ImportError: - return True - - def check_vat_cl(self, vat): - return stdnum.util.get_cc_module('cl', 'vat').is_valid(vat) if stdnum else True - - def check_vat_co(self, vat): - return stdnum.util.get_cc_module('co', 'vat').is_valid(vat) if stdnum else True - - # Argentinian VAT validation, contributed by ADHOC - def check_vat_ar(self, vat): - try: - import stdnum.ar - return stdnum.ar.cuit.is_valid(vat) - except ImportError: - return True - - def default_compact(self, vat): - return vat + int(vat) + except ValueError: + return False + return True def _fix_vat_number(self, vat): vat_country, vat_number = self._split_vat(vat) - check_func_name = 'compact_' + vat_country - check_func = globals().get(check_func_name) or getattr(self, 'default_compact') - vat_number = check_func(vat_number) + stdnum_vat_fix_func = getattr(stdnum.util.get_cc_module(vat_country, 'vat'), 'compact', None) + #If any localization module need to define vat fix method for it's country then we give first priority to it. + format_func_name = 'format_vat_' + vat_country + format_func = getattr(self, format_func_name, None) or stdnum_vat_fix_func + if format_func: + vat_number = format_func(vat_number) return vat_country.upper() + vat_number @api.model @@ -480,4 +488,3 @@ class ResPartner(models.Model): if values.get('vat'): values['vat'] = self._fix_vat_number(values['vat']) return super(ResPartner, self).write(values) - diff --git a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh index d4570c1f2c7..6814ba64569 100755 --- a/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh +++ b/addons/point_of_sale/tools/posbox/overwrite_before_init/etc/init_posbox_image.sh @@ -83,8 +83,8 @@ PKGS_TO_INSTALL=" python3-reportlab \ python3-requests \ python3-simplejson \ + python3-stdnum \ python3-tz \ - python3-vatnumber \ python3-werkzeug \ python3-serial \ python3-pip \ diff --git a/debian/control b/debian/control index 0530ba7ace6..4468eebc4f7 100644 --- a/debian/control +++ b/debian/control @@ -39,9 +39,9 @@ Depends: python3-qrcode, python3-reportlab, python3-requests, + python3-stdnum, python3-suds, python3-tz, - python3-vatnumber, python3-vobject, python3-werkzeug, python3-xlsxwriter, diff --git a/requirements.txt b/requirements.txt index f6057683875..69d331c9a69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ qrcode==6.1 reportlab==3.5.13 requests==2.21.0 zeep==3.2.0 -vatnumber==1.2 +python-stdnum==1.8 vobject==0.9.6.1 Werkzeug==0.14.1 XlsxWriter==1.1.2 diff --git a/setup.cfg b/setup.cfg index 3bcb7bb3686..f3a0c3160dd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,7 +45,6 @@ requires = python3-six python3-stdnum python3-suds - python3-vatnumber python3-vobject python3-werkzeug python3-xlwt diff --git a/setup.py b/setup.py index 80baf7e0b1a..cdcf6aa03b5 100644 --- a/setup.py +++ b/setup.py @@ -45,13 +45,13 @@ setup( 'pypdf2', 'pyserial', 'python-dateutil', + 'python-stdnum', 'pytz', 'pyusb >= 1.0.0b1', 'qrcode', 'reportlab', # windows binary pypi.python.org/pypi/reportlab 'requests', 'zeep', - 'vatnumber', 'vobject', 'werkzeug', 'xlsxwriter', diff --git a/setup/package.dfdebian b/setup/package.dfdebian index 433ea576714..1caf6033aff 100644 --- a/setup/package.dfdebian +++ b/setup/package.dfdebian @@ -48,10 +48,10 @@ RUN apt-get update -qq && \ python3-reportlab \ python3-requests \ python3-serial \ + python3-stdnum \ python3-suds \ python3-tz \ python3-usb \ - python3-vatnumber \ python3-vobject \ python3-werkzeug \ python3-xlsxwriter \ diff --git a/setup/package.dffedora b/setup/package.dffedora index 6f745e1b533..ade6d8fece4 100644 --- a/setup/package.dffedora +++ b/setup/package.dffedora @@ -39,8 +39,8 @@ RUN dnf update -d 0 -e 0 -y && \ python3-reportlab \ python3-requests \ python3-six \ + python3-stdnum \ python3-suds \ - python3-vatnumber \ python3-vobject \ python3-werkzeug \ python3-xlwt \ diff --git a/setup/win32/winpy_requirements.txt b/setup/win32/winpy_requirements.txt index cfeece68d92..45dc1bf830a 100644 --- a/setup/win32/winpy_requirements.txt +++ b/setup/win32/winpy_requirements.txt @@ -29,8 +29,8 @@ qrcode==5.3 reportlab>=3.3.0 requests==2.20.0 six==1.10.0 +stdnum==1.8 zeep==3.1.0 -vatnumber==1.2 vobject==0.9.3 Werkzeug>=0.11.11 XlsxWriter==0.9.3