From 822a32cbe9b7c25715b0fe7d7624ab1419b0c39d Mon Sep 17 00:00:00 2001 From: Holger Brunn Date: Tue, 16 Apr 2013 18:57:20 +0200 Subject: [PATCH 1/7] [FIX] cascading deletes for banking_import_transaction and account_bank_statement_line didn't work [ADD] function to split off some amount from a statement line [ADD] unsplit transactions/statement lines when deleting split ones [ADD] allow to manually match multiple invoices/move lines causing the statement line to be split according to the amounts given --- account_banking/banking_import_transaction.py | 70 +++++++- .../wizard/banking_transaction_wizard.py | 163 ++++++++++++------ .../wizard/banking_transaction_wizard.xml | 8 + 3 files changed, 189 insertions(+), 52 deletions(-) diff --git a/account_banking/banking_import_transaction.py b/account_banking/banking_import_transaction.py index da70a274c..99a616310 100644 --- a/account_banking/banking_import_transaction.py +++ b/account_banking/banking_import_transaction.py @@ -1587,6 +1587,22 @@ class banking_import_transaction(osv.osv): return res + def unlink(self, cr, uid, ids, context=None): + """ + Unsplit if this if a split transaction + """ + for this in self.browse(cr, uid, ids, context): + if this.parent_id: + this.parent_id.write( + {'transferred_amount': + this.parent_id.transferred_amount + \ + this.transferred_amount, + }) + this.parent_id.refresh() + return super(banking_import_transaction, self).unlink( + cr, uid, ids, context=context) + + column_map = { # used in bank_import.py, converting non-osv transactions 'statement_id': 'statement', @@ -1638,7 +1654,7 @@ class banking_import_transaction(osv.osv): 'duplicate': fields.boolean('duplicate'), 'statement_line_id': fields.many2one( 'account.bank.statement.line', 'Statement line', - ondelete='CASCADE'), + ondelete='cascade'), 'statement_id': fields.many2one( 'account.bank.statement', 'Statement'), 'parent_id': fields.many2one( @@ -1709,7 +1725,7 @@ class account_bank_statement_line(osv.osv): _columns = { 'import_transaction_id': fields.many2one( 'banking.import.transaction', - 'Import transaction', readonly=True, delete='cascade'), + 'Import transaction', readonly=True, ondelete='cascade'), 'match_multi': fields.related( 'import_transaction_id', 'match_multi', type='boolean', string='Multi match', readonly=True), @@ -1730,6 +1746,8 @@ class account_bank_statement_line(osv.osv): 'state': fields.selection( [('draft', 'Draft'), ('confirmed', 'Confirmed')], 'State', readonly=True, required=True), + 'parent_id': fields.many2one('account.bank.statement.line', + 'Parent'), } _defaults = { @@ -1872,6 +1890,8 @@ class account_bank_statement_line(osv.osv): def unlink(self, cr, uid, ids, context=None): """ Don't allow deletion of a confirmed statement line + If this statement line comes from a split transaction, give the + amount back """ if type(ids) is int: ids = [ids] @@ -1881,6 +1901,12 @@ class account_bank_statement_line(osv.osv): _('Confirmed Statement Line'), _("You cannot delete a confirmed Statement Line" ": '%s'" % line.name)) + if line.parent_id: + line.parent_id.write( + { + 'amount': line.parent_id.amount + line.amount, + }) + line.parent_id.refresh() return super(account_bank_statement_line, self).unlink( cr, uid, ids, context=context) @@ -1920,6 +1946,46 @@ class account_bank_statement_line(osv.osv): 'import_transaction_id': res}, context=context) + def split_off(self, cr, uid, ids, amount, context=None): + """ + Create a child statement line with amount, deduce that from this line, + change transactions accordingly + """ + if context is None: + context = {} + + transaction_pool = self.pool.get('banking.import.transaction') + + child_statement_ids = [] + for this in self.browse(cr, uid, ids, context): + transaction_data = transaction_pool.copy_data( + cr, uid, this.import_transaction_id.id) + transaction_data['transferred_amount'] = amount + transaction_data['message'] = ( + (transaction_data['message'] or '') + _(' (split)')) + transaction_data['parent_id'] = this.import_transaction_id.id + transaction_id = transaction_pool.create( + cr, + uid, + transaction_data, + context=dict( + context, transaction_no_duplicate_search=True)) + + statement_line_data = self.copy_data( + cr, uid, this.id) + statement_line_data['amount'] = amount + statement_line_data['name'] = ( + (statement_line_data['name'] or '') + _(' (split)')) + statement_line_data['import_transaction_id'] = transaction_id + statement_line_data['parent_id'] = this.id + + child_statement_ids.append( + self.create(cr, uid, statement_line_data, + context=context)) + this.write({'amount': this.amount - amount}) + + return child_statement_ids + account_bank_statement_line() class account_bank_statement(osv.osv): diff --git a/account_banking/wizard/banking_transaction_wizard.py b/account_banking/wizard/banking_transaction_wizard.py index 53d2158d3..25f5e85ff 100644 --- a/account_banking/wizard/banking_transaction_wizard.py +++ b/account_banking/wizard/banking_transaction_wizard.py @@ -96,6 +96,8 @@ class banking_transaction_wizard(osv.osv_memory): # which populates regular fields on the transaction manual_invoice_id = vals.pop('manual_invoice_id', False) manual_move_line_id = vals.pop('manual_move_line_id', False) + manual_invoice_ids = vals.pop('manual_invoice_ids', []) + manual_move_line_ids = vals.pop('manual_move_line_ids', []) # Support for writing fields.related is still flakey: # https://bugs.launchpad.net/openobject-server/+bug/915975 @@ -167,55 +169,96 @@ class banking_transaction_wizard(osv.osv_memory): _("No entry found for the selected invoice. " + "Try manual reconciliation.")) - if manual_move_line_id or manual_invoice_id: + if manual_move_line_id or manual_invoice_id \ + or manual_move_line_ids or manual_invoice_ids: move_line_obj = self.pool.get('account.move.line') invoice_obj = self.pool.get('account.invoice') statement_line_obj = self.pool.get('account.bank.statement.line') - for wiz in self.browse( - cr, uid, ids, context=context): - move_line_id = False - invoice_id = manual_invoice_id - if invoice_id: - invoice = invoice_obj.browse( - cr, uid, manual_invoice_id, context=context) + manual_invoice_ids = ( + ([manual_invoice_id] if manual_invoice_id else []) + + [i[1] for i in manual_invoice_ids if i[0]==4] + + [j for i in manual_invoice_ids if i[0]==6 for j in i[2]]) + manual_move_line_ids = ( + ([manual_move_line_id] if manual_move_line_id else []) + + [i[1] for i in manual_move_line_ids if i[0]==4] + + [j for i in manual_move_line_ids if i[0]==6 for j in i[2]]) + for wiz in self.browse(cr, uid, ids, context=context): + #write can be called multiple times for the same values + #that doesn't hurt above, but it does here + if wiz.match_type and ( + len(manual_move_line_ids) > 1 or + len(manual_invoice_ids) > 1): + continue + + todo = [] + + for invoice in invoice_obj.browse( + cr, uid, manual_invoice_ids, context=context): + found_move_line = False if invoice.move_id: for line in invoice.move_id.line_id: if line.account_id.type in ('receivable', 'payable'): - move_line_id = line.id + todo.append((invoice.id, line.id)) + found_move_line = True break - if not move_line_id: - osv.except_osv( + if not found_move_line: + raise osv.except_osv( _("Cannot select for reconcilion"), _("No entry found for the selected invoice. ")) - else: - move_line_id = manual_move_line_id - move_line = move_line_obj.read( - cr, uid, move_line_id, ['invoice'], context=context) - invoice_id = (move_line['invoice'] and - move_line['invoice'][0]) - vals = { - 'move_line_id': move_line_id, - 'move_line_ids': [(6, 0, [move_line_id])], - 'invoice_id': invoice_id, - 'invoice_ids': [(6, 0, invoice_id and - [invoice_id] or [])], - 'match_type': 'manual', - } - transaction_obj.clear_and_write( - cr, uid, wiz.import_transaction_id.id, - vals, context=context) - st_line_vals = { - 'account_id': move_line_obj.read( - cr, uid, move_line_id, - ['account_id'], context=context)['account_id'][0], - } - if invoice_id: - st_line_vals['partner_id'] = invoice_obj.read( - cr, uid, invoice_id, - ['partner_id'], context=context)['partner_id'][0] - statement_line_obj.write( - cr, uid, wiz.import_transaction_id.statement_line_id.id, - st_line_vals, context=context) + for move_line_id in manual_move_line_ids: + todo_entry = [False, move_line_id] + move_line=move_line_obj.read( + cr, + uid, + move_line_id, + ['invoice'], + context=context) + if move_line['invoice']: + todo_entry[0] = move_line['invoice'][0] + todo.append(todo_entry) + + while todo: + todo_entry = todo.pop() + move_line = move_line_obj.browse( + cr, uid, todo_entry[1], context) + transaction_id = wiz.import_transaction_id.id + statement_line_id = wiz.statement_line_id.id + + if len(todo) > 0: + statement_line_id = wiz.statement_line_id.split_off( + move_line.credit or move_line.debit)[0] + transaction_id = statement_line_obj.browse( + cr, + uid, + statement_line_id, + context=context).import_transaction_id.id + + vals = { + 'move_line_id': todo_entry[1], + 'move_line_ids': [(6, 0, [todo_entry[1]])], + 'invoice_id': todo_entry[0], + 'invoice_ids': [(6, 0, + [todo_entry[0]] if todo_entry[0] else [])], + 'match_type': 'manual', + } + + transaction_obj.clear_and_write( + cr, uid, transaction_id, vals, context=context) + + st_line_vals = { + 'account_id': move_line_obj.read( + cr, uid, todo_entry[1], + ['account_id'], context=context)['account_id'][0], + } + + if todo_entry[0]: + st_line_vals['partner_id'] = invoice_obj.read( + cr, uid, todo_entry[0], + ['partner_id'], context=context)['partner_id'][0] + + statement_line_obj.write( + cr, uid, statement_line_id, + st_line_vals, context=context) return res def trigger_write(self, cr, uid, ids, context=None): @@ -247,14 +290,21 @@ class banking_transaction_wizard(osv.osv_memory): account_id = setting.default_debit_account_id and setting.default_debit_account_id.id statement_pool.write(cr, uid, wiz.statement_line_id.id, {'account_id':account_id}) - self.write(cr, uid, wiz.id, {'partner_id': False}, context=context) + wiz.write({'partner_id': False}) + + if wiz.statement_line_id: + #delete splits causing an unsplit if this is a split + #transaction + statement_pool.unlink(cr, uid, + statement_pool.search(cr, uid, + [('parent_id', '=', wiz.statement_line_id.id)], + context=context), + context=context) + + if wiz.import_transaction_id: + wiz.import_transaction_id.clear_and_write() + - wizs = self.read( - cr, uid, ids, ['import_transaction_id'], context=context) - trans_ids = [x['import_transaction_id'][0] for x in wizs - if x['import_transaction_id']] - self.pool.get('banking.import.transaction').clear_and_write( - cr, uid, trans_ids, context=context) return True def reverse_duplicate(self, cr, uid, ids, context=None): @@ -281,7 +331,7 @@ class banking_transaction_wizard(osv.osv_memory): return res def button_done(self, cr, uid, ids, context=None): - return {'nodestroy': False, 'type': 'ir.actions.act_window_close'} + return {'type': 'ir.actions.act_window_close'} _defaults = { # 'match_type': _get_default_match_type, @@ -305,6 +355,9 @@ class banking_transaction_wizard(osv.osv_memory): 'statement_line_id', 'partner_id', type='many2one', relation='res.partner', string="Partner", readonly=True), + 'statement_line_parent_id': fields.related( + 'statement_line_id', 'parent_id', type='many2one', + relation='account.bank.statement.line', readonly=True), 'import_transaction_id': fields.related( 'statement_line_id', 'import_transaction_id', string="Import transaction", @@ -356,8 +409,18 @@ class banking_transaction_wizard(osv.osv_memory): 'manual_move_line_id': fields.many2one( 'account.move.line', 'Or match this entry', domain=[('account_id.reconcile', '=', True), - ('reconcile_id', '=', False)], - ), + ('reconcile_id', '=', False)]), + 'manual_invoice_ids': fields.many2many( + 'account.invoice', + 'banking_transaction_wizard_account_invoice_rel', + 'wizard_id', 'invoice_id', string='Match following invoices', + domain=[('reconciled', '=', False)]), + 'manual_move_line_ids': fields.many2many( + 'account.move.line', + 'banking_transaction_wizard_account_move_line_rel', + 'wizard_id', 'move_line_id', string='Or match this entries', + domain=[('account_id.reconcile', '=', True), + ('reconcile_id', '=', False)]), 'payment_option': fields.related('import_transaction_id','payment_option', string='Payment Difference', type='selection', required=True, selection=[('without_writeoff', 'Keep Open'),('with_writeoff', 'Reconcile Payment Balance')]), 'writeoff_analytic_id': fields.related( diff --git a/account_banking/wizard/banking_transaction_wizard.xml b/account_banking/wizard/banking_transaction_wizard.xml index 1d76fd29c..6a7f5c75b 100644 --- a/account_banking/wizard/banking_transaction_wizard.xml +++ b/account_banking/wizard/banking_transaction_wizard.xml @@ -9,6 +9,7 @@
+ @@ -114,6 +115,13 @@ type="object" string="Match"/> + + + +