~therp-nl/banking-addons/ba70-refactor_import_transactions

« back to all changes in this revision

Viewing changes to account_banking_iban_lookup/model/res_partner_bank.py

  • Committer: Yannick Vaucher
  • Author(s): stefan at therp
  • Date: 2014-03-17 07:38:04 UTC
  • mfrom: (216.1.18 ba70-deprecate_iban_lookup)
  • Revision ID: yannick.vaucher@camptocamp.com-20140317073804-is4i3it3wydh7gdj
Splitting off the online account number (i.e. IBAN) lookup functionality into a separate module

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
 
5
#              (C) 2011 - 2014 Therp BV (<http://therp.nl>).
 
6
#
 
7
#    All other contributions are (C) by their respective contributors
 
8
#
 
9
#    All Rights Reserved
 
10
#
 
11
#    This program is free software: you can redistribute it and/or modify
 
12
#    it under the terms of the GNU Affero General Public License as
 
13
#    published by the Free Software Foundation, either version 3 of the
 
14
#    License, or (at your option) any later version.
 
15
#
 
16
#    This program is distributed in the hope that it will be useful,
 
17
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
18
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
19
#    GNU Affero General Public License for more details.
 
20
#
 
21
#    You should have received a copy of the GNU Affero General Public License
 
22
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
23
#
 
24
##############################################################################
 
25
from openerp import SUPERUSER_ID
 
26
from openerp.osv import orm
 
27
from openerp.tools.translate import _
 
28
from openerp.addons.account_banking_iban_lookup import online
 
29
from openerp.addons.account_banking import sepa
 
30
from openerp.addons.account_banking.wizard.banktools import get_or_create_bank
 
31
 
 
32
 
 
33
def warning(title, message):
 
34
    '''Convenience routine'''
 
35
    return {'warning': {'title': title, 'message': message}}
 
36
 
 
37
 
 
38
class res_partner_bank(orm.Model):
 
39
    '''
 
40
    Extended functionality:
 
41
        1. BBAN and IBAN are considered equal
 
42
        2. Online lookup when an API is available (providing NL in this module)
 
43
        3. Banks are created on the fly when using IBAN + online
 
44
        4. IBAN formatting
 
45
        5. BBAN's are generated from IBAN when possible
 
46
    '''
 
47
    _inherit = 'res.partner.bank'
 
48
 
 
49
    def init(self, cr):
 
50
        '''
 
51
        Update existing iban accounts to comply to new regime
 
52
        '''
 
53
 
 
54
        partner_bank_obj = self.pool.get('res.partner.bank')
 
55
        bank_ids = partner_bank_obj.search(
 
56
            cr, SUPERUSER_ID, [('state', '=', 'iban')], limit=0)
 
57
        for bank in partner_bank_obj.read(cr, SUPERUSER_ID, bank_ids):
 
58
            write_vals = {}
 
59
            if bank['state'] == 'iban':
 
60
                iban_acc = sepa.IBAN(bank['acc_number'])
 
61
                if iban_acc.valid:
 
62
                    write_vals['acc_number_domestic'] = iban_acc.localized_BBAN
 
63
                    write_vals['acc_number'] = str(iban_acc)
 
64
                elif bank['acc_number'] != bank['acc_number'].upper():
 
65
                    write_vals['acc_number'] = bank['acc_number'].upper()
 
66
                if write_vals:
 
67
                    partner_bank_obj.write(
 
68
                        cr, SUPERUSER_ID, bank['id'], write_vals)
 
69
 
 
70
    @staticmethod
 
71
    def _correct_IBAN(acc_number):
 
72
        '''
 
73
        Routine to correct IBAN values and deduce localized values when valid.
 
74
        Note: No check on validity IBAN/Country
 
75
        '''
 
76
        iban = sepa.IBAN(acc_number)
 
77
        return (str(iban), iban.localized_BBAN)
 
78
 
 
79
    def create(self, cr, uid, vals, context=None):
 
80
        '''
 
81
        Create dual function IBAN account for SEPA countries
 
82
        '''
 
83
        if vals.get('state') == 'iban':
 
