2009-10-13 05:58:37 +00:00
# -*- coding: utf-8 -*-
2008-08-22 13:33:44 +00:00
##############################################################################
#
2010-05-11 06:59:53 +00:00
# OpenERP, Open Source Management Solution
2012-06-21 14:31:52 +00:00
# Copyright (C) 2004-2012 OpenERP SA (<http://openerp.com>)
2008-08-22 13:33:44 +00:00
#
2008-11-03 19:18:56 +00:00
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
2008-08-22 13:33:44 +00:00
#
2008-11-03 19:18:56 +00:00
# This program 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 General Public License for more details.
2008-08-22 13:33:44 +00:00
#
2008-11-03 19:18:56 +00:00
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2008-08-22 13:33:44 +00:00
#
2008-11-03 19:18:56 +00:00
##############################################################################
2011-07-06 07:00:52 +00:00
2011-09-20 12:10:54 +00:00
import logging
2009-04-15 08:57:38 +00:00
import string
2011-07-06 09:14:48 +00:00
import datetime
2011-07-06 07:00:52 +00:00
import re
2012-06-22 06:48:54 +00:00
_logger = logging . getLogger ( __name__ )
2011-07-06 07:00:52 +00:00
2011-09-20 12:10:54 +00:00
try :
import vatnumber
except ImportError :
2012-06-22 06:48:54 +00:00
_logger . warning ( " VAT validation partially unavailable because the `vatnumber` Python library cannot be found. "
2011-09-20 12:10:54 +00:00
" Install it to support more countries, for example with `easy_install vatnumber`. " )
vatnumber = None
2010-05-12 05:59:51 +00:00
2012-12-06 14:56:32 +00:00
from openerp . osv import fields , osv
2012-12-06 15:13:16 +00:00
from openerp . tools . misc import ustr
2012-12-06 14:56:32 +00:00
from openerp . tools . translate import _
2010-05-11 12:50:02 +00:00
_ref_vat = {
2012-01-18 15:30:05 +00:00
' at ' : ' ATU12345675 ' ,
2012-01-30 15:18:36 +00:00
' be ' : ' BE0477472701 ' ,
2012-01-18 15:30:05 +00:00
' bg ' : ' BG1234567892 ' ,
' ch ' : ' CHE-123.456.788 TVA or CH TVA 123456 ' , #Swiss by Yannick Vaucher @ Camptocamp
' cy ' : ' CY12345678F ' ,
' cz ' : ' CZ12345679 ' ,
' de ' : ' DE123456788 ' ,
' dk ' : ' DK12345674 ' ,
' ee ' : ' EE123456780 ' ,
' el ' : ' EL12345670 ' ,
' es ' : ' ESA12345674 ' ,
' fi ' : ' FI12345671 ' ,
' fr ' : ' FR32123456789 ' ,
' gb ' : ' GB123456782 ' ,
' gr ' : ' GR12345670 ' ,
' hu ' : ' HU12345676 ' ,
2011-09-20 13:02:27 +00:00
' hr ' : ' HR01234567896 ' , # Croatia, contributed by Milan Tribuson
2014-02-13 14:12:46 +00:00
' ie ' : ' IE1234567FA ' ,
2012-01-18 15:30:05 +00:00
' it ' : ' IT12345670017 ' ,
' lt ' : ' LT123456715 ' ,
' lu ' : ' LU12345613 ' ,
' lv ' : ' LV41234567891 ' ,
' mt ' : ' MT12345634 ' ,
' mx ' : ' MXABC123456T1B ' ,
' nl ' : ' NL123456782B90 ' ,
' no ' : ' NO123456785 ' ,
2014-07-29 09:42:10 +00:00
' pe ' : ' PER10254824220 or PED10254824220 ' ,
2012-01-18 15:30:05 +00:00
' pl ' : ' PL1234567883 ' ,
' pt ' : ' PT123456789 ' ,
' ro ' : ' RO1234567897 ' ,
' se ' : ' SE123456789701 ' ,
' si ' : ' SI12345679 ' ,
' sk ' : ' SK0012345675 ' ,
2011-07-06 07:00:52 +00:00
}
2008-08-22 13:33:44 +00:00
class res_partner ( osv . osv ) :
_inherit = ' res.partner '
2008-09-02 13:26:51 +00:00
2010-05-11 12:50:02 +00:00
def _split_vat ( self , vat ) :
vat_country , vat_number = vat [ : 2 ] . lower ( ) , vat [ 2 : ] . replace ( ' ' , ' ' )
return vat_country , vat_number
2011-11-28 16:21:27 +00:00
def simple_vat_check ( self , cr , uid , country_code , vat_number , context = None ) :
2008-08-22 13:33:44 +00:00
'''
Check the VAT number depending of the country .
http : / / sima - pc . com / nif . php
'''
2013-05-21 13:42:31 +00:00
if not ustr ( country_code ) . encode ( ' utf-8 ' ) . isalpha ( ) :
return False
2011-11-28 16:21:27 +00:00
check_func_name = ' check_vat_ ' + country_code
check_func = getattr ( self , check_func_name , None ) or \
getattr ( vatnumber , check_func_name , None )
if not check_func :
# No VAT validation available, default to check that the country code exists
res_country = self . pool . get ( ' res.country ' )
return bool ( res_country . search ( cr , uid , [ ( ' code ' , ' =ilike ' , country_code ) ] , context = context ) )
return check_func ( vat_number )
def vies_vat_check ( self , cr , uid , country_code , vat_number , context = None ) :
try :
# Validate against VAT Information Exchange System (VIES)
# see also http://ec.europa.eu/taxation_customs/vies/
return vatnumber . check_vies ( country_code . upper ( ) + vat_number )
except Exception :
# see http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl
# Fault code may contain INVALID_INPUT, SERVICE_UNAVAILABLE, MS_UNAVAILABLE,
# TIMEOUT or SERVER_BUSY. There is no way we can validate the input
# with VIES if any of these arise, including the first one (it means invalid
# country code or empty VAT number), so we fall back to the simple check.
return self . simple_vat_check ( cr , uid , country_code , vat_number , context = context )
2012-02-08 15:26:47 +00:00
def button_check_vat ( self , cr , uid , ids , context = None ) :
if not self . check_vat ( cr , uid , ids , context = context ) :
msg = self . _construct_constraint_msg ( cr , uid , ids , context = context )
2012-08-07 11:31:37 +00:00
raise osv . except_osv ( _ ( ' Error! ' ) , msg )
2012-08-24 05:53:12 +00:00
return True
2012-02-08 15:26:47 +00:00
2011-11-28 16:21:27 +00:00
def check_vat ( self , cr , uid , ids , context = None ) :
user_company = self . pool . get ( ' res.users ' ) . browse ( cr , uid , uid ) . company_id
if user_company . vat_check_vies :
# force full VIES online check
check_func = self . vies_vat_check
else :
# quick and partial off-line checksum validation
check_func = self . simple_vat_check
2010-11-19 13:48:01 +00:00
for partner in self . browse ( cr , uid , ids , context = context ) :
2008-08-22 13:33:44 +00:00
if not partner . vat :
2010-05-11 12:57:51 +00:00
continue
2010-05-11 12:50:02 +00:00
vat_country , vat_number = self . _split_vat ( partner . vat )
2011-11-28 16:21:27 +00:00
if not check_func ( cr , uid , vat_country , vat_number , context = context ) :
2014-04-05 12:53:20 +00:00
_logger . info ( _ ( " Importing VAT Number [ %s ] is not valid ! " % vat_number ) )
2008-08-22 13:33:44 +00:00
return False
return True
2010-05-12 05:59:51 +00:00
def vat_change ( self , cr , uid , ids , value , context = None ) :
2008-09-02 13:26:51 +00:00
return { ' value ' : { ' vat_subjected ' : bool ( value ) } }
2013-04-07 23:50:13 +00:00
def _commercial_fields ( self , cr , uid , context = None ) :
return super ( res_partner , self ) . _commercial_fields ( cr , uid , context = context ) + [ ' vat_subjected ' ]
2010-12-07 07:18:00 +00:00
def _construct_constraint_msg ( self , cr , uid , ids , context = None ) :
2010-05-11 12:50:02 +00:00
def default_vat_check ( cn , vn ) :
# by default, a VAT number is valid if:
# it starts with 2 letters
# has more than 3 characters
return cn [ 0 ] in string . ascii_lowercase and cn [ 1 ] in string . ascii_lowercase
vat_country , vat_number = self . _split_vat ( self . browse ( cr , uid , ids ) [ 0 ] . vat )
2011-09-20 12:10:54 +00:00
vat_no = " ' CC## ' (CC=Country Code, ##=VAT Number) "
2014-07-29 09:42:10 +00:00
error_partner = self . browse ( cr , uid , ids , context = context )
2010-05-11 12:50:02 +00:00
if default_vat_check ( vat_country , vat_number ) :
2011-09-20 12:10:54 +00:00
vat_no = _ref_vat [ vat_country ] if vat_country in _ref_vat else vat_no
2014-07-29 09:42:10 +00:00
if self . pool [ ' res.users ' ] . browse ( cr , uid , uid ) . company_id . vat_check_vies :
return ' \n ' + _ ( ' The VAT number [ %s ] for partner [ %s ] either failed the VIES VAT validation check or did not respect the expected format %s . ' ) % ( error_partner [ 0 ] . vat , error_partner [ 0 ] . name , vat_no )
2014-04-05 12:53:20 +00:00
return ' \n ' + _ ( ' The VAT number [ %s ] for partner [ %s ] does not seem to be valid. \n Note: the expected format is %s ' ) % ( error_partner [ 0 ] . vat , error_partner [ 0 ] . name , vat_no )
2010-05-11 12:50:02 +00:00
_constraints = [ ( check_vat , _construct_constraint_msg , [ " vat " ] ) ]
2008-09-02 13:26:51 +00:00
2008-08-22 13:33:44 +00:00
2012-01-26 15:59:34 +00:00
__check_vat_ch_re1 = re . compile ( r ' (MWST|TVA|IVA)[0-9] {6} $ ' )
__check_vat_ch_re2 = re . compile ( r ' E([0-9] {9} |-[0-9] {3} \ .[0-9] {3} \ .[0-9] {3} )(MWST|TVA|IVA)$ ' )
2012-01-18 15:30:05 +00:00
def check_vat_ch ( self , vat ) :
'''
Check Switzerland VAT number .
'''
# VAT number in Switzerland will change between 2011 and 2013
# http://www.estv.admin.ch/mwst/themen/00154/00589/01107/index.html?lang=fr
# Old format is "TVA 123456" we will admit the user has to enter ch before the number
# Format will becomes such as "CHE-999.999.99C TVA"
# Both old and new format will be accepted till end of 2013
2012-01-26 15:59:34 +00:00
# Accepted format are: (spaces are ignored)
2012-01-18 15:30:05 +00:00
# CH TVA ######
2012-01-26 15:59:34 +00:00
# CH IVA ######
# CH MWST #######
2012-01-18 15:30:05 +00:00
#
# CHE#########MWST
# CHE#########TVA
# CHE#########IVA
# CHE-###.###.### MWST
# CHE-###.###.### TVA
# CHE-###.###.### IVA
#
2012-01-26 15:59:34 +00:00
if self . __check_vat_ch_re1 . match ( vat ) :
return True
match = self . __check_vat_ch_re2 . match ( vat )
if match :
# For new TVA numbers, do a mod11 check
num = filter ( lambda s : s . isdigit ( ) , match . group ( 1 ) ) # get the digits only
factor = ( 5 , 4 , 3 , 2 , 7 , 6 , 5 , 4 )
csum = sum ( [ int ( num [ i ] ) * factor [ i ] for i in range ( 8 ) ] )
2012-06-21 14:31:52 +00:00
check = ( 11 - ( csum % 11 ) ) % 11
2012-01-26 15:59:34 +00:00
return check == int ( num [ 8 ] )
return False
2012-01-18 15:30:05 +00:00
2014-02-13 14:12:46 +00:00
def _ie_check_char ( self , vat ) :
vat = vat . zfill ( 8 )
extra = 0
if vat [ 7 ] not in ' W ' :
if vat [ 7 ] . isalpha ( ) :
extra = 9 * ( ord ( vat [ 7 ] ) - 64 )
else :
# invalid
return - 1
checksum = extra + sum ( ( 8 - i ) * int ( x ) for i , x in enumerate ( vat [ : 7 ] ) )
return ' WABCDEFGHIJKLMNOPQRSTUV ' [ checksum % 23 ]
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 .
TODO : remove when fixed upstream """
if len ( vat ) not in ( 8 , 9 ) or not vat [ 2 : 7 ] . isdigit ( ) :
return False
if len ( vat ) == 8 :
# Normalize pre-2013 numbers: final space or 'W' not significant
vat + = ' '
if vat [ : 7 ] . isdigit ( ) :
return vat [ 7 ] == self . _ie_check_char ( vat [ : 7 ] + vat [ 8 ] )
elif vat [ 1 ] in ( string . ascii_uppercase + ' +* ' ) :
# Deprecated format
# See http://www.revenue.ie/en/online/third-party-reporting/reporting-payment-details/faqs.html#section3
return vat [ 7 ] == self . _ie_check_char ( vat [ 2 : 7 ] + vat [ 0 ] + vat [ 8 ] )
return False
2012-01-18 15:30:05 +00:00
2014-07-29 09:42:10 +00:00
# Mexican VAT verification, contributed by Vauxoo
2011-09-20 12:10:54 +00:00
# and Panos Christeas <p_christ@hol.gr>
2011-04-29 08:36:25 +00:00
__check_vat_mx_re = re . compile ( r " (?P<primeras>[A-Za-z \ xd1 \ xf1&] { 3,4}) " \
2011-04-20 12:06:58 +00:00
r " [ \ -_]? " \
2011-04-29 08:36:25 +00:00
r " (?P<ano>[0-9] {2} )(?P<mes>[01][0-9])(?P<dia>[0-3][0-9]) " \
2011-04-20 12:06:58 +00:00
r " [ \ -_]? " \
2011-04-29 08:36:25 +00:00
r " (?P<code>[A-Za-z0-9& \ xd1 \ xf1] {3} )$ " )
2011-02-11 11:29:07 +00:00
def check_vat_mx ( self , vat ) :
2011-04-20 12:06:58 +00:00
''' Mexican VAT verification
2011-07-06 07:00:52 +00:00
2011-04-20 12:05:54 +00:00
Verificar RFC México
2011-02-11 11:29:07 +00:00
'''
2011-04-29 08:36:25 +00:00
# we convert to 8-bit encoding, to help the regex parse only bytes
vat = ustr ( vat ) . encode ( ' iso8859-1 ' )
2011-04-20 12:06:58 +00:00
m = self . __check_vat_mx_re . match ( vat )
if not m :
2011-04-20 12:05:54 +00:00
#No valid format
2011-02-11 11:29:07 +00:00
return False
2011-04-20 12:05:54 +00:00
try :
2011-04-29 08:36:25 +00:00
ano = int ( m . group ( ' ano ' ) )
if ano > 30 :
ano = 1900 + ano
else :
ano = 2000 + ano
datetime . date ( ano , int ( m . group ( ' mes ' ) ) , int ( m . group ( ' dia ' ) ) )
2011-04-20 12:06:58 +00:00
except ValueError :
2011-02-11 11:29:07 +00:00
return False
2011-07-06 07:00:52 +00:00
2011-04-20 12:05:54 +00:00
#Valid format and valid date
2011-02-11 11:29:07 +00:00
return True
2011-08-09 14:17:01 +00:00
2011-08-09 14:20:29 +00:00
2011-09-20 12:10:54 +00:00
# Norway VAT validation, contributed by Rolv Råen (adEgo) <rora@adego.no>
2011-04-19 10:17:34 +00:00
def check_vat_no ( self , vat ) :
'''
2011-08-07 11:25:56 +00:00
Check Norway VAT number . See http : / / www . brreg . no / english / coordination / number . html
2011-04-19 10:17:34 +00:00
'''
if len ( vat ) != 9 :
return False
try :
int ( vat )
2011-08-07 11:25:56 +00:00
except ValueError :
2011-04-19 10:17:34 +00:00
return False
sum = ( 3 * int ( vat [ 0 ] ) ) + ( 2 * int ( vat [ 1 ] ) ) + \
( 7 * int ( vat [ 2 ] ) ) + ( 6 * int ( vat [ 3 ] ) ) + \
( 5 * int ( vat [ 4 ] ) ) + ( 4 * int ( vat [ 5 ] ) ) + \
2011-09-15 10:11:30 +00:00
( 3 * int ( vat [ 6 ] ) ) + ( 2 * int ( vat [ 7 ] ) )
2011-08-09 14:17:01 +00:00
2011-04-19 10:17:34 +00:00
check = 11 - ( sum % 11 )
if check == 11 :
check = 0
if check == 10 :
2011-08-09 13:49:48 +00:00
# 10 is not a valid check digit for an organization number
return False
2011-08-07 11:25:56 +00:00
return check == int ( vat [ 8 ] )
2011-09-15 10:11:30 +00:00
2014-07-29 09:42:10 +00:00
# Peruvian VAT validation, contributed by Vauxoo
def check_vat_pe ( self , vat ) :
vat_type , vat = vat and len ( vat ) > = 2 and ( vat [ 0 ] , vat [ 1 : ] ) or ( False , False )
if vat_type and vat_type . upper ( ) == ' D ' :
#DNI
return True
elif vat_type and vat_type . upper ( ) == ' R ' :
#verify RUC
factor = ' 5432765432 '
sum = 0
dig_check = False
if len ( vat ) != 11 :
return False
try :
int ( vat )
except ValueError :
return False
for f in range ( 0 , 10 ) :
sum + = int ( factor [ f ] ) * int ( vat [ f ] )
subtraction = 11 - ( sum % 11 )
if subtraction == 10 :
dig_check = 0
elif subtraction == 11 :
dig_check = 1
else :
dig_check = subtraction
return int ( vat [ 10 ] ) == dig_check
else :
return False
2008-08-22 13:33:44 +00:00
2011-01-17 20:44:55 +00:00
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: