← Back to team overview

banking-addons-team team mailing list archive

[Merge] lp:~therp-nl/banking-addons/ba7.0-manual_multi_match into lp:banking-addons/banking-addons-70

 

Stefan Rijnhart (Therp) has proposed merging lp:~therp-nl/banking-addons/ba7.0-manual_multi_match into lp:banking-addons/banking-addons-70 with lp:~therp-nl/banking-addons/ba7.0-MIG-import as a prerequisite.

Commit message:
[IMP] Allow multiple entries when manually selecting invoices or entries

Requested reviews:
  Banking Addons Team (banking-addons-team)

For more details, see:
https://code.launchpad.net/~therp-nl/banking-addons/ba7.0-manual_multi_match/+merge/160669

Reconcile multiple manually selected entries by splitting up the bank statement line.

Forward port of https://code.launchpad.net/~therp-nl/banking-addons/6.1-match_multiple_invoices_split/+merge/159348 by Holger Brunn but for 7.0
-- 
https://code.launchpad.net/~therp-nl/banking-addons/ba7.0-manual_multi_match/+merge/160669
Your team Banking Addons Team is requested to review the proposed merge of lp:~therp-nl/banking-addons/ba7.0-manual_multi_match into lp:banking-addons/banking-addons-70.
=== modified file 'account_banking/__openerp__.py'
--- account_banking/__openerp__.py	2013-05-01 15:17:30 +0000
+++ account_banking/__openerp__.py	2013-05-01 15:17:30 +0000
@@ -43,6 +43,9 @@
         'wizard/banking_transaction_wizard.xml',
         'workflow/account_invoice.xml',
     ],
+    'js': [
+        'static/src/js/account_banking.js',
+    ],
     'external_dependencies': {
         'python' : ['BeautifulSoup'],
     },

=== modified file 'account_banking/account_banking_view.xml'
--- account_banking/account_banking_view.xml	2013-05-01 15:17:30 +0000
+++ account_banking/account_banking_view.xml	2013-05-01 15:17:30 +0000
@@ -248,10 +248,14 @@
                            position="after">
                         <field name="match_type"/>
                         <field name="residual"/>
-                        <button name="match_wizard" states="draft"
+                        <field name="parent_id" invisible="1" />
+                        <button name="match_wizard"
                                 string="Match"
                                 icon="terp-gtk-jump-to-ltr"
-                                type="object"/>
+                                attrs="{'invisible': ['|', ('parent_id', '!=', False),
+                                                           ('state', '!=', 'draft')]}"
+                                type="object"
+                                />
                         <field name="match_multi" invisible="1"/>
                         <field name="duplicate" invisible="1"/>
                         <field name="state"/>
@@ -328,10 +332,14 @@
                     <field name="amount"/>
                     <field name="match_type"/>
                     <field name="residual"/>
+                    <field name="parent_id" invisible="1" />
                     <button name="match_wizard" states="draft"
                             string="Match"
                             icon="terp-gtk-jump-to-ltr"
-                            type="object"/>
+                            attrs="{'invisible': ['|', ('parent_id', '!=', False),
+                                                       ('state', '!=', 'draft')]}"
+                            type="object"
+                            />
                     <field name="match_multi" invisible="1"/>
                     <field name="duplicate" invisible="1"/>
                     <field name="state"/>

=== modified file 'account_banking/banking_import_transaction.py'
--- account_banking/banking_import_transaction.py	2013-05-01 15:17:30 +0000
+++ account_banking/banking_import_transaction.py	2013-05-01 15:17:30 +0000
@@ -1294,6 +1294,21 @@
 
         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',
@@ -1345,7 +1360,7 @@
         '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',
             ondelete='CASCADE'),
@@ -1410,7 +1425,7 @@
     _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),
@@ -1430,6 +1445,8 @@
         'state': fields.selection(
             [('draft', 'Draft'), ('confirmed', 'Confirmed')], 'State',
             readonly=True, required=True),
+        'parent_id': fields.many2one('account.bank.statement.line',
+            'Parent'),
         }
 
     _defaults = {
@@ -1570,6 +1587,8 @@
     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]
@@ -1579,6 +1598,12 @@
                     _('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)
 
@@ -1618,7 +1643,45 @@
                         'import_transaction_id': res},
                     context=context)
 
-account_bank_statement_line()
+    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
 
 
 class account_bank_statement(orm.Model):

=== added directory 'account_banking/static'
=== added directory 'account_banking/static/src'
=== added directory 'account_banking/static/src/js'
=== added file 'account_banking/static/src/js/account_banking.js'
--- account_banking/static/src/js/account_banking.js	1970-01-01 00:00:00 +0000
+++ account_banking/static/src/js/account_banking.js	2013-05-01 15:17:30 +0000
@@ -0,0 +1,51 @@
+/*############################################################################
+#
+#    Copyright (C) 2013 Therp BV (<http://therp.nl>).
+#            
+#    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/>.
+#
+############################################################################*/
+
+openerp.account_banking = function(instance)
+{
+    var _t = instance.web._t;
+    instance.web.Dialog.include(
+    {
+        close: function()
+        {
+            this._super.apply(this, arguments);
+            if (this.dialog_title == _t("Match transaction"))
+            {   
+                // The match wizard can create or unlink a statement line
+                // Force a reload of the view so that the correct lines
+                // are shown.
+                var parent = this.getParent()
+                if (parent)
+                {
+                    var child = this.getParent().getChildren()[0];
+                    if (child.views) {
+                        _.each(child.views, function(view)
+                               {
+                                   if (view && view.controller)
+                                   {
+                                       view.controller.reload();
+                                   }
+                               }
+                              );
+                    }
+                }
+            }
+        },
+    });
+}