84
            iban = (vals.get('acc_number')
 
85
                    or vals.get('acc_number_domestic', False))
 
86
            vals['acc_number'], vals['acc_number_domestic'] = (
 
87
                self._correct_IBAN(iban))
 
88
        return super(res_partner_bank, self).create(
 
89
            cr, uid, vals, context)
 
90
 
 
91
    def write(self, cr, uid, ids, vals, context=None):
 
92
        '''
 
93
        Create dual function IBAN account for SEPA countries
 
94
 
 
95
        Update the domestic account number when the IBAN is
 
96
        written, or clear the domestic number on regular account numbers.
 
97
        '''
 
98
        if ids and isinstance(ids, (int, long)):
 
99
            ids = [ids]
 
100
        for account in self.read(
 
101
                cr, uid, ids, ['state', 'acc_number']):
 
102
            if 'state' in vals or 'acc_number' in vals:
 
103
                account.update(vals)
 
104
                if account['state'] == 'iban':
 
105
                    vals['acc_number'], vals['acc_number_domestic'] = (
 
106
                        self._correct_IBAN(account['acc_number']))
 
107
                else:
 
108
                    vals['acc_number_domestic'] = False
 
109
            super(res_partner_bank, self).write(
 
110
                cr, uid, account['id'], vals, context)
 
111
        return True
 
112
 
 
113
    def onchange_acc_number(
 
114
            self, cr, uid, ids, acc_number, acc_number_domestic,
 
115
            state, partner_id, country_id, context=None):
 
116
        if state == 'iban':
 
117
            return self.onchange_iban(
 
118
                cr, uid, ids, acc_number, acc_number_domestic,
 
119
                state, partner_id, country_id, context=None
 
120
                )
 
121
        else:
 
122
            return self.onchange_domestic(
 
123
                cr, uid, ids, acc_number,
 
124
                partner_id, country_id, context=None
 
125
                )
 
126
 
 
127
    def onchange_domestic(
 
128
            self, cr, uid, ids, acc_number,
 
129
            partner_id, country_id, context=None):
 
130
        '''
 
131
        Trigger to find IBAN. When found:
 
132
            1. Reformat BBAN
 
133
            2. Autocomplete bank
 
134
 
 
135
        TODO: prevent unnecessary assignment of country_ids and
 
136
        browsing of the country
 
137
        '''
 
138
        if not acc_number:
 
139
            return {}
 
140
 
 
141
        values = {}
 
142
        country_obj = self.pool.get('res.country')
 
143
        country_ids = []
 
144
        country = False
 
145
 
 
146
        # Pre fill country based on available data. This is just a default
 
147
        # which can be overridden by the user.
 
148
        # 1. Use provided country_id (manually filled)
 
149
        if country_id:
 
150
            country = country_obj.browse(cr, uid, country_id, context=context)
 
151
            country_ids = [country_id]
 
152
        # 2. Use country_id of found bank accounts
 
153
        # This can be usefull when there is no country set in the partners
 
154
        # addresses, but there was a country set in the address for the bank
 
155
        # account itself before this method was triggered.
 
156
        elif ids and len(ids) == 1:
 
157
            partner_bank_obj = self.pool.get('res.partner.bank')
 
158
            partner_bank_id = partner_bank_obj.browse(
 
159
                cr, uid, ids[0], context=context)
 
160
            if partner_bank_id.country_id:
 
161
                country = partner_bank_id.country_id
 
162
                country_ids = [country.id]
 
163
        # 3. Use country_id of default address of partner
 
164
        # The country_id of a bank account is a one time default on creation.
 
165
        # It originates in the same address we are about to check, but
 
166
        # modifications on that address afterwards are not transfered to the
 
167
        # bank account, hence the additional check.
 
168
        elif partner_id:
 
169
            partner_obj = self.pool.get('res.partner')
 
170
            country = partner_obj.browse(
 
171
                cr, uid, partner_id, context=context).country
 
172
            country_ids = country and [country.id] or []
 
173
        # 4. Without any of the above, take the country from the company of
 
174
        # the handling user
 
175
        if not country_ids:
 
