~taktik/openobject-addons/hui-extra-6.1

« back to all changes in this revision

Viewing changes to account_coda/wizard/account_coda_import.py

  • Committer: Luc De Meyer
  • Date: 2014-03-12 19:53:31 UTC
  • Revision ID: luc.demeyer@noviat.com-20140312195331-lxqjtcbxg9h5dd7e
update accounting modules

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
##############################################################################
3
3
#
4
4
#    OpenERP, Open Source Management Solution
5
 
#    
6
 
#    Copyright (c) 2013 Noviat nv/sa (www.noviat.be). All rights reserved.
7
 
 
5
#
 
6
#    Copyright (c) 2014 Noviat nv/sa (www.noviat.com). All rights reserved.
 
7
#
8
8
#    This program is free software: you can redistribute it and/or modify
9
9
#    it under the terms of the GNU Affero General Public License as
10
10
#    published by the Free Software Foundation, either version 3 of the
12
12
#
13
13
#    This program is distributed in the hope that it will be useful,
14
14
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
16
#    GNU Affero General Public License for more details.
17
17
#
18
18
#    You should have received a copy of the GNU Affero General Public License
19
 
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
19
#    along with this program. If not, see <http://www.gnu.org/licenses/>.
20
20
#
21
21
##############################################################################
22
22
 
 
23
from openerp.osv import osv, orm, fields
 
24
from openerp.tools.translate import _
 
25
from openerp import netsvc
23
26
import time
24
27
import base64
25
 
from osv import fields,osv
26
 
from tools.translate import _
27
 
import netsvc
28
28
import re
29
29
from traceback import format_exception
30
30
from sys import exc_info
31
31
import logging
32
32
_logger = logging.getLogger(__name__)
33
33
 
34
 
class account_coda_import(osv.osv_memory):
 
34
 
 
35
class account_coda_import(orm.TransientModel):
35
36
    _name = 'account.coda.import'
36
37
    _description = 'Import CODA File'
37
38
    _columns = {
38
39
        'coda_data': fields.binary('CODA File', required=True),
39
40
        'coda_fname': fields.char('CODA Filename', size=128, required=True),
 
41
        'period_id': fields.many2one('account.period', 'Force Period',
 
42
            domain=[('state','=','draft'),('special', '=', False)],
 
43
            help="Keep empty to use the period of the bank statement date."),
40
44
        'note':fields.text('Log'),
41
45
    }
42
46
    _defaults = {
43
47
        'coda_fname': lambda *a: '',
44
48
    }
45
49
 
 
50
    _skip_undefined = False # Bank Statements which have not been defined in the CODA configuration will be skipped if set to TRUE       
 
51
 
46
52
    def _check_account_payment(self, cr, uid, context=None):
47
53
        res = self.pool.get('ir.module.module').search(cr, uid, [('name', '=', 'account_payment'), ('state','=','installed')])
48
54
        return res and True or False
49
55
 
50
 
    def coda_parsing(self, cr, uid, ids, context=None, batch=False, codafile=None, codafilename=None):
 
56
    def coda_parsing(self, cr, uid, ids, context=None, batch=False, codafile=None, codafilename=None, period_id=None):
51
57
        if context is None:
52
58
            context = {}
53
59
        if batch:
 
60
            self.batch = True
54
61
            codafile = str(codafile)
55
62
            codafilename = codafilename
56
63
        else:
57
64
            data=self.browse(cr,uid,ids)[0]
58
65
            try:
59
66
                codafile = data.coda_data
60
 
                codafilename = data.coda_fname            
 
67
                codafilename = data.coda_fname
 
68
                period_id = data.period_id and data.period_id.id or False
61
69
            except:
62
 
                raise osv.except_osv(_('Error!'), _('Wizard in incorrect state. Please hit the Cancel button!'))
 
70
                raise orm.except_orm(_('Error!'), _('Wizard in incorrect state. Please hit the Cancel button!'))
63
71
                return {}
64
72
 
65
73
        company_obj = self.pool.get('res.company')    
87
95
        mod_obj = self.pool.get('ir.model.data')
88
96
        partner_obj = self.pool.get('res.partner')        
89
97
        account_mapping_obj = self.pool.get('coda.account.mapping.rule')
90
 
        
91
 
        if self._check_account_payment(cr, uid):
92
 
            payment_line_obj = self.pool.get('payment.line')
93
 
        else:
94
 
            payment_line_obj = False
95
98
                  
96
99
        wf_service = netsvc.LocalService('workflow')
97
100
        be_country_id = self.pool.get('res.country').search(cr, uid, [('code', '=', 'BE')])[0]
102
105
            coda_bank.update({'iban': partner_bank_obj.browse(cr, uid, coda_bank['bank_id'][0], context=context).iban})
103
106
            coda_bank.update({'acc_number': partner_bank_obj.browse(cr, uid, coda_bank['bank_id'][0], context=context).acc_number})
104
107
            coda_bank.update({'currency_name': currency_obj.browse(cr, uid, coda_bank['currency'][0], context=context).name})            
 
108
 
105
109
        trans_type_table = trans_type_obj.read(cr, uid, trans_type_obj.search(cr, uid, []), context=context)
106
110
        trans_code_table = trans_code_obj.read(cr, uid, trans_code_obj.search(cr, uid, []), context=context)
107
111
        trans_category_table = trans_category_obj.read(cr, uid, trans_category_obj.search(cr, uid, []), context=context)
110
114
        err_string = ''
111
115
        err_code = None
112
116
        err_log = ''
 
117
        coda_note = ''
113
118
        coda_statements = []
114
119
        recordlist = unicode(base64.decodestring(codafile), 'windows-1252', 'strict').split('\n')
115
 
        digits = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
 
120
        self.digits = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
116
121
        
117
122
        for line in recordlist:
118
123
            
134
139
                    err_code = 'R0001'
135
140
                    if batch:
136
141
                        return (err_code, err_string)
137
 
                    raise osv.except_osv(_('Data Error!'), err_string)
 
142
                    raise orm.except_orm(_('Data Error!'), err_string)
138
143
                coda_statement['coda_statement_lines'] = {}
139
144
                coda_statement['date'] = str2date(line[5:11])
140
145
                coda_statement['coda_creation_date'] = str2date(line[5:11])
152
157
                    err_code = 'W0001'
153
158
                    if batch:
154
159
                        return (err_code, err_string)
155
 
                    raise osv.except_osv(_('Warning !'), err_string)
 
160
                    raise orm.except_orm(_('Warning !'), err_string)
156
161
                
157
162
            elif line[0] == '1':
158
163
                if coda_version == '1':
167
172
                    err_code = 'R1001'
168
173
                    if batch:
169
174
                        return (err_code, err_string)
170
 
                    raise osv.except_osv(_('Data Error!'), err_string)
 
175
                    raise orm.except_orm(_('Data Error!'), err_string)
171
176
                elif line[1] == '2':    # Belgian bank account IBAN structure
172
177
                    coda_statement['acc_number']=line[5:21] 
173
178
                    coda_statement['currency'] = line[39:42]
176
181
                    err_code = 'R1002'
177
182
                    if batch:
178
183
                        return (err_code, err_string)
