[MERGE](Sort of :D)From Luc De Meyer Zip from apps.openerp.com
authordle@openerp.com <>
Fri, 30 Nov 2012 17:20:33 +0000 (18:20 +0100)
committerdle@openerp.com <>
Fri, 30 Nov 2012 17:20:33 +0000 (18:20 +0100)
bzr revid: dle@openerp.com-20121130172033-dinqyw8arg79eh31

addons/l10n_be_coda/l10n_be_coda.py
addons/l10n_be_coda/l10n_be_coda_view.xml
addons/l10n_be_coda/res_bank.py [new file with mode: 0755]
addons/l10n_be_coda/res_bank_view.xml [new file with mode: 0755]
addons/l10n_be_coda/wizard/account_coda_import.py

index 289e757..f9dfa3a 100644 (file)
@@ -2,9 +2,9 @@
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
-#
-#    Copyright (c) 2011 Noviat nv/sa (www.noviat.be). All rights reserved.
-#
+#    
+#    Copyright (c) 2012 Noviat nv/sa (www.noviat.be). All rights reserved.
+# 
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
 #    published by the Free Software Foundation, either version 3 of the
 from osv import osv, fields
 import decimal_precision as dp
 from tools.translate import _
+import logging
+_logger = logging.getLogger(__name__)
 
 class coda_bank_account(osv.osv):
     _name= 'coda.bank.account'
     _description= 'CODA Bank Account Configuration'
 
+    def _check_account_pain(self, cr, uid, context=None):
+        res = self.pool.get('ir.module.module').search(cr, uid, [('name', '=', 'account_pain'), ('state','=','installed')])
+        return res and True or False
+
     def _check_currency(self, cr, uid, ids, context=None):
         obj_cba = self.browse(cr, uid, ids[0], context=context)
-        if (obj_cba.state == 'normal') and obj_cba.journal and (obj_cba.currency != obj_cba.journal.currency and (not obj_cba.journal.currency and  obj_cba.currency != obj_cba.company_id.currency_id)):
-            return False
+        if (obj_cba.state == 'normal') and obj_cba.journal:
+            if obj_cba.journal.currency and (obj_cba.currency != obj_cba.journal.currency):
+                return False
+            if not obj_cba.journal.currency and (obj_cba.currency != obj_cba.company_id.currency_id):
+                return False
         return True
 
     _columns = {
         'name': fields.char('Name', size=64, required=True),
-        'bank_id': fields.many2one('res.partner.bank', 'Bank Account', required=True,
+        'bank_id': fields.many2one('res.partner.bank', 'Bank Account', required=True, 
             help='Bank Account Number.\nThe CODA import function will find its CODA processing parameters on this number.'),
         'description1': fields.char('Primary Account Description', size=35,
             help='The Primary or Secondary Account Description should match the corresponding Account Description in the CODA file.'),
@@ -44,34 +53,47 @@ class coda_bank_account(osv.osv):
             help='The Primary or Secondary Account Description should match the corresponding Account Description in the CODA file.'),
         'state': fields.selection([
             ('normal', 'Normal'),
-            ('info', 'Info')],
+            ('info', 'Info')], 
             'Type', required=True, select=1,
             help='No Bank Statements will be generated for CODA Bank Statements from Bank Accounts of type \'Info\'.'),
-        'journal': fields.many2one('account.journal', 'Journal',
-            domain=[('type', '=', 'bank')],
+        'journal': fields.many2one('account.journal', 'Journal', 
+            domain=[('type', '=', 'bank')], 
             states={'normal':[('required',True)],'info':[('required',False)]},
             help='Bank Journal for the Bank Statement'),
         'currency': fields.many2one('res.currency', 'Currency', required=True,
-            help='The currency of the CODA Bank Statement'),
+            help='The currency of the CODA Bank Statement'),  
         'coda_st_naming': fields.char('Bank Statement Naming Policy', size=64,
             help="Define the rules to create the name of the Bank Statements generated by the CODA processing." \
-                 "\nE.g. %(code)s%(y)s/%(paper)s"
+                 "\nE.g. %(code)s%(y)s/%(paper)s" \
                  "\n\nVariables:" \
                  "\nBank Journal Code: %(code)s" \
-                 "\nCurrent Year with Century: %(year)s" \
-                 "\nCurrent Year without Century: %(y)s" \
+                 "\nYear (of CODA 'New Balance Date') with Century: %(year)s" \
+                 "\nYear (of CODA 'New Balance Date') without Century: %(y)s" \
                  "\nCODA sequence number: %(coda)s" \
-                 "\nPaper Statement sequence number: %(paper)s"),
-        'def_payable': fields.many2one('account.account', 'Default Payable Account', domain=[('type', '=', 'payable')], required=True,
-            help= 'Set here the payable account that will be used, by default, if the partner is not found.'),
-        'def_receivable': fields.many2one('account.account', 'Default Receivable Account', domain=[('type', '=', 'receivable')], required=True,
-            help= 'Set here the receivable account that will be used, by default, if the partner is not found.',),
+                 "\nPaper Statement sequence number (as specified on 'Old Balance' record): %(paper_ob)s" \
+                 "\nPaper Statement sequence number (as specified on 'New Balance' record): %(paper)s"),
         'awaiting_account': fields.many2one('account.account', 'Default Account for Unrecognized Movement', domain=[('type', '!=', 'view')], required=True,
             help= 'Set here the default account that will be used if the partner cannot be unambiguously identified.'),
         'transfer_account': fields.many2one('account.account', 'Default Internal Transfer Account', domain=[('code', 'like', '58%'), ('type', '!=', 'view')], required=True,
             help= 'Set here the default account that will be used for internal transfer between own bank accounts (e.g. transfer between current and deposit bank accounts).'),
-        'find_bbacom': fields.boolean('Lookup Invoice', required=True, help='Partner lookup via the \'BBA\' Structured Communication field of the Invoice.'),
-        'find_partner': fields.boolean('Lookup Partner', required=True, help='Partner lookup via Bank Account Number.'),
+        'account_mapping_ids': fields.one2many('coda.account.mapping.rule', 'coda_bank_account_id', 'Account Mapping Rules'),
+        'find_payment': fields.boolean('Lookup Payment Reference', 
+            help='Invoice lookup and reconciliation via the SEPA EndToEndReference.' \
+                 '\nInstall the \'account_pain\' module if you want to take advantage of this feature.'),
+        'find_bbacom': fields.boolean('Lookup Structured Communication of type \'BBA\'', 
+            help='Partner lookup and reconciliation via the \'BBA\' Structured Communication.' \
+                 '\nA partial reconciliation will be created when there is no exact match between the Invoice and Bank Transaction amounts.'),
+        'find_inv_number': fields.boolean('Lookup Invoice Number',
+            help='Partner lookup and reconciliation via the Invoice Number when a communication in free format is used.' \
+                  '\nA reconciliation will only be created in case of exact match between the Invoice and Bank Transaction amounts.'),
+        'find_partner': fields.boolean('Lookup Partner',
+            help='Partner lookup via Bank Account Number in order to facilitate the reconciliation.'),
+        'update_partner': fields.boolean('Update Partner Bank Accounts',
+            help='Update Partner record when the Counterparty\'s Bank Account has not been registered yet.'),
+        'balance_start_enforce': fields.boolean('Prevent invalid Opening Balances',
+            help="Do not process Statements with an Opening Balance that doesn't match the previous Closing Balance."),
+        'discard_dup': fields.boolean('Discard Duplicates',
+            help="Duplicate Bank Statements will be discarded. Select the corresponding 'CODA Bank Statement' in order to view the contents of such duplicates."),        
         'active': fields.boolean('Active', help='If the active field is set to False, it will allow you to hide the Bank Account without removing it.'),
         'company_id': fields.many2one('res.company', 'Company', required=True),
     }
@@ -79,9 +101,13 @@ class coda_bank_account(osv.osv):
         'currency': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
         'state': 'normal',
         'coda_st_naming': '%(code)s/%(y)s/%(coda)s',
-        'active': True,
+        'active': True,   
+        'find_payment': _check_account_pain,
         'find_bbacom': True,
-        'find_partner': True,
+        'find_inv_number': True,                          
+        'find_partner': True,        
+        'update_partner': True,     
+        'balance_start_enforce': True,
         'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id,
     }
     _sql_constraints = [
@@ -107,19 +133,20 @@ class coda_bank_account(osv.osv):
         if not default:
             default = {}
         default = default.copy()
-        default.update(
-            journal_id=None,
-            description1=cba['description1'] or '',
-            description2=cba['description2'] or '',
-            name=_("%s (copy)") % (cba['name'] or ''),
-            state=cba['state'])
-        return super(coda_bank_account, self).copy(cr, uid, id, default, context)
+        default.update({'journal_id': None})     
+        default['description1'] = cba['description1'] or ''
+        default['description2'] = cba['description2'] or ''
+        default['name'] = (cba['name'] or '') + ' (copy)'
+        default['state'] = cba['state']        
+        return super(coda_bank_account, self).copy(cr, uid, id, default, context) 
 
     def onchange_state(self, cr, uid, ids, state):
         return state =='info' and {'value': {'journal': None}} or {}
 
 coda_bank_account()
 
+
+
 class account_coda(osv.osv):
     _name = 'account.coda'
     _description = 'Object to store CODA Data Files'
@@ -138,10 +165,10 @@ class account_coda(osv.osv):
         'date': fields.date.context_today,
         'user_id': lambda self,cr,uid,context: uid,
         'company_id': lambda s,cr,uid,c: s.pool.get('res.company')._company_default_get(cr, uid, 'account.coda', context=c),
-    }
+    }        
     _sql_constraints = [
         ('coda_uniq', 'unique (name, coda_creation_date)', 'This CODA has already been imported !')
-    ]
+    ]  
 
     def unlink(self, cr, uid, ids, context=None):
         if context is None:
@@ -150,79 +177,73 @@ class account_coda(osv.osv):
         coda_st_obj = self.pool.get('coda.bank.statement')
         bank_st_obj = self.pool.get('account.bank.statement')
         for coda in self.browse(cr, uid, ids, context=context):
-            for coda_statement in coda.statement_ids:
+            for coda_statement in coda.statement_ids:                 
                 if not context.get('coda_statement_unlink', False):
                     if coda_st_obj.exists(cr, uid, coda_statement.id, context=context):
-                        coda_st_obj.unlink(cr, uid, [coda_statement.id], context=context)
+                        coda_st_obj.unlink(cr, uid, [coda_statement.id], context=context)    
                 if not context.get('bank_statement_unlink', False):
                     if coda_st_obj.exists(cr, uid, coda_statement.id, context=context) and (coda_statement.type == 'normal') and bank_st_obj.exists(cr, uid, coda_statement.statement_id.id, context=context):
-                        bank_st_obj.unlink(cr, uid, [coda_statement.statement_id.id], context=context)
+                        bank_st_obj.unlink(cr, uid, [coda_statement.statement_id.id], context=context)                   
         context.update({'coda_unlink': False})
         return super(account_coda, self).unlink(cr, uid, ids, context=context)
-
+  
 account_coda()
 
-class account_coda_trans_type(osv.osv):
+class account_coda_trans_type(osv.osv):  
     _name = 'account.coda.trans.type'
     _description = 'CODA transaction type'
-    _rec_name = 'type'
+    _rec_name = 'type' 
     _columns = {
         'type': fields.char('Transaction Type', size=1, required=True),
         'parent_id': fields.many2one('account.coda.trans.type', 'Parent'),
         'description': fields.text('Description', translate=True),
     }
-
 account_coda_trans_type()
 
-class account_coda_trans_code(osv.osv):
+class account_coda_trans_code(osv.osv):  
     _name = 'account.coda.trans.code'
     _description = 'CODA transaction code'
-    _rec_name = 'code'
+    _rec_name = 'code' 
     _columns = {
         'code': fields.char('Code', size=2, required=True, select=1),
         'type': fields.selection([
                 ('code', 'Transaction Code'),
-                ('family', 'Transaction Family')],
-                'Type', required=True, select=1),
+                ('family', 'Transaction Family')], 
+                'Type', required=True, select=1), 
         'parent_id': fields.many2one('account.coda.trans.code', 'Family', select=1),
         'description': fields.char('Description', size=128, translate=True, select=2),
         'comment': fields.text('Comment', translate=True),
     }
-
 account_coda_trans_code()
 
-class account_coda_trans_category(osv.osv):
+class account_coda_trans_category(osv.osv):  
     _name = 'account.coda.trans.category'
     _description = 'CODA transaction category'
-    _rec_name = 'category'
+    _rec_name = 'category' 
     _columns = {
         'category': fields.char('Transaction Category', size=3, required=True),
         'description': fields.char('Description', size=256, translate=True),
     }
-
 account_coda_trans_category()
 
-class account_coda_comm_type(osv.osv):
+class account_coda_comm_type(osv.osv):  
     _name = 'account.coda.comm.type'
     _description = 'CODA structured communication type'
-    _rec_name = 'code'
+    _rec_name = 'code' 
     _columns = {
         'code': fields.char('Structured Communication Type', size=3, required=True, select=1),
         'description': fields.char('Description', size=128, translate=True),
     }
     _sql_constraints = [
         ('code_uniq', 'unique (code)','The Structured Communication Code must be unique !')
-    ]
-
+        ]
 account_coda_comm_type()
 
 class coda_bank_statement(osv.osv):
-    _name = 'coda.bank.statement'
-    _description = 'CODA Bank Statement'
-
-    def _default_journal_id(self, cr, uid, context=None):
-        if context is None:
-            context = {}
+    _name = 'coda.bank.statement' 
+    _description = 'CODA Bank Statement'    
+    
+    def _default_journal_id(self, cr, uid, context={}):
         if context.get('journal_id', False):
             return context['journal_id']
         return False
@@ -238,7 +259,7 @@ class coda_bank_statement(osv.osv):
             res[r] = round(res[r], 2)
         return res
 
-    def _get_period(self, cr, uid, context=None):
+    def _get_period(self, cr, uid, context={}):
         periods = self.pool.get('account.period').find(cr, uid)
         if periods:
             return periods[0]
@@ -249,19 +270,22 @@ class coda_bank_statement(osv.osv):
     _columns = {
         'name': fields.char('Name', size=64, required=True, readonly=True),
         'date': fields.date('Date', required=True, readonly=True),
+        'coda_creation_date': fields.date('CODA Creation Date', readonly=True),
+        'old_balance_date': fields.date('Old Balance Date', readonly=True),
+        'new_balance_date': fields.date('New Balance Date', readonly=True),
         'coda_id': fields.many2one('account.coda', 'CODA Data File', ondelete='cascade'),
         'type': fields.selection([
             ('normal', 'Normal'),
-            ('info', 'Info')],
+            ('info', 'Info')], 
             'Type', required=True, readonly=True,
             help='No Bank Statements are associated with CODA Bank Statements of type \'Info\'.'),
-        'statement_id': fields.many2one('account.bank.statement', 'Associated Bank Statement'),
+        'statement_id': fields.many2one('account.bank.statement', 'Associated Bank Statement'),        
         'journal_id': fields.many2one('account.journal', 'Journal', readonly=True, domain=[('type', '=', 'bank')]),
-        'coda_bank_account_id': fields.many2one('coda.bank.account', 'Bank Account', readonly=True),
+        'coda_bank_account_id': fields.many2one('coda.bank.account', 'Bank Account', readonly=True),        
         'period_id': fields.many2one('account.period', 'Period', required=True, readonly=True),
         'balance_start': fields.float('Starting Balance', digits_compute=dp.get_precision('Account'), readonly=True),
         'balance_end_real': fields.float('Ending Balance', digits_compute=dp.get_precision('Account'), readonly=True),
