From 908ade7d5ce1ee27d066f2078df7e158e44c0efc Mon Sep 17 00:00:00 2001 From: OpenERP instance user Date: Mon, 19 Dec 2011 21:58:28 +0100 Subject: [PATCH] [FIX] do an actual partial reconciliation at statement confirm time [FIX] no context in match_invoice() [FIX] transaction update and browse again after split [ADD] transaction parent_id relation for split records [ADD] residual and write-off infrastructure [ADD] option to disable reconciliation on bank transaction --- account_banking/account_banking.py | 4 +- account_banking/bank_import_transaction.py | 88 +++++++++++++++---- .../wizard/banking_transaction_wizard.py | 32 +++++-- .../wizard/banking_transaction_wizard.xml | 69 ++++++++++++--- 4 files changed, 154 insertions(+), 39 deletions(-) diff --git a/account_banking/account_banking.py b/account_banking/account_banking.py index bfccef41d..825b95905 100644 --- a/account_banking/account_banking.py +++ b/account_banking/account_banking.py @@ -453,7 +453,9 @@ class account_bank_statement(osv.osv): """ if st_line.reconcile_id: account_move_line_obj.write(cr, uid, [torec], { - 'reconcile_id': st_line.reconcile_id.id }, context=context) + (st_line.reconcile_id.line_partial_ids and + 'reconcile_partial_id' or 'reconcile_id'): + st_line.reconcile_id.id }, context=context) for move_line in (st_line.reconcile_id.line_id or []) + ( st_line.reconcile_id.line_partial_ids or []): netsvc.LocalService("workflow").trg_trigger( diff --git a/account_banking/bank_import_transaction.py b/account_banking/bank_import_transaction.py index 1fc389280..fa699407c 100644 --- a/account_banking/bank_import_transaction.py +++ b/account_banking/bank_import_transaction.py @@ -171,7 +171,8 @@ class banking_import_transaction(osv.osv): return False def _match_invoice(self, cr, uid, trans, move_lines, - partner_ids, bank_account_ids, log, linked_invoices): + partner_ids, bank_account_ids, log, linked_invoices, + context=None): ''' Find the invoice belonging to this reference - if there is one Use the sales journal to check. @@ -405,17 +406,15 @@ class banking_import_transaction(osv.osv): dict( transferred_amount = trans.transferred_amount - expected, transaction = trans.transaction + 'b', + parent_id = trans.id, ), context=context) + # update the current record self.write(cr, uid, trans.id, dict( - transaction = trans.transaction + 'a'), context) - # NOTE: the following is debatable. By copying the - # eyecatcher of the invoice itself, we enhance the - # tracability of the invoices, but we degrade the - # tracability of the bank transactions. When debugging, it - # is wise to disable this line. - # Stefan: disabled for interactive mode - # trans.reference = eyecatcher(move_line.invoice) - + transferred_amount = expected, + transaction = trans.transaction + 'a', + ), context) + # rebrowse the current record after writing + trans=self.browse(cr, uid, trans.id, context=context) if move_line: account_ids = [ x.id for x in bank_account_ids @@ -691,8 +690,16 @@ class banking_import_transaction(osv.osv): continue if transaction.match_type not in self.reconcile_map: raise osv.except_osv( - _("Cannot reconcile type %s" % transaction.match_type), - _("No method found to reconcile this type")) + _("Cannot reconcile"), + _("Cannot reconcile type %s. No method found to reconcile this type") % + transaction.match_type + ) + if transaction.residual and transaction.writeoff_account_id: + raise osv.except_osv( + _("Cannot reconcile"), + _("Bank transaction %s has a residual amount, and a write-off account. Write off not implemented.") % + transaction.statement_line_id.name + ) # run the method that is appropriate for this match type reconcile_id = self.reconcile_map[transaction.match_type]( self, cr, uid, transaction.id, context) @@ -1049,6 +1056,7 @@ class banking_import_transaction(osv.osv): transferred_amount = transaction.provision_costs, remote_currency = transaction.provision_costs_currency, message = transaction.provision_costs_description, + parent_id = transaction.id, ), context) injected.append(self.browse(cr, uid, cost_id, context)) @@ -1066,7 +1074,8 @@ class banking_import_transaction(osv.osv): provision_costs_currency = False, provision_costs_description = False, ), context=context) - + # rebrowse the current record after writing + transaction=self.browse(cr, uid, transaction.id, context=context) # Match full direct debit orders if transaction.type == bt.DIRECT_DEBIT: move_info = self._match_debit_order( # TODO reconcile preservation @@ -1154,9 +1163,9 @@ class banking_import_transaction(osv.osv): move_info, remainder = self._match_invoice( cr, uid, transaction, move_lines, partner_ids, partner_banks, results['log'], linked_invoices, - ) + context=context) if remainder: - injected.append(remainder) + injected.append(self.browse(cr, uid, remainder, context)) account_id = move_info and move_info.get('account_id', False) if not account_id: @@ -1269,6 +1278,38 @@ class banking_import_transaction(osv.osv): 'id': 'transaction' } + def _get_residual(self, cr, uid, ids, name, args, context=None): + """ + Calculate the residual against the candidate reconciliation. + When + - residual > 0 and transferred amount > 0, or + - residual < 0 and transferred amount < 0 + + the result is a partial reconciliation. In the other cases, + a new statement line can be split off. + + We should give users the option to reconcile with writeoff + or partial reconciliation / new statement line + """ + + if not ids: + return {} + res = dict([(x, False) for x in ids]) + move_line_obj = self.pool.get('account.move.line') + for transaction in self.browse(cr, uid, ids, context): + if (transaction.match_type in + [('invoice'), ('move'), ('manual')]): + rec_moves = ( + transaction.move_line_id.reconcile_id and + transaction.move_line_id.reconcile_id.line_id or + transaction.move_line_id.reconcile_partial_id and + transaction.move_line_id.reconcile_partial_id.line_partial_ids or + [transaction.move_line_id]) + res[transaction.id] = ( + move_line_obj.get_balance(cr, uid, [x.id for x in rec_moves]) + - transaction.transferred_amount) + return res + def _get_match_multi(self, cr, uid, ids, name, args, context=None): """ Indicate in the wizard that multiple matches have been found @@ -1333,11 +1374,13 @@ class banking_import_transaction(osv.osv): 'account.bank.statement.line', 'Statement line'), 'statement_id': fields.many2one( 'account.bank.statement', 'Statement'), - + 'parent_id': fields.many2one( + 'banking.import.transaction', 'Split off from this transaction'), # match fields 'match_type': fields.selection( - [('move','Move'), ('invoice', 'Invoice'), ('payment', 'Payment'), - ('payment_order', 'Payment order'), ('storno', 'Storno')], + [('manual', 'Manual'), ('move','Move'), ('invoice', 'Invoice'), + ('payment', 'Payment'), ('payment_order', 'Payment order'), + ('storno', 'Storno')], 'Match type'), 'match_multi': fields.function( _get_match_multi, method=True, string='Multi match', @@ -1359,6 +1402,12 @@ class banking_import_transaction(osv.osv): 'invoice_id': fields.many2one( 'account.invoice', 'Invoice to reconcile'), 'payment_line_id': fields.many2one('payment.line', 'Payment line'), + 'residual': fields.function( + _get_residual, method=True, string='Residual', type='float'), + 'writeoff_account_id': fields.many2one( + 'account.account', 'Write off account'), + 'writeoff_move_line_id': fields.many2one( + 'account.move.line', 'Write off move line'), } _defaults = { 'company_id': lambda s,cr,uid,c: @@ -1377,6 +1426,9 @@ class account_bank_statement_line(osv.osv): 'match_multi': fields.related( 'import_transaction_id', 'match_multi', type='boolean', string='Multi match'), + 'residual': fields.related( + 'import_transaction_id', 'residual', type='float', + string='Residual'), 'duplicate': fields.related( 'import_transaction_id', 'duplicate', type='boolean', string='Possible duplicate import'), diff --git a/account_banking/wizard/banking_transaction_wizard.py b/account_banking/wizard/banking_transaction_wizard.py index 97f9bb720..e55f5ffdf 100644 --- a/account_banking/wizard/banking_transaction_wizard.py +++ b/account_banking/wizard/banking_transaction_wizard.py @@ -113,12 +113,29 @@ class banking_transaction_wizard(osv.osv_memory): _("No entry found for the selected invoice. " + "Try manual reconciliation.")) - def select_match(self, cr, uid, ids, context=None): + def trigger_write(self, cr, uid, ids, context=None): """ Just a button that triggers a write. """ return True + def disable_match(self, cr, uid, ids, context=None): + vals = (dict([(x, False) for x in [ + 'match_type', + 'move_line_id', + 'invoice_id', + 'manual_invoice_id', + 'manual_move_line_id', + 'payment_line_id', + ]] + + [(x, [(6, 0, [])]) for x in [ + 'move_line_ids', + 'invoice_ids', + 'payment_order_ids', + ]])) + self.write(cr, uid, ids, vals, context=context) + return True + def reverse_duplicate(self, cr, uid, ids, context=None): if isinstance(ids, (int, float)): ids = [ids] @@ -144,7 +161,7 @@ class banking_transaction_wizard(osv.osv_memory): return {'nodestroy': False, 'type': 'ir.actions.act_window_close'} _defaults = { - 'match_type': _get_default_match_type, +# 'match_type': _get_default_match_type, } _columns = { @@ -162,6 +179,10 @@ class banking_transaction_wizard(osv.osv_memory): 'residual': fields.related( 'import_transaction_id', 'residual', type='float', string='Residual'), + 'writeoff_account_id': fields.related( + 'import_transaction_id', 'writeoff_account_id', + type='many2one', relation='account.account', + string='Write off account'), 'payment_line_id': fields.related( 'import_transaction_id', 'payment_line_id', string="Matching payment or storno", type='many2one', relation='payment.line'), @@ -189,10 +210,9 @@ class banking_transaction_wizard(osv.osv_memory): 'match_multi': fields.related( 'import_transaction_id', 'match_multi', type="boolean", string='Multiple matches'), - 'match_type': fields.selection( - [('manual', 'Manual'), ('move','Move'), ('invoice', 'Invoice'), - ('payment', 'Payment'), ('payment_order', 'Payment order'), - ('storno', 'Storno')], 'Match type', readonly=True), + 'match_type': fields.related( + 'import_transaction_id', 'match_type', + type="char", size=16, string='Match type', readonly=True), 'manual_invoice_id': fields.many2one( 'account.invoice', 'Match this invoice', domain=[('state', '=', 'open')]), diff --git a/account_banking/wizard/banking_transaction_wizard.xml b/account_banking/wizard/banking_transaction_wizard.xml index 9f1167df1..e211942bd 100644 --- a/account_banking/wizard/banking_transaction_wizard.xml +++ b/account_banking/wizard/banking_transaction_wizard.xml @@ -15,6 +15,8 @@ + + + + + + + attrs="{'invisible': [('match_type', 'not in', [('payment', 'storno')])]}" + /> + domain="[('id', 'in', invoice_ids[0][2])]" + /> + attrs="{'invisible': [('match_type', '!=', 'move')]}" + domain="[('id', 'in', move_line_ids[0][2])]" + /> + attrs="{'invisible': [('match_type', '!=', 'payment_order')]}" + domain="[('id', 'in', payment_order_ids[0][2])]" + />