=== modified file 'account_banking/wizard/banking_transaction_wizard.py'
--- account_banking/wizard/banking_transaction_wizard.py	2013-05-01 15:17:30 +0000
+++ account_banking/wizard/banking_transaction_wizard.py	2013-05-01 15:17:30 +0000
@@ -96,8 +96,8 @@
         # The following fields get never written
         # they are just triggers for manual matching
         # 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
@@ -169,55 +169,93 @@
                             _("No entry found for the selected invoice. " +
                               "Try manual reconciliation."))
 
-        if manual_move_line_id or manual_invoice_id:
+        if 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 = (
+                    [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 = (
+                    [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:
-                        orm.except_orm(
+                    if not found_move_line:
+                        raise orm.except_orm(
                             _("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):
@@ -249,14 +287,20 @@
                     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)
-            
-            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)
+            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()
+
         return self.create_act_window(cr, uid, ids, context=None)
 
     def reverse_duplicate(self, cr, uid, ids, context=None):
@@ -283,7 +327,7 @@
         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,
@@ -307,6 +351,9 @@
             '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",
@@ -318,10 +365,6 @@
             'import_transaction_id', 'writeoff_account_id',
             type='many2one', relation='account.account',
             string='Write-off account'),
-        'writeoff_journal_id': fields.related(
-            'import_transaction_id', 'writeoff_journal_id',
-            type='many2one', relation='account.journal',
-            string='Write-off journal'),
         'invoice_ids': fields.related(
             'import_transaction_id', 'invoice_ids', string="Matching invoices", 
             type='many2many', relation='account.invoice'),
@@ -343,14 +386,17 @@
         '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',
+        'manual_invoice_ids': fields.many2many(
+            'account.invoice',
+            'banking_transaction_wizard_account_invoice_rel',
+            'wizard_id', 'invoice_id', string='Match one or more invoices',
             domain=[('reconciled', '=', False)]),
-        'manual_move_line_id': fields.many2one(
-            'account.move.line', 'Or match this entry',
+        'manual_move_line_ids': fields.many2many(
+            'account.move.line',
+            'banking_transaction_wizard_account_move_line_rel',
+            'wizard_id', 'move_line_id', string='Or match one or more entries',
             domain=[('account_id.reconcile', '=', True),
-                    ('reconcile_id', '=', False)],
-            ),
+                    ('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(

=== modified file 'account_banking/wizard/banking_transaction_wizard.xml'
--- account_banking/wizard/banking_transaction_wizard.xml	2013-05-01 15:17:30 +0000
+++ account_banking/wizard/banking_transaction_wizard.xml	2013-05-01 15:17:30 +0000
@@ -8,6 +8,7 @@
             <field name="arch" type="xml">
                 <form string="Match transaction">
                     <!-- fields used for form logic -->
+                    <field name="statement_line_parent_id" invisible="True"/>
                     <field name="invoice_ids" invisible="True"/>
                     <field name="move_line_ids" invisible="True"/>
                     <field name="match_multi" invisible="True"/>
@@ -81,30 +82,18 @@
                                         name="trigger_match"
                                         type="object"
                                         string="Match again"/>
-                                <!-- Manual selection -->
                             </page>
+                            <!-- Manual selection -->
                             <page string="Manual match">
-                                <field name="manual_invoice_id"/>
-                                <!-- 
-                                     Specify alternative tree_view_ref as a
-                                     workaround for lp:1073521 in OpenERP 6.1
-                                     Need to also define 'view_mode' to prevent
-                                     an instant editable tree view
-                                     reconstruction by account.move.line's
-                                     fields_view_get().
-                                     Both are not needed in OpenERP 6.0 or 7.0.
-                                -->
-                                <field name="manual_move_line_id"
-                                       context="{
-                                                'tree_view_ref': 'account.view_move_line_tax_tree',
-                                                'view_mode': 'yes'
-                                                }"
-                                       />
-                                <newline/>
-                                <button colspan="1"
-                                        name="trigger_write"
+                                <field name="manual_invoice_ids" colspan="4"
+                                       context="{'search_default_partner_id': partner_id}"
+                                       />
+                                <field name="manual_move_line_ids" colspan="4"
+                                       context="{'search_default_partner_id': partner_id}"
+                                       />
+                                <button name="trigger_write"
                                         type="object"
-                                        string="Match"/>
+                                        string="Match" />
                             </page>
                             <page string="Write-Off" attrs="{'invisible': [('match_type', '=', False)]}">
                                 <group colspan="2" col="2">
@@ -131,7 +120,7 @@
                         </notebook>
                         <group colspan="2">
                             <separator/>
-                            <button icon="gtk-ok" string="Done" special="cancel"/>
+                            <button icon="gtk-ok" string="Close" special="cancel"/>
                         </group>
                     </group>
                 </form>

=== modified file 'account_direct_debit/model/account_move_line.py'
--- account_direct_debit/model/account_move_line.py	2013-01-24 11:04:21 +0000
+++ account_direct_debit/model/account_move_line.py	2013-05-01 15:17:30 +0000
@@ -58,7 +58,7 @@
         if not args:
             return []
         line_obj = self.pool.get('account.move.line')
-        query = line_obj._query_get(cr, uid, context={})
+        query = line_obj._query_get(cr, uid, context=context)
         where = ' and '.join(map(lambda x: '''(SELECT
         CASE WHEN l.amount_currency > 0
             THEN l.amount_currency


Follow ups