# -*- coding: utf-8 -*- import re from flectra import api, models, _ from flectra.exceptions import UserError, ValidationError def normalize_iban(iban): return re.sub('[\W_]', '', iban or '') def pretty_iban(iban): """ return iban in groups of four characters separated by a single space """ return ' '.join([iban[i:i + 4] for i in range(0, len(iban), 4)]) def get_bban_from_iban(iban): """ Returns the basic bank account number corresponding to an IBAN. Note : the BBAN is not the same as the domestic bank account number ! The relation between IBAN, BBAN and domestic can be found here : http://www.ecbs.org/iban.htm """ return normalize_iban(iban)[4:] def validate_iban(iban): iban = normalize_iban(iban) if not iban: raise ValidationError(_("No IBAN !")) country_code = iban[:2].lower() if country_code not in _map_iban_template: raise ValidationError(_("The IBAN is invalid, it should begin with the country code")) iban_template = _map_iban_template[country_code] if len(iban) != len(iban_template.replace(' ', '')): raise ValidationError(_("The IBAN does not seem to be correct. You should have entered something like this %s\n" "Where B = National bank code, S = Branch code, C = Account No, k = Check digit") % iban_template) check_chars = iban[4:] + iban[:4] digits = int(''.join(str(int(char, 36)) for char in check_chars)) # BASE 36: 0..9,A..Z -> 0..35 if digits % 97 != 1: raise ValidationError(_("This IBAN does not pass the validation check, please verify it.")) class ResPartnerBank(models.Model): _inherit = "res.partner.bank" @api.one @api.depends('acc_number') def _compute_acc_type(self): try: validate_iban(self.acc_number) self.acc_type = 'iban' except ValidationError: super(ResPartnerBank, self)._compute_acc_type() def get_bban(self): if self.acc_type != 'iban': raise UserError(_("Cannot compute the BBAN because the account number is not an IBAN.")) return get_bban_from_iban(self.acc_number) @api.model def create(self, vals): if (vals.get('acc_type') == 'iban') and vals.get('acc_number'): vals['acc_number'] = pretty_iban(normalize_iban(vals['acc_number'])) return super(ResPartnerBank, self).create(vals) @api.multi def write(self, vals): if (vals.get('acc_type') == 'iban') and vals.get('acc_number'): vals['acc_number'] = pretty_iban(normalize_iban(vals['acc_number'])) return super(ResPartnerBank, self).write(vals) @api.one @api.constrains('acc_number') def _check_iban(self): if self.acc_type == 'iban': validate_iban(self.acc_number) # Map ISO 3166-1 -> IBAN template, as described here : # http://en.wikipedia.org/wiki/International_Bank_Account_Number#IBAN_formats_by_country _map_iban_template = { 'ad': 'ADkk BBBB SSSS CCCC CCCC CCCC', # Andorra 'ae': 'AEkk BBBC CCCC CCCC CCCC CCC', # United Arab Emirates 'al': 'ALkk BBBS SSSK CCCC CCCC CCCC CCCC', # Albania 'at': 'ATkk BBBB BCCC CCCC CCCC', # Austria 'az': 'AZkk BBBB CCCC CCCC CCCC CCCC CCCC', # Azerbaijan 'ba': 'BAkk BBBS SSCC CCCC CCKK', # Bosnia and Herzegovina 'be': 'BEkk BBBC CCCC CCXX', # Belgium 'bg': 'BGkk BBBB SSSS DDCC CCCC CC', # Bulgaria 'bh': 'BHkk BBBB CCCC CCCC CCCC CC', # Bahrain 'br': 'BRkk BBBB BBBB SSSS SCCC CCCC CCCT N', # Brazil 'by': 'BYkk BBBB AAAA CCCC CCCC CCCC CCCC', # Belarus 'ch': 'CHkk BBBB BCCC CCCC CCCC C', # Switzerland 'cr': 'CRkk BBBC CCCC CCCC CCCC C', # Costa Rica 'cy': 'CYkk BBBS SSSS CCCC CCCC CCCC CCCC', # Cyprus 'cz': 'CZkk BBBB SSSS SSCC CCCC CCCC', # Czech Republic 'de': 'DEkk BBBB BBBB CCCC CCCC CC', # Germany 'dk': 'DKkk BBBB CCCC CCCC CC', # Denmark 'do': 'DOkk BBBB CCCC CCCC CCCC CCCC CCCC', # Dominican Republic 'ee': 'EEkk BBSS CCCC CCCC CCCK', # Estonia 'es': 'ESkk BBBB SSSS KKCC CCCC CCCC', # Spain 'fi': 'FIkk BBBB BBCC CCCC CK', # Finland 'fo': 'FOkk CCCC CCCC CCCC CC', # Faroe Islands 'fr': 'FRkk BBBB BGGG GGCC CCCC CCCC CKK', # France 'gb': 'GBkk BBBB SSSS SSCC CCCC CC', # United Kingdom 'ge': 'GEkk BBCC CCCC CCCC CCCC CC', # Georgia 'gi': 'GIkk BBBB CCCC CCCC CCCC CCC', # Gibraltar 'gl': 'GLkk BBBB CCCC CCCC CC', # Greenland 'gr': 'GRkk BBBS SSSC CCCC CCCC CCCC CCC', # Greece 'gt': 'GTkk BBBB MMTT CCCC CCCC CCCC CCCC', # Guatemala 'hr': 'HRkk BBBB BBBC CCCC CCCC C', # Croatia 'hu': 'HUkk BBBS SSSC CCCC CCCC CCCC CCCC', # Hungary 'ie': 'IEkk BBBB SSSS SSCC CCCC CC', # Ireland 'il': 'ILkk BBBS SSCC CCCC CCCC CCC', # Israel 'is': 'ISkk BBBB SSCC CCCC XXXX XXXX XX', # Iceland 'it': 'ITkk KBBB BBSS SSSC CCCC CCCC CCC', # Italy 'jo': 'JOkk BBBB NNNN CCCC CCCC CCCC CCCC CC', # Jordan 'kw': 'KWkk BBBB CCCC CCCC CCCC CCCC CCCC CC', # Kuwait 'kz': 'KZkk BBBC CCCC CCCC CCCC', # Kazakhstan 'lb': 'LBkk BBBB CCCC CCCC CCCC CCCC CCCC', # Lebanon 'li': 'LIkk BBBB BCCC CCCC CCCC C', # Liechtenstein 'lt': 'LTkk BBBB BCCC CCCC CCCC', # Lithuania 'lu': 'LUkk BBBC CCCC CCCC CCCC', # Luxembourg 'lv': 'LVkk BBBB CCCC CCCC CCCC C', # Latvia 'mc': 'MCkk BBBB BGGG GGCC CCCC CCCC CKK', # Monaco 'md': 'MDkk BBCC CCCC CCCC CCCC CCCC', # Moldova 'me': 'MEkk BBBC CCCC CCCC CCCC KK', # Montenegro 'mk': 'MKkk BBBC CCCC CCCC CKK', # Macedonia 'mr': 'MRkk BBBB BSSS SSCC CCCC CCCC CKK', # Mauritania 'mt': 'MTkk BBBB SSSS SCCC CCCC CCCC CCCC CCC', # Malta 'mu': 'MUkk BBBB BBSS CCCC CCCC CCCC CCCC CC', # Mauritius 'nl': 'NLkk BBBB CCCC CCCC CC', # Netherlands 'no': 'NOkk BBBB CCCC CCK', # Norway 'pk': 'PKkk BBBB CCCC CCCC CCCC CCCC', # Pakistan 'pl': 'PLkk BBBS SSSK CCCC CCCC CCCC CCCC', # Poland 'ps': 'PSkk BBBB XXXX XXXX XCCC CCCC CCCC C', # Palestinian 'pt': 'PTkk BBBB SSSS CCCC CCCC CCCK K', # Portugal 'qa': 'QAkk BBBB CCCC CCCC CCCC CCCC CCCC C', # Qatar 'ro': 'ROkk BBBB CCCC CCCC CCCC CCCC', # Romania 'rs': 'RSkk BBBC CCCC CCCC CCCC KK', # Serbia 'sa': 'SAkk BBCC CCCC CCCC CCCC CCCC', # Saudi Arabia 'se': 'SEkk BBBB CCCC CCCC CCCC CCCC', # Sweden 'si': 'SIkk BBSS SCCC CCCC CKK', # Slovenia 'sk': 'SKkk BBBB SSSS SSCC CCCC CCCC', # Slovakia 'sm': 'SMkk KBBB BBSS SSSC CCCC CCCC CCC', # San Marino 'tn': 'TNkk BBSS SCCC CCCC CCCC CCCC', # Tunisia 'tr': 'TRkk BBBB BRCC CCCC CCCC CCCC CC', # Turkey 'vg': 'VGkk BBBB CCCC CCCC CCCC CCCC', # Virgin Islands 'xk': 'XKkk BBBB CCCC CCCC CCCC', # Kosovo }