~therp-nl/banking-addons/ba61-lp1098699-fix_clieop_rounding_issue

« back to all changes in this revision

Viewing changes to wizard/banktools.py

  • Committer: Pieter J. Kersten
  • Date: 2010-01-26 20:55:24 UTC
  • Revision ID: p.j.kersten@edusense.nl-20100126205524-cc4jsho1p49acnn7
[META] Set structure

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- encoding: utf-8 -*-
2
 
##############################################################################
3
 
#
4
 
#    Copyright (C) 2009 EduSense BV (<http://www.edusense.nl>).
5
 
#    All Rights Reserved
6
 
#
7
 
#    This program is free software: you can redistribute it and/or modify
8
 
#    it under the terms of the GNU General Public License as published by
9
 
#    the Free Software Foundation, either version 3 of the License, or
10
 
#    (at your option) any later version.
11
 
#
12
 
#    This program is distributed in the hope that it will be useful,
13
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 
#    GNU General Public License for more details.
16
 
#
17
 
#    You should have received a copy of the GNU General Public License
18
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
 
#
20
 
##############################################################################
21
 
 
22
 
from tools.translate import _
23
 
 
24
 
__all__ = [
25
 
    'get_period', 
26
 
    'get_bank_account',
27
 
    'get_or_create_partner',
28
 
    'get_company_bank_account',
29
 
    'create_bank_account',
30
 
    'struct',
31
 
]
32
 
 
33
 
class struct(dict):
34
 
    '''
35
 
    Ease working with dicts. Allow dict.key alongside dict['key']
36
 
    '''
37
 
    def __setattr__(self, item, value):
38
 
        self.__setitem__(item, value)
39
 
 
40
 
    def __getattr__(self, item):
41
 
        return self.__getitem__(item)
42
 
 
43
 
    def show(self, indent=0, align=False, ralign=False):
44
 
        '''
45
 
        PrettyPrint method. Aligns keys right (ralign) or left (align).
46
 
        '''
47
 
        if align or ralign:
48
 
            width = 0
49
 
            for key in self.iterkeys():
50
 
                width = max(width, len(key))
51
 
            alignment = ''
52
 
            if not ralign:
53
 
                alignment = '-'
54
 
            fmt = '%*.*s%%%s%d.%ds: %%s' % (
55
 
                indent, indent, '', alignment, width, width
56
 
            )
57
 
        else:
58
 
            fmt = '%*.*s%%s: %%s' % (indent, indent, '')
59
 
        for item in self.iteritems():
60
 
            print fmt % item
61
 
 
62
 
import datetime
63
 
from account_banking import sepa
64
 
from account_banking.parsers.convert import *
65
 
 
66
 
def get_period(pool, cursor, uid, date, company, log):
67
 
    '''
68
 
    Get a suitable period for the given date range and the given company.
69
 
    '''
70
 
    fiscalyear_obj = pool.get('account.fiscalyear')
71
 
    period_obj = pool.get('account.period')
72
 
    if not date:
73
 
        date = date2str(datetime.datetime.today())
74
 
 
75
 
    search_date = date2str(date)
76
 
    fiscalyear_ids = fiscalyear_obj.search(cursor, uid, [
77
 
        ('date_start','<=', search_date), ('date_stop','>=', search_date),
78
 
        ('state','=','draft'), ('company_id','=',company.id)
79
 
    ])
80
 
    if not fiscalyear_ids:
81
 
        fiscalyear_ids = fiscalyear_obj.search(cursor, uid, [
82
 
            ('date_start','<=',search_date), ('date_stop','>=',search_date),
83
 
            ('state','=','draft'), ('company_id','=',None)
84
 
        ])
85
 
    if not fiscalyear_ids:
86
 
        log.append(
87
 
            _('No suitable fiscal year found for company %(company_name)s')
88
 
            % dict(company_name=company.name)
89
 
        )
90
 
        return False
91
 
    elif len(fiscalyear_ids) > 1:
92
 
        log.append(
93
 
            _('Multiple overlapping fiscal years found for date %(date)s')
94
 
            % dict(date=date)
95
 
        )
96
 
        return False
97
 
 
98
 
    fiscalyear_id = fiscalyear_ids[0]