179
 
                    raise osv.except_osv(_('Data Error!'), err_string)
 
184
                    raise orm.except_orm(_('Data Error!'), err_string)
180
185
                else:
181
186
                    err_string = _('\nUnsupported bank account structure !')
182
187
                    err_code = 'R1003'
183
188
                    if batch:
184
189
                        return (err_code, err_string)
185
 
                    raise osv.except_osv(_('Data Error!'), err_string)
 
190
                    raise orm.except_orm(_('Data Error!'), err_string)
186
191
                coda_statement['description'] = line[90:125].strip()
187
192
                cba_filter = lambda x: (coda_statement['acc_number'] in _get_acc_numbers(x['acc_number'])) \
188
193
                    and (coda_statement['currency'] == x['currency_name']) and (coda_statement['description'] in [x['description1'] or '', x['description2'] or ''])
194
199
                    coda_statement['currency_id'] = coda_bank['currency'][0]
195
200
                    coda_statement['coda_bank_account_id'] = coda_bank['id']  
196
201
                    coda_statement['account_mapping_ids'] = coda_bank['account_mapping_ids']  
197
 
                    coda_statement['coda_bank_params'] = coda_bank                      
198
 
                    awaiting_acc = coda_bank['awaiting_account'][0]
199
 
                    transfer_acc = coda_bank['transfer_account'][0]
200
 
                    find_payment = coda_bank['find_payment']
201
 
                    find_bbacom = coda_bank['find_bbacom']
202
 
                    find_inv_number = coda_bank['find_inv_number']                    
203
 
                    find_partner = coda_bank['find_partner']
204
 
                    update_partner = coda_bank['update_partner']   
205
 
                    coda_statement['balance_start_enforce'] = coda_bank['balance_start_enforce']
206
 
                    coda_statement['discard_dup'] = coda_bank['discard_dup']
 
202
                    coda_statement['coda_bank_params'] = coda_bank 
207
203
                    coda_statement['company_id'] = coda_bank['company_id'][0]
208
204
                    context['force_company'] = coda_statement['company_id']
209
 
                    company_currency_id = company_obj.read(cr, uid, coda_statement['company_id'], ['currency_id'])['currency_id'][0]
210
205
                    company_bank_ids = partner_bank_obj.search(cr, uid, [('company_id', '=', coda_statement['company_id'])])
211
206
                    company_bank_accounts = partner_bank_obj.read(cr, uid, company_bank_ids, ['acc_number'])
212
 
                    company_bank_accounts = [x['acc_number'].replace(' ', '') for x in company_bank_accounts]
 
207
                    self.company_bank_accounts = [x['acc_number'].replace(' ', '') for x in company_bank_accounts]
 
208
                    coda_statement['balance_start_enforce'] = coda_bank['balance_start_enforce']
 
209
                    coda_statement['discard_dup'] = coda_bank['discard_dup']
213
210
                else:
 
211
                    if self._skip_undefined:
 
212
                        coda_note += _("\n\nNo matching CODA Bank Account Configuration record found !") + \
 
213
                            _("\nPlease check if the 'Bank Account Number', 'Currency' and 'Account Description' fields of your configuration record match with '%s', '%s' and '%s' if you need to import statements for this Bank Account Number !") \
 
214
                            % (coda_statement['acc_number'], coda_statement['currency'], coda_statement['description'])
 
215
                        break                  
214
216
                    err_string = _("\nNo matching CODA Bank Account Configuration record found !") + \
215
217
                        _("\nPlease check if the 'Bank Account Number', 'Currency' and 'Account Description' fields of your configuration record match with '%s', '%s' and '%s' !") \
216
218
                        % (coda_statement['acc_number'], coda_statement['currency'], coda_statement['description'])
217
219
                    err_code = 'R1004'
218
220
                    if batch:
219
221
                        return (err_code, err_string)
220
 
                    raise osv.except_osv(_('Data Error!'), err_string)
 
222
                    raise orm.except_orm(_('Data Error!'), err_string)
221
223
                bal_start = list2float(line[43:58])             # old balance data
 
224
                if coda_bank['state'] == 'skip':
 
225
                    coda_note += _("\n\nThe CODA File contains a statement which is not processed since the associated CODA Bank Account Configuration record is defined as type 'Skip' !") + \
 
226
                        _("\nPlease adjust the settings for CODA Bank Account '%s' ('Bank Account Number'='%s', 'Currency'='%s' and 'Account Description'='%s') if you need to import statements for this Bank Account !") \
 
227
                        % (coda_bank['name'], coda_statement['acc_number'], coda_statement['currency'], coda_statement['description'])
 
228
                    break                  
222
229
                if line[42] == '1':    # 1= Debit
223
230
                    bal_start = - bal_start
224
231
                coda_statement['balance_start'] = bal_start            
238
245
                       'paper': coda_statement['paper_ob_seq_number'],
239
246
                    }
240
247
                else:
241
 
                    coda_statement['name'] = '/'                
 
248
                    coda_statement['name'] = '/'
 
249
                # hook to allow further customisation
 
250
                coda_statement, coda_note = self._coda_statement_init(cr, uid, coda_statement, coda_note, context=context)    
242
251
                
243
252
            elif line[0] == '2':
244
253
                # movement data record 2
295
304
                        err_code = 'R2001'
296
305
                        if batch:
297
306
                            return (err_code, err_string)
298
 
                        raise osv.except_osv(_('Data Error!'), err_string)
 
307
                        raise orm.except_orm(_('Data Error!'), err_string)
299
308
                    st_line['trans_type_id'] = trans_type[0]['id']
300
309
                    st_line['trans_type_desc'] = trans_type[0]['description']    
301
310
                        
323
332
                            err_code = 'R2010'
324
333
                            if batch:
325
334
                                return (err_code, err_string)
326
 
                            raise osv.except_osv(_('Data Error!'), err_string)
 
335
                            raise orm.except_orm(_('Data Error!'), err_string)
327
336
                        elif st_line['ref_move_detail'] != '0000': 
328
337
                            if glob_lvl_stack[-1] == 0:
329
338
                                # promote associated move record into a globalisation
355
364
                        err_code = 'R2002'
356
365
                        if batch:
357
366
                            return (err_code, err_string)
358
 
                        raise osv.except_osv(_('Data Error!'), err_string)                    
 
367
                        raise orm.except_orm(_('Data Error!'), err_string)                    
359
368
                    st_line['trans_family_id'] = trans_family[0]['id']
360
369
                    st_line['trans_family_desc'] = trans_family[0]['description']
361
370
                    st_line['trans_code'] = line[56:58]
384
393
                            err_code = 'R2003'
385
394
                            if batch:
386
395
                                return (err_code, err_string)
387
 
                            raise osv.except_osv(_('Data Error!'), err_string)
 
396
                            raise orm.except_orm(_('Data Error!'), err_string)
388
397
                        st_line['struct_comm_type_id'] = comm_type[0]['id']
389
398
                        st_line['struct_comm_type_desc'] = comm_type[0]['description']
390
399
                        st_line['communication'] = st_line['name'] = line[65:115]
435
444
                        err_code = 'R2004'
436
445
                        if batch:
437
446
                            return (err_code, err_string)
438
 
                        raise osv.except_osv(_('Error!'), err_string)                    
 
