mirror of
https://github.com/OCA/bank-payment.git
synced 2025-02-02 10:37:31 +02:00
[RFR] Refactor out custom IBAN wizardry
This commit is contained in:
@@ -33,7 +33,6 @@
|
|||||||
'category': 'Banking addons',
|
'category': 'Banking addons',
|
||||||
'depends': [
|
'depends': [
|
||||||
'account_voucher',
|
'account_voucher',
|
||||||
'account_iban_preserve_domestic',
|
|
||||||
],
|
],
|
||||||
'data': [
|
'data': [
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
@@ -47,14 +46,9 @@
|
|||||||
'js': [
|
'js': [
|
||||||
'static/src/js/account_banking.js',
|
'static/src/js/account_banking.js',
|
||||||
],
|
],
|
||||||
'external_dependencies': {
|
|
||||||
'python' : ['BeautifulSoup'],
|
|
||||||
},
|
|
||||||
'description': '''
|
'description': '''
|
||||||
Module to do banking.
|
Module to do banking.
|
||||||
|
|
||||||
Note: This module is depending on BeautifulSoup.
|
|
||||||
|
|
||||||
This modules tries to combine all current banking import and export
|
This modules tries to combine all current banking import and export
|
||||||
schemes. Rationale for this is that it is quite common to have foreign
|
schemes. Rationale for this is that it is quite common to have foreign
|
||||||
bank account numbers next to national bank account numbers. The current
|
bank account numbers next to national bank account numbers. The current
|
||||||
|
|||||||
@@ -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 import sepa
|
||||||
from openerp.addons.account_banking.wizard.banktools import get_or_create_bank
|
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):
|
class account_banking_account_settings(orm.Model):
|
||||||
'''Default Journal for Bank Account'''
|
'''Default Journal for Bank Account'''
|
||||||
@@ -562,413 +558,6 @@ class account_bank_statement_line(orm.Model):
|
|||||||
account_bank_statement_line()
|
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):
|
class invoice(orm.Model):
|
||||||
'''
|
'''
|
||||||
Create other reference types as well.
|
Create other reference types as well.
|
||||||
|
|||||||
@@ -286,35 +286,6 @@
|
|||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="view_partner_bank_account_banking_form_2" model="ir.ui.view">
|
|
||||||
<field name="name">res.partner.bank.form.banking-2</field>
|
|
||||||
<field name="model">res.partner.bank</field>
|
|
||||||
<field name="inherit_id" ref="base.view_partner_bank_form"/>
|
|
||||||
<field name="priority" eval="24"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<data>
|
|
||||||
<field name="acc_number" position="attributes">
|
|
||||||
<attribute name="on_change">onchange_acc_number(acc_number, acc_number_domestic, state, partner_id, country_id)</attribute>
|
|
||||||
</field>
|
|
||||||
<field name="acc_number_domestic" position="attributes">
|
|
||||||
<attribute name="on_change">onchange_domestic(acc_number_domestic, partner_id, country_id)</attribute>
|
|
||||||
</field>
|
|
||||||
</data>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<!-- Set trigger on BIC in res_bank form -->
|
|
||||||
<record id="view_res_bank_account_banking_form_1" model="ir.ui.view">
|
|
||||||
<field name="name">res.bank.form.banking-1</field>
|
|
||||||
<field name="model">res.bank</field>
|
|
||||||
<field name="inherit_id" ref="base.view_res_bank_form"/>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<field name="bic" position="replace">
|
|
||||||
<field name="bic" on_change="onchange_bic(bic, name)"/>
|
|
||||||
</field>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record model="ir.ui.view" id="view_bank_statement_line_tree">
|
<record model="ir.ui.view" id="view_bank_statement_line_tree">
|
||||||
<field name="name">Bank statement line tree view</field>
|
<field name="name">Bank statement line tree view</field>
|
||||||
<field name="model">account.bank.statement.line</field>
|
<field name="model">account.bank.statement.line</field>
|
||||||
|
|||||||
31
account_banking/res_bank.py
Normal file
31
account_banking/res_bank.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Copyright 2011 - 2014 Therp BV (<http://therp.nl>).
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
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
|
||||||
32
account_banking/res_partner_bank.py
Normal file
32
account_banking/res_partner_bank.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Copyright (C) 2011 - 2014 Therp BV (<http://therp.nl>).
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
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
|
||||||
@@ -19,6 +19,5 @@
|
|||||||
#
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
import iban
|
import iban
|
||||||
import online
|
|
||||||
IBAN = iban.IBAN
|
IBAN = iban.IBAN
|
||||||
BBAN = iban.BBAN
|
BBAN = iban.BBAN
|
||||||
|
|||||||
@@ -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, [
|
bank_account_ids = partner_bank_obj.search(cr, uid, [
|
||||||
('acc_number', '=', account_number)
|
('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 bank_account_ids:
|
||||||
if not fail:
|
if not fail:
|
||||||
log.append(
|
log.append(
|
||||||
@@ -237,7 +231,7 @@ def get_or_create_bank(pool, cr, uid, bic, online=False, code=None,
|
|||||||
bank_id = False
|
bank_id = False
|
||||||
|
|
||||||
if online:
|
if online:
|
||||||
info, address = sepa.online.bank_info(bic)
|
info, address = bank_obj.online_bank_info(cr, uid, bic, context=context)
|
||||||
if info:
|
if info:
|
||||||
bank_id = bank_obj.create(cr, uid, dict(
|
bank_id = bank_obj.create(cr, uid, dict(
|
||||||
code = info.code,
|
code = info.code,
|
||||||
@@ -301,7 +295,6 @@ def create_bank_account(pool, cr, uid, partner_id,
|
|||||||
owner_name = holder_name,
|
owner_name = holder_name,
|
||||||
country_id = country_id,
|
country_id = country_id,
|
||||||
)
|
)
|
||||||
bankcode = None
|
|
||||||
|
|
||||||
# Are we dealing with IBAN?
|
# Are we dealing with IBAN?
|
||||||
iban = sepa.IBAN(account_number)
|
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
|
# Take as much info as possible from IBAN
|
||||||
values.state = 'iban'
|
values.state = 'iban'
|
||||||
values.acc_number = str(iban)
|
values.acc_number = str(iban)
|
||||||
values.acc_number_domestic = iban.BBAN
|
|
||||||
bankcode = iban.bankcode + iban.countrycode
|
|
||||||
else:
|
else:
|
||||||
# No, try to convert to IBAN
|
# No, try to convert to IBAN
|
||||||
values.state = 'bank'
|
values.state = 'bank'
|
||||||
values.acc_number = values.acc_number_domestic = account_number
|
values.acc_number = account_number
|
||||||
|
|
||||||
if country_id:
|
if country_id:
|
||||||
country_code = pool.get('res.country').read(
|
country_code = pool.get('res.country').read(
|
||||||
cr, uid, country_id, ['code'], context=context)['code']
|
cr, uid, country_id, ['code'], context=context)['code']
|
||||||
if country_code in sepa.IBAN.countries:
|
if country_code in sepa.IBAN.countries:
|
||||||
account_info = sepa.online.account_info(
|
account_info = pool['res.partner.bank'].online_account_info(
|
||||||
country_code, values.acc_number)
|
cr, uid, country_code, values.acc_number, context=context)
|
||||||
if account_info:
|
if account_info:
|
||||||
values.acc_number = iban = account_info.iban
|
values.acc_number = iban = account_info.iban
|
||||||
values.state = 'iban'
|
values.state = 'iban'
|
||||||
bankcode = account_info.code
|
|
||||||
bic = account_info.bic
|
bic = account_info.bic
|
||||||
|
|
||||||
if bic:
|
if bic:
|
||||||
|
|||||||
4
account_banking_iban_lookup/__init__.py
Normal file
4
account_banking_iban_lookup/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from . import model
|
||||||
|
from . import online
|
||||||
|
from . import urlagent
|
||||||
|
|
||||||
47
account_banking_iban_lookup/__openerp__.py
Normal file
47
account_banking_iban_lookup/__openerp__.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Copyright (C) 2013 Therp BV (<http://therp.nl>)
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
{
|
||||||
|
'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,
|
||||||
|
}
|
||||||
2
account_banking_iban_lookup/model/__init__.py
Normal file
2
account_banking_iban_lookup/model/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import res_bank
|
||||||
|
from . import res_partner_bank
|
||||||
65
account_banking_iban_lookup/model/res_bank.py
Normal file
65
account_banking_iban_lookup/model/res_bank.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||||
|
# (C) 2011 - 2014 Therp BV (<http://therp.nl>).
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
288
account_banking_iban_lookup/model/res_partner_bank.py
Normal file
288
account_banking_iban_lookup/model/res_partner_bank.py
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||||
|
# (C) 2011 - 2013 Therp BV (<http://therp.nl>).
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
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)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
# Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
|
||||||
@@ -26,7 +26,7 @@ import re
|
|||||||
import urllib, urllib2
|
import urllib, urllib2
|
||||||
from BeautifulSoup import BeautifulSoup
|
from BeautifulSoup import BeautifulSoup
|
||||||
from openerp.addons.account_banking.sepa import postalcode
|
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.sepa.iban import IBAN
|
||||||
from openerp.addons.account_banking.struct import struct
|
from openerp.addons.account_banking.struct import struct
|
||||||
|
|
||||||
@@ -25,7 +25,6 @@ forms and to parse the results back in. It is heavily based on BeautifulSoup.
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
from BeautifulSoup import BeautifulSoup
|
|
||||||
|
|
||||||
__all__ = ['urlsplit', 'urljoin', 'pathbase', 'urlbase', 'SoupForm',
|
__all__ = ['urlsplit', 'urljoin', 'pathbase', 'urlbase', 'SoupForm',
|
||||||
'URLAgent'
|
'URLAgent'
|
||||||
15
account_banking_iban_lookup/view/res_bank.xml
Normal file
15
account_banking_iban_lookup/view/res_bank.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<record id="view_res_bank_account_banking_form_1" model="ir.ui.view">
|
||||||
|
<field name="name">Add BIC lookup to bank form</field>
|
||||||
|
<field name="model">res.bank</field>
|
||||||
|
<field name="inherit_id" ref="base.view_res_bank_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<field name="bic" position="replace">
|
||||||
|
<field name="bic" on_change="onchange_bic(bic, name)"/>
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</openerp>
|
||||||
23
account_banking_iban_lookup/view/res_partner_bank.xml
Normal file
23
account_banking_iban_lookup/view/res_partner_bank.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<openerp>
|
||||||
|
<data>
|
||||||
|
<record id="view_partner_bank_account_banking_form_2" model="ir.ui.view">
|
||||||
|
<field name="name">Add autocompletion methods to partner bank form</field>
|
||||||
|
<field name="model">res.partner.bank</field>
|
||||||
|
<field name="inherit_id" ref="base.view_partner_bank_form"/>
|
||||||
|
<field name="priority" eval="24"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<data>
|
||||||
|
<field name="acc_number" position="attributes">
|
||||||
|
<attribute name="on_change">onchange_acc_number(acc_number, acc_number_domestic, state, partner_id, country_id)</attribute>
|
||||||
|
</field>
|
||||||
|
<field name="acc_number_domestic" position="attributes">
|
||||||
|
<attribute name="on_change">onchange_domestic(acc_number_domestic, partner_id, country_id)</attribute>
|
||||||
|
</field>
|
||||||
|
</data>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</openerp>
|
||||||
|
|
||||||
|
|
||||||
@@ -24,7 +24,10 @@
|
|||||||
'author': 'EduSense BV',
|
'author': 'EduSense BV',
|
||||||
'website': 'http://www.edusense.nl',
|
'website': 'http://www.edusense.nl',
|
||||||
'category': 'Account Banking',
|
'category': 'Account Banking',
|
||||||
'depends': ['account_banking_payment'],
|
'depends': [
|
||||||
|
'account_banking_payment',
|
||||||
|
'account_iban_preserve_domestic',
|
||||||
|
],
|
||||||
'data': [
|
'data': [
|
||||||
'account_banking_nl_clieop.xml',
|
'account_banking_nl_clieop.xml',
|
||||||
'wizard/export_clieop_view.xml',
|
'wizard/export_clieop_view.xml',
|
||||||
|
|||||||
@@ -116,15 +116,25 @@ class banking_import_transaction(orm.Model):
|
|||||||
'''
|
'''
|
||||||
# TODO: Not sure what side effects are created when payments are done
|
# TODO: Not sure what side effects are created when payments are done
|
||||||
# for credited customer invoices, which will be matched later on too.
|
# 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]
|
digits = dp.get_precision('Account')(cr)[1]
|
||||||
candidates = [
|
candidates = [
|
||||||
x for x in payment_lines
|
x for x in payment_lines
|
||||||
if x.communication == trans.reference
|
if x.communication == trans.reference
|
||||||
and round(x.amount, digits) == -round(
|
and round(x.amount, digits) == -round(
|
||||||
trans.statement_line_id.amount, digits)
|
trans.statement_line_id.amount, digits)
|
||||||
and trans.remote_account in (x.bank_id.acc_number,
|
and bank_match(trans.remote_account, x.bank_id)]
|
||||||
x.bank_id.acc_number_domestic)
|
|
||||||
]
|
|
||||||
if len(candidates) == 1:
|
if len(candidates) == 1:
|
||||||
candidate = candidates[0]
|
candidate = candidates[0]
|
||||||
# Check cache to prevent multiple matching of a single payment
|
# Check cache to prevent multiple matching of a single payment
|
||||||
|
|||||||
Reference in New Issue
Block a user