99
 
    period_ids = period_obj.search(cursor, uid, [
100
 
        ('date_start','<=',search_date), ('date_stop','>=',search_date),
101
 
        ('fiscalyear_id','=',fiscalyear_id), ('state','=','draft')
102
 
    ])
103
 
    if not period_ids:
104
 
        log.append(_('No suitable period found for date %(date)s')
105
 
                   % dict(date=date)
106
 
        )
107
 
        return False
108
 
    if len(period_ids) != 1:
109
 
        log.append(_('Multiple overlapping periods for date %(date)s')
110
 
                   % dict(date=date)
111
 
        )
112
 
        return False
113
 
    return period_ids[0]
114
 
 
115
 
def get_bank_account(pool, cursor, uid, account_number, log, fail=False):
116
 
    '''
117
 
    Get the bank account with account number account_number
118
 
    '''
119
 
    # No need to search for nothing
120
 
    if not account_number:
121
 
        return False
122
 
 
123
 
    partner_bank_obj = pool.get('res.partner.bank')
124
 
    bank_account_ids = partner_bank_obj.search(cursor, uid, [
125
 
        ('acc_number', '=', account_number)
126
 
    ])
127
 
    if not bank_account_ids:
128
 
        bank_account_ids = partner_bank_obj.search(cursor, uid, [
129
 
            ('iban', '=', account_number)
130
 
        ])
131
 
    if not bank_account_ids:
132
 
        if not fail:
133
 
            log.append(
134
 
                _('Bank account %(account_no)s was not found in the database')
135
 
                % dict(account_no=account_number)
136
 
            )
137
 
        return False
138
 
    elif len(bank_account_ids) != 1:
139
 
        log.append(
140
 
            _('More than one bank account was found with the same number %(account_no)s')
141
 
            % dict(account_no=account_number)
142
 
        )
143
 
        return False
144
 
    return partner_bank_obj.browse(cursor, uid, bank_account_ids)[0]
145
 
 
146
 
def get_or_create_partner(pool, cursor, uid, name, log):
147
 
    '''
148
 
    Get or create the partner belonging to the account holders name <name>
149
 
    '''
150
 
    partner_obj = pool.get('res.partner')
151
 
    partner_ids = partner_obj.search(cursor, uid, [('name', 'ilike', name)])
152
 
    if not partner_ids:
153
 
        partner_id = partner_obj.create(cursor, uid, dict(
154
 
            name=name, active=True, comment='Generated by Import Bank Statements File',
155
 
        ))
156
 
    elif len(partner_ids) > 1:
157
 
        log.append(
158
 
            _('More then one possible match found for partner with name %(name)s')
159
 
            % {'name': name}
160
 
        )
161
 
        return False
162
 
    else:
163
 
        partner_id = partner_ids[0]
164
 
    return partner_obj.browse(cursor, uid, partner_id)[0]
165
 
 
166
 
def get_company_bank_account(pool, cursor, uid, account_number,
167
 
                             company, log):
168
 
    '''
169
 
    Get the matching bank account for this company.
170
 
    '''
171
 
    results = struct()
172
 
    bank_account = get_bank_account(pool, cursor, uid, account_number, log,
173
 
                                    fail=True)
174
 
    if not bank_account:
175
 
        return False
176
 
    if bank_account.partner_id.id != company.partner_id.id:
177
 
        log.append(
178
 
            _('Account %(account_no)s is not owned by %(partner)s')
179
 
            % dict(account_no = account_number,
180
 
                   partner = company.partner_id.name,
181
 
        ))
182
 
        return False
183
 
    results.account = bank_account
184
 
    bank_settings_obj = pool.get('account.banking.account.settings')
185
 
    bank_settings_ids = bank_settings_obj.search(cursor, uid, [
186
 
        ('partner_bank_id', '=', bank_account.id)
187
 
    ])
188
 
    if bank_settings_ids:
189
 
        settings = bank_settings_obj.browse(cursor, uid, bank_settings_ids)[0]
190
 
        results.journal_id = settings.journal_id
191
 
        results.default_debit_account_id = settings.default_debit_account_id
192
 
        results.default_credit_account_id = settings.default_credit_account_id
193
 
    return results