447
                        raise orm.except_orm(_('Error!'), err_string)                    
439
448
                    coda_statement['coda_statement_lines'][st_line_seq]['name'] += line[10:63]
440
449
                    coda_statement['coda_statement_lines'][st_line_seq]['communication'] += line[10:63]
441
450
                    coda_statement['coda_statement_lines'][st_line_seq]['payment_reference'] = line[63:98].strip()
449
458
                        err_code = 'R2005'
450
459
                        if batch:
451
460
                            return (err_code, err_string)
452
 
                        raise osv.except_osv(_('Error!'), err_string)                    
 
461
                        raise orm.except_orm(_('Error!'), err_string)                    
453
462
                    st_line = coda_statement_lines[st_line_seq]
454
463
                    if coda_version == '1':
455
464
                        counterparty_number = line[10:22].strip()
479
488
                        err_code = 'R2006'
480
489
                        if batch:
481
490
                            return (err_code, err_string)
482
 
                        raise osv.except_osv(_('Error!'), err_string)    
 
491
                        raise orm.except_orm(_('Error!'), err_string)    
483
492
                    """
484
493
 
485
 
                    # invoice matching and reconciliation 
486
 
                    if st_line['type'] == 'general':                    
487
 
                        match = False
488
 
                        payment_reference_match = False
489
 
                        inv_ids = None
490
 
                        partner_bank_ids = False
491
 
 
492
 
                        # check payment reference in bank statement line against payment order lines
493
 
                        payment_reference = st_line['payment_reference']
494
 
                        if payment_reference and find_payment and st_line['amount'] < 0:
495
 
                            payline_ids = payment_line_obj and payment_line_obj.search(cr, uid, [('name', '=', payment_reference)])
496
 
                            if payline_ids:
497
 
                                if len(payline_ids) == 1:
498
 
                                    payline = payment_line_obj.browse(cr, uid, payline_ids[0], context=context)
499
 
                                    match = True
500
 
                                    payment_reference_match = True
501
 
                                    st_line['partner_id'] = payline.partner_id.id
502
 
                                    if payline.move_line_id:
503
 
                                        st_line['reconcile'] = payline.move_line_id.id
504
 
                                        st_line['account_id'] = payline.move_line_id.account_id.id
505
 
                                    if payline._get_ml_inv_ref:
506
 
                                        inv_type = payline.ml_inv_ref.type                               
507
 
                                        st_line['type'] = inv_type == 'in_invoice' and 'supplier' or 'customer'
508
 
                                    else:
509
 
                                        st_line['type'] = 'general'
510
 
                                else:
511
 
                                    err_string = _('\nThe CODA parsing detected a payment reference ambiguity while processing movement data record 2.3, seq nr %s!'    \
512
 
                                        '\nPlease check your Payment Gateway configuration or contact your OpenERP support channel.') % line[2:10]                   
513
 
                                    err_code = 'R2007'
514
 
                                    if batch:
515
 
                                        return (err_code, err_string)
516
 
                                    raise osv.except_osv(_('Error!'), err_string)    
517
 
 
518
 
                        # check bba scor in bank statement line against open invoices
519
 
                        if st_line['struct_comm_bba'] and find_bbacom:
520
 
                            if st_line['amount'] > 0:
521
 
                                domain = [('type', 'in', ['out_invoice', 'in_refund'])]
522
 
                            else:
523
 
                                domain = [('type', 'in', ['in_invoice', 'out_refund'])]
524
 
                            inv_ids = inv_obj.search(cr , uid, 
525
 
                                    domain + [('state', '=', 'open'), ('reference' ,'=', st_line['struct_comm_bba']), ('reference_type' ,'=', 'bba')])
526
 
                            if not inv_ids:
527
 
                                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
528
 
                                    "\n        There is no invoice matching the Structured Communication '%s'!") \
529
 
                                    % (st_line['ref'], st_line['struct_comm_bba'])
530
 
                            elif len(inv_ids) == 1:
531
 
                                match = True 
532
 
                            elif len(inv_ids) > 1:
533
 
                                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
534
 
                                    "\n        There are multiple invoices matching the Structured Communication '%s'!" \
535
 
                                    "\n        A manual reconciliation is required.") \
536
 
                                    % (st_line['ref'], st_line['struct_comm_bba'])
537
 
 
538
 
                        # use free comm in bank statement line for lookup against open invoices
539
 
                        if not match and find_bbacom:
540
 
                            # extract possible bba scor from free form communication and try to find matching invoice
541
 
                            free_comm_digits = re.sub('\D', '', st_line['communication'] or '')
542
 
                            select = "SELECT id FROM (SELECT id, type, state, amount_total, number, reference_type, reference, " \
543
 
                                     "'%s'::text AS free_comm_digits FROM account_invoice) sq " \
544
 
                                     "WHERE state = 'open' AND reference_type = 'bba' " \
545
 
                                     "AND free_comm_digits LIKE '%%'||regexp_replace(reference, '\\\D', '', 'g')||'%%'" \
546
 
                                     % (free_comm_digits)
547
 
                            if st_line['amount'] > 0:
548
 
                                select2 = " AND type IN ('out_invoice', 'in_refund')"
549
 
                            else:
550
 
                                select2 = " AND type IN ('in_invoice', 'out_refund')"
551
 
                            cr.execute(select + select2)
552
 
                            res=cr.fetchall()
553
 
                            if res:
554
 
                                inv_ids = [x[0] for x in res]
555
 
                                if len(inv_ids) == 1:
556
 
                                    match = True
557
 
                        if not match and st_line['communication'] and find_inv_number:
558
 
                            # check matching invoice number in free form communication combined with matching amount
559
 
                            free_comm = repl_special(st_line['communication'].strip())
560
 
                            amount_fmt = '%.' + str(digits) + 'f'
561
 
                            if st_line['amount'] > 0:
562
 
                                amount_rounded = amount_fmt % round(st_line['amount'], digits)
563
 
                            else:
564
 
                                amount_rounded = amount_fmt % round(-st_line['amount'], digits)
565
 
                            select = "SELECT id FROM (SELECT id, type, state, amount_total, number, reference_type, reference, " \
566
 
                                     "'%s'::text AS free_comm FROM account_invoice) sq " \
567
 
                                     "WHERE state = 'open' AND amount_total = %s" \
568
 
                                     % (free_comm, amount_rounded)
569
 
                            # 'out_invoice', 'in_refund'
570
 
                            if st_line['amount'] > 0:
571
 
                                select2 = " AND type = 'out_invoice' AND free_comm ilike '%'||number||'%'"
572
 
                                cr.execute(select + select2)
573
 
                                res=cr.fetchall()
574
 
                                if res:
575
 
                                    inv_ids = [x[0] for x in res]
576
 
                                else: 
577
 
                                    select2 = " AND type = 'in_refund' AND free_comm ilike '%'||reference||'%'"
578
 
                                    cr.execute(select + select2)
579
 
                                    res=cr.fetchall()
580
 
                                    if res:
581
 
                                        inv_ids = [x[0] for x in res]
582
 
                            # 'in_invoice', 'out_refund'
583
 
                            else:
584
 
                                select2 = " AND type = 'in_invoice' AND free_comm ilike '%'||reference||'%'"
585
 
                                cr.execute(select + select2)
586
 
                                res=cr.fetchall()
587
 
                                if res:
588
 
                                    inv_ids = [x[0] for x in res]
589
 
                                else: 
590
 
                                    select2 = " AND type = 'out_refund' AND free_comm ilike '%'||number||'%'"
591
 
                                    cr.execute(select + select2)
592
 
                                    res=cr.fetchall()
593
 
                                    if res:
594
 
                                        inv_ids = [x[0] for x in res]
595
 
                            if inv_ids:
596
 
                                if len(inv_ids) == 1:
597
 
                                    match = True
598
 
                                elif len(inv_ids) > 1:
599
 
                                    coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
600
 
                                        "\n        There are multiple invoices matching the Invoice Amount and Reference." \
601
 
                                        "\n        A manual reconciliation is required.") \
602
 
                                        % (st_line['ref'])
603
 
                                
604
 
                        if not payment_reference_match and match and inv_ids and len(inv_ids) == 1:
605
 
                            invoice = inv_obj.browse(cr, uid, inv_ids[0], context=context)
606
 
                            partner = invoice.partner_id
607
 
                            st_line['partner_id'] = partner.id
608
 
                            if invoice.type in ['in_invoice', 'in_refund']:
609
 
                                st_line['account_id'] = partner.property_account_payable.id
610
 
                                st_line['type'] = 'supplier'
611
 
                            else:
612
 
                                st_line['account_id'] = partner.property_account_receivable.id
613
 
                                st_line['type'] = 'customer'
614
 
                            iml_ids = move_line_obj.search(cr, uid, [('move_id', '=', invoice.move_id.id), ('reconcile_id', '=', False), ('account_id.reconcile', '=', True)])
615
 
                            if iml_ids:
616
 
                                st_line['reconcile'] = iml_ids[0]
617
 
                            else:
618
 
                                err_string = _('\nThe CODA parsing detected a database inconsistency while processing movement data record 2.3, seq nr %s!'    \
619
 
                                    '\nPlease report this issue via your OpenERP support channel.') % line[2:10]                   
620
 
                                err_code = 'R2008'
621
 
                                if batch:
622
 
                                    return (err_code, err_string)
623
 
                                raise osv.except_osv(_('Error!'), err_string)    
624
 
                                
625
 
                        # lookup partner via counterparty_number when invoice lookup failed 
626
 
                        if not match and counterparty_number:
627
 
                            transfer_account =  filter(lambda x: counterparty_number in x, company_bank_accounts)
628
 
                            if transfer_account:
629
 
                                st_line['account_id'] = transfer_acc
630
 
                                match = True
631
 
                            elif find_partner:
632
 
                                partner_bank_ids = partner_bank_obj.search(cr,uid,[('acc_number','=', counterparty_number)])
633
 
                        if not match and find_partner and partner_bank_ids:
634
 
                            if len(partner_bank_ids) > 1:
635
 
                                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
636
 
                                    "\n        No partner record assigned: There are multiple partners with the same Bank Account Number '%s'!") \
637
 
                                    % (st_line['ref'], counterparty_number)
638
 
                            else:    
639
 
                                partner_bank = partner_bank_obj.browse(cr, uid, partner_bank_ids[0], context=context)
640
 
                                st_line['partner_id'] = partner_bank.partner_id.id
641
 
                                match = True
642
 
                                if st_line['amount'] < 0:
643
 
                                    st_line['account_id'] = partner_bank.partner_id.property_account_payable.id
644
 
                                    st_line['type'] = 'supplier'
645
 
                                else:
646
 
                                    st_line['account_id'] = partner_bank.partner_id.property_account_receivable.id
647
 
                                    st_line['type'] = 'customer'
648
 
                        elif not match and find_partner:
649
 
                            if counterparty_number:
650
 
                                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
651
 
                                    "\n        The bank account '%s' is not defined for the partner '%s'!") \
652
 
                                    % (st_line['ref'], counterparty_number, counterparty_name)
653
 
                            else:
654
 
                                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
655
 
                                    "\n        No matching partner record found!" \
656
 
                                    "\n        Please adjust the corresponding entry manually in the generated Bank Statement.") \
657
 
                                    % (st_line['ref']) 
658
 
                            st_line['account_id'] = awaiting_acc
659
 
                        
660
 
                        # add bank account to partner record
661
 
                        if match and st_line['account_id'] != transfer_acc and counterparty_number and update_partner:
662
 
                            partner_bank_ids = partner_bank_obj.search(cr,uid,[('acc_number','=', counterparty_number), ('partner_id', '=', st_line['partner_id'])], order='id')
663
 
                            if len(partner_bank_ids) > 1:
664
 
                                # clean up partner bank duplicates, keep most recently created
665
 
                                _logger.warn('Duplicate Bank Accounts for partner_id %s have been removed, ids = %s', 
666
 
                                    st_line['partner_id'], partner_bank_ids[:-1])
667
 
                                partner_bank_obj.unlink(cr,uid, partner_bank_ids[:-1])
668
 
                            if not partner_bank_ids:
669
 
                                feedback = update_partner_bank(self, cr, uid, st_line['counterparty_bic'], counterparty_number, st_line['partner_id'], counterparty_name)
670
 
                                if feedback:                                   
671
 
                                    coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':") % st_line['ref'] + feedback                                    
 
494
                    if st_line['type'] == 'general':  
 
495
                        st_line, coda_parsing_note = self._match_and_reconcile(cr, uid, st_line, coda_parsing_note, coda_statement, context=context)      
672
496
 
673
497
                    coda_statement_lines[st_line_seq] = st_line
674
498
                    coda_statement['coda_statement_lines'] = coda_statement_lines
 
499
                    
675
500
                    # end of processing movement data record 2.3
676
501
 
677
502
                else:
680
505
                    err_code = 'R2009'
681
506
                    if batch:
682
507
                        return (err_code, err_string)
683
 
                    raise osv.except_osv(_('Data Error!'), err_string)    
 
508
                    raise orm.except_orm(_('Data Error!'), err_string)    
684
509
 
685
510
            elif line[0] == '3':
686
511
                # information data record 3
696
521
                    info_line['struct_comm_type_desc'] = ''
697
522
                    info_line['communication'] = ''
698
523
                    info_line['ref'] = line[2:10]
 
524
                    info_line['ref_move'] = line[2:6]
 
525
                    info_line['ref_move_detail'] = line[6:10]
699
526
                    info_line['trans_ref'] = line[10:31]
700
527
                    # positions 32-38 : transaction code
701
528
                    info_line['trans_type'] = line[31]
705
532
                        err_code = 'R3001'
706
533
                        if batch:
707
534
                            return (err_code, err_string)
708
 
                        raise osv.except_osv(_('Data Error!'), err_string)                    
 
535
                        raise orm.except_orm(_('Data Error!'), err_string)                    
709
536
                    info_line['trans_type_desc'] = trans_type[0]['description']                         
710
537
                    info_line['trans_family'] = line[32:34]
711
538
                    trans_family =  filter(lambda x: (x['type'] == 'family') and (info_line['trans_family'] == x['code']), trans_code_table)
714
541
                        err_code = 'R3002'
715
542
                        if batch:
716
543
                            return (err_code, err_string)
717
 
                        raise osv.except_osv(_('Data Error!'), err_string)                    
 
544
                        raise orm.except_orm(_('Data Error!'), err_string)                    
718
545
                    info_line['trans_family_desc'] = trans_family[0]['description']
719
546
                    info_line['trans_code'] = line[34:36]
720
547
                    trans_code =  filter(lambda x: (x['type'] == 'code') and (info_line['trans_code'] == x['code']) and (trans_family[0]['id'] == x['parent_id'][0]), 
738
565
                            err_code = 'R3003'
739
566
                            if batch:
740
567
                                return (err_code, err_string)
741
 
                            raise osv.except_osv(_('Data Error!'), err_string)
 
568
                            raise orm.except_orm(_('Data Error!'), err_string)
742
569
                        info_line['struct_comm_type_desc'] = comm_type[0]['description']
743
570
                        info_line['communication'] = info_line['name'] = line[43:113]
744
571
                    else:
749
576
                    
750
577
                elif line[1] == '2':
751
578
                    # information data record 3.2
752
 
                    if coda_statement['coda_statement_lines'][st_line_seq]['ref'] != line[2:10]:
 
579
                    if coda_statement['coda_statement_lines'][st_line_seq]['ref_move'] != line[2:6]:
753
580
                        err_string = _('\nCODA parsing error on information data record 3.2, seq nr %s!'    \
754
581
                            '\nPlease report this issue via your OpenERP support channel.') % line[2:10]
755
582
                        err_code = 'R3004'
756
583
                        if batch:
757
584
                            return (err_code, err_string)
758
 
                        raise osv.except_osv(_('Error!'), err_string)
 
585
                        raise orm.except_orm(_('Error!'), err_string)
759
586
                    coda_statement['coda_statement_lines'][st_line_seq]['name'] += line[10:115]                        
760
587
                    coda_statement['coda_statement_lines'][st_line_seq]['communication'] += line[10:115]
761
588
                    
762
589
                elif line[1] == '3':
763
590
                    # information data record 3.3
764
 
                    if coda_statement['coda_statement_lines'][st_line_seq]['ref'] != line[2:10]:
 
591
                    if coda_statement['coda_statement_lines'][st_line_seq]['ref_move'] != line[2:6]:
765
592
                        err_string = _('\nCODA parsing error on information data record 3.3, seq nr %s!'    \
766
593
                            '\nPlease report this issue via your OpenERP support channel.') % line[2:10]
767
594
                        err_code = 'R3005'
768
595
                        if batch:
769
596
                            return (err_code, err_string)
770
 
                        raise osv.except_osv(_('Error!'), err_string)
 
597
                        raise orm.except_orm(_('Error!'), err_string)
771
598
                    coda_statement['coda_statement_lines'][st_line_seq]['name'] += line[10:100]
772
599
                    coda_statement['coda_statement_lines'][st_line_seq]['communication'] += line[10:100]
773
600
                   
808
635
                if line[41] == '1':    # 1=Debit
809
636
                    bal_end = - bal_end
810
637
                coda_statement['balance_end_real'] = bal_end
811
 
                if coda_statement['new_balance_date']:                
812
 
                    period_id = period_obj.search(cr , uid, [('date_start' ,'<=', coda_statement['new_balance_date']), ('date_stop','>=',coda_statement['new_balance_date']), ('company_id', '=', coda_statement['company_id'])])
813
 
                else:
814
 
                    period_id = period_obj.search(cr , uid, [('date_start' ,'<=', coda_statement['date']), ('date_stop','>=',coda_statement['date']), ('company_id', '=', coda_statement['company_id'])])
 
638
                if not period_id:
 
639
                    if coda_statement['new_balance_date']:                
 
640
                        period_id = period_obj.search(cr , uid, [('date_start' ,'<=', coda_statement['new_balance_date']), ('date_stop','>=',coda_statement['new_balance_date']), ('special', '=', False), ('company_id', '=', coda_statement['company_id'])])
 
641
                    else:
 
642
                        period_id = period_obj.search(cr , uid, [('date_start' ,'<=', coda_statement['date']), ('date_stop','>=',coda_statement['date']), ('special', '=', False), ('company_id', '=', coda_statement['company_id'])])
 
643
                    period_id = period_id and period_id[0]
815
644
                if not period_id:
816
645
                    if coda_statement['type'] == 'normal':
817
646
                        err_string = _("\nThe CODA Statement New Balance date doesn't fall within a defined Accounting Period!" \
819
648
                        err_code = 'R0002'
820
649
                        if batch:
821
650
                            return (err_code, err_string)
822
 
                        raise osv.except_osv(_('Data Error!'), err_string)
823
 
                coda_statement['period_id'] = period_id and period_id[0]
 
651
                        raise orm.except_orm(_('Data Error!'), err_string)
 
652
                coda_statement['period_id'] = period_id
824
653
                # update coda_statement['name'] with data from 8 record
825
654
                if coda_bank['coda_st_naming']:
826
655
                    coda_statement['name'] = coda_bank['coda_st_naming'] % {
850
679
        err_string = ''
851
680
        err_code = ''        
852
681
        coda_id = 0
853
 
        coda_note = ''
854
682
        line_note = ''
855
683
        
856
684
        try:
863
691
                })
864
692
            context.update({'coda_id': coda_id})
865
693
    
866
 
        except osv.except_osv, e:
 
694
        except orm.except_orm, e:
867
695
            cr.rollback()
868
696
            err_string = _('\nApplication Error : ') + str(e)
869
697
        except Exception, e:
876
704
            err_code = 'G0001'
877
705
            if batch:
878
706
                return (err_code, err_string)
879
 
            raise osv.except_osv(_('CODA Import failed !'), err_string)
 
707
            raise orm.except_orm(_('CODA Import failed !'), err_string)
880
708
 
881
709
        nb_err = 0
882
710
        err_string = ''
885
713
        
886
714
        for statement in coda_statements:
887
715
            
 
716
            coda_bank_params = coda_statement['coda_bank_params']
 
717
            
888
718
            # The CODA Statement info is written to two objects: 'coda.bank.statement' and 'account.bank.statement'
889
719
 
890
720
            try:
933
763
                if statement['type'] == 'normal' and not discard:
934
764
                    context.update({'ebanking_import': 1})
935
765
                    journal = journal_obj.browse(cr, uid, statement['journal_id'], context=context)
 
766
                    company_currency_id = journal.company_id.currency_id.id
936
767
                    balance_start_check_date = statement['first_transaction_date'] or statement['date']
937
768
                    cr.execute('SELECT balance_end_real \
938
769
                        FROM account_bank_statement \
1104
935
                            if glob_lvl_flag == 0: 
1105
936
                                line['globalisation_id'] = glob_id_stack[-1][2]
1106
937
                            if not line['account_id']:                               
1107
 
                                    line['account_id'] = awaiting_acc
 
938
                                    line['account_id'] = coda_bank_params['awaiting_account'][0]
1108
939
                                                                
1109
940
                            coda_st_line_id = coda_st_line_obj.create(cr, uid, {
1110
941
                                   'sequence': line['sequence'],
1143
974
                                    if line['reconcile']:
1144
975
                                        
1145
976
                                        move_line = move_line_obj.browse(cr, uid, line['reconcile'], context=context)
1146
 
                                        company_currency_id = journal.company_id.currency_id.id
1147
977
                                        currency_id = journal.currency.id
1148
978
                                        if currency_id and currency_id != company_currency_id:
1149
979
                                            multi_currency = True
1185
1015
                                            line_dr_ids += [(0, 0, voucher_line_vals)]
1186
1016
                                            
1187
1017
                                        voucher_vals = { 
1188
 
                                            'type': line['type'] == 'supplier' and 'payment' or 'receipt',
 
1018
                                            'type': line['amount'] > 0 and 'receipt' or 'payment',
1189
1019
                                            'name': line_name,
1190
1020
                                            'date': line['val_date'],
1191
1021
                                            'journal_id': statement['journal_id'],
1208
1038
                                        if line_cr_ids or line_dr_ids:
1209
1039
                                            voucher_id = voucher_obj.create(cr, uid, voucher_vals, context=context)
1210
1040
                                        else:
1211
 
                                            raise osv.except_osv(_('Error!'), _("Reconcile error while processing line with ref '%s' ! " \
 
1041
                                            raise orm.except_orm(_('Error!'), _("Reconcile error while processing line with ref '%s' ! " \
1212
1042
                                                  "\nPlease report this issue via your OpenERP support channel.") % line['ref'])
1213
1043
 
1214
1044
                                    # override default account mapping by mappings defined in rules engine
1221
1051
                                            'trans_category_id': line['trans_category_id'],
1222
1052
                                            'struct_comm_type_id': line['struct_comm_type_id'],
1223
1053
                                            'partner_id': line['partner_id'],
 
1054
                                            'freecomm': line['communication'] if not line['struct_comm_type'] else None,
 
1055
                                            'structcomm': line['communication'] if line['struct_comm_type'] else None,
1224
1056
                                            'context': context,
1225
1057
                                        }
1226
1058
                                        rule = account_mapping_obj.rule_get(cr, uid, **kwargs)
1230
1062
                                            line['action'] = rule[2]
1231
1063
 
1232
1064
                                    st_line_vals = {
1233
 
                                           'ref': line['ref'],                                                   
 
1065
                                           'ref': line['ref'],
1234
1066
                                           'name': line_name,
1235
1067
                                           'type' : line['type'],
1236
1068
                                           'val_date' : line['val_date'], 
1238
1070
                                           'amount': line['amount'],
1239
1071
                                           'partner_id': line['partner_id'] or 0,
1240
1072
                                           'counterparty_name': line['counterparty_name'],
1241
 
                                           'counterparty_bic': line['counterparty_bic'],                     
1242
 
                                           'counterparty_number': line['counterparty_number'],   
1243
 
                                           'counterparty_currency': line['counterparty_currency'],                                                                           
 
1073
                                           'counterparty_bic': line['counterparty_bic'],
 
1074
                                           'counterparty_number': line['counterparty_number'],
 
1075
                                           'counterparty_currency': line['counterparty_currency'],
1244
1076
                                           'account_id': line['account_id'],
1245
 
                                           'globalisation_id': line['globalisation_id'], 
1246
 
                                           'payment_reference': line['payment_reference'],  
 
1077
                                           'globalisation_id': line['globalisation_id'],
 
1078
                                           'payment_reference': line['payment_reference'],
1247
1079
                                           'statement_id': bk_st_id,
1248
1080
                                           'voucher_id': voucher_id,
1249
1081
                                           'note': line_note,
1250
1082
                                            }
1251
 
    
 
1083
 
1252
1084
                                    st_lines_vals = self._st_line_hook(cr, uid, statement['coda_bank_params'], st_line_vals, context=context)
1253
1085
                                    if not st_lines_vals:
1254
1086
                                        # removal of lines by the _coda_parsing_hook currently not supported :
1266
1098
                                            if line['amount'] == (line['amount'] > 0 and move_line.debit or -move_line.credit):
1267
1099
                                                try:
1268
1100
                                                    wf_service.trg_validate(uid, 'account.voucher', st_line_vals['voucher_id'], 'proforma_voucher', cr)
1269
 
                                                except osv.except_osv, e:
 
1101
                                                except (orm.except_orm, osv.except_osv), e:
1270
1102
                                                    statement['coda_parsing_note'] += _("\nReconciliation Error in Bank Statement '%s' line '%s':\n    %s") \
1271
1103
                                                        %(statement['name'], st_line_vals['ref'], e[1])
1272
1104
                                            if voucher.move_id:
1287
1119
                if st_balance['balance_end'] <> st_balance['balance_end_real']:
1288
1120
                    err_string += _('\nIncorrect ending Balance in CODA Statement %s for Bank Account %s!')  \
1289
1121
                        % (statement['coda_seq_number'], (statement['acc_number'] + ' (' + statement['currency'] + ') - ' + statement['description']))
1290
 
                    if statement['type'] == 'normal':
1291
 
                        nb_err += 1
1292
 
                        break
1293
 
                    else:
1294
 
                        statement['coda_parsing_note'] += '\n' + err_string
 
1122
                    statement['coda_parsing_note'] += '\n' + err_string
 
1123
                    #if statement['type'] == 'normal':
 
1124
                    #    nb_err += 1
 
1125
                    #    break
 
1126
                    #else:
 
1127
                    #    statement['coda_parsing_note'] += '\n' + err_string
1295
1128
                              
1296
1129
                if statement['type'] == 'normal':                          
1297
1130
                    bank_st_obj.button_dummy(cr, uid, [bk_st_id], context=context)      # calculate balance   
1318
1151
                if statement.get('separate_application') != '00000':
1319
1152
                    coda_note += _('\nCode Separate Application: %s') %statement['separate_application']                                                  \
1320
1153
 
1321
 
            except osv.except_osv, e:
 
1154
            except orm.except_orm, e:
1322
1155
                cr.rollback()
1323
1156
                nb_err += 1
1324
1157
                err_string += _('\nError ! ') + str(e)
1357
1190
            if batch:
1358
1191
                err_code = 'G0002'
1359
1192
                return (err_code, err_string)
1360
 
            raise osv.except_osv(_('CODA Import failed !'), err_string)
 
1193
            raise orm.except_orm(_('CODA Import failed !'), err_string)
1361
1194
            
1362
1195
        context.update({ 'bk_st_ids': bk_st_ids})
1363
1196
        model_data_ids = mod_obj.search(cr, uid, [('model', '=', 'ir.ui.view'), ('name', '=', 'account_coda_import_result_view')], context=context)
1377
1210
            'type': 'ir.actions.act_window',
1378
1211
        }
1379
1212
 
 
1213
    def _match_and_reconcile(self, cr, uid, st_line, coda_parsing_note, coda_statement, context=None):
 
1214
        """
 