176
            user = self.pool.get('res.users').browse(
 
177
                cr, uid, uid, context=context)
 
178
            # Try user companies partner (user no longer has address in 6.1)
 
179
            if (user.company_id and
 
180
                    user.company_id.partner_id and
 
181
                    user.company_id.partner_id.country):
 
182
                country_ids = [user.company_id.partner_id.country.id]
 
183
            else:
 
184
                if (user.company_id and user.company_id.partner_id and
 
185
                        user.company_id.partner_id.country):
 
186
                    country_ids = [user.company_id.partner_id.country.id]
 
187
                else:
 
188
                    # Ok, tried everything, give up and leave it to the user
 
189
                    return warning(_('Insufficient data'),
 
190
                                   _('Insufficient data to select online '
 
191
                                     'conversion database')
 
192
                                   )
 
193
        result = {'value': values}
 
194
        # Complete data with online database when available
 
195
        if country_ids:
 
196
            country = country_obj.browse(
 
197
                cr, uid, country_ids[0], context=context)
 
198
            values['country_id'] = country_ids[0]
 
199
        if country and country.code in sepa.IBAN.countries:
 
200
            info = online.account_info(country.code, acc_number)
 
201
            if info:
 
202
                iban_acc = sepa.IBAN(info.iban)
 
203
                if iban_acc.valid:
 
204
                    values['acc_number_domestic'] = iban_acc.localized_BBAN
 
205
                    values['acc_number'] = unicode(iban_acc)
 
206
                    values['state'] = 'iban'
 
207
                    bank_id, country_id = get_or_create_bank(
 
208
                        self.pool, cr, uid,
 
209
                        info.bic or iban_acc.BIC_searchkey,
 
210
                        name=info.bank)
 
211
                    if country_id:
 
212
                        values['country_id'] = country_id
 
213
                    values['bank'] = bank_id or False
 
214
                    if info.bic:
 
215
                        values['bank_bic'] = info.bic
 
216
                else:
 
217
                    info = None
 
218
            if info is None:
 
219
                result.update(warning(
 
220
                    _('Invalid data'),
 
221
                    _('The account number appears to be invalid for %s')
 
222
                    % country.name
 
223
                ))
 
224
            if info is False:
 
225
                if country.code in sepa.IBAN.countries:
 
226
                    acc_number_fmt = sepa.BBAN(acc_number, country.code)
 
227
                    if acc_number_fmt.valid:
 
228
                        values['acc_number_domestic'] = str(acc_number_fmt)
 
229
                    else:
 
230
                        result.update(warning(
 
231
                            _('Invalid format'),
 
232
                            _('The account number has the wrong format for %s')
 
233
                            % country.name
 
234
                        ))
 
235
        return result
 
236
 
 
237
    def onchange_iban(
 
238
            self, cr, uid, ids, acc_number, acc_number_domestic,
 
239
            state, partner_id, country_id, context=None):
 
240
        '''
 
241
        Trigger to verify IBAN. When valid:
 
242
            1. Extract BBAN as local account
 
243
            2. Auto complete bank
 
244
        '''
 
245
        if not acc_number:
 
246
            return {}
 
247
 
 
248
        iban_acc = sepa.IBAN(acc_number)
 
249
        if iban_acc.valid:
 
250
            bank_id, country_id = get_or_create_bank(
 
251
                self.pool, cr, uid, iban_acc.BIC_searchkey,
 
252
                code=iban_acc.BIC_searchkey
 
253
                )
 
254
            return {
 
255
                'value': dict(
 
256
                    acc_number_domestic=iban_acc.localized_BBAN,
 
257
                    acc_number=unicode(iban_acc),
 
258
                    country=country_id or False,
 
259
                    bank=bank_id or False,
 
260
                )
 
261
            }
 
262
        return warning(
 
263
            _('Invalid IBAN account number!'),
 
264
            _("The IBAN number doesn't seem to be correct"))
 
265
 
 
266
    def online_account_info(
 
267
            self, cr, uid, country_code, acc_number, context=None):
 
268
        """
 
269
        Overwrite API hook from account_banking
 
270
        """
 
271
        return online.account_info(country_code, acc_number)