-        'balance_end': fields.function(_end_balance, method=True, store=True, string='Balance'),
+        'balance_end': fields.function(_end_balance, method=True, store=True, string='Balance'),        
         'line_ids': fields.one2many('coda.bank.statement.line',
             'statement_id', 'CODA Bank Statement lines', readonly=True),
         'currency': fields.many2one('res.currency', 'Currency', required=True, readonly=True,
@@ -269,14 +293,14 @@ class coda_bank_statement(osv.osv):
         'company_id': fields.related('journal_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
     }
     _defaults = {
-        'type': 'normal',
+        'type': 'normal',        
         'currency': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.currency_id.id,
         'journal_id': _default_journal_id,
         'period_id': _get_period,
     }
 
     def search(self, cr, uid, args, offset=0, limit=None, order=None, context=None, count=False):
-        if context is None:
+        if context is None: 
             context = {}
         res = super(coda_bank_statement, self).search(cr, uid, args=args, offset=offset, limit=limit, order=order,
                 context=context, count=count)
@@ -290,20 +314,20 @@ class coda_bank_statement(osv.osv):
         context.update({'coda_statement_unlink': True})
         coda_obj = self.pool.get('account.coda')
         bank_st_obj = self.pool.get('account.bank.statement')
-
+        
         # find all CODA bank statements that are associated with the selected CODA bank statements via a common CODA file
-        new_ids = []
+        new_ids = []       
         for coda_statement in self.browse(cr, uid, ids, context=context):
             if coda_obj.exists(cr, uid, coda_statement.coda_id.id, context=context):
                 new_ids += [x.id for x in coda_obj.browse(cr, uid, coda_statement.coda_id.id, context=context).statement_ids]
 
-        # unlink CODA banks statements as well as associated bank statements and CODA files
+        # unlink CODA banks statements as well as associated bank statements and CODA files               
         for coda_statement in self.browse(cr, uid, new_ids, context=context):
-            if coda_statement.statement_id.state == 'confirm':
-                raise osv.except_osv(_('Invalid Action!'),
-                    _("Cannot delete CODA Bank Statement '%s' of journal '%s'." \
-                      "\nThe associated Bank Statement has already been confirmed." \
-                      "\nPlease undo this action first.") \
+            if coda_statement.statement_id.state == 'confirm': 
+                raise osv.except_osv(_('Invalid action !'),
+                    _("Cannot delete CODA Bank Statement '%s' of Journal '%s'." \
+                      "\nThe associated Bank Statement has already been confirmed !" \
+                      "\nPlease undo this action first!") \
                       % (coda_statement.name, coda_statement.journal_id.name))
             else:
                 if not context.get('coda_unlink', False):
@@ -311,11 +335,11 @@ class coda_bank_statement(osv.osv):
                         coda_obj.unlink(cr, uid, [coda_statement.coda_id.id], context=context)
                 if not context.get('bank_statement_unlink', False):
                     if coda_statement.statement_id and bank_st_obj.exists(cr, uid, coda_statement.statement_id.id, context=context):
-                        bank_st_obj.unlink(cr, uid, [coda_statement.statement_id.id], context=context)
+                        bank_st_obj.unlink(cr, uid, [coda_statement.statement_id.id], context=context)    
 
         context.update({'coda_statement_unlink': False})
         return super(coda_bank_statement, self).unlink(cr, uid, new_ids, context=context)
-
 coda_bank_statement()
 
 class account_bank_statement(osv.osv):
@@ -323,7 +347,10 @@ class account_bank_statement(osv.osv):
     _columns = {
         'coda_statement_id': fields.many2one('coda.bank.statement', 'Associated CODA Bank Statement'),
     }
-
+    _sql_constraints = [
+        ('name_company_uniq', 'unique (name,company_id)', 'The name of the Bank Statement must be unique per company !')
+    ]
+    
     def unlink(self, cr, uid, ids, context=None):
         if context is None:
             context = {}
@@ -332,15 +359,15 @@ class account_bank_statement(osv.osv):
         coda_st_obj = self.pool.get('coda.bank.statement')
 
         # find all statements that are associated with the selected bank statements via a common CODA file
-        ids_plus = []
+        ids_plus = []       
         for statement in self.browse(cr, uid, ids, context=context):
             if statement.coda_statement_id:
                 for x in coda_obj.browse(cr, uid, statement.coda_statement_id.coda_id.id, context=context).statement_ids:
                     if x.type == 'normal':
                         ids_plus += [x.statement_id.id]
-
+                
         # unlink banks statements as well as associated CODA bank statements and CODA files
-        for statement in self.browse(cr, uid, ids_plus, context=context):
+        for statement in self.browse(cr, uid, ids_plus, context=context):       
             if not context.get('coda_statement_unlink', False):
                 if statement.coda_statement_id and coda_st_obj.exists(cr, uid, statement.coda_statement_id.id, context=context):
                     coda_st_obj.unlink(cr, uid, [statement.coda_statement_id.id], context=context)
@@ -354,63 +381,109 @@ class account_bank_statement(osv.osv):
         context.update({'bank_statement_unlink': False})
         new_ids = list(set(ids + ids_plus))
         return super(account_bank_statement, self).unlink(cr, uid, new_ids, context=context)
-
+         
 account_bank_statement()
 
 class coda_bank_statement_line(osv.osv):
-    _name = 'coda.bank.statement.line'
-    _order = 'sequence'
+    _name = 'coda.bank.statement.line'     
+    _order = 'sequence'   
     _description = 'CODA Bank Statement Line'
     _columns = {
         'name': fields.char('Communication', size=268, required=True),
         'sequence': fields.integer('Sequence'),
         'date': fields.date('Entry Date', required=True),
-        'val_date': fields.date('Valuta Date'),
-        'account_id': fields.many2one('account.account','Account'),     # remove required=True
+        'val_date': fields.date('Valuta Date'),        
+        'account_id': fields.many2one('account.account','Account'),
         'type': fields.selection([
             ('supplier','Supplier'),
             ('customer','Customer'),
             ('general','General'),
-            ('globalisation','Globalisation'),
-            ('information','Information'),
-            ('communication','Free Communication'),
+            ('globalisation','Globalisation'),            
+            ('information','Information'),    
+            ('communication','Free Communication'),          
             ], 'Type', required=True),
-        'globalisation_level': fields.integer('Globalisation Level',
-                help="The value which is mentioned (1 to 9), specifies the hierarchy level"
-                     " of the globalisation of which this record is the first."
-                     "\nThe same code will be repeated at the end of the globalisation."),
-        'globalisation_amount': fields.float('Globalisation Amount', digits_compute=dp.get_precision('Account')),
+        'globalisation_level': fields.integer('Globalisation Level', 
+            help="The value which is mentioned (1 to 9), specifies the hierarchy level"
+                 " of the globalisation of which this record is the first."
+                 "\nThe same code will be repeated at the end of the globalisation."),
+        'globalisation_amount': fields.float('Globalisation Amount', digits_compute=dp.get_precision('Account')),      
         'globalisation_id': fields.many2one('account.bank.statement.line.global', 'Globalisation ID', readonly=True,
-            help="Code to identify transactions belonging to the same globalisation level within a batch payment"),
+            help="Code to identify transactions belonging to the same globalisation level within a batch payment"),                                                     
         'amount': fields.float('Amount', digits_compute=dp.get_precision('Account')),
         'partner_id': fields.many2one('res.partner', 'Partner'),
         'counterparty_name': fields.char('Counterparty Name', size=35),
-        'counterparty_bic': fields.char('Counterparty BIC', size=11),
-        'counterparty_number': fields.char('Counterparty Number', size=34),
-        'counterparty_currency': fields.char('Counterparty Currency', size=3),
+        'counterparty_bic': fields.char('Counterparty BIC', size=11),                     
+        'counterparty_number': fields.char('Counterparty Number', size=34),   
+        'counterparty_currency': fields.char('Counterparty Currency', size=3), 
+        'payment_reference': fields.char('Payment Reference', size=35,
+            help="Payment Reference. For SEPA (SCT or SDD) transactions, the PaymentInformationIdentification "\
+                 "is recorded in this field pertaining to a globalisation, and the EndToEndReference for " \
+                 "simple transactions or for the details of a globalisation."),       
         'statement_id': fields.many2one('coda.bank.statement', 'CODA Bank Statement',
             select=True, required=True, ondelete='cascade'),
         'coda_bank_account_id': fields.related('statement_id', 'coda_bank_account_id', type='many2one', relation='coda.bank.account', string='Bank Account', store=True, readonly=True),
         'ref': fields.char('Reference', size=32),
         'note': fields.text('Notes'),
-        'company_id': fields.related('statement_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
+        'company_id': fields.related('statement_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),        
     }
 
     def unlink(self, cr, uid, ids, context=None):
         if context is None:
             context = {}
         if context.get('block_statement_line_delete', False):
-            raise osv.except_osv('Warning', _('Delete operation not allowed.'))
+            raise osv.except_osv('Warning', _('Delete operation not allowed !'))
         return super(account_bank_statement_line, self).unlink(cr, uid, ids, context=context)
 
-coda_bank_statement_line()
+coda_bank_statement_line()       
 
 class account_bank_statement_line_global(osv.osv):
     _inherit = 'account.bank.statement.line.global'
     _columns = {
         'coda_statement_line_ids': fields.one2many('coda.bank.statement.line', 'globalisation_id', 'CODA Bank Statement Lines', readonly=True),
     }
-
 account_bank_statement_line_global()
 
+class coda_account_mapping_rule(osv.osv):
+    _name = 'coda.account.mapping.rule'
+    _description = 'Rules Engine to assign accounts during CODA parsing'
+    _order = 'sequence'
+    _columns = {
+        'coda_bank_account_id': fields.many2one('coda.bank.account', 'CODA Bank Account', ondelete='cascade'),
+        'sequence': fields.integer('Sequence', help='Determines the order of the rules to assign accounts'),
+        'name': fields.char('Rule Name', size=128, required=True),                
+        # matching criteria
+        'trans_type_id': fields.many2one('account.coda.trans.type', 'Transaction Type'),
+        'trans_family_id': fields.many2one('account.coda.trans.code', 'Transaction Family', domain=[('type', '=', 'family')]),
+        'trans_code_id': fields.many2one('account.coda.trans.code', 'Transaction Code', domain=[('type', '=', 'code')]),
+        'trans_category_id': fields.many2one('account.coda.trans.category', 'Transaction Category'),
+        'struct_comm_type_id': fields.many2one('account.coda.comm.type', 'Structured Communication Type'),
+        'partner_id': fields.many2one('res.partner', 'Partner', ondelete='cascade'),
+        # resulting general account
+        'account_id': fields.many2one('account.account', 'Account', ondelete='cascade', required=True, domain=[('type', '!=', 'view')]),
+        'active': fields.boolean('Active', help='Switch on/off this rule.'),
+    }
+    _defaults = {
+        'active': True,
+    }
+   
+    def account_id_get(self, cr, uid, coda_bank_account_id, trans_type_id=None, trans_family_id=None, 
+            trans_code_id=None, trans_category_id=None, struct_comm_type_id=None, partner_id=None, context=None):       
+        cr.execute('SELECT account_id, trans_type_id, trans_family_id, trans_code_id, trans_category_id, struct_comm_type_id, partner_id ' \
+            'FROM coda_account_mapping_rule ' \
+            'WHERE active = TRUE AND coda_bank_account_id = %s ' \
+            'ORDER BY sequence' % coda_bank_account_id
+        )
+        rules = cr.fetchall()    
+        condition = '(not rule[1] or (trans_type_id == rule[1])) and (not rule[2] or (trans_family_id == rule[2])) ' \
+            'and (not rule[3] or (trans_code_id == rule[3])) and (not rule[4] or (trans_category_id == rule[4])) ' \
+            'and (not rule[5] or (struct_comm_type_id == rule[5])) and (not rule[6] or (partner_id == rule[6]))'  
+        account_id = None
+        for rule in rules:
+            if eval(condition):
+                account_id = rule[0]
+                break
+        return account_id
+
+coda_account_mapping_rule()
+
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
index 1f01440..fb0a12b 100644 (file)
         <field name="model">coda.bank.account</field>
         <field name="arch" type="xml">
             <search string="CODA Bank Account Configuration">
-                <field name="name" filter_domain="['|', ('name','ilike',self), ('description1','ilike',self)]" string="CODA Bank Account"/>
                 <filter string="Normal" domain="[('state','=','normal')]" icon="terp-folder-green"/>
                 <filter string="Info" domain="[('state','=','info')]" icon="terp-folder-yellow"/>
+                <field name="name"/>
                 <field name="bank_id"/>
+                <field name="description1"/>
                 <field name="journal"/>
                 <field name="currency" groups="base.group_multi_currency"/>
                 <field name="company_id" groups="base.group_multi_company"/>
                 <field name="state" on_change="onchange_state(state)"/>
                 <field name="journal" attrs="{'invisible':[('state','=','info')]}"/>
                 <newline/>
-                <field name="def_payable"/>
-                <field name="def_receivable"/>
-                <field name="awaiting_account"/>
-                <field name="transfer_account"/>
+                <field name="find_payment"/>
                 <field name="find_bbacom"/>
+                <field name="find_inv_number"/>
                 <field name="find_partner"/>
-                <field name="company_id" widget='selection' groups="base.group_multi_company"/>
+                <field name="update_partner"/>
+                <field name="balance_start_enforce"/>
+                <field name="discard_dup"/>
                 <field name="active"/>
+                <field name="company_id" widget='selection' groups="base.group_multi_company"/>
             </group>
+            <notebook colspan="4">
+            <page string="Default Accounts">
+              <group string="Default Account Mapping">
+                  <field name="awaiting_account"/>
+                  <field name="transfer_account"/>
+              </group>
+            </page>
+            <page string="Accounts Mapping">
+              <field colspan="4" name="account_mapping_ids" nolabel="1">
+                <tree string="Account Mapping Rules">
+                  <field name="sequence"/>
+                  <field name="name"/>
+                  <field name="trans_type_id"/>
+                  <field name="trans_family_id"/>
+                  <field name="trans_code_id"/>
+                  <field name="trans_category_id"/>
+                  <field name="struct_comm_type_id"/>
+                  <field name="partner_id"/>
+                  <field name="account_id"/>
+                </tree>
+                <form string="Account Mapping Rules">
+                  <field name="name"/>
+                  <field name="account_id"/>
+                  <field name="sequence"/>
+                  <field name="active"/>
+                  <separator string="Conditions" colspan="4"/>
+                  <field name="trans_type_id"/>
+                  <field name="trans_family_id"/>
+                  <field name="trans_code_id"/>
+                  <field name="trans_category_id"/>
+                  <field name="struct_comm_type_id"/>
+                  <field name="partner_id"/>
+                </form>
+              </field>
+            </page>
+          </notebook>
         </form>
       </field>
     </record>
       <field name="name">coda.bank.statement.list</field>
       <field name="model">coda.bank.statement</field>
       <field name="arch" type="xml">
-        <tree colors="red:balance_end_real!=balance_end;blue:state=='draft' and (balance_end_real==balance_end)" string="CODA Bank Statements" create="false">
+        <tree colors="red:balance_end_real!=balance_end" string="CODA Bank Statements">
           <field name="name"/>
-          <field name="date"/>
-          <field name="period_id"/>
+          <field name="coda_creation_date"/>
           <field name="coda_bank_account_id"/>
+          <field name="old_balance_date"/>
+          <field name="new_balance_date"/>
           <field name="balance_start"/>
           <field name="balance_end_real"/>
           <field name="balance_end"/>
       <field name="name">coda.bank.statement.form</field>
       <field name="model">coda.bank.statement</field>
       <field name="arch" type="xml">
-          <form string="CODA Bank Statement" version="7.0" create="false">
-              <sheet>
-                  <group col="4">
-                      <field name="name"/>
-                      <field name="date"/>
-                      <field name="coda_bank_account_id"/>
-                      <field name="currency" groups="base.group_multi_currency"/>
-                      <field name="period_id"/>
-                      <field name="type"/>
-                      <newline/>
-                      <field name="balance_start"/>
-                      <field name="balance_end_real"/>
-                  </group>
-                  <notebook>
-                    <page string="Transactions">
-                      <field name="line_ids">
-                        <tree string="CODA Statement Lines">
-                          <field name="sequence" string="Seq"/>
-                          <field name="date"/>
-                          <field name="val_date"/>
-                          <field name="ref"/>
-                          <field name="name" width="250"/>
-                          <field name="type"/>
-                          <field name="partner_id"/>
-                          <field name="account_id"/>
-                          <field name="amount"/>
-                          <field name="globalisation_amount" string="Glob. Amount"/>
-                          <field name="globalisation_id" string="Glob. Id"/>
-                        </tree>
-                        <form string="CODA Statement Lines" version="7.0">
-                          <group col="4">
-                              <field name="sequence" string="Seq"/>
-                              <field name="date"/>
-                              <field name="val_date"/>
-                              <field name="name"/>
-                              <field name="type"/>
-                              <field name="partner_id"/>
-                              <field domain="[('type', '&lt;&gt;', 'view')]" name="account_id"/>
-                              <field name="amount"/>
-                              <field name="ref"/>
-                              <field name="globalisation_amount"/>
-                              <field name="globalisation_level"/>
-                              <field name="globalisation_id"/>
-                          </group>
-                          <separator string="Notes"/>
-                          <field name="note"/>
-                        </form>
-                      </field>
-                    </page>
-                  </notebook>
-                  <group colspan="4">
-                    <field name="balance_end"/>
-                  </group>
-              </sheet>
-          </form>
+          <form string="CODA Bank Statement">
+          <field name="name"/>
+          <field name="coda_creation_date"/>
+          <field name="coda_bank_account_id"/>
+          <field name="currency"/>
+          <field name="type"/>
+          <newline/>
+          <field name="balance_start"/>
+          <field name="balance_end_real"/>
+          <field name="old_balance_date"/>
+          <field name="new_balance_date"/>
+          <notebook colspan="4">
+            <page string="Transactions">
+              <field colspan="4" name="line_ids" nolabel="1">
+                <tree string="CODA Statement Lines">
+                  <field name="sequence" string="Seq"/>
+                  <field name="date"/>
+                  <field name="val_date"/>
+                  <field name="ref"/>
+                  <field name="name" width="250"/>
+                  <field name="type"/>
+                  <field name="partner_id"/>
+                  <field name="account_id"/>
+                  <field name="amount"/>
+                  <field name="globalisation_amount" string="Glob. Amount"/>
+                  <field name="globalisation_id" string="Glob. Id"/>
+                </tree>
+                <form string="CODA Statement Lines">
+                  <field name="sequence" string="Seq"/>
+                  <field name="date"/>
+                  <field name="val_date"/>
+                  <field name="name"/>
+                  <field name="type"/>
+                  <field name="partner_id"/>
+                  <field domain="[('type', '&lt;&gt;', 'view')]" name="account_id"/>
+                  <field name="amount"/>
+                  <field name="ref"/>
+                  <field name="globalisation_amount"/>
+                  <field name="globalisation_level"/>
+                  <field name="globalisation_id"/>
+                  <separator colspan="4" string="Notes"/>
+                  <field colspan="4" name="note" nolabel="1"/>
+                </form>
+              </field>
+            </page>
+          </notebook>
+          <group colspan="4">
+            <field name="balance_end"/>
+          </group>
+        </form>
       </field>
     </record>
 
         <field name="model">coda.bank.statement</field>
         <field name="arch" type="xml">
             <search string="Search CODA Bank Statements">
-                <field name="name" string="CODA Bank Statement"/>
-                <field name="date"/>
                 <filter string="Normal" domain="[('type','=','normal')]" icon="terp-folder-green"/>
                 <filter string="Info" domain="[('type','=','info')]" icon="terp-folder-yellow"/>
+                <field name="name"/>
+                <field name="coda_creation_date"/>
+                <field name="old_balance_date"/>
+                <field name="new_balance_date"/>
                 <field name="period_id"/>
                 <field name="coda_bank_account_id"/>
                 <field name="journal_id" domain="[('type', '=', 'bank')]"/>
                     <field name="amount"/>
                     <field name="globalisation_amount" string="Glob. Amount"/>
                     <field name="type"/>
+                    <field name="ref"/>
                     <field name="note"/>
                 </group>
                 <newline/>
diff --git a/addons/l10n_be_coda/res_bank.py b/addons/l10n_be_coda/res_bank.py
new file mode 100755 (executable)
index 0000000..6a35815
--- /dev/null
@@ -0,0 +1,33 @@
+# -*- encoding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    
+#    Copyright (c) 2012 Noviat nv/sa (www.noviat.be). All rights reserved.
+# 
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from osv import fields, osv
+
+class res_bank(osv.osv):
+    _inherit = 'res.bank'
+    _columns = {
+        'code': fields.char('Code', size=3,
+            help='Country specific Bank Code (used for bban to iban conversion).'),
+    }
+res_bank()
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/l10n_be_coda/res_bank_view.xml b/addons/l10n_be_coda/res_bank_view.xml
new file mode 100755 (executable)
index 0000000..fe4a71d
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+  <data>
+
+    <record id="view_res_bank_form_add_code" model="ir.ui.view">
+      <field name="name">res.bank.form.add.code</field>
+      <field name="model">res.bank</field>
+      <field name="inherit_id" ref="base.view_res_bank_form"/>
+      <field name="type">form</field>
+      <field name="arch" type="xml">
+        <field name="active" position="after">
+          <field name="code" select="1"/>
+        </field>
+      </field>
+    </record>
+
+    <record id="view_res_bank_tree_add_code" model="ir.ui.view">
+      <field name="name">res.bank.tree.add.code</field>
+      <field name="model">res.bank</field>
+      <field name="inherit_id" ref="base.view_res_bank_tree"/>
+      <field name="type">form</field>
+      <field name="arch" type="xml">
+        <field name="country" position="after">
+          <field name="code"/>
+        </field>
+      </field>
+    </record>
+
+  </data>
+</openerp>
+
index 46c545f..6f055ca 100644 (file)
@@ -2,9 +2,9 @@
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
-#
-#    Copyright (c) 2011 Noviat nv/sa (www.noviat.be). All rights reserved.
-#
+#    
+#    Copyright (c) 2012 Noviat nv/sa (www.noviat.be). All rights reserved.
+# 
 #    This program is free software: you can redistribute it and/or modify
 #    it under the terms of the GNU Affero General Public License as
 #    published by the Free Software Foundation, either version 3 of the
@@ -24,11 +24,11 @@ import time
 import base64
 from osv import fields,osv
 from tools.translate import _
-import logging
+import netsvc
 import re
 from traceback import format_exception
 from sys import exc_info
-from base_iban import base_iban
+import logging
 _logger = logging.getLogger(__name__)
 
 class account_coda_import(osv.osv_memory):
@@ -43,6 +43,10 @@ class account_coda_import(osv.osv_memory):
         'coda_fname': lambda *a: '',
     }
 
+    def _check_account_payment(self, cr, uid, context=None):
+        res = self.pool.get('ir.module.module').search(cr, uid, [('name', '=', 'account_payment'), ('state','=','installed')])
+        return res and True or False
+
     def coda_parsing(self, cr, uid, ids, context=None, batch=False, codafile=None, codafilename=None):
         if context is None:
             context = {}
@@ -53,12 +57,12 @@ class account_coda_import(osv.osv_memory):
             data=self.browse(cr,uid,ids)[0]
             try:
                 codafile = data.coda_data
-                codafilename = data.coda_fname
+                codafilename = data.coda_fname            
             except:
-                raise osv.except_osv(_('Error!'), _('Wizard in incorrect state. Please hit the Cancel button.'))
+                raise osv.except_osv(_('Error!'), _('Wizard in incorrect state. Please hit the Cancel button!'))
                 return {}
 
-        currency_obj = self.pool.get('res.currency')
+        currency_obj = self.pool.get('res.currency')    
         coda_bank_account_obj = self.pool.get('coda.bank.account')
         trans_type_obj = self.pool.get('account.coda.trans.type')
         trans_code_obj = self.pool.get('account.coda.trans.code')
@@ -80,13 +84,23 @@ class account_coda_import(osv.osv_memory):
         voucher_line_obj = self.pool.get('account.voucher.line')
         seq_obj = self.pool.get('ir.sequence')
         mod_obj = self.pool.get('ir.model.data')
+        partner_obj = self.pool.get('res.partner')        
+        account_mapping_obj = self.pool.get('coda.account.mapping.rule')
+        
+        if self._check_account_payment(cr, uid):
+            payment_line_obj = self.pool.get('payment.line')
+        else:
+            payment_line_obj = False
+                  
+        wf_service = netsvc.LocalService('workflow')
+        be_country_id = self.pool.get('res.country').search(cr, uid, [('code', '=', 'BE')])[0]
 
         coda_bank_table = coda_bank_account_obj.read(cr, uid, coda_bank_account_obj.search(cr, uid, []), context=context)
         for coda_bank in coda_bank_table:
             coda_bank.update({'journal_code': coda_bank['journal'] and journal_obj.browse(cr, uid, coda_bank['journal'][0], context=context).code or ''})
             coda_bank.update({'iban': partner_bank_obj.browse(cr, uid, coda_bank['bank_id'][0], context=context).iban})
             coda_bank.update({'acc_number': partner_bank_obj.browse(cr, uid, coda_bank['bank_id'][0], context=context).acc_number})
-            coda_bank.update({'currency_name': currency_obj.browse(cr, uid, coda_bank['currency'][0], context=context).name})
+            coda_bank.update({'currency_name': currency_obj.browse(cr, uid, coda_bank['currency'][0], context=context).name})            
         trans_type_table = trans_type_obj.read(cr, uid, trans_type_obj.search(cr, uid, []), context=context)
         trans_code_table = trans_code_obj.read(cr, uid, trans_code_obj.search(cr, uid, []), context=context)
         trans_category_table = trans_category_obj.read(cr, uid, trans_category_obj.search(cr, uid, []), context=context)
@@ -97,9 +111,10 @@ class account_coda_import(osv.osv_memory):
         err_log = ''
         coda_statements = []
         recordlist = unicode(base64.decodestring(codafile), 'windows-1252', 'strict').split('\n')
-
+        digits = self.pool.get('decimal.precision').precision_get(cr, uid, 'Account')
+        
         for line in recordlist:
-
+            
             if not line:
                 pass
             elif line[0] == '0':
@@ -108,42 +123,35 @@ class account_coda_import(osv.osv_memory):
                 coda_parsing_note = ''
                 coda_statement_lines = {}
                 st_line_seq = 0
-                glob_lvl_stack = [0]
                 # header data
-                coda_statement['currency'] = 'EUR'   # default currency
+                coda_statement['currency'] = 'EUR'   # default currency                
                 coda_statement['version'] = line[127]
                 coda_version = line[127]
                 if coda_version not in ['1','2']:
-                    err_string = _('\nCODA V%s statements are not supported, please contact your bank.') % coda_version
+                    err_string = _('\nCODA V%s statements are not supported, please contact your bank!') % coda_version
                     err_code = 'R0001'
                     if batch:
                         return (err_code, err_string)
                     raise osv.except_osv(_('Data Error!'), err_string)
                 coda_statement['coda_statement_lines'] = {}
                 coda_statement['date'] = str2date(line[5:11])
-                period_id = period_obj.search(cr , uid, [('date_start' ,'<=', coda_statement['date']), ('date_stop','>=',coda_statement['date'])])
-                if not period_id:
-                    err_string = _("\nThe CODA creation date doesn't fall within a defined Accounting Period." \
-                          "\nPlease create the Accounting Period for date %s.") % coda_statement['date']
-                    err_code = 'R0002'
-                    if batch:
-                        return (err_code, err_string)
-                    raise osv.except_osv(_('Data Error!'), err_string)
-                coda_statement['period_id'] = period_id[0]
+                coda_statement['coda_creation_date'] = str2date(line[5:11])
+                coda_statement['separate_application'] = line[83:88]
+                coda_statement['first_transaction_date'] = False                             
                 coda_statement['state'] = 'draft'
-
+        
                 coda_id = coda_obj.search(cr, uid,[
                     ('name', '=', codafilename),
                     ('coda_creation_date', '=', coda_statement['date']),
                     ])
                 if coda_id:
-                    err_string = _("\nCODA File with Filename '%s' and Creation Date '%s' has already been imported.") \
+                    err_string = _("\nCODA File with Filename '%s' and Creation Date '%s' has already been imported !") \
                         % (codafilename, coda_statement['date'])
                     err_code = 'W0001'
                     if batch:
                         return (err_code, err_string)
-                    raise osv.except_osv(_('Warning!'), err_string)
-
+                    raise osv.except_osv(_('Warning !'), err_string)
+                
             elif line[0] == '1':
                 if coda_version == '1':
                     coda_statement['acc_number'] = line[5:17]
@@ -151,47 +159,56 @@ class account_coda_import(osv.osv_memory):
                         coda_statement['currency'] = line[18:21]
                 elif line[1] == '0':                                # Belgian bank account BBAN structure
                     coda_statement['acc_number'] = line[5:17]
-                    coda_statement['currency'] = line[18:21]
+                    coda_statement['currency'] = line[18:21]             
                 elif line[1] == '1':    # foreign bank account BBAN structure
-                    err_string = _('\nForeign bank accounts with BBAN structure are not supported.')
+                    err_string = _('\nForeign bank accounts with BBAN structure are not supported !')
                     err_code = 'R1001'
                     if batch:
                         return (err_code, err_string)
                     raise osv.except_osv(_('Data Error!'), err_string)
                 elif line[1] == '2':    # Belgian bank account IBAN structure
-                    coda_statement['acc_number']=line[5:21]
+                    coda_statement['acc_number']=line[5:21] 
                     coda_statement['currency'] = line[39:42]
                 elif line[1] == '3':    # foreign bank account IBAN structure
-                    err_string = _('\nForeign bank accounts with IBAN structure are not supported.')
+                    err_string = _('\nForeign bank accounts with IBAN structure are not supported !')
                     err_code = 'R1002'
                     if batch:
                         return (err_code, err_string)
                     raise osv.except_osv(_('Data Error!'), err_string)
                 else:
-                    err_string = _('\nUnsupported bank account structure.')
+                    err_string = _('\nUnsupported bank account structure !')
                     err_code = 'R1003'
                     if batch:
                         return (err_code, err_string)
                     raise osv.except_osv(_('Data Error!'), err_string)
                 coda_statement['description'] = line[90:125].strip()
-                cba_filter = lambda x: ((coda_statement['acc_number'] in (x['iban'] or '')) or (coda_statement['acc_number'] == x['acc_number'])) \
-                    and (coda_statement['currency'] == x['currency_name']) and (coda_statement['description'] == (x['description1'] or x['description2'] or ''))
+                cba_filter = lambda x: (coda_statement['acc_number'] in _get_acc_numbers(x['acc_number'])) \
+                    and (coda_statement['currency'] == x['currency_name']) and (coda_statement['description'] in [x['description1'] or '', x['description2'] or ''])
                 coda_bank =  filter(cba_filter, coda_bank_table)
                 if coda_bank:
-                    coda_bank = coda_bank[0]
+                    coda_bank = coda_bank[0] 
                     coda_statement['type'] = coda_bank['state']
                     coda_statement['journal_id'] = coda_bank['journal'] and coda_bank['journal'][0]
                     coda_statement['currency_id'] = coda_bank['currency'][0]
-                    coda_statement['coda_bank_account_id'] = coda_bank['id']
-                    def_pay_acc = coda_bank['def_payable'][0]
-                    def_rec_acc = coda_bank['def_receivable'][0]
+                    coda_statement['coda_bank_account_id'] = coda_bank['id']  
+                    coda_statement['account_mapping_ids'] = coda_bank['account_mapping_ids']  
+                    coda_statement['coda_bank_params'] = coda_bank                      
                     awaiting_acc = coda_bank['awaiting_account'][0]
                     transfer_acc = coda_bank['transfer_account'][0]
+                    find_payment = coda_bank['find_payment']
                     find_bbacom = coda_bank['find_bbacom']
+                    find_inv_number = coda_bank['find_inv_number']                    
                     find_partner = coda_bank['find_partner']
+                    update_partner = coda_bank['update_partner']   
+                    coda_statement['balance_start_enforce'] = coda_bank['balance_start_enforce']
+                    coda_statement['discard_dup'] = coda_bank['discard_dup']
+                    company_id = coda_bank['company_id'][0]
+                    company_bank_ids = partner_bank_obj.search(cr, uid, [('company_id', '=', company_id)])
+                    company_bank_accounts = partner_bank_obj.read(cr, uid, company_bank_ids, ['acc_number'])
+                    company_bank_accounts = [x['acc_number'].replace(' ', '') for x in company_bank_accounts]
                 else:
-                    err_string = _("\nNo matching CODA Bank Account Configuration record found.") + \
-                        _("\nPlease check if the 'Bank Account Number', 'Currency' and 'Account Description' fields of your configuration record match with '%s', '%s' and '%s'.") \
+                    err_string = _("\nNo matching CODA Bank Account Configuration record found !") + \
+                        _("\nPlease check if the 'Bank Account Number', 'Currency' and 'Account Description' fields of your configuration record match with '%s', '%s' and '%s' !") \
                         % (coda_statement['acc_number'], coda_statement['currency'], coda_statement['description'])
                     err_code = 'R1004'
                     if batch:
@@ -200,56 +217,72 @@ class account_coda_import(osv.osv_memory):
                 bal_start = list2float(line[43:58])             # old balance data
                 if line[42] == '1':    # 1= Debit
                     bal_start = - bal_start
-                coda_statement['balance_start'] = bal_start
+                coda_statement['balance_start'] = bal_start            
+                coda_statement['old_balance_date'] = str2date(line[58:64])
                 coda_statement['acc_holder'] = line[64:90]
-                coda_statement['paper_seq_number'] = line[2:5]
+                coda_statement['paper_ob_seq_number'] = line[2:5]
                 coda_statement['coda_seq_number'] = line[125:128]
-                if coda_bank['coda_st_naming']:
-                    coda_statement['name'] = coda_bank['coda_st_naming'] % {
-                       'code': coda_bank['journal_code'] or '',
-                       'year': time.strftime('%Y'),
-                       'y': time.strftime('%y'),
-                       'coda': line[125:128],
-                       'paper': line[2:5],
-                    }
-                else:
-                    coda_statement['name'] = '/'
 
             elif line[0] == '2':
                 # movement data record 2
+                
                 if line[1] == '1':
                     # movement data record 2.1
                     st_line = {}
                     st_line_seq = st_line_seq + 1
                     st_line['sequence'] = st_line_seq
                     st_line['type'] = 'general'
-                    st_line['reconcile'] = False
+                    st_line['reconcile'] = False      
+                    st_line['trans_family'] = False
                     st_line['struct_comm_type'] = ''
+                    st_line['struct_comm_type_id'] = 0
                     st_line['struct_comm_type_desc'] = ''
-                    st_line['struct_comm_101'] = ''
+                    st_line['struct_comm_bba'] = ''
                     st_line['communication'] = ''
+                    st_line['payment_reference'] = ''               
                     st_line['partner_id'] = 0
                     st_line['account_id'] = 0
                     st_line['counterparty_name'] = ''
-                    st_line['counterparty_bic'] = ''
+                    st_line['counterparty_bic'] = ''                    
                     st_line['counterparty_number'] = ''
-                    st_line['counterparty_currency'] = ''
+                    st_line['counterparty_currency'] = ''                    
                     st_line['glob_lvl_flag'] = False
                     st_line['globalisation_id'] = 0
                     st_line['globalisation_code'] = ''
                     st_line['globalisation_amount'] = False
                     st_line['amount'] = False
-
+                          
                     st_line['ref'] = line[2:10]
+                    st_line['ref_move'] = line[2:6]
+                    st_line['ref_move_detail'] = line[6:10]
+                    
+                    if st_line_seq == 1:
+                        main_move_stack = [st_line] # initialise main_move_stack (used to link 2.1 detail records to 2.1 main record               
+                        glob_lvl_stack = [0]     # initialise globalisation stack
+                    elif st_line['ref_move_detail'] == '0000':
+                        glob_lvl_stack = [0]     # re-initialise globalisation stack
+                                      
                     st_line['trans_ref'] = line[10:31]
                     st_line_amt = list2float(line[32:47])
                     if line[31] == '1':    # 1=debit
                         st_line_amt = - st_line_amt
-                    # processing of amount depending on globalisation code
+                        
+                    st_line['trans_type'] = line[53]
+                    trans_type =  filter(lambda x: st_line['trans_type'] == x['type'], trans_type_table)
+                    if not trans_type:
+                        err_string = _('\nThe File contains an invalid CODA Transaction Type : %s!') % st_line['trans_type']
+                        err_code = 'R2001'
+                        if batch:
+                            return (err_code, err_string)
+                        raise osv.except_osv(_('Data Error!'), err_string)
+                    st_line['trans_type_id'] = trans_type[0]['id']
+                    st_line['trans_type_desc'] = trans_type[0]['description']    
+                        
+                    # processing of amount depending on globalisation code    
                     glob_lvl_flag = int(line[124])
-                    if glob_lvl_flag > 0:
-                        if glob_lvl_stack[-1] == glob_lvl_flag:
-                            st_line['glob_lvl_flag'] = glob_lvl_flag
+                    if glob_lvl_flag > 0: 
+                        if glob_lvl_stack[-1] == glob_lvl_flag: 
+                            st_line['glob_lvl_flag'] = glob_lvl_flag                            
                             st_line['amount'] = st_line_amt
                             glob_lvl_stack.pop()
                         else:
@@ -257,199 +290,388 @@ class account_coda_import(osv.osv_memory):
                             st_line['type'] = 'globalisation'
                             st_line['glob_lvl_flag'] = glob_lvl_flag
                             st_line['globalisation_amount'] = st_line_amt
-                            st_line['account_id'] = None
                     else:
                         st_line['amount'] = st_line_amt
-                    # positions 48-53 : Value date or 000000 if not known (DDMMYY)
+                        
+                    # The 'globalisation' concept can also be implemented without the globalisation level flag.
+                    # This is e.g. used by Europabank to give the details of Card Payments.
+                    if st_line['ref_move'] == main_move_stack[-1]['ref_move']:
+                        if st_line['ref_move_detail'] == '9999':
+                            # Current CODA parsing logic doesn't support > 9999 detail lines
+                            err_string = _('\nTransaction Detail Limit reached!')
+                            err_code = 'R2010'
+                            if batch:
+                                return (err_code, err_string)
+                            raise osv.except_osv(_('Data Error!'), err_string)
+                        elif st_line['ref_move_detail'] != '0000': 
+                            if glob_lvl_stack[-1] == 0:
+                                # promote associated move record into a globalisation
+                                glob_lvl_flag = 1
+                                glob_lvl_stack.append(glob_lvl_flag)
+                                main_st_line_seq = main_move_stack[-1]['sequence']
+                                to_promote = coda_statement['coda_statement_lines'][main_st_line_seq]
+                                if not main_move_stack[-1].get('detail_cnt'):
+                                    to_promote.update({
+                                       'type': 'globalisation',
+                                       'glob_lvl_flag': glob_lvl_flag,
+                                       'globalisation_amount': main_move_stack[-1]['amount'],
+                                       'amount': False,
+                                       'account_id': 0,
+                                       })
+                                    main_move_stack[-1]['promoted'] = True
+                            if not main_move_stack[-1].get('detail_cnt'):        
+                                main_move_stack[-1]['detail_cnt'] = 1
+                            else:
+                                main_move_stack[-1]['detail_cnt'] += 1
+
+                    # positions 48-53 : Valuta date or 000000 if not known (DDMMYY)
                     st_line['val_date'] = str2date(line[47:53])
                     # positions 54-61 : transaction code
-                    st_line['trans_type'] = line[53]
-                    trans_type =  filter(lambda x: st_line['trans_type'] == x['type'], trans_type_table)
-                    if not trans_type:
-                        err_string = _('\nThe File contains an invalid CODA Transaction Type : %s.') % st_line['trans_type']
-                        err_code = 'R2001'
-                        if batch:
-                            return (err_code, err_string)
-                        raise osv.except_osv(_('Data Error!'), err_string)
-                    st_line['trans_type_desc'] = trans_type[0]['description']
                     st_line['trans_family'] = line[54:56]
                     trans_family =  filter(lambda x: (x['type'] == 'family') and (st_line['trans_family'] == x['code']), trans_code_table)
                     if not trans_family:
-                        err_string = _('\nThe File contains an invalid CODA Transaction Family : %s.') % st_line['trans_family']
+                        err_string = _('\nThe File contains an invalid CODA Transaction Family : %s!') % st_line['trans_family']                       
                         err_code = 'R2002'
                         if batch:
                             return (err_code, err_string)
-                        raise osv.except_osv(_('Data Error!'), err_string)
+                        raise osv.except_osv(_('Data Error!'), err_string)                    
+                    st_line['trans_family_id'] = trans_family[0]['id']
                     st_line['trans_family_desc'] = trans_family[0]['description']
                     st_line['trans_code'] = line[56:58]
-                    trans_code =  filter(lambda x: (x['type'] == 'code') and (st_line['trans_code'] == x['code']) and (trans_family[0]['id'] == x['parent_id'][0]),
+                    trans_code =  filter(lambda x: (x['type'] == 'code') and (st_line['trans_code'] == x['code']) and (trans_family[0]['id'] == x['parent_id'][0]), 
                         trans_code_table)
                     if trans_code:
+                        st_line['trans_code_id'] = trans_code[0]['id']
                         st_line['trans_code_desc'] = trans_code[0]['description']
                     else:
+                        st_line['trans_code_id'] = None
                         st_line['trans_code_desc'] = _('Transaction Code unknown, please consult your bank.')
                     st_line['trans_category'] = line[58:61]
                     trans_category =  filter(lambda x: st_line['trans_category'] == x['category'], trans_category_table)
                     if trans_category:
+                        st_line['trans_category_id'] = trans_category[0]['id']
                         st_line['trans_category_desc'] = trans_category[0]['description']
                     else:
-                        st_line['trans_category_desc'] = _('Transaction Category unknown, please consult your bank.')
-                    # positions 61-115 : communication
+                        st_line['trans_category_id'] = None
+                        st_line['trans_category_desc'] = _('Transaction Category unknown, please consult your bank.')       
+                    # positions 61-115 : communication                
                     if line[61] == '1':
                         st_line['struct_comm_type'] = line[62:65]
                         comm_type =  filter(lambda x: st_line['struct_comm_type'] == x['code'], comm_type_table)
                         if not comm_type:
-                            err_string = _('\nThe File contains an invalid Structured Communication Type : %s.') % st_line['struct_comm_type']
+                            err_string = _('\nThe File contains an invalid Structured Communication Type : %s!') % st_line['struct_comm_type']
                             err_code = 'R2003'
                             if batch:
                                 return (err_code, err_string)
                             raise osv.except_osv(_('Data Error!'), err_string)
+                        st_line['struct_comm_type_id'] = comm_type[0]['id']
                         st_line['struct_comm_type_desc'] = comm_type[0]['description']
                         st_line['communication'] = st_line['name'] = line[65:115]
-                        if st_line['struct_comm_type'] == '101':
-                            bbacomm = line[65:77]
-                            st_line['struct_comm_101'] = st_line['name'] = '+++' + bbacomm[0:3] + '/' + bbacomm[3:7] + '/' + bbacomm[7:] + '+++'
+                        if st_line['struct_comm_type'] in ['101', '102']:
+                            bbacomm = line[65:77]   
+                            st_line['struct_comm_bba'] = st_line['name'] = '+++' + bbacomm[0:3] + '/' + bbacomm[3:7] + '/' + bbacomm[7:] + '+++'    
                     else:
                         st_line['communication'] = st_line['name'] = line[62:115]
                     st_line['entry_date'] = str2date(line[115:121])
-                    # positions 122-124 not processed
-                    coda_statement_lines[st_line_seq] = st_line
+                    if st_line['sequence'] == 1:
+                        coda_statement['first_transaction_date'] = st_line['entry_date']
+                    # positions 122-124 not processed 
+                    coda_statement_lines[st_line_seq] = st_line # store transaction 
+                  
+                    if st_line['ref_move'] != main_move_stack[-1]['ref_move']:
+                        if main_move_stack[-1].get('detail_cnt') and main_move_stack[-1].get('promoted'):
+                            # add closing globalisation level on previous detail record in order to correctly close
+                            # moves that have been 'promoted' to globalisation
+                            closeglobalise = coda_statement['coda_statement_lines'][st_line_seq-1]
+                            closeglobalise.update({
+                                    'glob_lvl_flag': main_move_stack[-1]['glob_lvl_flag'],
+                                    })
+                        else:
+                            # demote record with globalisation code from 'globalisation' to 'general' when no detail records
+                            # the same logic is repeated on the New Balance Record ('8 Record') in order to cope with CODA files
+                            # containing a single 2.1 record that needs to be 'demoted'. 
+                            if main_move_stack[-1]['type'] == 'globalisation' and not main_move_stack[-1].get('detail_cnt'):
+                                # demote record with globalisation code from 'globalisation' to 'general' when no detail records
+                                main_st_line_seq = main_move_stack[-1]['sequence']
+                                to_demote = coda_statement['coda_statement_lines'][main_st_line_seq]
+                                to_demote.update({
+                                    'type': 'general',
+                                    'glob_lvl_flag': 0,
+                                    'globalisation_amount': False,
+                                    'amount': main_move_stack[-1]['globalisation_amount'],
+                                    })
+                        main_move_stack.pop()
+                        main_move_stack.append(st_line)
                     coda_statement['coda_statement_lines'] = coda_statement_lines
+                    
                 elif line[1] == '2':
                     # movement data record 2.2
-                    if coda_statement['coda_statement_lines'][st_line_seq]['ref'] != line[2:10]:
-                        err_string = _('\nCODA parsing error on movement data record 2.2, seq nr %s.'    \
+                    if coda_statement['coda_statement_lines'][st_line_seq]['ref'][0:4] != line[2:6]:
+                        err_string = _('\nCODA parsing error on movement data record 2.2, seq nr %s!'    \
                             '\nPlease report this issue via your OpenERP support channel.') % line[2:10]
                         err_code = 'R2004'
                         if batch:
                             return (err_code, err_string)
-                        raise osv.except_osv(_('Error!'), err_string)
+                        raise osv.except_osv(_('Error!'), err_string)                    
                     coda_statement['coda_statement_lines'][st_line_seq]['name'] += line[10:63]
                     coda_statement['coda_statement_lines'][st_line_seq]['communication'] += line[10:63]
+                    coda_statement['coda_statement_lines'][st_line_seq]['payment_reference'] = line[63:98].strip()
                     coda_statement['coda_statement_lines'][st_line_seq]['counterparty_bic'] = line[98:109].strip()
+                                
                 elif line[1] == '3':
                     # movement data record 2.3
-                    if coda_statement['coda_statement_lines'][st_line_seq]['ref'] != line[2:10]:
-                        err_string = _('\nCODA parsing error on movement data record 2.3, seq nr %s.'    \
+                    if coda_statement['coda_statement_lines'][st_line_seq]['ref'][0:4] != line[2:6]:
+                        err_string = _('\nCODA parsing error on movement data record 2.3, seq nr %s!'    \
                             '\nPlease report this issue via your OpenERP support channel.') % line[2:10]
                         err_code = 'R2005'
                         if batch:
                             return (err_code, err_string)
-                        raise osv.except_osv(_('Error!'), err_string)
+                        raise osv.except_osv(_('Error!'), err_string)                    
                     st_line = coda_statement_lines[st_line_seq]
                     if coda_version == '1':
-                        counterparty_number = line[10:22]
+                        counterparty_number = line[10:22].strip()
                         counterparty_name = line[47:125].strip()
                         counterparty_currency = ''
                     else:
                         if line[22] == ' ':
-                            counterparty_number = line[10:22]
+                            counterparty_number = line[10:22].strip()
                             counterparty_currency = line[23:26].strip()
                         else:
                             counterparty_number = line[10:44].strip()
-                            counterparty_currency = line[44:47].strip()
+                            counterparty_currency = line[44:47].strip()                           
                         counterparty_name = line[47:82].strip()
                         st_line['name'] += line[82:125]
                         st_line['communication'] += line[82:125]
                     st_line['counterparty_number'] = counterparty_number
                     st_line['counterparty_currency'] = counterparty_currency
                     st_line['counterparty_name'] = counterparty_name
+                    """
+                    TO DO:
+                    
+                    replace code infra by check on flag 128 and copy info in Notes Field.
+                    
                     if counterparty_currency not in [coda_bank['currency_name'], '']:
-                        err_string = _('\nCODA parsing error on movement data record 2.3, seq nr %s.'    \
-                            '\nPlease report this issue via your OpenERP support channel.') % line[2:10]
+                        err_string = _('\nCODA parsing error on movement data record 2.3, seq nr %s!'    \
+                            '\nPlease report this issue via your OpenERP support channel.') % line[2:10]                   
                         err_code = 'R2006'
                         if batch:
                             return (err_code, err_string)
-                        raise osv.except_osv(_('Error!'), err_string)
+                        raise osv.except_osv(_('Error!'), err_string)    
+                    """
 
-                    # partner matching and reconciliation
-                    if st_line['type'] == 'general':
+                    # invoice matching and reconciliation 
+                    if st_line['type'] == 'general':                    
                         match = False
-                        bank_ids = False
-                        # prepare reconciliation for bba scor
-                        reference = st_line['struct_comm_101']
-                        if reference and find_bbacom:
-                            inv_ids = inv_obj.search(cr , uid, [('reference' ,'=', reference), ('reference_type' ,'=', 'bba')])
-                            if inv_ids:
-                                invoice = inv_obj.browse(cr, uid, inv_ids[0])
-                                partner = invoice.partner_id
-                                st_line['partner_id'] = partner.id
-                                if invoice.type in ['in_invoice', 'in_refund']:
-                                    st_line['account_id'] = partner.property_account_payable.id or def_pay_acc
-                                    st_line['type'] = 'supplier'
+                        payment_reference_match = False
+                        inv_ids = None
+                        partner_bank_ids = False
+
+                        # check payment reference in bank statement line against payment order lines
+                        payment_reference = st_line['payment_reference']
+                        if payment_reference and find_payment and st_line['amount'] < 0:
+                            payline_ids = payment_line_obj and payment_line_obj.search(cr, uid, [('name', '=', payment_reference)])
+                            if payline_ids:
+                                if len(payline_ids) == 1:
+                                    payline = payment_line_obj.browse(cr, uid, payline_ids[0])
+                                    match = True
+                                    payment_reference_match = True
+                                    st_line['partner_id'] = payline.partner_id.id
+                                    if payline.move_line_id:
+                                        st_line['reconcile'] = payline.move_line_id.id
+                                        st_line['account_id'] = payline.move_line_id.account_id.id
+                                    if payline._get_ml_inv_ref:
+                                        inv_type = payline.ml_inv_ref.type                               
+                                        st_line['type'] = inv_type == 'in_invoice' and 'supplier' or 'customer'
+                                    else:
+                                        st_line['type'] = 'general'
                                 else:
-                                    st_line['account_id'] = partner.property_account_receivable.id or def_rec_acc
-                                    st_line['type'] = 'customer'
-                                if invoice.type in ['in_invoice', 'out_invoice']:
-                                    iml_ids = move_line_obj.search(cr, uid, [('move_id', '=', invoice.move_id.id), ('reconcile_id', '=', False), ('account_id.reconcile', '=', True)])
-                                if iml_ids:
-                                    st_line['reconcile'] = iml_ids[0]
-                                match = True
+                                    err_string = _('\nThe CODA parsing detected a payment reference ambiguity while processing movement data record 2.3, seq nr %s!'    \
+                                        '\nPlease check your Payment Gateway configuration or contact your OpenERP support channel.') % line[2:10]                   
+                                    err_code = 'R2007'
+                                    if batch:
+                                        return (err_code, err_string)
+                                    raise osv.except_osv(_('Error!'), err_string)    
+
+                        # check bba scor in bank statement line against open invoices
+                        if st_line['struct_comm_bba'] and find_bbacom:
+                            if st_line['amount'] > 0:
+                                domain = [('type', 'in', ['out_invoice', 'in_refund'])]
                             else:
-                                coda_parsing_note += _("\n    Bank Statement '%s' line '%s':" \
-                                    "\n        There is no invoice matching the Structured Communication '%s'." \
+                                domain = [('type', 'in', ['in_invoice', 'out_refund'])]
+                            inv_ids = inv_obj.search(cr , uid, 
+                                    domain + [('state', '=', 'open'), ('reference' ,'=', st_line['struct_comm_bba']), ('reference_type' ,'=', 'bba')])
+                            if not inv_ids:
+                                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
+                                    "\n        There is no invoice matching the Structured Communication '%s'!" \
                                     "\n        Please verify and adjust the invoice and perform the import again or otherwise change the corresponding entry manually in the generated Bank Statement.") \
-                                    % (coda_statement['name'], st_line['ref'], reference)
-                        # lookup partner via counterparty_number
+                                    % (st_line['ref'], st_line['struct_comm_bba'])
+                            elif len(inv_ids) == 1:
+                                match = True 
+                            elif len(inv_ids) > 1:
+                                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
+                                    "\n        There are multiple invoices matching the Structured Communication '%s'!" \
+                                    "\n        A manual reconciliation is required.") \
+                                    % (st_line['ref'], st_line['struct_comm_bba'])
+
+                        # use free comm in bank statement line for lookup against open invoices
+                        if not match and find_bbacom:
+                            # extract possible bba scor from free form communication and try to find matching invoice
+                            free_comm_digits = re.sub('\D', '', st_line['communication'] or '')
+                            select = "SELECT id FROM (SELECT id, type, state, amount_total, number, reference_type, reference, " \
+                                     "'%s'::text AS free_comm_digits FROM account_invoice) sq " \
+                                     "WHERE state = 'open' AND reference_type = 'bba' " \
+                                     "AND free_comm_digits LIKE '%%'||regexp_replace(reference, '\\\D', '', 'g')||'%%'" \
+                                     % (free_comm_digits)
+                            if st_line['amount'] > 0:
+                                select2 = " AND type IN ('out_invoice', 'in_refund')"
+                            else:
+                                select2 = " AND type IN ('in_invoice', 'out_refund')"
+                            cr.execute(select + select2)
+                            res=cr.fetchall()
+                            if res:
+                                inv_ids = [x[0] for x in res]
+                                if len(inv_ids) == 1:
+                                    match = True
+                        if not match and st_line['communication'] and find_inv_number:
+                            # check matching invoice number in free form communication combined with matching amount
+                            free_comm = repl_special(st_line['communication'].strip())
+                            amount_fmt = '%.' + str(digits) + 'f'
+                            if st_line['amount'] > 0:
+                                amount_rounded = amount_fmt % round(st_line['amount'], digits)
+                            else:
+                                amount_rounded = amount_fmt % round(-st_line['amount'], digits)
+                            select = "SELECT id FROM (SELECT id, type, state, amount_total, number, reference_type, reference, " \
+                                     "'%s'::text AS free_comm FROM account_invoice) sq " \
+                                     "WHERE state = 'open' AND amount_total = %s" \
+                                     % (free_comm, amount_rounded)
+                            # 'out_invoice', 'in_refund'
+                            if st_line['amount'] > 0:
+                                select2 = " AND type = 'out_invoice' AND free_comm ilike '%'||number||'%'"
+                                cr.execute(select + select2)
+                                res=cr.fetchall()
+                                if res:
+                                    inv_ids = [x[0] for x in res]
+                                else: 
+                                    select2 = " AND type = 'in_refund' AND free_comm ilike '%'||reference||'%'"
+                                    cr.execute(select + select2)
+                                    res=cr.fetchall()
+                                    if res:
+                                        inv_ids = [x[0] for x in res]
+                            # 'in_invoice', 'out_refund'
+                            else:
+                                select2 = " AND type = 'in_invoice' AND free_comm ilike '%'||reference||'%'"
+                                cr.execute(select + select2)
+                                res=cr.fetchall()
+                                if res:
+                                    inv_ids = [x[0] for x in res]
+                                else: 
+                                    select2 = " AND type = 'out_refund' AND free_comm ilike '%'||number||'%'"
+                                    cr.execute(select + select2)
+                                    res=cr.fetchall()
+                                    if res:
+                                        inv_ids = [x[0] for x in res]
+                            if inv_ids:
+                                if len(inv_ids) == 1:
+                                    match = True
+                                elif len(inv_ids) > 1:
+                                    coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
+                                        "\n        There are multiple invoices matching the Invoice Amount and Reference." \
+                                        "\n        A manual reconciliation is required.") \
+                                        % (st_line['ref'])
+                                
+                        if not payment_reference_match and match and inv_ids and len(inv_ids) == 1:
+                            invoice = inv_obj.browse(cr, uid, inv_ids[0])
+                            partner = invoice.partner_id
+                            st_line['partner_id'] = partner.id
+                            if invoice.type in ['in_invoice', 'in_refund']:
+                                st_line['account_id'] = partner.property_account_payable.id
+                                st_line['type'] = 'supplier'
+                            else:
+                                st_line['account_id'] = partner.property_account_receivable.id
+                                st_line['type'] = 'customer'
+                            iml_ids = move_line_obj.search(cr, uid, [('move_id', '=', invoice.move_id.id), ('reconcile_id', '=', False), ('account_id.reconcile', '=', True)])
+                            if iml_ids:
+                                st_line['reconcile'] = iml_ids[0]
+                            else:
+                                err_string = _('\nThe CODA parsing detected a database inconsistency while processing movement data record 2.3, seq nr %s!'    \
+                                    '\nPlease report this issue via your OpenERP support channel.') % line[2:10]                   
+                                err_code = 'R2008'
+                                if batch:
+                                    return (err_code, err_string)
+                                raise osv.except_osv(_('Error!'), err_string)    
+                                
+                        # lookup partner via counterparty_number when invoice lookup failed 
                         if not match and counterparty_number:
-                            cba_filter = lambda x: ((counterparty_number in (x['iban'] or '')) or (counterparty_number == x['acc_number'])) \
-                                and (x['state'] == 'normal')
-                            transfer_account =  filter(cba_filter, coda_bank_table)
+                            transfer_account =  filter(lambda x: counterparty_number in x, company_bank_accounts)
                             if transfer_account:
                                 st_line['account_id'] = transfer_acc
                                 match = True
                             elif find_partner:
-                                counterparty_number = base_iban._pretty_iban(base_iban._format_iban(counterparty_number))
-                                bank_ids = partner_bank_obj.search(cr,uid,[('acc_number','=', counterparty_number)])
-                        if not match and find_partner and bank_ids:
-                            if len(bank_ids) > 1:
-                                coda_parsing_note += _("\n    Bank Statement '%s' line '%s':" \
-                                    "\n        No partner record assigned: There are multiple partners with the same Bank Account Number '%s'." \
+                                partner_bank_ids = partner_bank_obj.search(cr,uid,[('acc_number','=', counterparty_number)])
+                        if not match and find_partner and partner_bank_ids:
+                            if len(partner_bank_ids) > 1:
+                                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
+                                    "\n        No partner record assigned: There are multiple partners with the same Bank Account Number '%s'!" \
                                     "\n        Please correct the configuration and perform the import again or otherwise change the corresponding entry manually in the generated Bank Statement.") \
-                                    % (coda_statement['name'], st_line['ref'], counterparty_number)
-                            else:
-                                bank = partner_bank_obj.browse(cr, uid, bank_ids[0], context)
-                                st_line['partner_id'] = bank.partner_id.id
+                                    % (st_line['ref'], counterparty_number)
+                            else:    
+                                partner_bank = partner_bank_obj.browse(cr, uid, partner_bank_ids[0], context)
+                                st_line['partner_id'] = partner_bank.partner_id.id
                                 match = True
                                 if st_line['amount'] < 0:
-                                    st_line['account_id'] = bank.partner_id.property_account_payable.id or def_pay_acc
+                                    st_line['account_id'] = partner_bank.partner_id.property_account_payable.id
                                     st_line['type'] = 'supplier'
                                 else:
-                                    st_line['account_id'] = bank.partner_id.property_account_receivable.id or def_rec_acc
+                                    st_line['account_id'] = partner_bank.partner_id.property_account_receivable.id
                                     st_line['type'] = 'customer'
                         elif not match and find_partner:
                             if counterparty_number:
-                                coda_parsing_note += _("\n    Bank Statement '%s' line '%s':" \
-                                    "\n        The bank account '%s' is not defined for the partner '%s'." \
+                                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
+                                    "\n        The bank account '%s' is not defined for the partner '%s'!" \
                                     "\n        Please correct the configuration and perform the import again or otherwise change the corresponding entry manually in the generated Bank Statement.") \
-                                    % (coda_statement['name'], st_line['ref'],
-                                    counterparty_number, counterparty_name)
+                                    % (st_line['ref'], counterparty_number, counterparty_name)
                             else:
-                                coda_parsing_note += _("\n    Bank Statement '%s' line '%s':" \
-                                    "\n        No matching partner record found." \
+                                coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':" \
+                                    "\n        No matching partner record found!" \
                                     "\n        Please adjust the corresponding entry manually in the generated Bank Statement.") \
-                                    % (coda_statement['name'], st_line['ref'])
+                                    % (st_line['ref']) 
                             st_line['account_id'] = awaiting_acc
-                    # end of partner record lookup
+                        
+                        # add bank account to partner record
+                        if match and st_line['account_id'] != transfer_acc and counterparty_number and update_partner:
+                            partner_bank_ids = partner_bank_obj.search(cr,uid,[('acc_number','=', counterparty_number), ('partner_id', '=', st_line['partner_id'])], order='id')
+                            if len(partner_bank_ids) > 1:
+                                # clean up partner bank duplicates, keep most recently created
+                                _logger.warn('Duplicate Bank Accounts for partner_id %s have been removed, ids = %s', 
+                                    st_line['partner_id'], partner_bank_ids[:-1])
+                                partner_bank_obj.unlink(cr,uid, partner_bank_ids[:-1])
+                            if not partner_bank_ids:
+                                feedback = update_partner_bank(self, cr, uid, st_line['counterparty_bic'], counterparty_number, st_line['partner_id'], counterparty_name)
+                                if feedback:                                   
+                                    coda_parsing_note += _("\n    Bank Statement '%%(name)s' line '%s':") % st_line['ref'] + feedback                                    
+
                     coda_statement_lines[st_line_seq] = st_line
                     coda_statement['coda_statement_lines'] = coda_statement_lines
+                    # end of processing movement data record 2.3
+
                 else:
                     # movement data record 2.x (x <> 1,2,3)
-                    err_string = _('\nMovement data records of type 2.%s are not supported.') % line[1]
-                    err_code = 'R2007'
+                    err_string = _('\nMovement data records of type 2.%s are not supported !') % line[1]
+                    err_code = 'R2009'
                     if batch:
                         return (err_code, err_string)
-                    raise osv.except_osv(_('Data Error!'), err_string)
+                    raise osv.except_osv(_('Data Error!'), err_string)    
 
             elif line[0] == '3':
                 # information data record 3
+                
                 if line[1] == '1':
                     # information data record 3.1
                     info_line = {}
                     info_line['entry_date'] = st_line['entry_date']
                     info_line['type'] = 'information'
                     st_line_seq = st_line_seq + 1
-                    info_line['sequence'] = st_line_seq
+                    info_line['sequence'] = st_line_seq                    
                     info_line['struct_comm_type'] = ''
                     info_line['struct_comm_type_desc'] = ''
                     info_line['communication'] = ''
@@ -463,19 +685,19 @@ class account_coda_import(osv.osv_memory):
                         err_code = 'R3001'
                         if batch:
                             return (err_code, err_string)
-                        raise osv.except_osv(_('Data Error!'), err_string)
-                    info_line['trans_type_desc'] = trans_type[0]['description']
+                        raise osv.except_osv(_('Data Error!'), err_string)                    
+                    info_line['trans_type_desc'] = trans_type[0]['description']                         
                     info_line['trans_family'] = line[32:34]
                     trans_family =  filter(lambda x: (x['type'] == 'family') and (info_line['trans_family'] == x['code']), trans_code_table)
                     if not trans_family:
-                        err_string = _('\nThe File contains an invalid CODA Transaction Family : %s.') % st_line['trans_family']
+                        err_string = _('\nThe File contains an invalid CODA Transaction Family : %s!') % st_line['trans_family']                       
                         err_code = 'R3002'
                         if batch:
                             return (err_code, err_string)
-                        raise osv.except_osv(_('Data Error!'), err_string)
+                        raise osv.except_osv(_('Data Error!'), err_string)                    
                     info_line['trans_family_desc'] = trans_family[0]['description']
                     info_line['trans_code'] = line[34:36]
-                    trans_code =  filter(lambda x: (x['type'] == 'code') and (info_line['trans_code'] == x['code']) and (trans_family[0]['id'] == x['parent_id']),
+                    trans_code =  filter(lambda x: (x['type'] == 'code') and (info_line['trans_code'] == x['code']) and (trans_family[0]['id'] == x['parent_id'][0]), 
                         trans_code_table)
                     if trans_code:
                         info_line['trans_code_desc'] = trans_code[0]['description']
@@ -486,13 +708,13 @@ class account_coda_import(osv.osv_memory):
                     if trans_category:
                         info_line['trans_category_desc'] = trans_category[0]['description']
                     else:
-                        info_line['trans_category_desc'] = _('Transaction Category unknown, please consult your bank.')
-                    # positions 40-113 : communication
+                        info_line['trans_category_desc'] = _('Transaction Category unknown, please consult your bank.')       
+                    # positions 40-113 : communication                
                     if line[39] == '1':
                         info_line['struct_comm_type'] = line[40:43]
                         comm_type = filter(lambda x: info_line['struct_comm_type'] == x['code'], comm_type_table)
                         if not comm_type:
-                            err_string = _('\nThe File contains an invalid Structured Communication Type : %s.') % info_line['struct_comm_type']
+                            err_string = _('\nThe File contains an invalid Structured Communication Type : %s!') % info_line['struct_comm_type']
                             err_code = 'R3003'
                             if batch:
                                 return (err_code, err_string)
@@ -504,21 +726,23 @@ class account_coda_import(osv.osv_memory):
                     # positions 114-128 not processed
                     coda_statement_lines[st_line_seq] = info_line
                     coda_statement['coda_statement_lines'] = coda_statement_lines
+                    
                 elif line[1] == '2':
                     # information data record 3.2
                     if coda_statement['coda_statement_lines'][st_line_seq]['ref'] != line[2:10]:
-                        err_string = _('\nCODA parsing error on information data record 3.2, seq nr %s.'    \
+                        err_string = _('\nCODA parsing error on information data record 3.2, seq nr %s!'    \
                             '\nPlease report this issue via your OpenERP support channel.') % line[2:10]
                         err_code = 'R3004'
                         if batch:
                             return (err_code, err_string)
                         raise osv.except_osv(_('Error!'), err_string)
-                    coda_statement['coda_statement_lines'][st_line_seq]['name'] += line[10:115]
+                    coda_statement['coda_statement_lines'][st_line_seq]['name'] += line[10:115]                        
                     coda_statement['coda_statement_lines'][st_line_seq]['communication'] += line[10:115]
+                    
                 elif line[1] == '3':
                     # information data record 3.3
                     if coda_statement['coda_statement_lines'][st_line_seq]['ref'] != line[2:10]:
-                        err_string = _('\nCODA parsing error on information data record 3.3, seq nr %s.'    \
+                        err_string = _('\nCODA parsing error on information data record 3.3, seq nr %s!'    \
                             '\nPlease report this issue via your OpenERP support channel.') % line[2:10]
                         err_code = 'R3005'
                         if batch:
@@ -526,7 +750,7 @@ class account_coda_import(osv.osv_memory):
                         raise osv.except_osv(_('Error!'), err_string)
                     coda_statement['coda_statement_lines'][st_line_seq]['name'] += line[10:100]
                     coda_statement['coda_statement_lines'][st_line_seq]['communication'] += line[10:100]
-
+                   
             elif line[0] == '4':
                 # free communication data record 4
                 comm_line = {}
@@ -537,21 +761,64 @@ class account_coda_import(osv.osv_memory):
                 comm_line['communication'] = comm_line['name'] = line[32:112]
                 coda_statement_lines[st_line_seq] = comm_line
                 coda_statement['coda_statement_lines'] = coda_statement_lines
-
+    
             elif line[0] == '8':
                 # new balance record
+                last_transaction = main_move_stack[-1]
+                if last_transaction['type'] == 'globalisation'  and not last_transaction.get('detail_cnt'):
+                    # demote record with globalisation code from 'globalisation' to 'general' when no detail records
+                    main_st_line_seq = main_move_stack[-1]['sequence']
+                    to_demote = coda_statement['coda_statement_lines'][main_st_line_seq]
+                    to_demote.update({
+                        'type': 'general',
+                        'glob_lvl_flag': 0,
+                        'globalisation_amount': False,
+                        'amount': main_move_stack[-1]['globalisation_amount'],
+                        })
+                    # add closing globalisation level on previous detail record in order to correctly close
+                    # moves that have been 'promoted' to globalisation
+                    if main_move_stack[-1].get('detail_cnt') and main_move_stack[-1].get('promoted'):
+                        closeglobalise = coda_statement['coda_statement_lines'][st_line_seq-1]
+                        closeglobalise.update({
+                                'glob_lvl_flag': main_move_stack[-1]['glob_lvl_flag'],
+                                })
+                coda_statement['paper_nb_seq_number'] = line[1:4]
                 bal_end = list2float(line[42:57])
+                coda_statement['new_balance_date'] = str2date(line[57:63])
                 if line[41] == '1':    # 1=Debit
                     bal_end = - bal_end
                 coda_statement['balance_end_real'] = bal_end
-
+                if coda_statement['new_balance_date']:                
+                    period_id = period_obj.search(cr , uid, [('date_start' ,'<=', coda_statement['new_balance_date']), ('date_stop','>=',coda_statement['new_balance_date'])])
+                else:
+                    period_id = period_obj.search(cr , uid, [('date_start' ,'<=', coda_statement['date']), ('date_stop','>=',coda_statement['date'])])                    
+                if not period_id:
+                    err_string = _("\nThe CODA Statement New Balance date doesn't fall within a defined Accounting Period!" \
+                          "\nPlease create the Accounting Period for date %s.") % coda_statement['new_balance_date']
+                    err_code = 'R0002'
+                    if batch:
+                        return (err_code, err_string)
+                    raise osv.except_osv(_('Data Error!'), err_string)
+                coda_statement['period_id'] = period_id[0]
+                if coda_bank['coda_st_naming']:
+                    coda_statement['name'] = coda_bank['coda_st_naming'] % {
+                       'code': coda_bank['journal_code'] or '',         
+                       'year': coda_statement['new_balance_date'] and coda_statement['new_balance_date'][:4] or coda_statement['date'][:4],
+                       'y': coda_statement['new_balance_date'] and coda_statement['new_balance_date'][2:4] or coda_statement['date'][2:4],
+                       'coda': coda_statement['coda_seq_number'],
+                       'paper_ob': coda_statement['paper_ob_seq_number'],
+                       'paper': coda_statement['paper_nb_seq_number'],
+                    }
+                else:
+                    coda_statement['name'] = '/'
+    
             elif line[0] == '9':
                 # footer record
-                coda_statement['balance_min'] = list2float(line[22:37])
+                coda_statement['balance_min'] = list2float(line[22:37])  
                 coda_statement['balance_plus'] = list2float(line[37:52])
                 if not bal_end:
                     coda_statement['balance_end_real'] = coda_statement['balance_start'] + coda_statement['balance_plus'] - coda_statement['balance_min']
-                if coda_parsing_note:
+                if coda_parsing_note:                
                     coda_statement['coda_parsing_note'] = '\nStatement Line matching results:' + coda_parsing_note
                 else:
                     coda_statement['coda_parsing_note'] = ''
@@ -559,11 +826,11 @@ class account_coda_import(osv.osv_memory):
         #end for
 
         err_string = ''
-        err_code = ''
+        err_code = ''        
         coda_id = 0
         coda_note = ''
         line_note = ''
-
+        
         try:
             coda_id = coda_obj.create(cr, uid,{
                 'name' : codafilename,
@@ -573,7 +840,7 @@ class account_coda_import(osv.osv_memory):
                 'user_id': uid,
                 })
             context.update({'coda_id': coda_id})
-
+    
         except osv.except_osv, e:
             cr.rollback()
             err_string = _('\nApplication Error : ') + str(e)
@@ -587,78 +854,127 @@ class account_coda_import(osv.osv_memory):
             err_code = 'G0001'
             if batch:
                 return (err_code, err_string)
-            raise osv.except_osv(_('CODA Import failed.'), err_string)
+            raise osv.except_osv(_('CODA Import failed !'), err_string)
 
         nb_err = 0
         err_string = ''
         coda_st_ids = []
-        bk_st_ids = []
-
+        bk_st_ids = []      
+        
         for statement in coda_statements:
-
+            
             # The CODA Statement info is written to two objects: 'coda.bank.statement' and 'account.bank.statement'
 
             try:
-
+                normal2info = False
+                lines = statement['coda_statement_lines']
+                if not statement['first_transaction_date']: 
+                    normal2info = True # don't create a bank statement for CODA's without transactions
+                else:
+                    line_vals = [x for x in lines.itervalues()]
+                    transactions = filter(lambda x: (x['type'] in ['globalisation', 'general', 'supplier', 'customer']) and x['amount'], line_vals)
+                    if not transactions:
+                        normal2info = True # don't create a bank statement for CODA's without transactions
+                if normal2info:
+                    statement['type'] = 'info' # don't create a bank statement for CODA's without transactions
+                    statement['coda_parsing_note'] += _("\n\nThe CODA Statement %s does not contain transactions, hence no Bank Statement has been created." \
+                        "\nSelect the 'CODA Bank Statement' to check the contents of %s.") \
+                        %(statement['name'], statement['name'])
+                
                 coda_st_id = coda_st_obj.create(cr, uid, {
                     'name': statement['name'],
                     'type': statement['type'],
                     'coda_bank_account_id': statement['coda_bank_account_id'],
-                    'currency': statement['currency_id'],
+                    'currency': statement['currency_id'],                    
                     'journal_id': statement['journal_id'],
                     'coda_id': coda_id,
                     'date': statement['date'],
+                    'coda_creation_date': statement['coda_creation_date'],
                     'period_id': statement['period_id'],
+                    'old_balance_date': statement['old_balance_date'],
+                    'new_balance_date': statement['new_balance_date'],                    
                     'balance_start': statement['balance_start'],
                     'balance_end_real': statement['balance_end_real'],
-                    'state':'draft',
                 })
                 coda_st_ids.append(coda_st_id)
 
-                if statement['type'] == 'normal':
+                # check duplicates for CODA's of type 'normal'
+                discard = False
+                if statement['type'] == 'normal' and statement['discard_dup']:
+                    dup_ids = bank_st_obj.search(cr, uid, [('name', '=', statement['name']), ('company_id', '=', company_id)])
+                    if dup_ids:
+                        discard = True
+                        statement['type'] = 'info' # don't create a bank statement for duplicates
+                        statement['coda_parsing_note'] += _("\n\nThe Bank Statement %s already exists, hence no duplicate Bank Statement has been created." \
+                        "\nSelect the 'CODA Bank Statement' to check the contents of %s.") \
+                        %(statement['name'], statement['name'])
+
+                # create only bank statement for CODA's of type 'normal'
+                if statement['type'] == 'normal' and not discard:
                     context.update({'ebanking_import': 1})
                     journal = journal_obj.browse(cr, uid, statement['journal_id'], context=context)
+                    balance_start_check_date = statement['first_transaction_date'] or statement['date']
                     cr.execute('SELECT balance_end_real \
                         FROM account_bank_statement \
                         WHERE journal_id = %s and date < %s \
-                        ORDER BY date DESC,id DESC LIMIT 1', (statement['journal_id'], statement['date']))
+                        ORDER BY date DESC,id DESC LIMIT 1', (statement['journal_id'], balance_start_check_date))
                     res = cr.fetchone()
                     balance_start_check = res and res[0]
                     if balance_start_check == None:
                         if journal.default_debit_account_id and (journal.default_credit_account_id == journal.default_debit_account_id):
                             balance_start_check = journal.default_debit_account_id.balance
                         else:
-                            nb_err += 1
-                            err_string += _('\nConfiguration Error!\nPlease verify the Default Debit and Credit Account settings in journal %s.') % journal.name
+                            nb_err += 1 
+                            err_string += _('\nConfiguration Error in journal %s!'    \
+                                '\nPlease verify the Default Debit and Credit Account settings.') % journal.name
                             break
-                    if balance_start_check <> statement['balance_start']:
-                            nb_err += 1
-                            err_string += _('\nThe CODA Statement %s Starting Balance (%.2f) does not correspond with the previous Closing Balance (%.2f) in journal %s.')  \
-                                % (statement['name'], statement['balance_start'], balance_start_check, journal.name)
+                    if balance_start_check <> statement['balance_start']:                       
+                        balance_start_err_string = _('\nThe CODA Statement %s Starting Balance (%.2f) does not correspond with the previous Closing Balance (%.2f) in journal %s!')  \
+                            % (statement['name'], statement['balance_start'], balance_start_check, journal.name)   
+                        if statement['balance_start_enforce']:
+                            nb_err += 1 
+                            err_string += balance_start_err_string   
                             break
+                        else:
+                            statement['coda_parsing_note'] += '\n' + balance_start_err_string
 
-                    bk_st_id = bank_st_obj.create(cr, uid, {
+                    st_vals = {
                         'name': statement['name'],
                         'journal_id': statement['journal_id'],
                         'coda_statement_id': coda_st_id,
-                        'date': statement['date'],
+                        'date': statement['new_balance_date'],
                         'period_id': statement['period_id'],
                         'balance_start': statement['balance_start'],
                         'balance_end_real': statement['balance_end_real'],
                         'state': 'draft',
-                    })
+                    }
+                    st_hook_error, st_vals = self._statement_hook(cr, uid, statement['coda_bank_params'], st_vals, context=context)
+                    if st_hook_error:
+                        nb_err += 1 
+                        err_string += st_hook_error   
+                        break
+                    bk_st_id = bank_st_obj.create(cr, uid, st_vals, context=context)
                     bk_st_ids.append(bk_st_id)
                     coda_st_obj.write(cr, uid, [coda_st_id], {'statement_id': bk_st_id}, context=context)
-
-                glob_id_stack = [(0, '', 0, '')]          # stack with tuples (glob_lvl_flag, glob_code, glob_id, glob_name)
-                lines = statement['coda_statement_lines']
+                            
                 st_line_seq = 0
 
                 for x in lines:
                     line = lines[x]
+                    
+                    if not line['type'] == 'communication':
+                        if line['trans_family'] in st_line_name_families:
+                            line['name'] = get_st_line_name(line, context)                       
+                        if line['type'] == 'information':
+                            if line['struct_comm_type'] in parse_comms_info:
+                                line['name'], line['communication'] = parse_comm_info(self, cr, uid, line, comm_type_table, context)
+                            elif line['struct_comm_type'] in parse_comms_move:
+                                line['name'], line['communication'] = parse_comm_move(self, cr, uid, line, comm_type_table, context)                                
+                        elif line['struct_comm_type'] in parse_comms_move:
+                                line['name'], line['communication'] = parse_comm_move(self, cr, uid, line, comm_type_table, context)
 
                     # handling non-transactional records : line['type'] in ['information', 'communication']
-
+                    
                     if line['type'] == 'information':
 
                         line['globalisation_id'] = glob_id_stack[-1][2]
@@ -674,25 +990,25 @@ class account_coda_import(osv.osv_memory):
                               line['trans_category'], line['trans_category_desc'],
                               line['struct_comm_type'], line['struct_comm_type_desc'],
                               line['communication'])
-
+    
                         coda_st_line_id = coda_st_line_obj.create(cr, uid, {
                                    'sequence': line['sequence'],
-                                   'ref': line['ref'],
+                                   'ref': line['ref'],                                           
                                    'name': line['name'].strip() or '/',
-                                   'type' : 'information',
-                                   'date': line['entry_date'],
+                                   'type' : 'information',               
+                                   'date': line['entry_date'],                
                                    'statement_id': coda_st_id,
                                    'note': line_note,
                                    })
-
+                            
                     elif line['type'] == 'communication':
 
                         line_note = _('Free Communication:\n %s')                  \
                             %(line['communication'])
-
+    
                         coda_st_line_id = coda_st_line_obj.create(cr, uid, {
                                    'sequence': line['sequence'],
-                                   'ref': line['ref'],
+                                   'ref': line['ref'],                                                 
                                    'name': line['name'].strip() or '/',
                                    'type' : 'communication',
                                    'date': statement['date'],
@@ -700,34 +1016,39 @@ class account_coda_import(osv.osv_memory):
                                    'note': line_note,
                                    })
 
-                    # handling transactional records, # line['type'] in ['globalisation', 'general', 'supplier', 'customer']
+                    # handling transactional records, # line['type'] in ['globalisation', 'general', 'supplier', 'customer'] 
 
                     else:
-
+                        
+                        if line['ref_move_detail'] == '0000':
+                            glob_id_stack = [(0, '', 0, '')]  # initialise stack with tuples (glob_lvl_flag, glob_code, glob_id, glob_name)
+                    
                         glob_lvl_flag = line['glob_lvl_flag']
-                        if glob_lvl_flag:
-                            if glob_id_stack[-1][0] == glob_lvl_flag:
+                        if glob_lvl_flag: 
+                            if glob_id_stack[-1][0] == glob_lvl_flag: 
                                 line['globalisation_id'] = glob_id_stack[-1][2]
                                 glob_id_stack.pop()
                             else:
                                 glob_name = line['name'].strip() or '/'
                                 glob_code = seq_obj.get(cr, uid, 'statement.line.global')
                                 glob_id = glob_obj.create(cr, uid, {
-                                    'code': glob_code,
+                                    'code': glob_code,                                                                
                                     'name': glob_name,
                                     'type': 'coda',
                                     'parent_id': glob_id_stack[-1][2],
                                     'amount': line['globalisation_amount'],
+                                    'payment_reference': line['payment_reference']
                                 })
                                 line['globalisation_id'] = glob_id
                                 glob_id_stack.append((glob_lvl_flag, glob_code, glob_id, glob_name))
-
-                        line_note = _('Partner name: %s \nPartner Account Number: %s' \
+    
+                        line_note = _('Partner Name: %s \nPartner Account Number: %s' \
                             '\nTransaction Type: %s - %s'                             \
                             '\nTransaction Family: %s - %s'                           \
                             '\nTransaction Code: %s - %s'                             \
                             '\nTransaction Category: %s - %s'                         \
                             '\nStructured Communication Type: %s - %s'                \
+                            '\nPayment Reference: %s'                                 \
                             '\nCommunication: %s')                                    \
                             %(line['counterparty_name'], line['counterparty_number'],
                               line['trans_type'], line['trans_type_desc'],
@@ -735,135 +1056,208 @@ class account_coda_import(osv.osv_memory):
                               line['trans_code'], line['trans_code_desc'],
                               line['trans_category'], line['trans_category_desc'],
                               line['struct_comm_type'], line['struct_comm_type_desc'],
+                              line['payment_reference'],                              
                               line['communication'])
-
+    
                         if line['type'] == 'globalisation':
-
+                            
                             coda_st_line_id = coda_st_line_obj.create(cr, uid, {
                                    'sequence': line['sequence'],
-                                   'ref': line['ref'],
+                                   'ref': line['ref'],                                                  
                                    'name': line['name'].strip() or '/',
                                    'type' : 'globalisation',
-                                   'val_date' : line['val_date'],
+                                   'val_date' : line['val_date'], 
                                    'date': line['entry_date'],
-                                   'globalisation_level': line['glob_lvl_flag'],
-                                   'globalisation_amount': line['globalisation_amount'],
-                                   'globalisation_id': line['globalisation_id'],
+                                   'globalisation_level': line['glob_lvl_flag'],  
+                                   'globalisation_amount': line['globalisation_amount'],                                                      
+                                   'globalisation_id': line['globalisation_id'], 
                                    'partner_id': line['partner_id'] or 0,
                                    'account_id': line['account_id'],
+                                   'payment_reference': line['payment_reference'],  
                                    'statement_id': coda_st_id,
                                    'note': line_note,
                                    })
 
-                        else:       # line['type'] in ['general', 'supplier', 'customer']
+                        else:       # line['type'] in ['general', 'supplier', 'customer']                        
 
-                            if glob_lvl_flag == 0:
+                            if glob_lvl_flag == 0: 
                                 line['globalisation_id'] = glob_id_stack[-1][2]
-                            if not line['account_id']:
+                            if not line['account_id']:                               
                                     line['account_id'] = awaiting_acc
-
+                                                                
                             coda_st_line_id = coda_st_line_obj.create(cr, uid, {
                                    'sequence': line['sequence'],
-                                   'ref': line['ref'],
-                                   'name': line['name'] or '/',
+                                   'ref': line['ref'],      
+                                   'name': line['name'].strip() or '/',                                             
                                    'type' : line['type'],
-                                   'val_date' : line['val_date'],
+                                   'val_date' : line['val_date'], 
                                    'date': line['entry_date'],
                                    'amount': line['amount'],
                                    'partner_id': line['partner_id'] or 0,
                                    'counterparty_name': line['counterparty_name'],
-                                   'counterparty_bic': line['counterparty_bic'],
-                                   'counterparty_number': line['counterparty_number'],
-                                   'counterparty_currency': line['counterparty_currency'],
+                                   'counterparty_bic': line['counterparty_bic'],                     
+                                   'counterparty_number': line['counterparty_number'],   
+                                   'counterparty_currency': line['counterparty_currency'],                                    
                                    'account_id': line['account_id'],
-                                   'globalisation_level': line['glob_lvl_flag'],
-                                   'globalisation_id': line['globalisation_id'],
+                                   'globalisation_level': line['glob_lvl_flag'],  
+                                   'globalisation_id': line['globalisation_id'], 
+                                   'payment_reference': line['payment_reference'],  
                                    'statement_id': coda_st_id,
                                    'note': line_note,
                                    })
 
                             if statement['type'] == 'normal':
-
-                                st_line_seq += 1
-                                voucher_id = False
-                                line_name = line['name'].strip()
-                                if not line_name:
-                                    if line['globalisation_id']:
-                                        line_name = glob_id_stack[-1][3]
-                                    else:
-                                        line_name = '/'
-
-                                if line['reconcile']:
-                                    voucher_vals = {
-                                        'type': line['type'] == 'supplier' and 'payment' or 'receipt',
-                                        'name': line_name,
-                                        'partner_id': line['partner_id'],
-                                        'journal_id': statement['journal_id'],
-                                        'account_id': journal.default_credit_account_id.id,
-                                        'company_id': journal.company_id.id,
-                                        'currency_id': journal.company_id.currency_id.id,
-                                        'date': line['entry_date'],
-                                        'amount': abs(line['amount']),
-                                        'period_id': statement['period_id'],
-                                    }
-                                    voucher_id = voucher_obj.create(cr, uid, voucher_vals, context=context)
-
-                                    move_line = move_line_obj.browse(cr, uid, line['reconcile'], context=context)
-                                    voucher_dict = voucher_obj.onchange_partner_id(cr, uid, [],
-                                        partner_id = line['partner_id'],
-                                        journal_id = statement['journal_id'],
-                                        price = abs(line['amount']),
-                                        currency_id = journal.company_id.currency_id.id,
-                                        ttype = line['type'] == 'supplier' and 'payment' or 'receipt',
-                                        date = line['val_date'],
-                                        context = context)
-                                    #_logger.warning('voucher_dict = %s' % voucher_dict)
-                                    voucher_line_vals = False
-                                    if voucher_dict['value']['line_ids']:
-                                        for line_dict in voucher_dict['value']['line_ids']:
-                                            if line_dict['move_line_id'] == move_line.id:
-                                                voucher_line_vals = line_dict
-                                    if voucher_line_vals:
-                                        voucher_line_vals.update({
-                                            'voucher_id': voucher_id,
+                                if line['amount'] != 0.0:                                   
+                                    st_line_seq += 1
+                                    voucher_id = False
+                                    line_name = line['name'].strip()
+                                    if not line_name:
+                                        if line['globalisation_id']:
+                                            line_name = glob_id_stack[-1][3] or '/'
+                                        else:
+                                            line_name = '/'
+    
+                                    if line['reconcile']:
+                                        
+                                        move_line = move_line_obj.browse(cr, uid, line['reconcile'], context=context)
+                                        company_currency_id = journal.company_id.currency_id.id
+                                        currency_id = journal.currency.id
+                                        if currency_id and currency_id != company_currency_id:
+                                            multi_currency = True
+                                        else:
+                                            multi_currency = False
+                                        context_multi_currency = context.copy()
+                                        context_multi_currency.update({'date': line['entry_date']})
+                                
+                                        line_cr_ids = []
+                                        line_dr_ids = []
+                                        amount_remaining = abs(line['amount'])    
+                                        
+                                        #for entry in entries:
+                                        if move_line.reconcile_partial_id:
+                                            amount_original = abs(move_line.amount_residual_currency)
+                                        else:
+                                            amount_original = move_line.credit or move_line.debit
+                                            if multi_currency:
+                                                if move_line.currency_id and move_line.amount_currency:
+                                                    amount_original = abs(move_line.amount_currency)
+                                                else:
+                                                    amount_original = currency_obj.compute(cr, uid, company_currency_id, currency_id, original_amount, context=context_multi_currency)
+                                        if amount_remaining > 0:
+                                            amount_voucher_line = min(amount_original, abs(amount_remaining))
+                                            amount_remaining -= amount_voucher_line
+                                        else:
+                                            amount_voucher_line = 0.0
+                                        voucher_line_vals = {
+                                            'name': move_line.name,
+                                            'account_id': move_line.account_id.id,
+                                            'amount': amount_voucher_line,
+                                            'reconcile': amount_voucher_line == amount_original,
+                                            'type': move_line.credit and 'dr' or 'cr',
+                                            'move_line_id': move_line.id,
+                                        }
+                                        if voucher_line_vals['type'] == 'cr':
+                                            line_cr_ids += [(0, 0, voucher_line_vals)]
+                                        else:
+                                            line_dr_ids += [(0, 0, voucher_line_vals)]
+                                            
+                                        voucher_vals = { 
+                                            'type': line['type'] == 'supplier' and 'payment' or 'receipt',
+                                            'name': line_name,
+                                            'date': line['val_date'],
+                                            'journal_id': statement['journal_id'],
+                                            'account_id': journal.default_credit_account_id.id,
+                                            'line_cr_ids': line_cr_ids,
+                                            'line_dr_ids': line_dr_ids,
+                                            'pre_line': len(line_dr_ids) > 0 and True or False,
+                                            'period_id': statement['period_id'],
+                                            'currency_id': journal.currency and journal.currency.id or journal.company_id.currency_id.id,
+                                            'company_id': journal.company_id.id,
+                                            'state': 'draft',
                                             'amount': abs(line['amount']),
-                                        })
-                                        voucher_line_obj.create(cr, uid, voucher_line_vals, context=context)
-
-                                bank_st_line_id = bank_st_line_obj.create(cr, uid, {
-                                       'sequence': st_line_seq,
-                                       'ref': line['ref'],
-                                       'name': line_name,
-                                       'type' : line['type'],
-                                       'val_date' : line['val_date'],
-                                       'date': line['entry_date'],
-                                       'amount': line['amount'],
-                                       'partner_id': line['partner_id'] or 0,
-                                       'counterparty_name': line['counterparty_name'],
-                                       'counterparty_bic': line['counterparty_bic'],
-                                       'counterparty_number': line['counterparty_number'],
-                                       'counterparty_currency': line['counterparty_currency'],
-                                       'account_id': line['account_id'],
-                                       'globalisation_id': line['globalisation_id'],
-                                       'statement_id': bk_st_id,
-                                       'voucher_id': voucher_id,
-                                       'note': line_note,
-                                        })
+                                            'reference': line['ref'],
+                                            'number': statement['name'] + '/' + str(st_line_seq),
+                                            'partner_id': line['partner_id'],
+                                        }
+                                
+                                        #_logger.warn('voucher_vals=%s, context=%s', voucher_vals, context)
+                                        context['journal_id'] = journal.id # add journal to context for __get_payment_rate_currency method
+                                        if line_cr_ids or line_dr_ids:
+                                            voucher_id = voucher_obj.create(cr, uid, voucher_vals, context=context)
+                                        else:
+                                            raise osv.except_osv(_('Error!'), _("Reconcile error while processing line with ref '%s' ! " \
+                                                  "\nPlease report this issue via your OpenERP support channel.") % line['ref'])
+
+                                    # override default account mapping by mappings defined in rules engine
+                                    if statement['account_mapping_ids']:
+                                        kwargs = {
+                                            'coda_bank_account_id': statement['coda_bank_account_id'],
+                                            'trans_type_id': line['trans_type_id'],
+                                            'trans_family_id': line['trans_family_id'],
+                                            'trans_code_id': line['trans_code_id'],
+                                            'trans_category_id': line['trans_category_id'],
+                                            'struct_comm_type_id': line['struct_comm_type_id'],
+                                            'partner_id': line['partner_id'],
+                                            'context': context,
+                                        }
+                                        account_id = account_mapping_obj.account_id_get(cr, uid, **kwargs)
+                                        if account_id:
+                                            line['account_id'] = account_id
+
+                                    st_line_vals = {
+                                           'ref': line['ref'],                                                   
+                                           'name': line_name,
+                                           'type' : line['type'],
+                                           'val_date' : line['val_date'], 
+                                           'date': line['entry_date'],
+                                           'amount': line['amount'],
+                                           'partner_id': line['partner_id'] or 0,
+                                           'counterparty_name': line['counterparty_name'],
+                                           'counterparty_bic': line['counterparty_bic'],                     
+                                           'counterparty_number': line['counterparty_number'],   
+                                           'counterparty_currency': line['counterparty_currency'],                                                                           
+                                           'account_id': line['account_id'],
+                                           'globalisation_id': line['globalisation_id'], 
+                                           'payment_reference': line['payment_reference'],  
+                                           'statement_id': bk_st_id,
+                                           'voucher_id': voucher_id,
+                                           'note': line_note,
+                                            }
+    
+                                    st_lines_vals = self._st_line_hook(cr, uid, statement['coda_bank_params'], st_line_vals, context=context)
+                                    if not st_lines_vals:
+                                        # removal of lines by the _coda_parsing_hook currently not supported :
+                                        # this qould require resequencing/renaming of linked vouchers & moves as well as removal of associated voucher/moves
+                                        err_string += _('\nError in _st_line_hook while processing CODA Statement %s for Bank Account %s!')  \
+                                            % (statement['coda_seq_number'], (statement['acc_number'] + ' (' + statement['currency'] + ') - ' + statement['description']))
+                                        nb_err += 1
+                                    for st_line_vals in st_lines_vals:
+                                        # To DO : update sequence of statement lines and naming of associated vouchers/moves when the hook returns multiple lines
+                                        st_line_vals.update({'sequence': st_line_seq})
+                                        bank_st_line_id = bank_st_line_obj.create(cr, uid, st_line_vals, context=context)
+                                        if st_line_vals['voucher_id']:
+                                            voucher = voucher_obj.browse(cr, uid, st_line_vals['voucher_id'], context=context)
+                                            move_line = move_line_obj.browse(cr, uid, line['reconcile'], context=context)
+                                            if line['amount'] == (line['amount'] > 0 and move_line.debit or -move_line.credit):
+                                                wf_service.trg_validate(uid, 'account.voucher', st_line_vals['voucher_id'], 'proforma_voucher', cr)
+                                            if voucher.move_id:
+                                                move_line_obj.write(cr, uid, [x.id for x in voucher.move_ids], {'statement_id': bk_st_id}, context=context)
+
                 # end 'for x in lines'
 
                 coda_st_obj.write(cr, uid, [coda_st_id], {}, context=context)           # calculate balance
                 st_balance = coda_st_obj.read(cr, uid, coda_st_id, ['balance_end', 'balance_end_real'], context=context)
                 if st_balance['balance_end'] <> st_balance['balance_end_real']:
-                    err_string += _('\nIncorrect ending Balance in CODA Statement %s for Bank Account %s.')  \
+                    err_string += _('\nIncorrect ending Balance in CODA Statement %s for Bank Account %s!')  \
                         % (statement['coda_seq_number'], (statement['acc_number'] + ' (' + statement['currency'] + ') - ' + statement['description']))
                     if statement['type'] == 'normal':
                         nb_err += 1
                         break
                     else:
                         statement['coda_parsing_note'] += '\n' + err_string
-
-                if statement['type'] == 'normal':
-                    bank_st_obj.button_dummy(cr, uid, [bk_st_id], context=context)      # calculate balance
+                              
+                if statement['type'] == 'normal':                          
+                    bank_st_obj.button_dummy(cr, uid, [bk_st_id], context=context)      # calculate balance   
                     journal_name = journal.name
                 else:
                     journal_name = _('None')
@@ -879,33 +1273,38 @@ class account_coda_import(osv.osv_memory):
                     %(journal_name,
                       coda_version,
                       statement['coda_seq_number'],
-                      statement['paper_seq_number'],
+                      statement['paper_nb_seq_number'],
                       (statement['acc_number'] + ' (' + statement['currency'] + ') - ' + statement['description']),
                       statement['acc_holder'],
                       statement['date'], float(statement['balance_start']), float(statement['balance_end_real']),
-                      statement['coda_parsing_note'])
+                      statement['coda_parsing_note'] % {'name':coda_statement['name']})
+                if statement.get('separate_application') != '00000':
+                    coda_note += _('\nCode Separate Application: %s') %statement['separate_application']                                                  \
 
             except osv.except_osv, e:
                 cr.rollback()
                 nb_err += 1
                 err_string += _('\nError ! ') + str(e)
                 tb = ''.join(format_exception(*exc_info()))
-                _logger.error('Application Error while processing Statement %s\n%s' % (statement.get('name', '/'),tb))
+                _logger.error('Application Error while processing Statement %s\n%s',
+                    statement.get('name', '/'), tb)
             except Exception, e:
                 cr.rollback()
                 nb_err += 1
                 err_string += _('\nSystem Error : ') + str(e)
                 tb = ''.join(format_exception(*exc_info()))
-                _logger.error('System Error while processing Statement %s\n%s' % (statement.get('name', '/'),tb))
+                _logger.error('System Error while processing Statement %s\n%s', 
+                    statement.get('name', '/'),tb)
             except :
                 cr.rollback()
                 nb_err += 1
                 err_string = _('\nUnknown Error : ') + str(e)
                 tb = ''.join(format_exception(*exc_info()))
-                _logger.error('Unknown Error while processing Statement %s\n%s' % (statement.get('name', '/'),tb))
+                _logger.error('Unknown Error while processing Statement %s\n%s',
+                    statement.get('name', '/'),tb)
 
         # end 'for statement in coda_statements'
-
+                          
         coda_note_header = _('CODA File is Imported  :')
         coda_note_footer = _('\n\nNumber of statements : ') + str(len(coda_st_ids))
         err_log = err_log + _('\nNumber of errors : ') + str(nb_err) + '\n'
@@ -921,13 +1320,13 @@ class account_coda_import(osv.osv_memory):
             if batch:
                 err_code = 'G0002'
                 return (err_code, err_string)
-            raise osv.except_osv(_('CODA Import failed.'), err_string)
-
+            raise osv.except_osv(_('CODA Import failed !'), err_string)
+            
         context.update({ 'bk_st_ids': bk_st_ids})
         model_data_ids = mod_obj.search(cr, uid, [('model', '=', 'ir.ui.view'), ('name', '=', 'account_coda_import_result_view')], context=context)
         resource_id = mod_obj.read(cr, uid, model_data_ids, fields=['res_id'], context=context)[0]['res_id']
         self.write(cr, uid, ids, {'note': note}, context=context)
-
+        
         return {
             'name': _('Import CODA File result'),
             'res_id': ids[0],
@@ -941,10 +1340,26 @@ class account_coda_import(osv.osv_memory):
             'type': 'ir.actions.act_window',
         }
 
+    def _statement_hook(self, cr, uid, coda_bank_params, st_vals, context=None):
+        """ 
+        Use this method to take customer specific actions based upon the bank statement data.
+        Returns:
+            st_hook_error : False or string with error message
+            st_vals 
+        """
+        return False, st_vals
+
+    def _st_line_hook(self, cr, uid, coda_bank_params, st_line_vals, context=None):
+        """ 
+        Use this method to adapt the statement line created by the
+        CODA parsing to customer specific needs.
+        """
+        return [st_line_vals]
+
     def action_open_coda_statements(self, cr, uid, ids, context=None):
         if context is None:
             context = {}
-        module, xml_id = 'l10n_be_coda', 'action_coda_bank_statements'
+        module, xml_id = 'account_coda', 'action_coda_bank_statements'
         res_model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, module, xml_id)
         action = self.pool.get('ir.actions.act_window').read(cr, uid, res_id, context=context)
         domain = eval(action.get('domain') or '[]')
@@ -962,11 +1377,21 @@ class account_coda_import(osv.osv_memory):
         domain += [('id','in', context.get('bk_st_ids', False))]
         action.update({'domain': domain})
         return action
-
+        
 account_coda_import()
 
+def repl_special(s):
+    s = s.replace("\'", "\'" + "'")
+    return s
+
 def str2date(date_str):
-    return time.strftime('%Y-%m-%d', time.strptime(date_str,'%d%m%y'))
+    try:
+        return time.strftime('%Y-%m-%d', time.strptime(date_str,'%d%m%y'))
+    except:
+        return False
+
+def str2time(time_str):
+    return time_str[:2] + ':' + time_str[2:]
 
 def str2float(str):
     try:
@@ -975,9 +1400,517 @@ def str2float(str):
         return 0.0
 
 def list2float(lst):
+            try:
+                return str2float((lambda s : s[:-3] + '.' + s[-3:])(lst))
+            except:
+                return 0.0
+
+def number2float(s, d):
     try:
-        return str2float((lambda s : s[:-3] + '.' + s[-3:])(lst))
+        return float(s[:len(s)-d] + '.' + s[len(s)-d:])
     except:
-        return 0.0
+        return False
+
+def _get_acc_numbers(acc_number):
+    #TODO this method is needed because the iban and bank account fields have been merged together. But sometimes we
+    #   need to retrieve the normal bank account from the IBAN. This should be globalized and defined as a method on the
+    #   bank account class. Each country part of the IBAN area should define its own code to do so.
+    acc_number = acc_number.replace(' ', '')
+    if acc_number.lower().startswith('be'):
+        return [acc_number[4:], acc_number]
+    return [acc_number]
+
+from base_iban.base_iban import _ref_iban, _format_iban
+
+def calc_iban_checksum(country, bban):
+    bban = bban.replace(' ', '').upper() + country.upper() + '00'
+    base = ''
+    for c in bban:
+        if c.isdigit():
+            base += c
+        else:
+            base += str(ord(c) - ord('A') + 10)
+    kk = 98 - int(base) % 97
+    return str(kk).rjust(2, '0')
+
+def check_bban(country, bban):
+    if country == 'BE':
+        try:
+            int(bban)
+        except:
+            return False        
+        if len(bban) != 12:
+            return False
+    return True
+
+def check_iban(iban):
+    """
+    Check the IBAN number (logic partially based upon base_iban module, cf. is_iban_valid method)
+    """
+    iban = _format_iban(iban).lower()
+    if iban[:2] not in _ref_iban:
+        return False
+    if len(iban) != len(_format_iban(_ref_iban[iban[:2]])):
+        return False
+    #the four first digits have to be shifted to the end
+    iban = iban[4:] + iban[:4]
+    #letters have to be transformed into numbers (a = 10, b = 11, ...)
+    iban2 = ""
+    for char in iban:
+        if char.isalpha():
+            iban2 += str(ord(char)-87)
+        else:
+            iban2 += char
+    #iban is correct if modulo 97 == 1
+    return int(iban2) % 97 == 1
 
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+def get_bank(self, cr, uid, bic, iban):
+
+    country_obj = self.pool.get('res.country')   
+    bank_obj = self.pool.get('res.bank')
+
+    bank_id = False
+    bank_name = False
+    feedback = False   
+    bank_country = iban[:2]
+    try:
+        bank_country_id = country_obj.search(cr, uid, [('code', '=', bank_country)])[0]
+    except:
+        feedback = _("\n        Bank lookup failed due to missing Country definition for Country Code '%s' !") \
+            % (bank_country)
+    else:
+        if iban[:2] == 'BE' and 'code' in bank_obj.fields_get_keys(cr, uid):
+            # To DO : extend for other countries
+            bank_code = iban[4:7]
+            if bic:
+                bank_ids = bank_obj.search(cr, uid, [('bic', '=', bic), ('code', '=', bank_code), ('country', '=', bank_country_id)])
+                if bank_ids:
+                    bank_id = bank_ids[0]
+                else:
+                    bank_id = bank_obj.create(cr, uid, {
+                        'name': bic,
+                        'code': bank_code,
+                        'bic': bic,
+                        'country': bank_country_id,
+                        })
+            else:
+                bank_ids = bank_obj.search(cr, uid, [('code', '=', bank_code), ('country', '=', bank_country_id)])
+                if bank_ids:
+                    bank_id = bank_ids[0]
+                    bank_data = bank_obj.read(cr, uid, bank_id, fields=['bic', 'name'])
+                    bic = bank_data['bic']
+                    bank_name = bank_data['name']
+                else:
+                    country = country_obj.browse(cr, uid, bank_country_id)
+                    feedback = _("\n        Bank lookup failed. Please define a Bank with Code '%s' and Country '%s' !") \
+                        % (bank_code, country.name)
+        else:
+            if not bic: 
+                feedback = _("\n        Bank lookup failed due to missing BIC in Bank Statement for IBAN '%s' !") \
+                    % (iban)
+            else:
+                bank_ids = bank_obj.search(cr, uid, [('bic', '=', bic), ('country', '=', bank_country_id)])
+                if not bank_ids:
+                    bank_name = bic
+                    bank_id = bank_obj.create(cr, uid, {
+                        'name': bank_name,
+                        'bic': bic,
+                        'country': bank_country_id,
+                        })
+                else:
+                   bank_id = bank_ids[0]
+                   
+    return bank_id, bic, bank_name, feedback
+                
+def update_partner_bank(self, cr, uid, bic, iban, partner_id, counterparty_name):
+    partner_bank_obj = self.pool.get('res.partner.bank')
+    bank_id = False
+    feedback = False
+    if check_iban(iban):
+        bank_id, bic, bank_name, feedback = get_bank(self, cr, uid, bic, iban)
+        if not bank_id:
+            return feedback
+    else:
+        bban = iban
+        #convert belgian BBAN numbers to IBAN
+        if check_bban('BE', iban):
+            kk = calc_iban_checksum('BE', iban)
+            iban = 'BE' + kk + iban
+            bank_id, bic, bank_name, feedback = get_bank(self, cr, uid, bic, iban)
+            if not bank_id:
+                return feedback
+
+    if bank_id:
+        partner_bank_id = partner_bank_obj.create(cr, uid, {
+            'partner_id': partner_id,
+            'name': counterparty_name,
+            'bank': bank_id,
+            'state': 'iban',
+            'bank_bic': bic,
+            'bank_name': bank_name,            
+            'acc_number': iban,
+            })
+    return feedback
+
+indent = '\n' + 8*' '
+st_line_name_families = ['13', '35', '41', '80']
+parse_comms_move = ['100', '101', '102', '103', '105', '106', '107', '108', '111', '113', '114', '115', '121', '122', '123', '124', '125', '126', '127']
+parse_comms_info = ['001', '002', '004', '005', '006', '007', '107', '008', '009', '010', '011']
+
+def get_st_line_name(line, context):
+    #_logger.warn('get_st_line_name - entry').warn('ref = %s, family = %s, code = %s, categ = %s', line['ref'], line['trans_family'], line['trans_code'], line['trans_category'])
+    st_line_name = line['name']
+
+    if line['trans_family'] == '35' and line['trans_code'] in ['01', '37']:
+        st_line_name = ', '.join([_('Closing'), line['trans_code_desc'], line['trans_category_desc']])
+
+    if line['trans_family'] in ['13', '41']:
+        st_line_name = ', '.join([line['trans_family_desc'], line['trans_code_desc'], line['trans_category_desc']])
+
+    if line['trans_family'] in ['80']:
+        st_line_name = ', '.join([line['trans_code_desc'], line['trans_category_desc']])
+
+    return st_line_name
+
+def parse_comm_move(self, cr, uid, line, comm_type_table, context):
+    #_logger.warn('parse_comm_move - entry, ref = %s, family = %s, code = %s, categ = %s, comm_type = %s, name = %s', line['ref'], line['trans_family'], line['trans_code'], line['trans_category'], line['struct_comm_type'], line['name'])
+    comm_type = line['struct_comm_type']
+    comm = st_line_comm = line['communication']
+    st_line_name = line['name']
+
+    if comm_type in ['115', '121', '122', '126']:
+        _logger.warn('The parsing of Structured Commmunication Type %s has not yet been implemented. ' \
+            'Please contact Noviat (info@noviat.be) for more information about the development roadmap', comm_type)
+
+    if comm_type == '100':
+        st_line_name = _('Payment with ISO 11649 structured format communication')
+        st_line_comm = '\n' + indent + _('Payment with a structured format communication applying the ISO standard 11649') + ':'
+        st_line_comm += indent + _('Structured creditor reference to remittance information')
+        st_line_comm += indent + comm
+
+    if comm_type in ['101', '102']:
+        st_line_name = st_line_comm = '+++' + comm[0:3] + '/' + comm[3:7] + '/' + comm[7:12] + '+++'
+
+    if comm_type == '103':
+        st_line_name = ', '.join([line['trans_family_desc'], _('Number')])
+        st_line_comm = comm
+
+    if comm_type == '105':
+        st_line_name = filter(lambda x: comm_type == x['code'], comm_type_table)[0]['description']
+        amount_1 = list2float(comm[0:15])
+        amount_2 = list2float(comm[15:30])
+        rate = number2float(comm[30:42], 8)
+        currency = comm[42:45]
+        struct_format_comm = comm[45:57].strip()
+        country_code = comm[57:59]
+        amount_3 = list2float(comm[59:74])
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Gross amount in the currency of the account') + ': %.2f' % amount_1
+        st_line_comm += indent + _('Gross amount in the original currency') + ': %.2f' % amount_2
+        st_line_comm += indent + _('Rate') + ': %.4f' % rate
+        st_line_comm += indent + _('Currency') + ': %s' % currency
+        st_line_comm += indent + _('Structured format communication') + ': %s' % struct_format_comm
+        st_line_comm += indent + _('Country code of the principal') + ': %s' % country_code
+        st_line_comm += indent + _('Equivalent in EUR') + ': %.2f' % amount_3
+
+    if comm_type == '106':
+        if not st_line_name and line['trans_family'] not in st_line_name_families:
+            st_line_name = _('VAT, withholding tax on income, commission, etc.')
+        interest = comm[30:42].strip('0')
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Equivalent in the currency of the account') + ': %.2f' % list2float(comm[0:15])
+        st_line_comm += indent + _('Amount on which % is calculated') + ': %.2f' % list2float(comm[15:30])
+        st_line_comm += indent + _('Percent') + ': %.4f' % number2float(comm[30:42], 8)
+        st_line_comm += indent + (comm[42] == 1 and _('Minimum applicable') or _('Minimum not applicable'))
+        st_line_comm += indent + _('Equivalent in EUR') + ': %.2f' % list2float(comm[43:58])
+
+    if comm_type == '107':
+        paid_refusals = {
+            '0': _('paid'),
+            '1': _('direct debit cancelled or nonexistent'),
+            '2': _('refusal - other reason'),
+            'D': _('payer disagrees'),
+            'E': _('direct debit number linked to another identification number of the creditor')}        
+        st_line_name = _('Direct debit - DOM\'80')
+        direct_debit_number = comm[0:12].strip()
+        pivot_date = str2date(comm[12:18])
+        comm_zone = comm[18:48]
+        paid_refusal = paid_refusals.get(comm[48], '')
+        creditor_number = comm[49:60].strip()
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Direct Debit Number') + ': %s' % direct_debit_number
+        st_line_comm += indent + _('Central (Pivot) Date') + ': %s' % pivot_date
+        st_line_comm += indent + _('Communication Zone') + ': %s' % comm_zone
+        st_line_comm += indent + _('Paid or reason for refusal') + ': %s' % paid_refusal
+        st_line_comm += indent + _("Creditor's Number") + ': %s' % creditor_number
+
+    if comm_type == '108':
+        st_line_name = _('Closing, period from %s to %s') % (str2date(comm[42:48]), str2date(comm[48:54]))
+        interest = comm[30:42].strip('0')
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Equivalent in the currency of the account') + ': %.2f' % list2float(comm[0:15])
+        if interest:
+            st_line_comm += indent + _('Interest rates, calculation basis') + ': %.2f' % list2float(comm[15:30]) + \
+                indent + _('Interest') + ': %.2f' % list2float(comm[30:42])
+
+    if comm_type =='111':
+        card_schemes = {
+            '1': 'Bancontact/Mister Cash',
+            '2': _('Private'),
+            '3': 'Maestro',
+            '5': 'TINA',
+            '9': _('Other')}
+        trans_types = {
+            '1': _('Withdrawal'),
+            '2': _('Cumulative on network'),
+            '7': _('Distribution sector'),
+            '8': _('Teledata'),
+            '9': _('Fuel')}
+        st_line_name = _('POS credit - globalisation')
+        card_scheme = card_schemes.get(comm[0], '')
+        pos_number = comm[1:7].strip()
+        period_number = comm[7:10].strip()
+        first_sequence_number = comm[10:16].strip()
+        trans_first_date = str2date(comm[16:22])
+        last_sequence_number = comm[22:28].strip()
+        trans_last_date = str2date(comm[28:34])
+        trans_type = trans_types.get(comm[34], '')
+        terminal_name = comm[35:50].strip()
+        terminal_city = comm[51:60].strip()
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Card Scheme') + ': %s' % card_scheme
+        st_line_comm += indent + _('POS Number') + ': %s' % pos_number
+        st_line_comm += indent + _('Period Number') + ': %s' % period_number
+        st_line_comm += indent + _('First Transaction Sequence Number') + ': %s' % first_sequence_number
+        st_line_comm += indent + _('Date of first transaction') + ': %s' % trans_first_date
+        st_line_comm += indent + _('Last Transaction Sequence Number') + ': %s' % last_sequence_number
+        st_line_comm += indent + _('Date of last transaction') + ': %s' % trans_last_date
+        st_line_comm += indent + _('Transaction Type') + ': %s' % trans_type
+        st_line_comm += indent + _('Terminal Identification') + ': %s' % terminal_name + ', ' + terminal_city
+
+    if comm_type == '113':
+        card_schemes = {
+            '1': 'Bancontact/Mister Cash',
+            '2': 'Maestro',
+            '3': _('Private'),
+            '9': _('Other')}
+        trans_types = {
+            '1': _('Withdrawal'),
+            '2': _('Proton loading'),
+            '3': _('Reimbursement Proton balance'),
+            '4': _('Reversal of purchases'),
+            '7': _('Distribution sector'),
+            '8': _('Teledata'),
+            '9': _('Fuel')}
+        product_codes = {
+            '01': _('premium with lead substitute'),
+            '02': _('europremium'),
+            '03': _('diesel'),
+            '04': _('LPG'),
+            '06': _('premium plus 98 oct'),
+            '07': _('regular unleaded'),
+            '08': _('domestic fuel oil'),
+            '09': _('lubricants'),
+            '10': _('petrol'),
+            '11': _('premium 99+'),
+            '12': _('Avgas'),
+            '16': _('other types'),
+            }
+        st_line_name = _('ATM/POS debit')
+        card_number = comm[0:16].strip()
+        card_scheme = card_schemes.get(comm[16], '')
+        terminal_number = comm[17:23].strip()
+        sequence_number = comm[23:29].strip()
+        trans_date = comm[29:35].strip() and str2date(comm[29:35]) or ''
+        trans_hour = comm[35:39].strip() and str2time(comm[35:39]) or ''
+        trans_type = trans_types.get(comm[39], '')
+        terminal_name = comm[40:56].strip()
+        terminal_city = comm[56:66].strip()
+        orig_amount = comm[66:81].strip() and list2float(comm[66:81])
+        rate = number2float(comm[81:93], 8)
+        currency = comm[93:96]
+        volume = number2float(comm[96:101], 2)
+        product_code = product_codes.get(comm[101:103], '')
+        unit_price = number2float(comm[103:108], 2)
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Card Number') + ': %s' % card_number
+        st_line_comm += indent + _('Card Scheme') + ': %s' % card_scheme
+        if terminal_number:
+            st_line_comm += indent + _('Terminal Number') + ': %s' % terminal_number
+        st_line_comm += indent + _('Transaction Sequence Number') + ': %s' % sequence_number
+        st_line_comm += indent + _('Time') + ': %s' % trans_date + ' ' + trans_hour
+        st_line_comm += indent + _('Transaction Type') + ': %s' % trans_type
+        st_line_comm += indent + _('Terminal Identification') + ': %s' % terminal_name + ', ' + terminal_city
+        if orig_amount:
+            st_line_comm += indent + _('Original Amount') + ': %.2f' % orig_amount
+            st_line_comm += indent + _('Rate') + ': %.4f' % rate
+            st_line_comm += indent + _('Currency') + ': %s' % currency
+        if volume:
+            st_line_comm += indent + _('Volume') + ': %.2f' % volume
+        if product_code:
+            st_line_comm += indent + _('Product Code') + ': %s' % product_code
+        if unit_price:
+            st_line_comm += indent + _('Unit Price') + ': %.2f' % unit_price
+
+    if comm_type == '114':
+        card_schemes = {
+            '1': 'Bancontact/Mister Cash',
+            '2': 'Maestro',
+            '3': _('Private'),
+            '5': 'TINA',
+            '9': _('Other')}
+        trans_types = {
+            '1': _('Withdrawal'),
+            '7': _('Distribution sector'),
+            '8': _('Teledata'),
+            '9': _('Fuel')}
+        st_line_name = _('POS credit - individual transaction')
+        card_scheme = card_schemes.get(comm[0], '')
+        pos_number = comm[1:7].strip()
+        period_number = comm[7:10].strip()
+        sequence_number = comm[10:16].strip()
+        trans_date = str2date(comm[16:22])
+        trans_hour = str2time(comm[22:26])
+        trans_type = trans_types.get(comm[26], '')
+        terminal_name = comm[27:43].strip()
+        terminal_city = comm[43:53].strip()
+        trans_reference = comm[53:69].strip()
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Card Scheme') + ': %s' % card_scheme
+        st_line_comm += indent + _('POS Number') + ': %s' % pos_number
+        st_line_comm += indent + _('Period Number') + ': %s' % period_number
+        st_line_comm += indent + _('Transaction Sequence Number') + ': %s' % sequence_number
+        st_line_comm += indent + _('Time') + ': %s' % trans_date + ' ' + trans_hour
+        st_line_comm += indent + _('Transaction Type') + ': %s' % trans_type
+        st_line_comm += indent + _('Terminal Identification') + ': %s' % terminal_name + ', ' + terminal_city
+        st_line_comm += indent + _('Transaction Reference') + ': %s' % trans_reference
+
+    if comm_type == '123':
+        starting_date = str2date(comm[0:6])
+        maturity_date = comm[6:12] == '999999' and _('guarantee without fixed term') or str2date(comm[0:6])
+        basic_amount = list2float(comm[12:27])        
+        percent = number2float(comm[27:39], 8)
+        term = comm[39:43].lstrip('0')
+        minimum = comm[43] == '1' and True or False 
+        guarantee_number = comm[44:57].strip()
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Starting Date') + ': %s' % starting_date
+        st_line_comm += indent + _('Maturity Date') + ': %s' % maturity_date
+        st_line_comm += indent + _('Basic Amount') + ': %.2f' % basic_amount
+        st_line_comm += indent + _('Percentage') + ': %.4f' % percent
+        st_line_comm += indent + _('Term in days') + ': %s' % term
+        st_line_comm += indent + (minimum and _('Minimum applicable') or _('Minimum not applicable'))
+        st_line_comm += indent + _('Guarantee Number') + ': %s' % guarantee_number
+                
+    if comm_type == '124':
+        card_issuers = {
+            '1': 'Mastercard',
+            '2': 'Visa',
+            '3': 'American Express',
+            '4': 'Diners Club',
+            '9': _('Other')}
+        st_line_name = _('Settlement credit cards')
+        card_number = comm[0:20].strip()
+        card_issuer = card_issuers.get(comm[20], '')
+        invoice_number = comm[21:33].strip()
+        identification_number = comm[33:48].strip()
+        date = comm[48:54].strip() and str2date(comm[48:54]) or ''
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Card Number') + ': %s' % card_number
+        st_line_comm += indent + _('Issuing Institution') + ': %s' % card_issuer
+        st_line_comm += indent + _('Invoice Number') + ': %s' % invoice_number
+        st_line_comm += indent + _('Identification Number') + ': %s' % identification_number
+        st_line_comm += indent + _('Date') + ': %s' % date
+                
+    if comm_type == '125':
+        if line['trans_family'] not in st_line_name_families:
+            st_line_name = _('Credit')
+        credit_account = comm[0:27].strip()
+        if check_bban('BE', credit_account):
+            credit_account = '-'.join([credit_account[:3], credit_account[3:10], credit_account[10:]])
+        old_balance = list2float(comm[27:42])
+        new_balance = list2float(comm[42:57])
+        amount = list2float(comm[57:72])
+        currency = comm[72:75]
+        start_date = str2date(comm[75:81])
+        end_date = str2date(comm[81:87])
+        rate = number2float(comm[87:99], 8)
+        trans_reference = comm[99:112].strip()
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Credit Account Number') + ': %s' % credit_account
+        st_line_comm += indent + _('Old Balance') + ': %.2f' % old_balance
+        st_line_comm += indent + _('New Balance') + ': %.2f' % new_balance
+        st_line_comm += indent + _('Amount') + ': %.2f' % amount
+        st_line_comm += indent + _('Currency') + ': %s' % currency
+        st_line_comm += indent + _('Starting Date') + ': %s' % start_date
+        st_line_comm += indent + _('End Date') + ': %s' % end_date
+        st_line_comm += indent + _('Nominal Interest Rate or Rate of Charge') + ': %.4f' % rate
+        st_line_comm += indent + _('Transaction Reference') + ': %s' % trans_reference
+        
+    if comm_type == '127':
+        direct_debit_types = {
+            '0': _('unspecified'),
+            '1': _('recurrent'),
+            '2': _('one-off'),
+            '3': _('1-st (recurrent)'),
+            '4': _('last (recurrent)')} 
+        direct_debit_schemes = {
+            '0': _('unspecified'),
+            '1': _('SEPA core'),
+            '2': _('SEPA B2B')} 
+        paid_refusals = {
+            '0': _('paid'),
+            '1': _('technical problem'),
+            '2': _('refusal - reason not specified'),
+            '3': _('debtor disagrees'),
+            '4': _('debtor\'s account problem')}     
+        R_types = {
+            '0': _('paid'),
+            '1': _('reject'),
+            '2': _('return'),
+            '3': _('refund'),
+            '4': _('reversal'),
+            '5': _('cancellation')} 
+        st_line_name = _('European direct debit (SEPA)')
+        settlement_date = str2date(comm[0:6])
+        direct_debit_type = direct_debit_types.get(comm[6], '')
+        direct_debit_scheme = direct_debit_schemes.get(comm[7], '')
+        paid_refusal = paid_refusals.get(comm[8], '')
+        creditor_id = comm[9:44].strip()
+        mandate_ref = comm[44:79].strip()
+        comm_zone = comm[79:141]
+        R_type = R_types.get(comm[141], '')
+        reason = comm[142:146].strip()
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Settlement_Date') + ': %s' % settlement_date
+        st_line_comm += indent + _('Direct Debit Type') + ': %s' % direct_debit_type
+        st_line_comm += indent + _('Direct Debit Scheme') + ': %s' % direct_debit_scheme
+        st_line_comm += indent + _('Paid or reason for refusal') + ': %s' % paid_refusal
+        st_line_comm += indent + _('Creditor\'s Identification Code') + ': %s' % creditor_id
+        st_line_comm += indent + _('Mandate Reference') + ': %s' % mandate_ref
+        st_line_comm += indent + _('Communication') + ': %s' % comm_zone
+        st_line_comm += indent + _('R transaction Type') + ': %s' % R_type
+        st_line_comm += indent + _('Reason') + ': %s' % reason
+        
+    return st_line_name, st_line_comm
+
+def parse_comm_info(self, cr, uid, line, comm_type_table, context):
+    comm_type = line['struct_comm_type']
+    comm = st_line_comm = line['communication']
+    st_line_name = line['name']
+    
+    if comm_type == '001':
+        st_line_name = filter(lambda x: comm_type == x['code'], comm_type_table)[0]['description']
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Name') + ': %s' % comm[0:70].strip()
+        st_line_comm += indent + _('Street') + ': %s' % comm[70:105].strip()
+        st_line_comm += indent + _('Locality') + ': %s' % comm[105:140].strip()
+        st_line_comm += indent + _('Identification Code') + ': %s' % comm[140:175].strip()
+
+    if comm_type in ['002', '004', '005']:
+        st_line_name = filter(lambda x: comm_type == x['code'], comm_type_table)[0]['description']
+        st_line_comm = comm.strip()
+
+    if comm_type == '006':
+        amount_sign = comm[48]
+        amount = (comm[48] == '1' and '-' or '') + ('%.2f' % list2float(comm[33:48])) + ' ' + comm[30:33]
+        st_line_name = filter(lambda x: comm_type == x['code'], comm_type_table)[0]['description']
+        st_line_comm = '\n' + indent + st_line_name + indent + _('Description of the detail') + ': %s' % comm[0:30].strip()
+        st_line_comm += indent + _('Amount') + ': %s' % amount
+        st_line_comm += indent + _('Category') + ': %s' % comm[49:52].strip()
+
+    if comm_type not in ['001', '002', '004', '005', '006']:
+        _logger.warn('The parsing of Structured Commmunication Type %s has not yet been implemented. ' \
+            'Please contact Noviat (info@noviat.be) for more information about the development roadmap', comm_type)
+
+    return st_line_name, st_line_comm
+    
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
\ No newline at end of file