194
 
 
195
 
def get_iban_bic_NL(bank_acc):
196
 
    '''
197
 
    Consult the Dutch online banking database to check both the account number
198
 
    and the bank to which it belongs. Will not work offline, is limited to
199
 
    banks operating in the Netherlands and will only convert Dutch local
200
 
    account numbers.
201
 
    '''
202
 
    import urllib, urllib2
203
 
    from BeautifulSoup import BeautifulSoup
204
 
 
205
 
    IBANlink = 'http://www.ibannl.org/iban_check.php'
206
 
    data = urllib.urlencode(dict(number=bank_acc, method='POST'))
207
 
    request = urllib2.Request(IBANlink, data)
208
 
    response = urllib2.urlopen(request)
209
 
    soup = BeautifulSoup(response)
210
 
    result = struct()
211
 
    for _pass, td in enumerate(soup.findAll('td')):
212
 
        if _pass % 2 == 1:
213
 
            result[attr] = td.find('font').contents[0]
214
 
        else:
215
 
            attr = td.find('strong').contents[0][:4].strip().lower()
216
 
    if result:
217
 
        result.account = bank_acc
218
 
        result.country_id = result.bic[4:6]
219
 
        # Nationalized bank code
220
 
        result.code = result.bic[:6]
221
 
        # All Dutch banks use generic channels
222
 
        result.bic += 'XXX'
223
 
        return result
224
 
    return None
225
 
 
226
 
online_account_info = {
227
 
    # TODO: Add more online data banks
228
 
    'NL': get_iban_bic_NL,
229
 
}
230
 
 
231
 
def create_bank_account(pool, cursor, uid, partner_id,
232
 
                        account_number, holder_name, log
233
 
                        ):
234
 
    '''
235
 
    Create a matching bank account with this holder for this partner.
236
 
    '''
237
 
    values = struct(
238
 
        partner_id = partner_id,
239
 
        owner_name = holder_name,
240
 
    )
241
 
    # Are we dealing with IBAN?
242
 
    iban = sepa.IBAN(account_number)
243
 
    if iban.valid:
244
 
        values.state = 'iban'
245
 
        values.acc_number = iban.BBAN
246
 
        bankcode = iban.bankcode + iban.countrycode
247
 
    else:
248
 
        # No, try to convert to IBAN
249
 
        country = pool.get('res.partner').browse(
250
 
            cursor, uid, partner_id).country_id
251
 
        values.state = 'bank'
252
 
        values.acc_number = account_number
253
 
        if country.code in sepa.IBAN.countries \
254
 
           and country.code in online_account_info \
255
 
           :
256
 
            account_info = online_account_info[country.code](values.acc_number)
257
 
            if account_info and iban in account_info:
258
 
                values.iban = iban = account_info.iban
259
 
                values.state = 'iban'
260
 
                bankcode = account_info.code
261
 
                bic = account_info.bic
262
 
            else:
263
 
                bankcode = None
264
 
                bic = None
265
 
 
266
 
    if bankcode:
267
 
        # Try to link bank
268
 
        bank_obj = pool.get('res.bank')
269
 
        bank_ids = bank_obj.search(cursor, uid, [
270
 
            ('code', 'ilike', bankcode)
271
 
        ])
272
 
        if not bank_ids and bic:
273
 
            bank_ids = bank_obj.search(cursor, uid, [
274
 
                ('bic', 'ilike', bic)
275
 
            ])
276
 
        if bank_ids:
277
 
            # Check BIC on existing banks
278
 
            values.bank_id = bank_ids[0]
279
 
            bank = bank_obj.browse(cursor, uid, values.bank_id)
280
 
            if not bank.bic:
281
 
                bank_obj.write(cursor, uid, values.bank_id, dict(bic=bic))
282
 
        else:
283
 
            # New bank - create
284
 
            values.bank_id = bank_obj.create(cursor, uid, dict(
285
 
                code = account_info.code,
286
 
                bic = account_info.bic,
287
 
                name = account_info.bank,
288
 
                country_id = country.id,
289
 
            ))
290
 
 
291
 
    # Create bank account and return
292
 
    return pool.get('res.partner.bank').create(cursor, uid, values)
293
 
 
294
 
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: