Merge lp:~therp-nl/banking-addons/6.1-match_multiple_invoices_split into lp:banking-addons/6.1

Proposed by Stefan Rijnhart (Opener)
Status: Merged
Merged at revision: 172
Proposed branch: lp:~therp-nl/banking-addons/6.1-match_multiple_invoices_split
Merge into: lp:banking-addons/6.1
Diff against target: 558 lines (+265/-90)
7 files modified
account_banking/__openerp__.py (+3/-0)
account_banking/account_banking_view.xml (+8/-2)
account_banking/banking_import_transaction.py (+73/-3)
account_banking/i18n/nl.po (+6/-6)
account_banking/static/src/js/account_banking.js (+51/-0)
account_banking/wizard/banking_transaction_wizard.py (+113/-57)
account_banking/wizard/banking_transaction_wizard.xml (+11/-22)
To merge this branch: bzr merge lp:~therp-nl/banking-addons/6.1-match_multiple_invoices_split
Reviewer Review Type Date Requested Status
Holger Brunn (Therp) Approve
Stefan Rijnhart (Opener) testing, code review Approve
Review via email: mp+159348@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

Unholding because what was claimed to be a problem is a works as designed: Invoices obviously only get paid after you confirm a match, that also applies to split matches.

Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) :
review: Approve (testing, code review)
Revision history for this message
Holger Brunn (Therp) (hbrunn) :
review: Approve
170. By Stefan Rijnhart (Opener)

[FIX] Calculation of residual in the case of split transactions
[FIX] Transaction amount when credit amounts are split
[FIX] Restore bank account's partner when disabling match

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'account_banking/__openerp__.py'
2--- account_banking/__openerp__.py 2012-05-07 11:49:08 +0000
3+++ account_banking/__openerp__.py 2013-06-03 09:55:43 +0000
4@@ -47,6 +47,9 @@
5 'wizard/banking_transaction_wizard.xml',
6 'workflow/account_invoice.xml',
7 ],
8+ 'js': [
9+ 'static/src/js/account_banking.js',
10+ ],
11 'demo_xml': [],
12 'external_dependencies': {
13 'python' : ['BeautifulSoup'],
14
15=== modified file 'account_banking/account_banking_view.xml'
16--- account_banking/account_banking_view.xml 2013-04-25 08:15:21 +0000
17+++ account_banking/account_banking_view.xml 2013-06-03 09:55:43 +0000
18@@ -292,9 +292,12 @@
19 <xpath expr="/form/notebook/page/field[@name='line_ids']/tree/field[@name='amount']" position="after">
20 <field name="match_type"/>
21 <field name="residual"/>
22- <button name="match_wizard" states="draft"
23+ <field name="parent_id" invisible="1" />
24+ <button name="match_wizard"
25 string="Match"
26 icon="terp-gtk-jump-to-ltr"
27+ attrs="{'invisible': ['|', ('parent_id', '!=', False),
28+ ('state', '!=', 'draft')]}"
29 type="object"/>
30 <field name="match_multi" invisible="1"/>
31 <field name="duplicate" invisible="1"/>
32@@ -523,9 +526,12 @@
33 <field name="amount"/>
34 <field name="match_type"/>
35 <field name="residual"/>
36- <button name="match_wizard" states="draft"
37+ <field name="parent_id" invisible="1" />
38+ <button name="match_wizard"
39 string="Match"
40 icon="terp-gtk-jump-to-ltr"
41+ attrs="{'invisible': ['|', ('parent_id', '!=', False),
42+ ('state', '!=', 'draft')]}"
43 type="object"/>
44 <field name="match_multi" invisible="1"/>
45 <field name="duplicate" invisible="1"/>
46
47=== modified file 'account_banking/banking_import_transaction.py'
48--- account_banking/banking_import_transaction.py 2013-05-20 08:07:48 +0000
49+++ account_banking/banking_import_transaction.py 2013-06-03 09:55:43 +0000
50@@ -1485,7 +1485,7 @@
51 not(transaction.move_currency_amount is False)):
52 res[transaction.id] = (
53 transaction.move_currency_amount -
54- transaction.transferred_amount
55+ transaction.statement_line_id.amount
56 )
57 return res
58
59@@ -1572,6 +1572,22 @@
60
61 return res
62
63+ def unlink(self, cr, uid, ids, context=None):
64+ """
65+ Unsplit if this if a split transaction
66+ """
67+ for this in self.browse(cr, uid, ids, context):
68+ if this.parent_id:
69+ this.parent_id.write(
70+ {'transferred_amount':
71+ this.parent_id.transferred_amount + \
72+ this.transferred_amount,
73+ })
74+ this.parent_id.refresh()
75+ return super(banking_import_transaction, self).unlink(
76+ cr, uid, ids, context=context)
77+
78+
79 column_map = {
80 # used in bank_import.py, converting non-osv transactions
81 'statement_id': 'statement',
82@@ -1623,7 +1639,7 @@
83 'duplicate': fields.boolean('duplicate'),
84 'statement_line_id': fields.many2one(
85 'account.bank.statement.line', 'Statement line',
86- ondelete='CASCADE'),
87+ ondelete='cascade'),
88 'statement_id': fields.many2one(
89 'account.bank.statement', 'Statement'),
90 'parent_id': fields.many2one(
91@@ -1694,7 +1710,7 @@
92 _columns = {
93 'import_transaction_id': fields.many2one(
94 'banking.import.transaction',
95- 'Import transaction', readonly=True, delete='cascade'),
96+ 'Import transaction', readonly=True, ondelete='cascade'),
97 'match_multi': fields.related(
98 'import_transaction_id', 'match_multi', type='boolean',
99 string='Multi match', readonly=True),
100@@ -1715,6 +1731,8 @@
101 'state': fields.selection(
102 [('draft', 'Draft'), ('confirmed', 'Confirmed')], 'State',
103 readonly=True, required=True),
104+ 'parent_id': fields.many2one('account.bank.statement.line',
105+ 'Parent'),
106 }
107
108 _defaults = {
109@@ -1857,6 +1875,8 @@
110 def unlink(self, cr, uid, ids, context=None):
111 """
112 Don't allow deletion of a confirmed statement line
113+ If this statement line comes from a split transaction, give the
114+ amount back
115 """
116 if type(ids) is int:
117 ids = [ids]
118@@ -1866,6 +1886,12 @@
119 _('Confirmed Statement Line'),
120 _("You cannot delete a confirmed Statement Line"
121 ": '%s'" % line.name))
122+ if line.parent_id:
123+ line.parent_id.write(
124+ {
125+ 'amount': line.parent_id.amount + line.amount,
126+ })
127+ line.parent_id.refresh()
128 return super(account_bank_statement_line, self).unlink(
129 cr, uid, ids, context=context)
130
131@@ -1905,6 +1931,50 @@
132 'import_transaction_id': res},
133 context=context)
134
135+ def split_off(self, cr, uid, ids, amount, context=None):
136+ """
137+ Create a child statement line with amount, deduce that from this line,
138+ change transactions accordingly
139+ """
140+ if context is None:
141+ context = {}
142+
143+ transaction_pool = self.pool.get('banking.import.transaction')
144+
145+ child_statement_ids = []
146+ for this in self.browse(cr, uid, ids, context):
147+ transaction_data = transaction_pool.copy_data(
148+ cr, uid, this.import_transaction_id.id)
149+ transaction_data['transferred_amount'] = amount
150+ transaction_data['message'] = (
151+ (transaction_data['message'] or '') + _(' (split)'))
152+ transaction_data['parent_id'] = this.import_transaction_id.id
153+ transaction_id = transaction_pool.create(
154+ cr,
155+ uid,
156+ transaction_data,
157+ context=dict(
158+ context, transaction_no_duplicate_search=True))
159+
160+ statement_line_data = self.copy_data(
161+ cr, uid, this.id)
162+ statement_line_data['amount'] = amount
163+ statement_line_data['name'] = (
164+ (statement_line_data['name'] or '') + _(' (split)'))
165+ statement_line_data['import_transaction_id'] = transaction_id
166+ statement_line_data['parent_id'] = this.id
167+ statement_line_id = self.create(
168+ cr, uid, statement_line_data, context=context)
169+
170+ child_statement_ids.append(statement_line_id)
171+ transaction_pool.write(
172+ cr, uid, transaction_id, {
173+ 'statement_line_id': statement_line_id,
174+ }, context=context)
175+ this.write({'amount': this.amount - amount})
176+
177+ return child_statement_ids
178+
179 account_bank_statement_line()
180
181 class account_bank_statement(osv.osv):
182
183=== modified file 'account_banking/i18n/nl.po'
184--- account_banking/i18n/nl.po 2012-05-02 15:09:49 +0000
185+++ account_banking/i18n/nl.po 2013-06-03 09:55:43 +0000
186@@ -139,9 +139,9 @@
187 msgstr "remote_bank_bic"
188
189 #. module: account_banking
190-#: field:banking.transaction.wizard,manual_invoice_id:0
191-msgid "Match this invoice"
192-msgstr "Match deze factuur"
193+#: field:banking.transaction.wizard,manual_invoice_ids:0
194+msgid "Match one or more invoices"
195+msgstr "Match een of meerdere facturen"
196
197 #. module: account_banking
198 #: field:banking.import.transaction,remote_bank_ibei:0
199@@ -1022,9 +1022,9 @@
200 msgstr "Herzien"
201
202 #. module: account_banking
203-#: field:banking.transaction.wizard,manual_move_line_id:0
204-msgid "Or match this entry"
205-msgstr "Of koppel deze boekingsregel"
206+#: field:banking.transaction.wizard,manual_move_line_ids:0
207+msgid "Or match one or more entries"
208+msgstr "Of koppel deze boekingsregel(s)"
209
210 #. module: account_banking
211 #: help:payment.mode.type,name:0
212
213=== added directory 'account_banking/static'
214=== added directory 'account_banking/static/src'
215=== added directory 'account_banking/static/src/js'
216=== added file 'account_banking/static/src/js/account_banking.js'
217--- account_banking/static/src/js/account_banking.js 1970-01-01 00:00:00 +0000
218+++ account_banking/static/src/js/account_banking.js 2013-06-03 09:55:43 +0000
219@@ -0,0 +1,51 @@
220+/*############################################################################
221+#
222+# Copyright (C) 2013 Therp BV (<http://therp.nl>).
223+#
224+# All other contributions are (C) by their respective contributors
225+#
226+# All Rights Reserved
227+#
228+# WARNING: This program as such is intended to be used by professional
229+# programmers who take the whole responsability of assessing all potential
230+# consequences resulting from its eventual inadequacies and bugs
231+# End users who are looking for a ready-to-use solution with commercial
232+# garantees and support are strongly adviced to contract EduSense BV
233+#
234+# This program is free software: you can redistribute it and/or modify
235+# it under the terms of the GNU General Public License as published by
236+# the Free Software Foundation, either version 3 of the License, or
237+# (at your option) any later version.
238+#
239+# This program is distributed in the hope that it will be useful,
240+# but WITHOUT ANY WARRANTY; without even the implied warranty of
241+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
242+# GNU General Public License for more details.
243+#
244+# You should have received a copy of the GNU General Public License
245+# along with this program. If not, see <http://www.gnu.org/licenses/>.
246+#
247+############################################################################*/
248+
249+openerp.account_banking = function(openerp)
250+{
251+ var _t = openerp.web._t;
252+ openerp.web.Dialog.include(
253+ {
254+ on_close: function()
255+ {
256+ this._super.apply(this, arguments);
257+ if(this.dialog_title == _t("Match transaction"))
258+ {
259+ if(this.widget_parent.widget_children[0].views.form.controller)
260+ {
261+ this.widget_parent.widget_children[0].views.form.controller.reload();
262+ }
263+ if(this.widget_parent.widget_children[0].views.page.controller)
264+ {
265+ this.widget_parent.widget_children[0].views.page.controller.reload();
266+ }
267+ }
268+ },
269+ });
270+}
271
272=== modified file 'account_banking/wizard/banking_transaction_wizard.py'
273--- account_banking/wizard/banking_transaction_wizard.py 2012-12-01 18:31:34 +0000
274+++ account_banking/wizard/banking_transaction_wizard.py 2013-06-03 09:55:43 +0000
275@@ -94,8 +94,8 @@
276 # The following fields get never written
277 # they are just triggers for manual matching
278 # which populates regular fields on the transaction
279- manual_invoice_id = vals.pop('manual_invoice_id', False)
280- manual_move_line_id = vals.pop('manual_move_line_id', False)
281+ manual_invoice_ids = vals.pop('manual_invoice_ids', [])
282+ manual_move_line_ids = vals.pop('manual_move_line_ids', [])
283
284 # Support for writing fields.related is still flakey:
285 # https://bugs.launchpad.net/openobject-server/+bug/915975
286@@ -167,55 +167,93 @@
287 _("No entry found for the selected invoice. " +
288 "Try manual reconciliation."))
289
290- if manual_move_line_id or manual_invoice_id:
291+ if manual_move_line_ids or manual_invoice_ids:
292 move_line_obj = self.pool.get('account.move.line')
293 invoice_obj = self.pool.get('account.invoice')
294 statement_line_obj = self.pool.get('account.bank.statement.line')
295- for wiz in self.browse(
296- cr, uid, ids, context=context):
297- move_line_id = False
298- invoice_id = manual_invoice_id
299- if invoice_id:
300- invoice = invoice_obj.browse(
301- cr, uid, manual_invoice_id, context=context)
302+ manual_invoice_ids = (
303+ [i[1] for i in manual_invoice_ids if i[0]==4] +
304+ [j for i in manual_invoice_ids if i[0]==6 for j in i[2]])
305+ manual_move_line_ids = (
306+ [i[1] for i in manual_move_line_ids if i[0]==4] +
307+ [j for i in manual_move_line_ids if i[0]==6 for j in i[2]])
308+ for wiz in self.browse(cr, uid, ids, context=context):
309+ #write can be called multiple times for the same values
310+ #that doesn't hurt above, but it does here
311+ if wiz.match_type and (
312+ len(manual_move_line_ids) > 1 or
313+ len(manual_invoice_ids) > 1):
314+ continue
315+
316+ todo = []
317+
318+ for invoice in invoice_obj.browse(
319+ cr, uid, manual_invoice_ids, context=context):
320+ found_move_line = False
321 if invoice.move_id:
322 for line in invoice.move_id.line_id:
323 if line.account_id.type in ('receivable', 'payable'):
324- move_line_id = line.id
325+ todo.append((invoice.id, line.id))
326+ found_move_line = True
327 break
328- if not move_line_id:
329- osv.except_osv(
330+ if not found_move_line:
331+ raise osv.except_osv(
332 _("Cannot select for reconcilion"),
333 _("No entry found for the selected invoice. "))
334- else:
335- move_line_id = manual_move_line_id
336- move_line = move_line_obj.read(
337- cr, uid, move_line_id, ['invoice'], context=context)
338- invoice_id = (move_line['invoice'] and
339- move_line['invoice'][0])
340- vals = {
341- 'move_line_id': move_line_id,
342- 'move_line_ids': [(6, 0, [move_line_id])],
343- 'invoice_id': invoice_id,
344- 'invoice_ids': [(6, 0, invoice_id and
345- [invoice_id] or [])],
346- 'match_type': 'manual',
347- }
348- transaction_obj.clear_and_write(
349- cr, uid, wiz.import_transaction_id.id,
350- vals, context=context)
351- st_line_vals = {
352- 'account_id': move_line_obj.read(
353- cr, uid, move_line_id,
354- ['account_id'], context=context)['account_id'][0],
355- }
356- if invoice_id:
357- st_line_vals['partner_id'] = invoice_obj.read(
358- cr, uid, invoice_id,
359- ['partner_id'], context=context)['partner_id'][0]
360- statement_line_obj.write(
361- cr, uid, wiz.import_transaction_id.statement_line_id.id,
362- st_line_vals, context=context)
363+ for move_line_id in manual_move_line_ids:
364+ todo_entry = [False, move_line_id]
365+ move_line=move_line_obj.read(
366+ cr,
367+ uid,
368+ move_line_id,
369+ ['invoice'],
370+ context=context)
371+ if move_line['invoice']:
372+ todo_entry[0] = move_line['invoice'][0]
373+ todo.append(todo_entry)
374+
375+ while todo:
376+ todo_entry = todo.pop()
377+ move_line = move_line_obj.browse(
378+ cr, uid, todo_entry[1], context)
379+ transaction_id = wiz.import_transaction_id.id
380+ statement_line_id = wiz.statement_line_id.id
381+
382+ if len(todo) > 0:
383+ statement_line_id = wiz.statement_line_id.split_off(
384+ move_line.debit or -move_line.credit)[0]
385+ transaction_id = statement_line_obj.browse(
386+ cr,
387+ uid,
388+ statement_line_id,
389+ context=context).import_transaction_id.id
390+
391+ vals = {
392+ 'move_line_id': todo_entry[1],
393+ 'move_line_ids': [(6, 0, [todo_entry[1]])],
394+ 'invoice_id': todo_entry[0],
395+ 'invoice_ids': [(6, 0,
396+ [todo_entry[0]] if todo_entry[0] else [])],
397+ 'match_type': 'manual',
398+ }
399+
400+ transaction_obj.clear_and_write(
401+ cr, uid, transaction_id, vals, context=context)
402+
403+ st_line_vals = {
404+ 'account_id': move_line_obj.read(
405+ cr, uid, todo_entry[1],
406+ ['account_id'], context=context)['account_id'][0],
407+ }
408+
409+ if todo_entry[0]:
410+ st_line_vals['partner_id'] = invoice_obj.read(
411+ cr, uid, todo_entry[0],
412+ ['partner_id'], context=context)['partner_id'][0]
413+
414+ statement_line_obj.write(
415+ cr, uid, statement_line_id,
416+ st_line_vals, context=context)
417 return res
418
419 def trigger_write(self, cr, uid, ids, context=None):
420@@ -247,14 +285,26 @@
421 account_id = setting.default_debit_account_id and setting.default_debit_account_id.id
422 statement_pool.write(cr, uid, wiz.statement_line_id.id, {'account_id':account_id})
423
424- self.write(cr, uid, wiz.id, {'partner_id': False}, context=context)
425-
426- wizs = self.read(
427- cr, uid, ids, ['import_transaction_id'], context=context)
428- trans_ids = [x['import_transaction_id'][0] for x in wizs
429- if x['import_transaction_id']]
430- self.pool.get('banking.import.transaction').clear_and_write(
431- cr, uid, trans_ids, context=context)
432+ # Restore partner id from the bank account or else reset
433+ partner_id = False
434+ if (wiz.statement_line_id.partner_bank_id and
435+ wiz.statement_line_id.partner_bank_id.partner_id):
436+ partner_id = wiz.statement_line_id.partner_bank_id.partner_id.id
437+ wiz.write({'partner_id': partner_id})
438+
439+ if wiz.statement_line_id:
440+ #delete splits causing an unsplit if this is a split
441+ #transaction
442+ statement_pool.unlink(cr, uid,
443+ statement_pool.search(cr, uid,
444+ [('parent_id', '=', wiz.statement_line_id.id)],
445+ context=context),
446+ context=context)
447+
448+ if wiz.import_transaction_id:
449+ wiz.import_transaction_id.clear_and_write()
450+
451+
452 return True
453
454 def reverse_duplicate(self, cr, uid, ids, context=None):
455@@ -281,7 +331,7 @@
456 return res
457
458 def button_done(self, cr, uid, ids, context=None):
459- return {'nodestroy': False, 'type': 'ir.actions.act_window_close'}
460+ return {'type': 'ir.actions.act_window_close'}
461
462 _defaults = {
463 # 'match_type': _get_default_match_type,
464@@ -305,6 +355,9 @@
465 'statement_line_id', 'partner_id',
466 type='many2one', relation='res.partner',
467 string="Partner", readonly=True),
468+ 'statement_line_parent_id': fields.related(
469+ 'statement_line_id', 'parent_id', type='many2one',
470+ relation='account.bank.statement.line', readonly=True),
471 'import_transaction_id': fields.related(
472 'statement_line_id', 'import_transaction_id',
473 string="Import transaction",
474@@ -350,14 +403,17 @@
475 'match_type': fields.related(
476 'import_transaction_id', 'match_type',
477 type="char", size=16, string='Match type', readonly=True),
478- 'manual_invoice_id': fields.many2one(
479- 'account.invoice', 'Match this invoice',
480+ 'manual_invoice_ids': fields.many2many(
481+ 'account.invoice',
482+ 'banking_transaction_wizard_account_invoice_rel',
483+ 'wizard_id', 'invoice_id', string='Match one or more invoices',
484 domain=[('reconciled', '=', False)]),
485- 'manual_move_line_id': fields.many2one(
486- 'account.move.line', 'Or match this entry',
487+ 'manual_move_line_ids': fields.many2many(
488+ 'account.move.line',
489+ 'banking_transaction_wizard_account_move_line_rel',
490+ 'wizard_id', 'move_line_id', string='Or match one or more entries',
491 domain=[('account_id.reconcile', '=', True),
492- ('reconcile_id', '=', False)],
493- ),
494+ ('reconcile_id', '=', False)]),
495 'payment_option': fields.related('import_transaction_id','payment_option', string='Payment Difference', type='selection', required=True,
496 selection=[('without_writeoff', 'Keep Open'),('with_writeoff', 'Reconcile Payment Balance')]),
497 'writeoff_analytic_id': fields.related(
498
499=== modified file 'account_banking/wizard/banking_transaction_wizard.xml'
500--- account_banking/wizard/banking_transaction_wizard.xml 2013-01-13 14:11:44 +0000
501+++ account_banking/wizard/banking_transaction_wizard.xml 2013-06-03 09:55:43 +0000
502@@ -9,6 +9,7 @@
503 <form string="Match transaction">
504 <!-- fields used for form logic -->
505 <field name="payment_order_ids" invisible="True"/>
506+ <field name="statement_line_parent_id" invisible="True"/>
507 <field name="invoice_ids" invisible="True"/>
508 <field name="move_line_ids" invisible="True"/>
509 <field name="match_multi" invisible="True"/>
510@@ -89,30 +90,18 @@
511 name="trigger_match"
512 type="object"
513 string="Match again"/>
514- <!-- Manual selection -->
515 </page>
516+ <!-- Manual selection -->
517 <page string="Manual match">
518- <field name="manual_invoice_id"/>
519- <!--
520- Specify alternative tree_view_ref as a
521- workaround for lp:1073521 in OpenERP 6.1
522- Need to also define 'view_mode' to prevent
523- an instant editable tree view
524- reconstruction by account.move.line's
525- fields_view_get().
526- Both are not needed in OpenERP 6.0 or 7.0.
527- -->
528- <field name="manual_move_line_id"
529- context="{
530- 'tree_view_ref': 'account.view_move_line_tax_tree',
531- 'view_mode': 'yes'
532- }"
533- />
534- <newline/>
535- <button colspan="1"
536- name="trigger_write"
537+ <field name="manual_invoice_ids" colspan="4"
538+ context="{'search_default_partner_id': partner_id}"
539+ />
540+ <field name="manual_move_line_ids" colspan="4"
541+ context="{'search_default_partner_id': partner_id}"
542+ />
543+ <button name="trigger_write"
544 type="object"
545- string="Match"/>
546+ string="Match" />
547 </page>
548 <page string="Write-Off" attrs="{'invisible': [('match_type', '=', False)]}">
549 <group colspan="2" col="2">
550@@ -139,7 +128,7 @@
551 </notebook>
552 <group colspan="2">
553 <separator/>
554- <button icon="gtk-ok" string="Done" special="cancel"/>
555+ <button icon="gtk-ok" string="Close" special="cancel"/>
556 </group>
557 </group>
558 </form>

Subscribers

People subscribed via source and target branches