1215
        Matching and Reconciliation logic. 
 
1216
        
 
1217
        Returns: st_line, coda_parsing_note
 
1218
        
 
1219
        Remark: 
 
1220
        Don't use 'raise' when the CODA is processed in batch mode (check if self.batch is True) 
 
1221
        """
 
1222
        
 
1223
        # match on payment reference
 
1224
        st_line, coda_parsing_note, match = self._match_payment_reference(cr, uid, st_line, coda_parsing_note, coda_statement, context=context)
 
1225
        if match:
 
1226
            return st_line, coda_parsing_note
 
1227
        
 
1228
        # match on invoice
 
1229
        st_line, coda_parsing_note, match = self._match_invoice(cr, uid, st_line, coda_parsing_note, coda_statement, context=context)
 
1230
        if match:
 
1231
            return st_line, coda_parsing_note
 
1232
        
 
1233
        # match on sale order 
 
1234
        st_line, coda_parsing_note, match = self._match_sale_order(cr, uid, st_line, coda_parsing_note, coda_statement, context=context)
 
1235
        if match:
 
1236
            return st_line, coda_parsing_note
 
1237
                                                 
 
1238
        # check if internal_transfer or partner via counterparty_number when invoice lookup failed 
 
1239
        st_line, coda_parsing_note, match = self._match_counterparty(cr, uid, st_line, coda_parsing_note, coda_statement, context=context)
 
1240
        if match:
 
1241
            return st_line, coda_parsing_note
 
1242
        
 
1243
        return st_line, coda_parsing_note
 
1244
 
 
1245
    def _match_payment_reference(self, cr, uid, st_line, coda_parsing_note, coda_statement, context=None):
 
1246
        """ placeholder for ISO 20022 Payment Order matching, cf. module account_coda_pain """
 
1247
        return st_line, coda_parsing_note, {}
 
1248
    
 
1249
    def _match_sale_order(self, cr, uid, st_line, coda_parsing_note, coda_statement, context=None):
 
1250
        """ placeholder for sale order matching, cf. module account_coda_sale """
 
1251
        return st_line, coda_parsing_note, {}
 
1252
 
 
1253
    def _match_invoice(self, cr, uid, st_line, coda_parsing_note, coda_statement, context=None):
 
1254
        
 
1255
        coda_bank_params = coda_statement['coda_bank_params']
 
1256
        find_bbacom = coda_bank_params['find_bbacom']
 
1257
        find_inv_number = coda_bank_params['find_inv_number']
 
1258
        
 
1259
        inv_obj = self.pool.get('account.invoice')
 
1260
        move_line_obj = self.pool.get('account.move.line')
 
1261
        match={}
 
1262
        inv_ids = None
 
1263
        
 
1264
        # check bba scor in bank statement line against open invoices
 
1265
        if st_line['struct_comm_bba'] and find_bbacom:
 
1266
            if st_line['amount'] > 0:
 
1267
                domain = [('type', 'in', ['out_invoice', 'in_refund'])]
 
1268
            else:
 
1269
                domain = [('type', 'in', ['in_invoice', 'out_refund'])]
 
1270
            inv_ids = inv_obj.search(cr , uid, 
 
1271
                domain + [('state', '=', 'open'), ('reference' ,'=', st_line['struct_comm_bba']), ('reference_type' ,'=', 'bba')])
 
1272
            if not inv_ids:
 
1273
                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
 
1274
                    "\n        There is no invoice matching the Structured Communication '%s'!") \
 
1275
                    % (st_line['ref'], st_line['struct_comm_bba'])
 
1276
            elif len(inv_ids) == 1:
 
1277
                match['invoice_id'] = inv_ids[0] 
 
1278
            elif len(inv_ids) > 1:
 
1279
                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
 
1280
                    "\n        There are multiple invoices matching the Structured Communication '%s'!" \
 
1281
                    "\n        A manual reconciliation is required.") \
 
1282
                    % (st_line['ref'], st_line['struct_comm_bba'])
 
1283
    
 
1284
        # use free comm in bank statement line for lookup against open invoices
 
1285
        if not match and find_bbacom:
 
1286
            # extract possible bba scor from free form communication and try to find matching invoice
 
1287
            free_comm_digits = re.sub('\D', '', st_line['communication'] or '')
 
1288
            select = "SELECT id FROM (SELECT id, type, state, amount_total, number, reference_type, reference, " \
 
1289
                     "'%s'::text AS free_comm_digits FROM account_invoice) sq " \
 
1290
                     "WHERE state = 'open' AND reference_type = 'bba' " \
 
1291
                     "AND free_comm_digits LIKE '%%'||regexp_replace(reference, '\\\D', '', 'g')||'%%'" \
 
1292
                     % (free_comm_digits)
 
1293
            if st_line['amount'] > 0:
 
1294
                select2 = " AND type IN ('out_invoice', 'in_refund')"
 
1295
            else:
 
1296
                select2 = " AND type IN ('in_invoice', 'out_refund')"
 
1297
            cr.execute(select + select2)
 
1298
            res=cr.fetchall()
 
1299
            if res:
 
1300
                inv_ids = [x[0] for x in res]
 
1301
                if len(inv_ids) == 1:
 
1302
                    match['invoice_id'] = inv_ids[0]
 
1303
        if not match and st_line['communication'] and find_inv_number:
 
1304
            # check matching invoice number in free form communication combined with matching amount
 
1305
            free_comm = repl_special(st_line['communication'].strip())
 
1306
            amount_fmt = '%.' + str(self.digits) + 'f'
 
1307
            if st_line['amount'] > 0:
 
1308
                amount_rounded = amount_fmt % round(st_line['amount'], self.digits)
 
1309
            else:
 
1310
                amount_rounded = amount_fmt % round(-st_line['amount'], self.digits)
 
1311
            select = "SELECT id FROM (SELECT id, type, state, amount_total, number, reference_type, reference, " \
 
1312
                     "'%s'::text AS free_comm FROM account_invoice WHERE state = 'open' AND company_id = %s) sq " \
 
1313
                     "WHERE amount_total = %s" \
 
1314
                     % (free_comm, coda_statement['company_id'], amount_rounded)
 
1315
                      
 
1316
            # 'out_invoice', 'in_refund'
 
1317
            if st_line['amount'] > 0:
 
1318
                select2 = " AND type = 'out_invoice' AND free_comm ilike '%'||number||'%'"
 
1319
                cr.execute(select + select2)
 
1320
                res=cr.fetchall()
 
1321
                if res:
 
1322
                    inv_ids = [x[0] for x in res]
 
1323
                else: 
 
1324
                    select2 = " AND type = 'in_refund' AND free_comm ilike '%'||reference||'%'"
 
1325
                    cr.execute(select + select2)
 
1326
                    res=cr.fetchall()
 
1327
                    if res:
 
1328
                        inv_ids = [x[0] for x in res]
 
1329
    
 
1330
            # 'in_invoice', 'out_refund'
 
1331
            else:
 
1332
                select2 = " AND type = 'in_invoice' AND free_comm ilike '%'||reference||'%'"
 
1333
                cr.execute(select + select2)
 
1334
                res=cr.fetchall()
 
1335
                if res:
 
1336
                    inv_ids = [x[0] for x in res]
 
1337
                else: 
 
1338
                    select2 = " AND type = 'out_refund' AND free_comm ilike '%'||number||'%'"
 
1339
                    cr.execute(select + select2)
 
1340
                    res=cr.fetchall()
 
1341
                    if res:
 
1342
                        inv_ids = [x[0] for x in res]
 
1343
            if inv_ids:
 
1344
                if len(inv_ids) == 1:
 
1345
                    match['invoice_id'] = inv_ids[0]
 
1346
                elif len(inv_ids) > 1:
 
1347
                    coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
 
1348
                        "\n        There are multiple invoices matching the Invoice Amount and Reference." \
 
1349
                        "\n        A manual reconciliation is required.") \
 
1350
                        % (st_line['ref'])
 
1351
    
 
1352
        if match:
 
1353
            invoice = inv_obj.browse(cr, uid, inv_ids[0], context=context)
 
1354
            partner = invoice.partner_id
 
1355
            st_line['partner_id'] = partner.id
 
1356
            st_line['account_id'] = invoice.account_id.id
 
1357
            st_line['type'] = (invoice.account_id.type == 'receivable') and 'customer' or 'supplier'
 
1358
            iml_ids = move_line_obj.search(cr, uid, [('move_id', '=', invoice.move_id.id), ('reconcile_id', '=', False), ('account_id.reconcile', '=', True)])
 
1359
            if iml_ids:
 
1360
                st_line['reconcile'] = iml_ids[0]
 
1361
            else:
 
1362
                err_string = _('\nThe CODA parsing detected a database inconsistency while processing movement data record 2.3, seq nr %s!'    \
 
1363
                    '\nPlease report this issue via your OpenERP support channel.') % line[2:10]                   
 
1364
                err_code = 'R2008'
 
1365
                if self.batch:
 
1366
                    return (err_code, err_string)
 
1367
                raise orm.except_orm(_('Error!'), err_string)    
 
1368
 
 
1369
        return st_line, coda_parsing_note, match
 
1370
 
 
1371
    def _match_counterparty(self, cr, uid, st_line, coda_parsing_note, coda_statement, context=None):
 
1372
        
 
1373
        coda_bank_params = coda_statement['coda_bank_params']
 
1374
        transfer_acc = coda_bank_params['transfer_account'][0]
 
1375
        find_partner = coda_bank_params['find_partner']
 
1376
        update_partner = coda_bank_params['update_partner']
 
1377
 
 
1378
        partner_bank_obj = self.pool.get('res.partner.bank')
 
1379
        match={}
 
1380
        partner_bank_ids = None
 
1381
        counterparty_number = st_line['counterparty_number']
 
1382
        if counterparty_number:
 
1383
            transfer_accounts =  filter(lambda x: counterparty_number in x, self.company_bank_accounts)
 
1384
            if transfer_accounts:
 
1385
                if counterparty_number not in _get_acc_numbers(coda_statement['acc_number']): # exclude transactions from counterparty_number = bank account number of this statement 
 
1386
                    st_line['account_id'] = transfer_acc
 
1387
                    match['transfer_account'] = True
 
1388
            elif find_partner:
 
1389
                partner_bank_ids = partner_bank_obj.search(cr,uid,[('acc_number','=', counterparty_number)])
 
1390
        if not match and find_partner and partner_bank_ids:
 
1391
            partner_banks = partner_bank_obj.browse(cr, uid, partner_bank_ids, context=context)
 
1392
            # filter out partners that belong to other companies
 
1393
            # TO DO : adapt this logic to cope with res.partner record rule customisations
 
1394
            partner_bank_ids2 = []
 
1395
            for pb in partner_banks:
 
1396
                add_pb = True
 
1397
                try:
 
1398
                    if pb.partner_id.company_id and pb.partner_id.company_id.id != coda_statement['company_id']:
 
1399
                        add_pb = False
 
1400
                except:
 
1401
                    add_pb = False
 
1402
                if add_pb:
 
1403
                    partner_bank_ids2.append(pb.id)
 
1404
            if len(partner_bank_ids2) > 1:
 
1405
                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
 
1406
                    "\n        No partner record assigned: There are multiple partners with the same Bank Account Number '%s'!") \
 
1407
                    % (st_line['ref'], counterparty_number)
 
1408
            elif len(partner_bank_ids2) == 1:    
 
1409
                partner_bank = partner_bank_obj.browse(cr, uid, partner_bank_ids2[0], context=context)
 
1410
                st_line['partner_id'] = partner_bank.partner_id.id
 
1411
                match['partner_id'] = st_line['partner_id'] 
 
1412
                if st_line['amount'] < 0:
 
1413
                    st_line['account_id'] = partner_bank.partner_id.property_account_payable.id
 
1414
                    st_line['type'] = 'supplier'
 
1415
                else:
 
1416
                    st_line['account_id'] = partner_bank.partner_id.property_account_receivable.id
 
1417
                    st_line['type'] = 'customer'
 
1418
        elif not match and find_partner:
 
1419
            if counterparty_number:
 
1420
                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
 
1421
                    "\n        The bank account '%s' is not defined for the partner '%s'!") \
 
1422
                    % (st_line['ref'], counterparty_number, st_line['counterparty_name'])
 
1423
            else:
 
1424
                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
 
1425
                    "\n        No matching partner record found!" \
 
1426
                    "\n        Please adjust the corresponding entry manually in the generated Bank Statement.") \
 
1427
                    % (st_line['ref']) 
 
1428
         
 
1429
        # add bank account to partner record
 
1430
        if match and st_line['account_id'] != transfer_acc and counterparty_number and update_partner:
 
1431
            partner_bank_ids = partner_bank_obj.search(cr,uid,[('acc_number','=', counterparty_number), ('partner_id', '=', st_line['partner_id'])], order='id')
 
1432
            if len(partner_bank_ids) > 1:
 
1433
                # clean up partner bank duplicates, keep most recently created
 
1434
                _logger.warn('Duplicate Bank Accounts for partner_id %s have been removed, ids = %s', 
 
1435
                    st_line['partner_id'], partner_bank_ids[:-1])
 
1436
                partner_bank_obj.unlink(cr,uid, partner_bank_ids[:-1])
 
1437
            if not partner_bank_ids:
 
1438
                feedback = update_partner_bank(self, cr, uid, st_line['counterparty_bic'], counterparty_number, st_line['partner_id'], st_line['counterparty_name'])
 
1439
                if feedback:                                   
 
1440
                    coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':") % st_line['ref'] + feedback                                    
 
1441
 
 
1442
        return st_line, coda_parsing_note, match
 
1443
 
 
1444
    def _coda_statement_init(self, cr, uid, coda_statement, coda_note, context=None):
 
1445
        """ 
 
1446
        Use this method to take customer specific actions once a specific statement has been identified in a coda file.
 
1447
        Returns:
 
1448
            coda_statement : e.g. add parameters
 
1449
            coda_note: e.g. add extra info to the CODA Note
 
1450
        """
 
1451
        return coda_statement, coda_note
 
1452
 
1380
1453
    def _statement_hook(self, cr, uid, coda_bank_params, st_vals, context=None):
1381
1454
        """ 
1382
1455
        Use this method to take customer specific actions based upon the bank statement data.
1417
1490
        
1418
1491
account_coda_import()
1419
1492
 
 
1493
 
1420
1494
def repl_special(s):
1421
1495
    s = s.replace("\'", "\'" + "'")
1422
1496
    return s