[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) <qdp@openerp.com>
This commit is contained in:
@@ -2,63 +2,13 @@
|
|||||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
|
||||||
import string
|
import string
|
||||||
import re
|
import re
|
||||||
|
import stdnum
|
||||||
_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
|
|
||||||
|
|
||||||
from odoo import api, models, tools, _
|
from odoo import api, models, tools, _
|
||||||
from odoo.tools.misc import ustr
|
from odoo.tools.misc import ustr
|
||||||
from odoo.exceptions import ValidationError
|
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 = {
|
_eu_country_vat = {
|
||||||
'GR': 'EL'
|
'GR': 'EL'
|
||||||
@@ -67,42 +17,51 @@ _eu_country_vat = {
|
|||||||
_eu_country_vat_inverse = {v: k for k, v in _eu_country_vat.items()}
|
_eu_country_vat_inverse = {v: k for k, v in _eu_country_vat.items()}
|
||||||
|
|
||||||
_ref_vat = {
|
_ref_vat = {
|
||||||
|
'al': 'ALJ91402501L',
|
||||||
|
'ar': 'AR200-5536168-2 or 20055361682',
|
||||||
'at': 'ATU12345675',
|
'at': 'ATU12345675',
|
||||||
'be': 'BE0477472701',
|
'be': 'BE0477472701',
|
||||||
'bg': 'BG1234567892',
|
'bg': 'BG1234567892',
|
||||||
'ch': 'CHE-123.456.788 TVA or CH TVA 123456', # Swiss by Yannick Vaucher @ Camptocamp
|
'ch': 'CHE-123.456.788 TVA or CH TVA 123456', # Swiss by Yannick Vaucher @ Camptocamp
|
||||||
'cl': 'CL76086428-5',
|
'cl': 'CL76086428-5',
|
||||||
'co': 'CO213123432-1 or CO213.123.432-1',
|
'co': 'CO213123432-1 or CO213.123.432-1',
|
||||||
'cy': 'CY12345678F',
|
'cy': 'CY10259033P',
|
||||||
'cz': 'CZ12345679',
|
'cz': 'CZ12345679',
|
||||||
'de': 'DE123456788',
|
'de': 'DE123456788',
|
||||||
'dk': 'DK12345674',
|
'dk': 'DK12345674',
|
||||||
|
'do': 'DO1-01-85004-3 or 101850043',
|
||||||
|
'ec': 'EC1792060346-001',
|
||||||
'ee': 'EE123456780',
|
'ee': 'EE123456780',
|
||||||
'el': 'EL12345670',
|
'el': 'EL12345670',
|
||||||
'es': 'ESA12345674',
|
'es': 'ESA12345674',
|
||||||
'fi': 'FI12345671',
|
'fi': 'FI12345671',
|
||||||
'fr': 'FR32123456789',
|
'fr': 'FR23334175221',
|
||||||
'gb': 'GB123456782',
|
'gb': 'GB123456782',
|
||||||
'gr': 'GR12345670',
|
'gr': 'GR12345670',
|
||||||
'hu': 'HU12345676',
|
'hu': 'HU12345676',
|
||||||
'hr': 'HR01234567896', # Croatia, contributed by Milan Tribuson
|
'hr': 'HR01234567896', # Croatia, contributed by Milan Tribuson
|
||||||
'ie': 'IE1234567FA',
|
'ie': 'IE1234567FA',
|
||||||
|
'is': 'IS062199',
|
||||||
'it': 'IT12345670017',
|
'it': 'IT12345670017',
|
||||||
'lt': 'LT123456715',
|
'lt': 'LT123456715',
|
||||||
'lu': 'LU12345613',
|
'lu': 'LU12345613',
|
||||||
'lv': 'LV41234567891',
|
'lv': 'LV41234567891',
|
||||||
|
'mc': 'FR53000004605',
|
||||||
'mt': 'MT12345634',
|
'mt': 'MT12345634',
|
||||||
'mx': 'ABC123456T1B',
|
'mx': 'MXGODE561231GR8 or GODE561231GR8',
|
||||||
'nl': 'NL123456782B90',
|
'nl': 'NL123456782B90',
|
||||||
'no': 'NO123456785',
|
'no': 'NO123456785',
|
||||||
'pe': '10XXXXXXXXY or 20XXXXXXXXY or 15XXXXXXXXY or 16XXXXXXXXY or 17XXXXXXXXY',
|
'pe': '10XXXXXXXXY or 20XXXXXXXXY or 15XXXXXXXXY or 16XXXXXXXXY or 17XXXXXXXXY',
|
||||||
'pl': 'PL1234567883',
|
'pl': 'PL1234567883',
|
||||||
'pt': 'PT123456789',
|
'pt': 'PT123456789',
|
||||||
'ro': 'RO1234567897',
|
'ro': 'RO1234567897',
|
||||||
|
'rs': 'RS101134702',
|
||||||
|
'ru': 'RU123456789047',
|
||||||
'se': 'SE123456789701',
|
'se': 'SE123456789701',
|
||||||
'si': 'SI12345679',
|
'si': 'SI12345679',
|
||||||
'sk': 'SK0012345675',
|
'sk': 'SK2022749619',
|
||||||
'tr': 'TR1234567890 (VERGINO) veya TR12345678901 (TCKIMLIKNO)' # Levent Karakas @ Eska Yazilim A.S.
|
'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():
|
if not ustr(country_code).encode('utf-8').isalpha():
|
||||||
return False
|
return False
|
||||||
check_func_name = 'check_vat_' + country_code
|
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:
|
if not check_func:
|
||||||
# No VAT validation available, default to check that the country code exists
|
# No VAT validation available, default to check that the country code exists
|
||||||
if country_code.upper() == 'EU':
|
if country_code.upper() == 'EU':
|
||||||
@@ -138,7 +97,7 @@ class ResPartner(models.Model):
|
|||||||
def _check_vies(self, vat):
|
def _check_vies(self, vat):
|
||||||
# Store the VIES result in the cache. In case an exception is raised during the request
|
# 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.
|
# (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
|
@api.model
|
||||||
def vies_vat_check(self, country_code, vat_number):
|
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]))
|
checksum = extra + sum((8-i) * int(x) for i, x in enumerate(vat[:7]))
|
||||||
return 'WABCDEFGHIJKLMNOPQRSTUV'[checksum % 23]
|
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):
|
def check_vat_ie(self, vat):
|
||||||
""" Temporary Ireland VAT validation to support the new format
|
""" Temporary Ireland VAT validation to support the new format
|
||||||
introduced in January 2013 in Ireland, until upstream is fixed.
|
introduced in January 2013 in Ireland, until upstream is fixed.
|
||||||
@@ -396,6 +375,43 @@ class ResPartner(models.Model):
|
|||||||
dig_check = 1
|
dig_check = 1
|
||||||
return int(vat[10]) == dig_check
|
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.
|
# VAT validation in Turkey, contributed by # Levent Karakas @ Eska Yazilim A.S.
|
||||||
def check_vat_tr(self, vat):
|
def check_vat_tr(self, vat):
|
||||||
|
|
||||||
@@ -439,35 +455,27 @@ class ResPartner(models.Model):
|
|||||||
|
|
||||||
return False
|
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:
|
try:
|
||||||
import stdnum.al
|
int(vat)
|
||||||
return stdnum.al.vat.is_valid(vat)
|
except ValueError:
|
||||||
except ImportError:
|
return False
|
||||||
return True
|
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
|
|
||||||
|
|
||||||
def _fix_vat_number(self, vat):
|
def _fix_vat_number(self, vat):
|
||||||
vat_country, vat_number = self._split_vat(vat)
|
vat_country, vat_number = self._split_vat(vat)
|
||||||
check_func_name = 'compact_' + vat_country
|
stdnum_vat_fix_func = getattr(stdnum.util.get_cc_module(vat_country, 'vat'), 'compact', None)
|
||||||
check_func = globals().get(check_func_name) or getattr(self, 'default_compact')
|
#If any localization module need to define vat fix method for it's country then we give first priority to it.
|
||||||
vat_number = check_func(vat_number)
|
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
|
return vat_country.upper() + vat_number
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
@@ -480,4 +488,3 @@ class ResPartner(models.Model):
|
|||||||
if values.get('vat'):
|
if values.get('vat'):
|
||||||
values['vat'] = self._fix_vat_number(values['vat'])
|
values['vat'] = self._fix_vat_number(values['vat'])
|
||||||
return super(ResPartner, self).write(values)
|
return super(ResPartner, self).write(values)
|
||||||
|
|
||||||
|
|||||||
@@ -83,8 +83,8 @@ PKGS_TO_INSTALL="
|
|||||||
python3-reportlab \
|
python3-reportlab \
|
||||||
python3-requests \
|
python3-requests \
|
||||||
python3-simplejson \
|
python3-simplejson \
|
||||||
|
python3-stdnum \
|
||||||
python3-tz \
|
python3-tz \
|
||||||
python3-vatnumber \
|
|
||||||
python3-werkzeug \
|
python3-werkzeug \
|
||||||
python3-serial \
|
python3-serial \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
|
|||||||
Vendored
+1
-1
@@ -39,9 +39,9 @@ Depends:
|
|||||||
python3-qrcode,
|
python3-qrcode,
|
||||||
python3-reportlab,
|
python3-reportlab,
|
||||||
python3-requests,
|
python3-requests,
|
||||||
|
python3-stdnum,
|
||||||
python3-suds,
|
python3-suds,
|
||||||
python3-tz,
|
python3-tz,
|
||||||
python3-vatnumber,
|
|
||||||
python3-vobject,
|
python3-vobject,
|
||||||
python3-werkzeug,
|
python3-werkzeug,
|
||||||
python3-xlsxwriter,
|
python3-xlsxwriter,
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ qrcode==6.1
|
|||||||
reportlab==3.5.13
|
reportlab==3.5.13
|
||||||
requests==2.21.0
|
requests==2.21.0
|
||||||
zeep==3.2.0
|
zeep==3.2.0
|
||||||
vatnumber==1.2
|
python-stdnum==1.8
|
||||||
vobject==0.9.6.1
|
vobject==0.9.6.1
|
||||||
Werkzeug==0.14.1
|
Werkzeug==0.14.1
|
||||||
XlsxWriter==1.1.2
|
XlsxWriter==1.1.2
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ requires =
|
|||||||
python3-six
|
python3-six
|
||||||
python3-stdnum
|
python3-stdnum
|
||||||
python3-suds
|
python3-suds
|
||||||
python3-vatnumber
|
|
||||||
python3-vobject
|
python3-vobject
|
||||||
python3-werkzeug
|
python3-werkzeug
|
||||||
python3-xlwt
|
python3-xlwt
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ setup(
|
|||||||
'pypdf2',
|
'pypdf2',
|
||||||
'pyserial',
|
'pyserial',
|
||||||
'python-dateutil',
|
'python-dateutil',
|
||||||
|
'python-stdnum',
|
||||||
'pytz',
|
'pytz',
|
||||||
'pyusb >= 1.0.0b1',
|
'pyusb >= 1.0.0b1',
|
||||||
'qrcode',
|
'qrcode',
|
||||||
'reportlab', # windows binary pypi.python.org/pypi/reportlab
|
'reportlab', # windows binary pypi.python.org/pypi/reportlab
|
||||||
'requests',
|
'requests',
|
||||||
'zeep',
|
'zeep',
|
||||||
'vatnumber',
|
|
||||||
'vobject',
|
'vobject',
|
||||||
'werkzeug',
|
'werkzeug',
|
||||||
'xlsxwriter',
|
'xlsxwriter',
|
||||||
|
|||||||
@@ -48,10 +48,10 @@ RUN apt-get update -qq && \
|
|||||||
python3-reportlab \
|
python3-reportlab \
|
||||||
python3-requests \
|
python3-requests \
|
||||||
python3-serial \
|
python3-serial \
|
||||||
|
python3-stdnum \
|
||||||
python3-suds \
|
python3-suds \
|
||||||
python3-tz \
|
python3-tz \
|
||||||
python3-usb \
|
python3-usb \
|
||||||
python3-vatnumber \
|
|
||||||
python3-vobject \
|
python3-vobject \
|
||||||
python3-werkzeug \
|
python3-werkzeug \
|
||||||
python3-xlsxwriter \
|
python3-xlsxwriter \
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ RUN dnf update -d 0 -e 0 -y && \
|
|||||||
python3-reportlab \
|
python3-reportlab \
|
||||||
python3-requests \
|
python3-requests \
|
||||||
python3-six \
|
python3-six \
|
||||||
|
python3-stdnum \
|
||||||
python3-suds \
|
python3-suds \
|
||||||
python3-vatnumber \
|
|
||||||
python3-vobject \
|
python3-vobject \
|
||||||
python3-werkzeug \
|
python3-werkzeug \
|
||||||
python3-xlwt \
|
python3-xlwt \
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ qrcode==5.3
|
|||||||
reportlab>=3.3.0
|
reportlab>=3.3.0
|
||||||
requests==2.20.0
|
requests==2.20.0
|
||||||
six==1.10.0
|
six==1.10.0
|
||||||
|
stdnum==1.8
|
||||||
zeep==3.1.0
|
zeep==3.1.0
|
||||||
vatnumber==1.2
|
|
||||||
vobject==0.9.3
|
vobject==0.9.3
|
||||||
Werkzeug>=0.11.11
|
Werkzeug>=0.11.11
|
||||||
XlsxWriter==0.9.3
|
XlsxWriter==0.9.3
|
||||||
|
|||||||
Reference in New Issue
Block a user