From 7c19d3fdb0d60189957c86da27d32686af0af53d Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Tue, 21 Jan 2014 18:03:17 +0100 Subject: [PATCH 01/15] [RFR] Refactor out custom IBAN wizardry --- account_banking/__openerp__.py | 6 - account_banking/account_banking.py | 411 ------------------ account_banking/account_banking_view.xml | 29 -- account_banking/res_bank.py | 31 ++ account_banking/res_partner_bank.py | 32 ++ account_banking/sepa/__init__.py | 1 - account_banking/wizard/banktools.py | 18 +- account_banking_iban_lookup/__init__.py | 4 + account_banking_iban_lookup/__openerp__.py | 47 ++ account_banking_iban_lookup/model/__init__.py | 2 + account_banking_iban_lookup/model/res_bank.py | 65 +++ .../model/res_partner_bank.py | 288 ++++++++++++ .../online.py | 4 +- .../urlagent.py | 1 - account_banking_iban_lookup/view/res_bank.xml | 15 + .../view/res_partner_bank.xml | 23 + account_banking_nl_clieop/__openerp__.py | 5 +- .../model/banking_import_transaction.py | 16 +- 18 files changed, 530 insertions(+), 468 deletions(-) create mode 100644 account_banking/res_bank.py create mode 100644 account_banking/res_partner_bank.py create mode 100644 account_banking_iban_lookup/__init__.py create mode 100644 account_banking_iban_lookup/__openerp__.py create mode 100644 account_banking_iban_lookup/model/__init__.py create mode 100644 account_banking_iban_lookup/model/res_bank.py create mode 100644 account_banking_iban_lookup/model/res_partner_bank.py rename {account_banking/sepa => account_banking_iban_lookup}/online.py (98%) rename {account_banking/sepa => account_banking_iban_lookup}/urlagent.py (99%) create mode 100644 account_banking_iban_lookup/view/res_bank.xml create mode 100644 account_banking_iban_lookup/view/res_partner_bank.xml diff --git a/account_banking/__openerp__.py b/account_banking/__openerp__.py index 27a346d6c..0f94399cb 100644 --- a/account_banking/__openerp__.py +++ b/account_banking/__openerp__.py @@ -33,7 +33,6 @@ 'category': 'Banking addons', 'depends': [ 'account_voucher', - 'account_iban_preserve_domestic', ], 'data': [ 'security/ir.model.access.csv', @@ -47,14 +46,9 @@ 'js': [ 'static/src/js/account_banking.js', ], - 'external_dependencies': { - 'python' : ['BeautifulSoup'], - }, 'description': ''' Module to do banking. - Note: This module is depending on BeautifulSoup. - This modules tries to combine all current banking import and export schemes. Rationale for this is that it is quite common to have foreign bank account numbers next to national bank account numbers. The current diff --git a/account_banking/account_banking.py b/account_banking/account_banking.py index a0a6afac9..70448a3bf 100644 --- a/account_banking/account_banking.py +++ b/account_banking/account_banking.py @@ -70,10 +70,6 @@ from openerp.addons.decimal_precision import decimal_precision as dp from openerp.addons.account_banking import sepa from openerp.addons.account_banking.wizard.banktools import get_or_create_bank -def warning(title, message): - '''Convenience routine''' - return {'warning': {'title': title, 'message': message}} - class account_banking_account_settings(orm.Model): '''Default Journal for Bank Account''' @@ -562,413 +558,6 @@ class account_bank_statement_line(orm.Model): account_bank_statement_line() -class res_partner_bank(orm.Model): - ''' - This is a hack to circumvent the very limited but widely used base_iban - dependency. The usage of __mro__ requires inside information of - inheritence. This code is tested and works - it bypasses base_iban - altogether. Be sure to use 'super' for inherited classes from here though. - - Extended functionality: - 1. BBAN and IBAN are considered equal - 2. Online databases are checked when available - 3. Banks are created on the fly when using IBAN - 4. Storage is uppercase, not lowercase - 5. Presentation is formal IBAN - 6. BBAN's are generated from IBAN when possible - 7. In the absence of online databanks, BBAN's are checked on format - using IBAN specs. - ''' - _inherit = 'res.partner.bank' - - def __init__(self, *args, **kwargs): - ''' - Locate founder (first non inherited class) in inheritance tree. - Defaults to super() - Algorithm should prevent moving unknown classes between - base.res_partner_bank and this module's res_partner_bank. - ''' - self._founder = super(res_partner_bank, self) - self._founder.__init__(*args, **kwargs) - mro = self.__class__.__mro__ - for i in range(len(mro)): - if mro[i].__module__.startswith('openerp.addons.base.'): - self._founder = mro[i] - break - - def init(self, cr): - ''' - Update existing iban accounts to comply to new regime - ''' - - partner_bank_obj = self.pool.get('res.partner.bank') - bank_ids = partner_bank_obj.search( - cr, SUPERUSER_ID, [('state', '=', 'iban')], limit=0) - for bank in partner_bank_obj.read(cr, SUPERUSER_ID, bank_ids): - write_vals = {} - if bank['state'] == 'iban': - iban_acc = sepa.IBAN(bank['acc_number']) - if iban_acc.valid: - write_vals['acc_number_domestic'] = iban_acc.localized_BBAN - write_vals['acc_number'] = str(iban_acc) - elif bank['acc_number'] != bank['acc_number'].upper(): - write_vals['acc_number'] = bank['acc_number'].upper() - if write_vals: - partner_bank_obj.write( - cr, SUPERUSER_ID, bank['id'], write_vals) - - @staticmethod - def _correct_IBAN(acc_number): - ''' - Routine to correct IBAN values and deduce localized values when valid. - Note: No check on validity IBAN/Country - ''' - iban = sepa.IBAN(acc_number) - return (str(iban), iban.localized_BBAN) - - def create(self, cr, uid, vals, context=None): - ''' - Create dual function IBAN account for SEPA countries - ''' - if vals.get('state') == 'iban': - iban = (vals.get('acc_number') - or vals.get('acc_number_domestic', False)) - vals['acc_number'], vals['acc_number_domestic'] = ( - self._correct_IBAN(iban)) - return self._founder.create(self, cr, uid, vals, context) - - def write(self, cr, uid, ids, vals, context=None): - ''' - Create dual function IBAN account for SEPA countries - - Update the domestic account number when the IBAN is - written, or clear the domestic number on regular account numbers. - ''' - if ids and isinstance(ids, (int, long)): - ids = [ids] - for account in self.read( - cr, uid, ids, ['state', 'acc_number']): - if 'state' in vals or 'acc_number' in vals: - account.update(vals) - if account['state'] == 'iban': - vals['acc_number'], vals['acc_number_domestic'] = ( - self._correct_IBAN(account['acc_number'])) - else: - vals['acc_number_domestic'] = False - self._founder.write(self, cr, uid, account['id'], vals, context) - return True - - def search(self, cr, uid, args, *rest, **kwargs): - ''' - Overwrite search, as both acc_number and iban now can be filled, so - the original base_iban 'search and search again fuzzy' tactic now can - result in doubled findings. Also there is now enough info to search - for local accounts when a valid IBAN was supplied. - - Chosen strategy: create complex filter to find all results in just - one search - ''' - - def is_term(arg): - '''Flag an arg as term or otherwise''' - return isinstance(arg, (list, tuple)) and len(arg) == 3 - - def extended_filter_term(term): - ''' - Extend the search criteria in term when appropriate. - ''' - extra_term = None - if term[0].lower() == 'acc_number' and term[1] in ('=', '=='): - iban = sepa.IBAN(term[2]) - if iban.valid: - # Some countries can't convert to BBAN - try: - bban = iban.localized_BBAN - # Prevent empty search filters - if bban: - extra_term = ('acc_number_domestic', term[1], bban) - except: - pass - if extra_term: - return ['|', term, extra_term] - return [term] - - def extended_search_expression(args): - ''' - Extend the search expression in args when appropriate. - The expression itself is in reverse polish notation, so recursion - is not needed. - ''' - if not args: - return [] - - all = [] - if is_term(args[0]) and len(args) > 1: - # Classic filter, implicit '&' - all += ['&'] - - for arg in args: - if is_term(arg): - all += extended_filter_term(arg) - else: - all += arg - return all - - # Extend search filter - newargs = extended_search_expression(args) - - # Original search - results = super(res_partner_bank, self).search( - cr, uid, newargs, *rest, **kwargs) - return results - - def read( - self, cr, uid, ids, fields=None, context=None, load='_classic_read'): - ''' - Convert IBAN electronic format to IBAN display format - SR 2012-02-19: do we really need this? Fields are converted upon write already. - ''' - if fields and 'state' not in fields: - fields.append('state') - records = self._founder.read(self, cr, uid, ids, fields, context, load) - is_list = True - if not isinstance(records, list): - records = [records,] - is_list = False - for record in records: - if 'acc_number' in record and record['state'] == 'iban': - record['acc_number'] = unicode(sepa.IBAN(record['acc_number'])) - if is_list: - return records - return records[0] - - def check_iban(self, cr, uid, ids, context=None): - ''' - Check IBAN number - ''' - for bank_acc in self.browse(cr, uid, ids, context=context): - if bank_acc.state == 'iban' and bank_acc.acc_number: - iban = sepa.IBAN(bank_acc.acc_number) - if not iban.valid: - return False - return True - - def get_bban_from_iban(self, cr, uid, ids, context=None): - ''' - Return the local bank account number aka BBAN from the IBAN. - ''' - res = {} - for record in self.browse(cr, uid, ids, context): - if not record.state == 'iban': - res[record.id] = False - else: - iban_acc = sepa.IBAN(record.acc_number) - try: - res[record.id] = iban_acc.localized_BBAN - except NotImplementedError: - res[record.id] = False - return res - - def onchange_acc_number( - self, cr, uid, ids, acc_number, acc_number_domestic, - state, partner_id, country_id, context=None): - if state == 'iban': - return self.onchange_iban( - cr, uid, ids, acc_number, acc_number_domestic, - state, partner_id, country_id, context=None - ) - else: - return self.onchange_domestic( - cr, uid, ids, acc_number, - partner_id, country_id, context=None - ) - - def onchange_domestic( - self, cr, uid, ids, acc_number, - partner_id, country_id, context=None): - ''' - Trigger to find IBAN. When found: - 1. Reformat BBAN - 2. Autocomplete bank - - TODO: prevent unnecessary assignment of country_ids and - browsing of the country - ''' - if not acc_number: - return {} - - values = {} - country_obj = self.pool.get('res.country') - country_ids = [] - country = False - - # Pre fill country based on available data. This is just a default - # which can be overridden by the user. - # 1. Use provided country_id (manually filled) - if country_id: - country = country_obj.browse(cr, uid, country_id, context=context) - country_ids = [country_id] - # 2. Use country_id of found bank accounts - # This can be usefull when there is no country set in the partners - # addresses, but there was a country set in the address for the bank - # account itself before this method was triggered. - elif ids and len(ids) == 1: - partner_bank_obj = self.pool.get('res.partner.bank') - partner_bank_id = partner_bank_obj.browse(cr, uid, ids[0], context=context) - if partner_bank_id.country_id: - country = partner_bank_id.country_id - country_ids = [country.id] - # 3. Use country_id of default address of partner - # The country_id of a bank account is a one time default on creation. - # It originates in the same address we are about to check, but - # modifications on that address afterwards are not transfered to the - # bank account, hence the additional check. - elif partner_id: - partner_obj = self.pool.get('res.partner') - country = partner_obj.browse(cr, uid, partner_id, context=context).country - country_ids = country and [country.id] or [] - # 4. Without any of the above, take the country from the company of - # the handling user - if not country_ids: - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) - # Try user companies partner (user no longer has address in 6.1) - if (user.company_id and - user.company_id.partner_id and - user.company_id.partner_id.country - ): - country_ids = [user.company_id.partner_id.country.id] - else: - if (user.company_id and user.company_id.partner_id and - user.company_id.partner_id.country): - country_ids = [user.company_id.partner_id.country.id] - else: - # Ok, tried everything, give up and leave it to the user - return warning(_('Insufficient data'), - _('Insufficient data to select online ' - 'conversion database') - ) - result = {'value': values} - # Complete data with online database when available - if country_ids: - country = country_obj.browse( - cr, uid, country_ids[0], context=context) - values['country_id'] = country_ids[0] - if country and country.code in sepa.IBAN.countries: - try: - info = sepa.online.account_info(country.code, acc_number) - if info: - iban_acc = sepa.IBAN(info.iban) - if iban_acc.valid: - values['acc_number_domestic'] = iban_acc.localized_BBAN - values['acc_number'] = unicode(iban_acc) - values['state'] = 'iban' - bank_id, country_id = get_or_create_bank( - self.pool, cr, uid, - info.bic or iban_acc.BIC_searchkey, - name = info.bank - ) - if country_id: - values['country_id'] = country_id - values['bank'] = bank_id or False - if info.bic: - values['bank_bic'] = info.bic - else: - info = None - if info is None: - result.update(warning( - _('Invalid data'), - _('The account number appears to be invalid for %s') - % country.name - )) - except NotImplementedError: - if country.code in sepa.IBAN.countries: - acc_number_fmt = sepa.BBAN(acc_number, country.code) - if acc_number_fmt.valid: - values['acc_number_domestic'] = str(acc_number_fmt) - else: - result.update(warning( - _('Invalid format'), - _('The account number has the wrong format for %s') - % country.name - )) - return result - - def onchange_iban( - self, cr, uid, ids, acc_number, acc_number_domestic, - state, partner_id, country_id, context=None): - ''' - Trigger to verify IBAN. When valid: - 1. Extract BBAN as local account - 2. Auto complete bank - ''' - if not acc_number: - return {} - - iban_acc = sepa.IBAN(acc_number) - if iban_acc.valid: - bank_id, country_id = get_or_create_bank( - self.pool, cr, uid, iban_acc.BIC_searchkey, - code=iban_acc.BIC_searchkey - ) - return { - 'value': dict( - acc_number_domestic = iban_acc.localized_BBAN, - acc_number = unicode(iban_acc), - country = country_id or False, - bank = bank_id or False, - ) - } - return warning(_('Invalid IBAN account number!'), - _("The IBAN number doesn't seem to be correct") - ) - -res_partner_bank() - - -class res_bank(orm.Model): - ''' - Add a on_change trigger to automagically fill bank details from the - online SWIFT database. Allow hand filled names to overrule SWIFT names. - ''' - _inherit = 'res.bank' - - def onchange_bic(self, cr, uid, ids, bic, name, context=None): - ''' - Trigger to auto complete other fields. - ''' - if not bic: - return {} - - info, address = sepa.online.bank_info(bic) - if not info: - return {} - - if address and address.country_id: - country_id = self.pool.get('res.country').search( - cr, uid, [('code','=',address.country_id)] - ) - country_id = country_id and country_id[0] or False - else: - country_id = False - - return { - 'value': dict( - # Only the first eight positions of BIC are used for bank - # transfers, so ditch the rest. - bic = info.bic[:8], - street = address.street, - street2 = - address.has_key('street2') and address.street2 or False, - zip = address.zip, - city = address.city, - country = country_id, - name = name and name or info.name, - ) - } - -res_bank() - - class invoice(orm.Model): ''' Create other reference types as well. diff --git a/account_banking/account_banking_view.xml b/account_banking/account_banking_view.xml index dcb931700..332867dfe 100644 --- a/account_banking/account_banking_view.xml +++ b/account_banking/account_banking_view.xml @@ -286,35 +286,6 @@ - - res.partner.bank.form.banking-2 - res.partner.bank - - - - - - onchange_acc_number(acc_number, acc_number_domestic, state, partner_id, country_id) - - - onchange_domestic(acc_number_domestic, partner_id, country_id) - - - - - - - - res.bank.form.banking-1 - res.bank - - - - - - - - Bank statement line tree view account.bank.statement.line diff --git a/account_banking/res_bank.py b/account_banking/res_bank.py new file mode 100644 index 000000000..a73033242 --- /dev/null +++ b/account_banking/res_bank.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright 2011 - 2014 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp.osv import orm + + +class ResBank(orm.Model): + _inherit = 'res.bank' + + def online_bank_info(self, cr, uid, bic, context=None): + """ + API hook for legacy online lookup of BICs, + to be removed in OpenERP 8.0. + """ + return False, False diff --git a/account_banking/res_partner_bank.py b/account_banking/res_partner_bank.py new file mode 100644 index 000000000..5c3dfbfac --- /dev/null +++ b/account_banking/res_partner_bank.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2011 - 2014 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp.osv import orm + + +class ResBank(orm.Model): + _inherit = 'res.bank' + + def online_account_info( + self, cr, uid, country_code, acc_number, context=None): + """ + API hook for legacy online lookup of account info, + to be removed in OpenERP 8.0. + """ + return False diff --git a/account_banking/sepa/__init__.py b/account_banking/sepa/__init__.py index 8106ea2ce..0735d2123 100644 --- a/account_banking/sepa/__init__.py +++ b/account_banking/sepa/__init__.py @@ -19,6 +19,5 @@ # ############################################################################## import iban -import online IBAN = iban.IBAN BBAN = iban.BBAN diff --git a/account_banking/wizard/banktools.py b/account_banking/wizard/banktools.py index 0afdfcadf..1fae20cfe 100644 --- a/account_banking/wizard/banktools.py +++ b/account_banking/wizard/banktools.py @@ -63,12 +63,6 @@ def get_bank_accounts(pool, cr, uid, account_number, log, fail=False): bank_account_ids = partner_bank_obj.search(cr, uid, [ ('acc_number', '=', account_number) ]) - if not bank_account_ids: - # SR 2012-02-19 does the search() override in res_partner_bank - # provides this result on the previous query? - bank_account_ids = partner_bank_obj.search(cr, uid, [ - ('acc_number_domestic', '=', account_number) - ]) if not bank_account_ids: if not fail: log.append( @@ -237,7 +231,7 @@ def get_or_create_bank(pool, cr, uid, bic, online=False, code=None, bank_id = False if online: - info, address = sepa.online.bank_info(bic) + info, address = bank_obj.online_bank_info(cr, uid, bic, context=context) if info: bank_id = bank_obj.create(cr, uid, dict( code = info.code, @@ -301,7 +295,6 @@ def create_bank_account(pool, cr, uid, partner_id, owner_name = holder_name, country_id = country_id, ) - bankcode = None # Are we dealing with IBAN? iban = sepa.IBAN(account_number) @@ -309,23 +302,20 @@ def create_bank_account(pool, cr, uid, partner_id, # Take as much info as possible from IBAN values.state = 'iban' values.acc_number = str(iban) - values.acc_number_domestic = iban.BBAN - bankcode = iban.bankcode + iban.countrycode else: # No, try to convert to IBAN values.state = 'bank' - values.acc_number = values.acc_number_domestic = account_number + values.acc_number = account_number if country_id: country_code = pool.get('res.country').read( cr, uid, country_id, ['code'], context=context)['code'] if country_code in sepa.IBAN.countries: - account_info = sepa.online.account_info( - country_code, values.acc_number) + account_info = pool['res.partner.bank'].online_account_info( + cr, uid, country_code, values.acc_number, context=context) if account_info: values.acc_number = iban = account_info.iban values.state = 'iban' - bankcode = account_info.code bic = account_info.bic if bic: diff --git a/account_banking_iban_lookup/__init__.py b/account_banking_iban_lookup/__init__.py new file mode 100644 index 000000000..5a8e49bea --- /dev/null +++ b/account_banking_iban_lookup/__init__.py @@ -0,0 +1,4 @@ +from . import model +from . import online +from . import urlagent + diff --git a/account_banking_iban_lookup/__openerp__.py b/account_banking_iban_lookup/__openerp__.py new file mode 100644 index 000000000..36d236a88 --- /dev/null +++ b/account_banking_iban_lookup/__openerp__.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2013 Therp BV () +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +{ + 'name': 'Banking Addons - Iban lookup (legacy)', + 'version': '0.1', + 'license': 'AGPL-3', + 'author': 'Therp BV', + 'website': 'https://launchpad.net/banking-addons', + 'category': 'Banking addons', + 'depends': [ + 'account_banking', + 'account_iban_preserve_domestic', + ], + 'external_dependencies': { + 'python' : ['BeautifulSoup'], + }, + 'description': ''' +This addons contains the legacy infrastructure for autocompletion of IBANs +and BBANs. + +The autocompletion was implemented for Dutch IBANs, but as it turns out +the online database that it consults does not get updated. As a result, +the autocompletion will come up with outdated IBANs and BICs. + +This module is deprecated and will be dropped in OpenERP 8.0. + ''', + 'auto_install': False, + 'installable': True, +} diff --git a/account_banking_iban_lookup/model/__init__.py b/account_banking_iban_lookup/model/__init__.py new file mode 100644 index 000000000..fde86433b --- /dev/null +++ b/account_banking_iban_lookup/model/__init__.py @@ -0,0 +1,2 @@ +from . import res_bank +from . import res_partner_bank diff --git a/account_banking_iban_lookup/model/res_bank.py b/account_banking_iban_lookup/model/res_bank.py new file mode 100644 index 000000000..616b2e235 --- /dev/null +++ b/account_banking_iban_lookup/model/res_bank.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2009 EduSense BV (). +# (C) 2011 - 2014 Therp BV (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp.osv import orm +from openerp.addons.account_banking_iban_lookup import online + + +class ResBank(orm.Model): + _inherit = 'res.bank' + + def online_bank_info(self, cr, uid, bic, context=None): + """ + Overwrite existing API hook from account_banking + """ + return online.bank_info(bic) + + def onchange_bic( + self, cr, uid, ids, bic, name, context=None): + + if not bic: + return {} + + info, address = online.bank_info(bic) + if not info: + return {} + + if address and address.country_id: + country_id = self.pool.get('res.country').search( + cr, uid, [('code','=',address.country_id)] + ) + country_id = country_id and country_id[0] or False + else: + country_id = False + + return { + 'value': dict( + # Only the first eight positions of BIC are used for bank + # transfers, so ditch the rest. + bic = info.bic[:8], + street = address.street, + street2 = + address.has_key('street2') and address.street2 or False, + zip = address.zip, + city = address.city, + country = country_id, + name = name and name or info.name, + ) + } diff --git a/account_banking_iban_lookup/model/res_partner_bank.py b/account_banking_iban_lookup/model/res_partner_bank.py new file mode 100644 index 000000000..ec8649c5e --- /dev/null +++ b/account_banking_iban_lookup/model/res_partner_bank.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# Copyright (C) 2009 EduSense BV (). +# (C) 2011 - 2013 Therp BV (). +# +# All other contributions are (C) by their respective contributors +# +# All Rights Reserved +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## +from openerp import SUPERUSER_ID +from openerp.osv import orm +from openerp.tools.translate import _ +from openerp.addons.account_banking_iban_lookup import online +from openerp.addons.account_banking import sepa +from openerp.addons.account_banking.wizard.banktools import get_or_create_bank + +def warning(title, message): + '''Convenience routine''' + return {'warning': {'title': title, 'message': message}} + + +class res_partner_bank(orm.Model): + ''' + This is a hack to circumvent the very limited but widely used base_iban + dependency. The usage of __mro__ requires inside information of + inheritence. This code is tested and works - it bypasses base_iban + altogether. Be sure to use 'super' for inherited classes from here though. + + Extended functionality: + 1. BBAN and IBAN are considered equal + 3. Banks are created on the fly when using IBAN + 4. Storage is uppercase, not lowercase + 5. Presentation is formal IBAN + 6. BBAN's are generated from IBAN when possible + ''' + _inherit = 'res.partner.bank' + + def __init__(self, *args, **kwargs): + ''' + Locate founder (first non inherited class) in inheritance tree. + Defaults to super() + Algorithm should prevent moving unknown classes between + base.res_partner_bank and this module's res_partner_bank. + ''' + self._founder = super(res_partner_bank, self) + self._founder.__init__(*args, **kwargs) + mro = self.__class__.__mro__ + for i in range(len(mro)): + if mro[i].__module__.startswith('openerp.addons.base.'): + self._founder = mro[i] + break + + def init(self, cr): + ''' + Update existing iban accounts to comply to new regime + ''' + + partner_bank_obj = self.pool.get('res.partner.bank') + bank_ids = partner_bank_obj.search( + cr, SUPERUSER_ID, [('state', '=', 'iban')], limit=0) + for bank in partner_bank_obj.read(cr, SUPERUSER_ID, bank_ids): + write_vals = {} + if bank['state'] == 'iban': + iban_acc = sepa.IBAN(bank['acc_number']) + if iban_acc.valid: + write_vals['acc_number_domestic'] = iban_acc.localized_BBAN + write_vals['acc_number'] = str(iban_acc) + elif bank['acc_number'] != bank['acc_number'].upper(): + write_vals['acc_number'] = bank['acc_number'].upper() + if write_vals: + partner_bank_obj.write( + cr, SUPERUSER_ID, bank['id'], write_vals) + + @staticmethod + def _correct_IBAN(acc_number): + ''' + Routine to correct IBAN values and deduce localized values when valid. + Note: No check on validity IBAN/Country + ''' + iban = sepa.IBAN(acc_number) + return (str(iban), iban.localized_BBAN) + + def create(self, cr, uid, vals, context=None): + ''' + Create dual function IBAN account for SEPA countries + ''' + if vals.get('state') == 'iban': + iban = (vals.get('acc_number') + or vals.get('acc_number_domestic', False)) + vals['acc_number'], vals['acc_number_domestic'] = ( + self._correct_IBAN(iban)) + return self._founder.create(self, cr, uid, vals, context) + + def write(self, cr, uid, ids, vals, context=None): + ''' + Create dual function IBAN account for SEPA countries + + Update the domestic account number when the IBAN is + written, or clear the domestic number on regular account numbers. + ''' + if ids and isinstance(ids, (int, long)): + ids = [ids] + for account in self.read( + cr, uid, ids, ['state', 'acc_number']): + if 'state' in vals or 'acc_number' in vals: + account.update(vals) + if account['state'] == 'iban': + vals['acc_number'], vals['acc_number_domestic'] = ( + self._correct_IBAN(account['acc_number'])) + else: + vals['acc_number_domestic'] = False + self._founder.write(self, cr, uid, account['id'], vals, context) + return True + + def onchange_acc_number( + self, cr, uid, ids, acc_number, acc_number_domestic, + state, partner_id, country_id, context=None): + if state == 'iban': + return self.onchange_iban( + cr, uid, ids, acc_number, acc_number_domestic, + state, partner_id, country_id, context=None + ) + else: + return self.onchange_domestic( + cr, uid, ids, acc_number, + partner_id, country_id, context=None + ) + + def onchange_domestic( + self, cr, uid, ids, acc_number, + partner_id, country_id, context=None): + ''' + Trigger to find IBAN. When found: + 1. Reformat BBAN + 2. Autocomplete bank + + TODO: prevent unnecessary assignment of country_ids and + browsing of the country + ''' + if not acc_number: + return {} + + values = {} + country_obj = self.pool.get('res.country') + country_ids = [] + country = False + + # Pre fill country based on available data. This is just a default + # which can be overridden by the user. + # 1. Use provided country_id (manually filled) + if country_id: + country = country_obj.browse(cr, uid, country_id, context=context) + country_ids = [country_id] + # 2. Use country_id of found bank accounts + # This can be usefull when there is no country set in the partners + # addresses, but there was a country set in the address for the bank + # account itself before this method was triggered. + elif ids and len(ids) == 1: + partner_bank_obj = self.pool.get('res.partner.bank') + partner_bank_id = partner_bank_obj.browse(cr, uid, ids[0], context=context) + if partner_bank_id.country_id: + country = partner_bank_id.country_id + country_ids = [country.id] + # 3. Use country_id of default address of partner + # The country_id of a bank account is a one time default on creation. + # It originates in the same address we are about to check, but + # modifications on that address afterwards are not transfered to the + # bank account, hence the additional check. + elif partner_id: + partner_obj = self.pool.get('res.partner') + country = partner_obj.browse(cr, uid, partner_id, context=context).country + country_ids = country and [country.id] or [] + # 4. Without any of the above, take the country from the company of + # the handling user + if not country_ids: + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + # Try user companies partner (user no longer has address in 6.1) + if (user.company_id and + user.company_id.partner_id and + user.company_id.partner_id.country + ): + country_ids = [user.company_id.partner_id.country.id] + else: + if (user.company_id and user.company_id.partner_id and + user.company_id.partner_id.country): + country_ids = [user.company_id.partner_id.country.id] + else: + # Ok, tried everything, give up and leave it to the user + return warning(_('Insufficient data'), + _('Insufficient data to select online ' + 'conversion database') + ) + result = {'value': values} + # Complete data with online database when available + if country_ids: + country = country_obj.browse( + cr, uid, country_ids[0], context=context) + values['country_id'] = country_ids[0] + if country and country.code in sepa.IBAN.countries: + try: + info = online.account_info(country.code, acc_number) + if info: + iban_acc = sepa.IBAN(info.iban) + if iban_acc.valid: + values['acc_number_domestic'] = iban_acc.localized_BBAN + values['acc_number'] = unicode(iban_acc) + values['state'] = 'iban' + bank_id, country_id = get_or_create_bank( + self.pool, cr, uid, + info.bic or iban_acc.BIC_searchkey, + name = info.bank + ) + if country_id: + values['country_id'] = country_id + values['bank'] = bank_id or False + if info.bic: + values['bank_bic'] = info.bic + else: + info = None + if info is None: + result.update(warning( + _('Invalid data'), + _('The account number appears to be invalid for %s') + % country.name + )) + except NotImplementedError: + if country.code in sepa.IBAN.countries: + acc_number_fmt = sepa.BBAN(acc_number, country.code) + if acc_number_fmt.valid: + values['acc_number_domestic'] = str(acc_number_fmt) + else: + result.update(warning( + _('Invalid format'), + _('The account number has the wrong format for %s') + % country.name + )) + return result + + def onchange_iban( + self, cr, uid, ids, acc_number, acc_number_domestic, + state, partner_id, country_id, context=None): + ''' + Trigger to verify IBAN. When valid: + 1. Extract BBAN as local account + 2. Auto complete bank + ''' + if not acc_number: + return {} + + iban_acc = sepa.IBAN(acc_number) + if iban_acc.valid: + bank_id, country_id = get_or_create_bank( + self.pool, cr, uid, iban_acc.BIC_searchkey, + code=iban_acc.BIC_searchkey + ) + return { + 'value': dict( + acc_number_domestic = iban_acc.localized_BBAN, + acc_number = unicode(iban_acc), + country = country_id or False, + bank = bank_id or False, + ) + } + return warning(_('Invalid IBAN account number!'), + _("The IBAN number doesn't seem to be correct") + ) + + def online_account_info( + self, cr, uid, country_code, acc_number, context=None): + """ + Overwrite API hook from account_banking + """ + return online.account_info(country_code, acc_number) diff --git a/account_banking/sepa/online.py b/account_banking_iban_lookup/online.py similarity index 98% rename from account_banking/sepa/online.py rename to account_banking_iban_lookup/online.py index 4fe94db85..96583ede7 100644 --- a/account_banking/sepa/online.py +++ b/account_banking_iban_lookup/online.py @@ -1,4 +1,4 @@ -# -*- encoding: utf-8 -*- +# -*- coding: utf-8 -*- ############################################################################## # # Copyright (C) 2009 EduSense BV (). @@ -26,7 +26,7 @@ import re import urllib, urllib2 from BeautifulSoup import BeautifulSoup from openerp.addons.account_banking.sepa import postalcode -from openerp.addons.account_banking.sepa.urlagent import URLAgent, SoupForm +from openerp.addons.account_banking_iban_lookup.urlagent import URLAgent, SoupForm from openerp.addons.account_banking.sepa.iban import IBAN from openerp.addons.account_banking.struct import struct diff --git a/account_banking/sepa/urlagent.py b/account_banking_iban_lookup/urlagent.py similarity index 99% rename from account_banking/sepa/urlagent.py rename to account_banking_iban_lookup/urlagent.py index bf5bd1042..404d6b9e1 100644 --- a/account_banking/sepa/urlagent.py +++ b/account_banking_iban_lookup/urlagent.py @@ -25,7 +25,6 @@ forms and to parse the results back in. It is heavily based on BeautifulSoup. ''' import urllib -from BeautifulSoup import BeautifulSoup __all__ = ['urlsplit', 'urljoin', 'pathbase', 'urlbase', 'SoupForm', 'URLAgent' diff --git a/account_banking_iban_lookup/view/res_bank.xml b/account_banking_iban_lookup/view/res_bank.xml new file mode 100644 index 000000000..e79d99b95 --- /dev/null +++ b/account_banking_iban_lookup/view/res_bank.xml @@ -0,0 +1,15 @@ + + + + + Add BIC lookup to bank form + res.bank + + + + + + + + + diff --git a/account_banking_iban_lookup/view/res_partner_bank.xml b/account_banking_iban_lookup/view/res_partner_bank.xml new file mode 100644 index 000000000..97d77a218 --- /dev/null +++ b/account_banking_iban_lookup/view/res_partner_bank.xml @@ -0,0 +1,23 @@ + + + + + Add autocompletion methods to partner bank form + res.partner.bank + + + + + + onchange_acc_number(acc_number, acc_number_domestic, state, partner_id, country_id) + + + onchange_domestic(acc_number_domestic, partner_id, country_id) + + + + + + + + diff --git a/account_banking_nl_clieop/__openerp__.py b/account_banking_nl_clieop/__openerp__.py index 9e7aac5b9..e1744aa95 100644 --- a/account_banking_nl_clieop/__openerp__.py +++ b/account_banking_nl_clieop/__openerp__.py @@ -24,7 +24,10 @@ 'author': 'EduSense BV', 'website': 'http://www.edusense.nl', 'category': 'Account Banking', - 'depends': ['account_banking_payment'], + 'depends': [ + 'account_banking_payment', + 'account_iban_preserve_domestic', + ], 'data': [ 'account_banking_nl_clieop.xml', 'wizard/export_clieop_view.xml', diff --git a/account_banking_payment/model/banking_import_transaction.py b/account_banking_payment/model/banking_import_transaction.py index 59fd5b6c9..60ada2a3a 100644 --- a/account_banking_payment/model/banking_import_transaction.py +++ b/account_banking_payment/model/banking_import_transaction.py @@ -116,15 +116,25 @@ class banking_import_transaction(orm.Model): ''' # TODO: Not sure what side effects are created when payments are done # for credited customer invoices, which will be matched later on too. + + def bank_match(account, partner_bank): + """ + Compare account number with or without presence + of the domestic number field + """ + return ( + account == partner_bank.acc_number + or (hasattr(partner_bank, 'acc_number_domestic') + and account == partner_bank.acc_number_domestic) + ) + digits = dp.get_precision('Account')(cr)[1] candidates = [ x for x in payment_lines if x.communication == trans.reference and round(x.amount, digits) == -round( trans.statement_line_id.amount, digits) - and trans.remote_account in (x.bank_id.acc_number, - x.bank_id.acc_number_domestic) - ] + and bank_match(trans.remote_account, x.bank_id)] if len(candidates) == 1: candidate = candidates[0] # Check cache to prevent multiple matching of a single payment From 40ab4b08a7ab066377d518bcab9cbd154b9eb0a0 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Tue, 21 Jan 2014 18:39:57 +0100 Subject: [PATCH 02/15] [FIX] Module name and credits --- account_banking_iban_lookup/__openerp__.py | 5 +++-- account_banking_nl_multibank/__openerp__.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/account_banking_iban_lookup/__openerp__.py b/account_banking_iban_lookup/__openerp__.py index 36d236a88..bee51368a 100644 --- a/account_banking_iban_lookup/__openerp__.py +++ b/account_banking_iban_lookup/__openerp__.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Copyright (C) 2013 Therp BV () +# Copyright (C) 2009 EduSense BV (). +# (C) 2011 - 2014 Therp BV (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -22,7 +23,7 @@ 'name': 'Banking Addons - Iban lookup (legacy)', 'version': '0.1', 'license': 'AGPL-3', - 'author': 'Therp BV', + 'author': 'Banking addons community', 'website': 'https://launchpad.net/banking-addons', 'category': 'Banking addons', 'depends': [ diff --git a/account_banking_nl_multibank/__openerp__.py b/account_banking_nl_multibank/__openerp__.py index f81b53a19..ddf0ba59a 100644 --- a/account_banking_nl_multibank/__openerp__.py +++ b/account_banking_nl_multibank/__openerp__.py @@ -20,7 +20,7 @@ ############################################################################## { - 'name': 'Account Banking', + 'name': 'Account Banking - NL Multibank import', 'version': '0.62', 'license': 'AGPL-3', 'author': 'EduSense BV', From ef7b208267afd601183823a2a8b8297c61f4c566 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Tue, 21 Jan 2014 21:38:31 +0100 Subject: [PATCH 03/15] [FIX] Missing imports [FIX] Model and class name [FIX] Keep and improve the account number expansion in res.partner.bank::search(). We need to search for the prettified IBANs as stored in the database. Maybe any matching up until now was by BBAN conversion? --- account_banking/__init__.py | 2 + account_banking/res_partner_bank.py | 77 +++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/account_banking/__init__.py b/account_banking/__init__.py index adb630837..1d7fc15bc 100644 --- a/account_banking/__init__.py +++ b/account_banking/__init__.py @@ -31,5 +31,7 @@ import account_banking import parsers import wizard import res_partner +import res_bank +import res_partner_bank # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/account_banking/res_partner_bank.py b/account_banking/res_partner_bank.py index 5c3dfbfac..fb06aa7cf 100644 --- a/account_banking/res_partner_bank.py +++ b/account_banking/res_partner_bank.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- ############################################################################## # -# Copyright (C) 2011 - 2014 Therp BV (). +# Copyright (C) 2009 EduSense BV (). +# (C) 2011 - 2014 Therp BV (). # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as @@ -18,10 +19,11 @@ # ############################################################################## from openerp.osv import orm +from openerp.addons.account_banking import sepa -class ResBank(orm.Model): - _inherit = 'res.bank' +class ResPartnerBank(orm.Model): + _inherit = 'res.partner.bank' def online_account_info( self, cr, uid, country_code, acc_number, context=None): @@ -30,3 +32,72 @@ class ResBank(orm.Model): to be removed in OpenERP 8.0. """ return False + + def search(self, cr, uid, args, *rest, **kwargs): + ''' + Overwrite search, as both acc_number and iban now can be filled, so + the original base_iban 'search and search again fuzzy' tactic now can + result in doubled findings. Also there is now enough info to search + for local accounts when a valid IBAN was supplied. + + Chosen strategy: create complex filter to find all results in just + one search + ''' + + def is_term(arg): + '''Flag an arg as term or otherwise''' + return isinstance(arg, (list, tuple)) and len(arg) == 3 + + def extended_filter_term(term): + ''' + Extend the search criteria in term when appropriate. + ''' + extra_terms = [] + if term[0].lower() == 'acc_number' and term[1] in ('=', '=='): + iban = sepa.IBAN(term[2]) + import pdb + pdb.set_trace() + if iban.valid: + extra_terms.append(('acc_number', term[1], iban.__repr__())) + if 'acc_number_domestic' in self._columns: + # Some countries can't convert to BBAN + try: + bban = iban.localized_BBAN + # Prevent empty search filters + if bban: + extra_terms.append(('acc_number_domestic', term[1], bban)) + except: + pass + result = [term] + for extra_term in extra_terms: + result = ['|'] + result + [extra_term] + return result + + def extended_search_expression(args): + ''' + Extend the search expression in args when appropriate. + The expression itself is in reverse polish notation, so recursion + is not needed. + ''' + if not args: + return [] + + all = [] + if is_term(args[0]) and len(args) > 1: + # Classic filter, implicit '&' + all += ['&'] + + for arg in args: + if is_term(arg): + all += extended_filter_term(arg) + else: + all += arg + return all + + # Extend search filter + newargs = extended_search_expression(args) + + # Original search + results = super(ResPartnerBank, self).search( + cr, uid, newargs, *rest, **kwargs) + return results From d06b154f0748720f40aaaf802ac4f52d35a1a028 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Tue, 21 Jan 2014 21:40:59 +0100 Subject: [PATCH 04/15] [FIX] Remove debug statement --- account_banking/res_partner_bank.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/account_banking/res_partner_bank.py b/account_banking/res_partner_bank.py index fb06aa7cf..a983cc26d 100644 --- a/account_banking/res_partner_bank.py +++ b/account_banking/res_partner_bank.py @@ -55,8 +55,6 @@ class ResPartnerBank(orm.Model): extra_terms = [] if term[0].lower() == 'acc_number' and term[1] in ('=', '=='): iban = sepa.IBAN(term[2]) - import pdb - pdb.set_trace() if iban.valid: extra_terms.append(('acc_number', term[1], iban.__repr__())) if 'acc_number_domestic' in self._columns: From 1b7b6140e22f01bb5530c26bac953acb98807b9d Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Tue, 21 Jan 2014 21:54:39 +0100 Subject: [PATCH 05/15] [IMP] Enjoy IBAN normalization also during payment match --- .../model/banking_import_transaction.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/account_banking_payment/model/banking_import_transaction.py b/account_banking_payment/model/banking_import_transaction.py index 60ada2a3a..cb4665369 100644 --- a/account_banking_payment/model/banking_import_transaction.py +++ b/account_banking_payment/model/banking_import_transaction.py @@ -122,19 +122,17 @@ class banking_import_transaction(orm.Model): Compare account number with or without presence of the domestic number field """ - return ( - account == partner_bank.acc_number - or (hasattr(partner_bank, 'acc_number_domestic') - and account == partner_bank.acc_number_domestic) - ) - + return partner_bank.id in self.pool['res.partner.bank'].search( + cr, uid, [('acc_number', '=', account)]) + digits = dp.get_precision('Account')(cr)[1] candidates = [ - x for x in payment_lines - if x.communication == trans.reference - and round(x.amount, digits) == -round( - trans.statement_line_id.amount, digits) - and bank_match(trans.remote_account, x.bank_id)] + line for line in payment_lines + if (line.communication == trans.reference + and round(line.amount, digits) == -round( + trans.statement_line_id.amount, digits) + and bank_match(trans.remote_account, line.bank_id)) + ] if len(candidates) == 1: candidate = candidates[0] # Check cache to prevent multiple matching of a single payment From fb558fd5911018bec73d4fdb7b48cb6fe97767a2 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sun, 9 Feb 2014 19:43:10 +0100 Subject: [PATCH 06/15] [FIX] Load view files --- account_banking_iban_lookup/__init__.py | 3 +-- account_banking_iban_lookup/__openerp__.py | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/account_banking_iban_lookup/__init__.py b/account_banking_iban_lookup/__init__.py index 5a8e49bea..57e76fefc 100644 --- a/account_banking_iban_lookup/__init__.py +++ b/account_banking_iban_lookup/__init__.py @@ -1,4 +1,3 @@ -from . import model from . import online from . import urlagent - +from . import model diff --git a/account_banking_iban_lookup/__openerp__.py b/account_banking_iban_lookup/__openerp__.py index bee51368a..5ddaf70bc 100644 --- a/account_banking_iban_lookup/__openerp__.py +++ b/account_banking_iban_lookup/__openerp__.py @@ -30,6 +30,10 @@ 'account_banking', 'account_iban_preserve_domestic', ], + 'data': [ + 'view/res_bank.xml', + 'view/res_partner_bank.xml', + ], 'external_dependencies': { 'python' : ['BeautifulSoup'], }, From 27612bd9c8b28a2fcab3e1d3a7b1740de674e9ea Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sun, 9 Feb 2014 20:53:33 +0100 Subject: [PATCH 07/15] [RFR] Remove founder class hack. This will introduce some duplicated functionality between the deprecated module and base_iban [IMP] Disregard spaces when comparing IBAN, as we can now no longer assume that IBANs are in the legacy account_banking formatting --- account_banking/res_partner_bank.py | 12 +++++++++-- .../model/res_partner_bank.py | 21 ++++--------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/account_banking/res_partner_bank.py b/account_banking/res_partner_bank.py index a983cc26d..3c5256e94 100644 --- a/account_banking/res_partner_bank.py +++ b/account_banking/res_partner_bank.py @@ -52,11 +52,20 @@ class ResPartnerBank(orm.Model): ''' Extend the search criteria in term when appropriate. ''' + result = [term] extra_terms = [] if term[0].lower() == 'acc_number' and term[1] in ('=', '=='): iban = sepa.IBAN(term[2]) if iban.valid: - extra_terms.append(('acc_number', term[1], iban.__repr__())) + # Disregard spaces when comparing IBANs + cr.execute( + """ + SELECT id FROM res_partner_bank + WHERE replace(acc_number, ' ', '') = %s + """, (term[2].replace(' ', ''),)) + ids = [row[0] for row in cr.fetchall()] + result = [('id', 'in', ids)] + if 'acc_number_domestic' in self._columns: # Some countries can't convert to BBAN try: @@ -66,7 +75,6 @@ class ResPartnerBank(orm.Model): extra_terms.append(('acc_number_domestic', term[1], bban)) except: pass - result = [term] for extra_term in extra_terms: result = ['|'] + result + [extra_term] return result diff --git a/account_banking_iban_lookup/model/res_partner_bank.py b/account_banking_iban_lookup/model/res_partner_bank.py index ec8649c5e..f37de42be 100644 --- a/account_banking_iban_lookup/model/res_partner_bank.py +++ b/account_banking_iban_lookup/model/res_partner_bank.py @@ -50,21 +50,6 @@ class res_partner_bank(orm.Model): ''' _inherit = 'res.partner.bank' - def __init__(self, *args, **kwargs): - ''' - Locate founder (first non inherited class) in inheritance tree. - Defaults to super() - Algorithm should prevent moving unknown classes between - base.res_partner_bank and this module's res_partner_bank. - ''' - self._founder = super(res_partner_bank, self) - self._founder.__init__(*args, **kwargs) - mro = self.__class__.__mro__ - for i in range(len(mro)): - if mro[i].__module__.startswith('openerp.addons.base.'): - self._founder = mro[i] - break - def init(self, cr): ''' Update existing iban accounts to comply to new regime @@ -104,7 +89,8 @@ class res_partner_bank(orm.Model): or vals.get('acc_number_domestic', False)) vals['acc_number'], vals['acc_number_domestic'] = ( self._correct_IBAN(iban)) - return self._founder.create(self, cr, uid, vals, context) + return super(res_partner_bank, self).create( + cr, uid, vals, context) def write(self, cr, uid, ids, vals, context=None): ''' @@ -124,7 +110,8 @@ class res_partner_bank(orm.Model): self._correct_IBAN(account['acc_number'])) else: vals['acc_number_domestic'] = False - self._founder.write(self, cr, uid, account['id'], vals, context) + super(res_partner_bank, self).write( + cr, uid, account['id'], vals, context) return True def onchange_acc_number( From a205d6319f9a6c0f45be7a91c7912ea234cde3fa Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sun, 9 Feb 2014 21:05:56 +0100 Subject: [PATCH 08/15] [IMP] Docstring --- account_banking/res_partner_bank.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/account_banking/res_partner_bank.py b/account_banking/res_partner_bank.py index 3c5256e94..c73071694 100644 --- a/account_banking/res_partner_bank.py +++ b/account_banking/res_partner_bank.py @@ -34,15 +34,11 @@ class ResPartnerBank(orm.Model): return False def search(self, cr, uid, args, *rest, **kwargs): - ''' - Overwrite search, as both acc_number and iban now can be filled, so - the original base_iban 'search and search again fuzzy' tactic now can - result in doubled findings. Also there is now enough info to search - for local accounts when a valid IBAN was supplied. - - Chosen strategy: create complex filter to find all results in just - one search - ''' + """ + When a complete IBAN is searched, also search for its BBAN + if we have the domestic column. Disregard spaces + when comparing IBANs. + """ def is_term(arg): '''Flag an arg as term or otherwise''' @@ -104,6 +100,5 @@ class ResPartnerBank(orm.Model): newargs = extended_search_expression(args) # Original search - results = super(ResPartnerBank, self).search( + return super(ResPartnerBank, self).search( cr, uid, newargs, *rest, **kwargs) - return results From 32ed8c6bcc8c00c17ceac2fc0ed4a8fd4c203e56 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sun, 9 Feb 2014 21:07:49 +0100 Subject: [PATCH 09/15] [RFR] Replace a variable named after a reserved word --- account_banking/res_partner_bank.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/account_banking/res_partner_bank.py b/account_banking/res_partner_bank.py index c73071694..513489cc1 100644 --- a/account_banking/res_partner_bank.py +++ b/account_banking/res_partner_bank.py @@ -84,17 +84,17 @@ class ResPartnerBank(orm.Model): if not args: return [] - all = [] + result = [] if is_term(args[0]) and len(args) > 1: # Classic filter, implicit '&' - all += ['&'] + result += ['&'] for arg in args: if is_term(arg): - all += extended_filter_term(arg) + result += extended_filter_term(arg) else: - all += arg - return all + result += arg + return result # Extend search filter newargs = extended_search_expression(args) From 6d8e0506ad86769958e8f1af3a413e65de831a05 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sun, 9 Feb 2014 21:15:15 +0100 Subject: [PATCH 10/15] [IMP] Docstring --- .../model/banking_import_transaction.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/account_banking_payment/model/banking_import_transaction.py b/account_banking_payment/model/banking_import_transaction.py index cb4665369..da6d268e4 100644 --- a/account_banking_payment/model/banking_import_transaction.py +++ b/account_banking_payment/model/banking_import_transaction.py @@ -119,8 +119,12 @@ class banking_import_transaction(orm.Model): def bank_match(account, partner_bank): """ - Compare account number with or without presence - of the domestic number field + Returns whether a given account number is equivalent to a + partner bank in the database. We simply call the search method, + which checks IBAN, domestic and disregards from spaces in IBANs. + + :param account: string representation of a bank account number + :param partner_bank: browse record of model res.partner.bank """ return partner_bank.id in self.pool['res.partner.bank'].search( cr, uid, [('acc_number', '=', account)]) From 51c59fed03f1bb4f7e89f8aef8d9485f9cfca6fa Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sun, 9 Feb 2014 21:21:20 +0100 Subject: [PATCH 11/15] [IMP] Docstring --- .../model/res_partner_bank.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/account_banking_iban_lookup/model/res_partner_bank.py b/account_banking_iban_lookup/model/res_partner_bank.py index f37de42be..d384a8e38 100644 --- a/account_banking_iban_lookup/model/res_partner_bank.py +++ b/account_banking_iban_lookup/model/res_partner_bank.py @@ -36,17 +36,12 @@ def warning(title, message): class res_partner_bank(orm.Model): ''' - This is a hack to circumvent the very limited but widely used base_iban - dependency. The usage of __mro__ requires inside information of - inheritence. This code is tested and works - it bypasses base_iban - altogether. Be sure to use 'super' for inherited classes from here though. - Extended functionality: 1. BBAN and IBAN are considered equal - 3. Banks are created on the fly when using IBAN - 4. Storage is uppercase, not lowercase - 5. Presentation is formal IBAN - 6. BBAN's are generated from IBAN when possible + 2. Online lookup when an API is available (providing NL in this module) + 3. Banks are created on the fly when using IBAN + online + 4. IBAN formatting + 5. BBAN's are generated from IBAN when possible ''' _inherit = 'res.partner.bank' From 4e1524c0ef7763a8bdb140fabeea4f1d8843cbe8 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sat, 15 Mar 2014 16:13:15 +0100 Subject: [PATCH 12/15] [FIX] Remove unused imports --- account_banking/account_banking.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/account_banking/account_banking.py b/account_banking/account_banking.py index 70448a3bf..b40343a8e 100644 --- a/account_banking/account_banking.py +++ b/account_banking/account_banking.py @@ -65,10 +65,8 @@ Modifications are extensive: from openerp.osv import orm, fields from openerp.osv.osv import except_osv from openerp.tools.translate import _ -from openerp import netsvc, SUPERUSER_ID +from openerp import netsvc from openerp.addons.decimal_precision import decimal_precision as dp -from openerp.addons.account_banking import sepa -from openerp.addons.account_banking.wizard.banktools import get_or_create_bank class account_banking_account_settings(orm.Model): From 22c01077124d6d1531807a7776a4090fdd04ad5d Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sat, 15 Mar 2014 16:54:37 +0100 Subject: [PATCH 13/15] [RFR] Flake8 --- account_banking_iban_lookup/model/res_bank.py | 24 ++++---- .../model/res_partner_bank.py | 60 ++++++++++--------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/account_banking_iban_lookup/model/res_bank.py b/account_banking_iban_lookup/model/res_bank.py index 616b2e235..a8395a73e 100644 --- a/account_banking_iban_lookup/model/res_bank.py +++ b/account_banking_iban_lookup/model/res_bank.py @@ -3,7 +3,7 @@ # # Copyright (C) 2009 EduSense BV (). # (C) 2011 - 2014 Therp BV (). -# +# # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the @@ -42,10 +42,9 @@ class ResBank(orm.Model): return {} if address and address.country_id: - country_id = self.pool.get('res.country').search( - cr, uid, [('code','=',address.country_id)] - ) - country_id = country_id and country_id[0] or False + country_ids = self.pool.get('res.country').search( + cr, uid, [('code', '=', address.country_id)]) + country_id = country_ids[0] if country_ids else False else: country_id = False @@ -53,13 +52,12 @@ class ResBank(orm.Model): 'value': dict( # Only the first eight positions of BIC are used for bank # transfers, so ditch the rest. - bic = info.bic[:8], - street = address.street, - street2 = - address.has_key('street2') and address.street2 or False, - zip = address.zip, - city = address.city, - country = country_id, - name = name and name or info.name, + bic=info.bic[:8], + street=address.street, + street2=address.get('street2', False), + zip=address.zip, + city=address.city, + country=country_id, + name=name or info.name, ) } diff --git a/account_banking_iban_lookup/model/res_partner_bank.py b/account_banking_iban_lookup/model/res_partner_bank.py index 004a01761..1eeea85c6 100644 --- a/account_banking_iban_lookup/model/res_partner_bank.py +++ b/account_banking_iban_lookup/model/res_partner_bank.py @@ -2,8 +2,8 @@ ############################################################################## # # Copyright (C) 2009 EduSense BV (). -# (C) 2011 - 2013 Therp BV (). -# +# (C) 2011 - 2014 Therp BV (). +# # All other contributions are (C) by their respective contributors # # All Rights Reserved @@ -29,6 +29,7 @@ from openerp.addons.account_banking_iban_lookup import online from openerp.addons.account_banking import sepa from openerp.addons.account_banking.wizard.banktools import get_or_create_bank + def warning(title, message): '''Convenience routine''' return {'warning': {'title': title, 'message': message}} @@ -49,7 +50,7 @@ class res_partner_bank(orm.Model): ''' Update existing iban accounts to comply to new regime ''' - + partner_bank_obj = self.pool.get('res.partner.bank') bank_ids = partner_bank_obj.search( cr, SUPERUSER_ID, [('state', '=', 'iban')], limit=0) @@ -90,14 +91,14 @@ class res_partner_bank(orm.Model): def write(self, cr, uid, ids, vals, context=None): ''' Create dual function IBAN account for SEPA countries - + Update the domestic account number when the IBAN is written, or clear the domestic number on regular account numbers. ''' if ids and isinstance(ids, (int, long)): ids = [ids] for account in self.read( - cr, uid, ids, ['state', 'acc_number']): + cr, uid, ids, ['state', 'acc_number']): if 'state' in vals or 'acc_number' in vals: account.update(vals) if account['state'] == 'iban': @@ -110,8 +111,8 @@ class res_partner_bank(orm.Model): return True def onchange_acc_number( - self, cr, uid, ids, acc_number, acc_number_domestic, - state, partner_id, country_id, context=None): + self, cr, uid, ids, acc_number, acc_number_domestic, + state, partner_id, country_id, context=None): if state == 'iban': return self.onchange_iban( cr, uid, ids, acc_number, acc_number_domestic, @@ -124,8 +125,8 @@ class res_partner_bank(orm.Model): ) def onchange_domestic( - self, cr, uid, ids, acc_number, - partner_id, country_id, context=None): + self, cr, uid, ids, acc_number, + partner_id, country_id, context=None): ''' Trigger to find IBAN. When found: 1. Reformat BBAN @@ -154,7 +155,8 @@ class res_partner_bank(orm.Model): # account itself before this method was triggered. elif ids and len(ids) == 1: partner_bank_obj = self.pool.get('res.partner.bank') - partner_bank_id = partner_bank_obj.browse(cr, uid, ids[0], context=context) + partner_bank_id = partner_bank_obj.browse( + cr, uid, ids[0], context=context) if partner_bank_id.country_id: country = partner_bank_id.country_id country_ids = [country.id] @@ -165,22 +167,23 @@ class res_partner_bank(orm.Model): # bank account, hence the additional check. elif partner_id: partner_obj = self.pool.get('res.partner') - country = partner_obj.browse(cr, uid, partner_id, context=context).country + country = partner_obj.browse( + cr, uid, partner_id, context=context).country country_ids = country and [country.id] or [] # 4. Without any of the above, take the country from the company of # the handling user if not country_ids: - user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + user = self.pool.get('res.users').browse( + cr, uid, uid, context=context) # Try user companies partner (user no longer has address in 6.1) if (user.company_id and - user.company_id.partner_id and - user.company_id.partner_id.country - ): + user.company_id.partner_id and + user.company_id.partner_id.country): country_ids = [user.company_id.partner_id.country.id] else: if (user.company_id and user.company_id.partner_id and - user.company_id.partner_id.country): - country_ids = [user.company_id.partner_id.country.id] + user.company_id.partner_id.country): + country_ids = [user.company_id.partner_id.country.id] else: # Ok, tried everything, give up and leave it to the user return warning(_('Insufficient data'), @@ -204,8 +207,7 @@ class res_partner_bank(orm.Model): bank_id, country_id = get_or_create_bank( self.pool, cr, uid, info.bic or iban_acc.BIC_searchkey, - name = info.bank - ) + name=info.bank) if country_id: values['country_id'] = country_id values['bank'] = bank_id or False @@ -233,8 +235,8 @@ class res_partner_bank(orm.Model): return result def onchange_iban( - self, cr, uid, ids, acc_number, acc_number_domestic, - state, partner_id, country_id, context=None): + self, cr, uid, ids, acc_number, acc_number_domestic, + state, partner_id, country_id, context=None): ''' Trigger to verify IBAN. When valid: 1. Extract BBAN as local account @@ -251,19 +253,19 @@ class res_partner_bank(orm.Model): ) return { 'value': dict( - acc_number_domestic = iban_acc.localized_BBAN, - acc_number = unicode(iban_acc), - country = country_id or False, - bank = bank_id or False, + acc_number_domestic=iban_acc.localized_BBAN, + acc_number=unicode(iban_acc), + country=country_id or False, + bank=bank_id or False, ) } - return warning(_('Invalid IBAN account number!'), - _("The IBAN number doesn't seem to be correct") - ) + return warning( + _('Invalid IBAN account number!'), + _("The IBAN number doesn't seem to be correct")) def online_account_info( self, cr, uid, country_code, acc_number, context=None): """ - Overwrite API hook from account_banking + Overwrite API hook from account_banking """ return online.account_info(country_code, acc_number) From 538857456f5f43c10614b1c03165175994b30b48 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sat, 15 Mar 2014 16:56:54 +0100 Subject: [PATCH 14/15] [FIX] Flake8 --- account_banking_iban_lookup/__openerp__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/account_banking_iban_lookup/__openerp__.py b/account_banking_iban_lookup/__openerp__.py index 5ddaf70bc..63eaade1c 100644 --- a/account_banking_iban_lookup/__openerp__.py +++ b/account_banking_iban_lookup/__openerp__.py @@ -35,7 +35,7 @@ 'view/res_partner_bank.xml', ], 'external_dependencies': { - 'python' : ['BeautifulSoup'], + 'python': ['BeautifulSoup'], }, 'description': ''' This addons contains the legacy infrastructure for autocompletion of IBANs From 1b25c80e326b6b6e22761d52a7bda4d0e1a7b071 Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sat, 15 Mar 2014 17:05:33 +0100 Subject: [PATCH 15/15] [RFR] Flake8 --- account_banking/res_partner_bank.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/account_banking/res_partner_bank.py b/account_banking/res_partner_bank.py index 6dabe1ac4..b36a1aaa8 100644 --- a/account_banking/res_partner_bank.py +++ b/account_banking/res_partner_bank.py @@ -3,7 +3,7 @@ # # Copyright (C) 2009 EduSense BV (). # (C) 2011 - 2014 Therp BV (). -# +# # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as # published by the Free Software Foundation, either version 3 of the @@ -28,7 +28,7 @@ class ResPartnerBank(orm.Model): def online_account_info( self, cr, uid, country_code, acc_number, context=None): """ - API hook for legacy online lookup of account info, + API hook for legacy online lookup of account info, to be removed in OpenERP 8.0. """ return False @@ -85,7 +85,7 @@ class ResPartnerBank(orm.Model): if is_term(args[0]) and len(args) > 1: # Classic filter, implicit '&' result += ['&'] - + for arg in args: if is_term(arg): result += extended_filter_term(arg) @@ -95,7 +95,7 @@ class ResPartnerBank(orm.Model): # Extend search filter newargs = extended_search_expression(args) - + # Original search return super(ResPartnerBank, self).search( cr, uid, newargs, *rest, **kwargs)