Merge lp:~therp-nl/purchase-wkfl/7.0-add_purchase_reset_invoice_method into lp:~purchase-core-editors/purchase-wkfl/7.0

Proposed by Stefan Rijnhart (Opener)
Status: Rejected
Rejected by: Stefan Rijnhart (Opener)
Proposed branch: lp:~therp-nl/purchase-wkfl/7.0-add_purchase_reset_invoice_method
Merge into: lp:~purchase-core-editors/purchase-wkfl/7.0
Diff against target: 1112 lines (+1036/-0)
14 files modified
purchase_reset_invoice_method/__init__.py (+1/-0)
purchase_reset_invoice_method/__openerp__.py (+57/-0)
purchase_reset_invoice_method/i18n/nl.po (+135/-0)
purchase_reset_invoice_method/i18n/purchase_reset_invoice_method.pot (+128/-0)
purchase_reset_invoice_method/model/__init__.py (+4/-0)
purchase_reset_invoice_method/model/account_invoice_line.py (+32/-0)
purchase_reset_invoice_method/model/purchase_order.py (+129/-0)
purchase_reset_invoice_method/model/purchase_reset_invoice_method.py (+40/-0)
purchase_reset_invoice_method/model/stock_picking.py (+37/-0)
purchase_reset_invoice_method/tests/__init__.py (+5/-0)
purchase_reset_invoice_method/tests/purchase_reset_invoice_method.py (+362/-0)
purchase_reset_invoice_method/view/purchase_order.xml (+22/-0)
purchase_reset_invoice_method/view/purchase_reset_invoice_method.xml (+28/-0)
purchase_reset_invoice_method/workflow/purchase.xml (+56/-0)
To merge this branch: bzr merge lp:~therp-nl/purchase-wkfl/7.0-add_purchase_reset_invoice_method
Reviewer Review Type Date Requested Status
Pedro Manuel Baeza Needs Resubmitting
Review via email: mp+209295@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Pedro Manuel Baeza (pedro.baeza) wrote :

This project is now hosted on https://github.com/OCA/purchase-workflow. Please move your proposal there. This guide may help you https://github.com/OCA/maintainers-tools/wiki/How-to-move-a-Merge-Proposal-to-GitHub

review: Needs Resubmitting
Revision history for this message
Stefan Rijnhart (Opener) (stefan-opener) wrote :

Unmerged revisions

31. By Stefan Rijnhart (Opener)

[IMP] Module description

30. By Stefan Rijnhart (Opener)

[FIX] Test count

29. By Stefan Rijnhart (Opener)

[ADD] Module to reset invoice method after confirmation and draft invoicing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'purchase_reset_invoice_method'
2=== added file 'purchase_reset_invoice_method/__init__.py'
3--- purchase_reset_invoice_method/__init__.py 1970-01-01 00:00:00 +0000
4+++ purchase_reset_invoice_method/__init__.py 2014-03-04 16:10:35 +0000
5@@ -0,0 +1,1 @@
6+from . import model
7
8=== added file 'purchase_reset_invoice_method/__openerp__.py'
9--- purchase_reset_invoice_method/__openerp__.py 1970-01-01 00:00:00 +0000
10+++ purchase_reset_invoice_method/__openerp__.py 2014-03-04 16:10:35 +0000
11@@ -0,0 +1,57 @@
12+# -*- coding: utf-8 -*-
13+##############################################################################
14+#
15+# OpenERP, Open Source Management Solution
16+# This module copyright (C) 2013 Therp BV (<http://therp.nl>).
17+#
18+# This program is free software: you can redistribute it and/or modify
19+# it under the terms of the GNU Affero General Public License as
20+# published by the Free Software Foundation, either version 3 of the
21+# License, or (at your option) any later version.
22+#
23+# This program is distributed in the hope that it will be useful,
24+# but WITHOUT ANY WARRANTY; without even the implied warranty of
25+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26+# GNU Affero General Public License for more details.
27+#
28+# You should have received a copy of the GNU Affero General Public License
29+# along with this program. If not, see <http://www.gnu.org/licenses/>.
30+#
31+##############################################################################
32+{
33+ "name": "Reset invoice method of confirmed purchase orders",
34+ "version": "0.1",
35+ "author": "Therp BV",
36+ "category": 'Purchase Management',
37+ 'description': """
38+Description
39+===========
40+Allow the user to reset the invoice method of confirmed purchase orders.
41+By having this option, you don't have to know upfront how your supplier
42+is going to invoice you for the orders that you place with them.
43+Changing the invoice method will unlink any draft invoices for the order,
44+reset the invoice setting of associated stock pickings or order lines
45+and update the order workflow.
46+
47+Known limitations
48+=================
49+There may not be any invoices for the order which have been confirmed. This
50+would normally include any invoices which have been reset to 'draft' state, as
51+such invoices cannot be deleted from OpenERP either.
52+
53+Converting orders with invoicing method 'picking' only works for orders which
54+have been only been invoiced after installation of this module, as OpenERP
55+does not properly register which invoice line comes from which picking by
56+itself.
57+ """,
58+ 'website': 'http://therp.nl',
59+ 'depends': [
60+ 'purchase'
61+ ],
62+ 'data': [
63+ 'view/purchase_reset_invoice_method.xml',
64+ 'view/purchase_order.xml',
65+ 'workflow/purchase.xml',
66+ ],
67+ 'installable': True,
68+}
69
70=== added directory 'purchase_reset_invoice_method/i18n'
71=== added file 'purchase_reset_invoice_method/i18n/nl.po'
72--- purchase_reset_invoice_method/i18n/nl.po 1970-01-01 00:00:00 +0000
73+++ purchase_reset_invoice_method/i18n/nl.po 2014-03-04 16:10:35 +0000
74@@ -0,0 +1,135 @@
75+# Translation of OpenERP Server.
76+# This file contains the translation of the following modules:
77+# * purchase_reset_invoice_method
78+#
79+msgid ""
80+msgstr ""
81+"Project-Id-Version: OpenERP Server 7.0\n"
82+"Report-Msgid-Bugs-To: \n"
83+"POT-Creation-Date: 2014-03-03 12:37+0000\n"
84+"PO-Revision-Date: 2014-03-03 12:37+0000\n"
85+"Last-Translator: <>\n"
86+"Language-Team: \n"
87+"MIME-Version: 1.0\n"
88+"Content-Type: text/plain; charset=UTF-8\n"
89+"Content-Transfer-Encoding: \n"
90+"Plural-Forms: \n"
91+
92+#. module: purchase_reset_invoice_method
93+#: view:purchase.order:0
94+msgid "Reset"
95+msgstr "Reset"
96+
97+#. module: purchase_reset_invoice_method
98+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:69
99+#, python-format
100+msgid "The new invoice method is the same as the old."
101+msgstr "De nieuwe factuurcontrole is dezelfde als de oude."
102+
103+#. module: purchase_reset_invoice_method
104+#: view:purchase.reset.invoice_method:0
105+msgid "or"
106+msgstr "or"
107+
108+#. module: purchase_reset_invoice_method
109+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:90
110+#, python-format
111+msgid "This order has an old invoice created from a picking, but without the picking reference registered."
112+msgstr "This order has an old invoice created from a picking, but without the picking reference registered."
113+
114+#. module: purchase_reset_invoice_method
115+#: code:_description:0
116+#: model:ir.model,name:purchase_reset_invoice_method.model_purchase_reset_invoice_method
117+#, python-format
118+msgid "purchase.reset.invoice_method"
119+msgstr "purchase.reset.invoice_method"
120+
121+#. module: purchase_reset_invoice_method
122+#: view:purchase.reset.invoice_method:0
123+msgid "Confirm"
124+msgstr "OK"
125+
126+#. module: purchase_reset_invoice_method
127+#: selection:purchase.reset.invoice_method,invoice_method:0
128+msgid "Based on incoming shipments"
129+msgstr "Gebaseerd op inkomende leveringen"
130+
131+#. module: purchase_reset_invoice_method
132+#: field:account.invoice.line,invoiced_stock_move_id:0
133+msgid "Invoiced stock move"
134+msgstr "Gefactureerd inkomend product"
135+
136+#. module: purchase_reset_invoice_method
137+#: code:_description:0
138+#: model:ir.model,name:purchase_reset_invoice_method.model_account_invoice_line
139+#, python-format
140+msgid "Invoice Line"
141+msgstr "Factuurregel"
142+
143+#. module: purchase_reset_invoice_method
144+#: code:_description:0
145+#: model:ir.model,name:purchase_reset_invoice_method.model_purchase_order
146+#, python-format
147+msgid "Purchase Order"
148+msgstr "Inkooporder"
149+
150+#. module: purchase_reset_invoice_method
151+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:78
152+#, python-format
153+msgid "This order has an invoice which is not in draft state. Cannot reset the invoice method"
154+msgstr "Deze order heeft een factuur die al bevestigd is, daarom kan de factuurcontrole niet meer gewijzigd worden."
155+
156+#. module: purchase_reset_invoice_method
157+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:62
158+#, python-format
159+msgid "Please convert a single order at once."
160+msgstr "Er kan maar één order tegelijkertijd gewijzigd worden."
161+
162+#. module: purchase_reset_invoice_method
163+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:34
164+#: view:purchase.reset.invoice_method:0
165+#, python-format
166+msgid "Reset invoice method"
167+msgstr "Factuurcontrole resetten"
168+
169+#. module: purchase_reset_invoice_method
170+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:61
171+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:68
172+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:77
173+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:89
174+#, python-format
175+msgid "Error"
176+msgstr "Fout"
177+
178+#. module: purchase_reset_invoice_method
179+#: view:purchase.reset.invoice_method:0
180+msgid "Cancel"
181+msgstr "Annuleer"
182+
183+#. module: purchase_reset_invoice_method
184+#: selection:purchase.reset.invoice_method,invoice_method:0
185+msgid "Based on Purchase Order lines"
186+msgstr "Gebaseerd op orderregels"
187+
188+#. module: purchase_reset_invoice_method
189+#: field:purchase.reset.invoice_method,invoice_method:0
190+msgid "Invoicing Control"
191+msgstr "Factuurcontrole"
192+
193+#. module: purchase_reset_invoice_method
194+#: code:_description:0
195+#: model:ir.model,name:purchase_reset_invoice_method.model_stock_picking
196+#, python-format
197+msgid "Picking List"
198+msgstr "Verzamellijst"
199+
200+#. module: purchase_reset_invoice_method
201+#: field:purchase.reset.invoice_method,order_id:0
202+msgid "Order"
203+msgstr "Order"
204+
205+#. module: purchase_reset_invoice_method
206+#: selection:purchase.reset.invoice_method,invoice_method:0
207+msgid "Based on generated draft invoice"
208+msgstr "Gebaseerd op één factuur voor de hele order"
209+
210
211=== added file 'purchase_reset_invoice_method/i18n/purchase_reset_invoice_method.pot'
212--- purchase_reset_invoice_method/i18n/purchase_reset_invoice_method.pot 1970-01-01 00:00:00 +0000
213+++ purchase_reset_invoice_method/i18n/purchase_reset_invoice_method.pot 2014-03-04 16:10:35 +0000
214@@ -0,0 +1,128 @@
215+# Translation of OpenERP Server.
216+# This file contains the translation of the following modules:
217+# * purchase_reset_invoice_method
218+#
219+msgid ""
220+msgstr ""
221+
222+#. module: purchase_reset_invoice_method
223+#: view:purchase.order:0
224+msgid "Reset"
225+msgstr ""
226+
227+#. module: purchase_reset_invoice_method
228+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:69
229+#, python-format
230+msgid "The new invoice method is the same as the old."
231+msgstr ""
232+
233+#. module: purchase_reset_invoice_method
234+#: view:purchase.reset.invoice_method:0
235+msgid "or"
236+msgstr ""
237+
238+#. module: purchase_reset_invoice_method
239+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:90
240+#, python-format
241+msgid ""
242+"This order has an old invoice created from a picking, but without the "
243+"picking reference registered."
244+msgstr ""
245+
246+#. module: purchase_reset_invoice_method
247+#: code:_description:0
248+#: model:ir.model,name:purchase_reset_invoice_method.model_purchase_reset_invoice_method
249+#, python-format
250+msgid "purchase.reset.invoice_method"
251+msgstr ""
252+
253+#. module: purchase_reset_invoice_method
254+#: view:purchase.reset.invoice_method:0
255+msgid "Confirm"
256+msgstr ""
257+
258+#. module: purchase_reset_invoice_method
259+#: selection:purchase.reset.invoice_method,invoice_method:0
260+msgid "Based on incoming shipments"
261+msgstr ""
262+
263+#. module: purchase_reset_invoice_method
264+#: field:account.invoice.line,invoiced_stock_move_id:0
265+msgid "Invoiced stock move"
266+msgstr ""
267+
268+#. module: purchase_reset_invoice_method
269+#: code:_description:0
270+#: model:ir.model,name:purchase_reset_invoice_method.model_account_invoice_line
271+#, python-format
272+msgid "Invoice Line"
273+msgstr ""
274+
275+#. module: purchase_reset_invoice_method
276+#: code:_description:0
277+#: model:ir.model,name:purchase_reset_invoice_method.model_purchase_order
278+#, python-format
279+msgid "Purchase Order"
280+msgstr ""
281+
282+#. module: purchase_reset_invoice_method
283+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:78
284+#, python-format
285+msgid ""
286+"This order has an invoice which is not in draft state. Cannot reset the "
287+"invoice method"
288+msgstr ""
289+
290+#. module: purchase_reset_invoice_method
291+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:62
292+#, python-format
293+msgid "Please convert a single order at once."
294+msgstr ""
295+
296+#. module: purchase_reset_invoice_method
297+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:34
298+#: view:purchase.reset.invoice_method:0
299+#, python-format
300+msgid "Reset invoice method"
301+msgstr ""
302+
303+#. module: purchase_reset_invoice_method
304+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:61
305+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:68
306+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:77
307+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:89
308+#, python-format
309+msgid "Error"
310+msgstr ""
311+
312+#. module: purchase_reset_invoice_method
313+#: view:purchase.reset.invoice_method:0
314+msgid "Cancel"
315+msgstr ""
316+
317+#. module: purchase_reset_invoice_method
318+#: selection:purchase.reset.invoice_method,invoice_method:0
319+msgid "Based on Purchase Order lines"
320+msgstr ""
321+
322+#. module: purchase_reset_invoice_method
323+#: field:purchase.reset.invoice_method,invoice_method:0
324+msgid "Invoicing Control"
325+msgstr ""
326+
327+#. module: purchase_reset_invoice_method
328+#: code:_description:0
329+#: model:ir.model,name:purchase_reset_invoice_method.model_stock_picking
330+#, python-format
331+msgid "Picking List"
332+msgstr ""
333+
334+#. module: purchase_reset_invoice_method
335+#: field:purchase.reset.invoice_method,order_id:0
336+msgid "Order"
337+msgstr ""
338+
339+#. module: purchase_reset_invoice_method
340+#: selection:purchase.reset.invoice_method,invoice_method:0
341+msgid "Based on generated draft invoice"
342+msgstr ""
343
344=== added directory 'purchase_reset_invoice_method/model'
345=== added file 'purchase_reset_invoice_method/model/__init__.py'
346--- purchase_reset_invoice_method/model/__init__.py 1970-01-01 00:00:00 +0000
347+++ purchase_reset_invoice_method/model/__init__.py 2014-03-04 16:10:35 +0000
348@@ -0,0 +1,4 @@
349+from . import account_invoice_line
350+from . import stock_picking
351+from . import purchase_reset_invoice_method
352+from . import purchase_order
353
354=== added file 'purchase_reset_invoice_method/model/account_invoice_line.py'
355--- purchase_reset_invoice_method/model/account_invoice_line.py 1970-01-01 00:00:00 +0000
356+++ purchase_reset_invoice_method/model/account_invoice_line.py 2014-03-04 16:10:35 +0000
357@@ -0,0 +1,32 @@
358+#-*- coding: utf-8 -*-
359+##############################################################################
360+#
361+# OpenERP, Open Source Management Solution
362+# This module Copyright (C) 2014 Therp BV (<http://therp.nl>).
363+#
364+# This program is free software: you can redistribute it and/or modify
365+# it under the terms of the GNU Affero General Public License as
366+# published by the Free Software Foundation, either version 3 of the
367+# License, or (at your option) any later version.
368+#
369+# This program is distributed in the hope that it will be useful,
370+# but WITHOUT ANY WARRANTY; without even the implied warranty of
371+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
372+# GNU Affero General Public License for more details.
373+#
374+# You should have received a copy of the GNU Affero General Public License
375+# along with this program. If not, see <http://www.gnu.org/licenses/>.
376+#
377+##############################################################################
378+
379+from openerp.osv import orm, fields
380+
381+
382+class InvoiceLine(orm.Model):
383+ _inherit = 'account.invoice.line'
384+
385+ _columns = {
386+ 'invoiced_stock_move_id': fields.many2one(
387+ 'stock.move', 'Invoiced stock move',
388+ readonly=True),
389+ }
390
391=== added file 'purchase_reset_invoice_method/model/purchase_order.py'
392--- purchase_reset_invoice_method/model/purchase_order.py 1970-01-01 00:00:00 +0000
393+++ purchase_reset_invoice_method/model/purchase_order.py 2014-03-04 16:10:35 +0000
394@@ -0,0 +1,129 @@
395+# -*- coding: utf-8 -*-
396+##############################################################################
397+#
398+# OpenERP, Open Source Management Solution
399+# This module Copyright (C) 2014 Therp BV (<http://therp.nl>).
400+#
401+# This program is free software: you can redistribute it and/or modify
402+# it under the terms of the GNU Affero General Public License as
403+# published by the Free Software Foundation, either version 3 of the
404+# License, or (at your option) any later version.
405+#
406+# This program is distributed in the hope that it will be useful,
407+# but WITHOUT ANY WARRANTY; without even the implied warranty of
408+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
409+# GNU Affero General Public License for more details.
410+#
411+# You should have received a copy of the GNU Affero General Public License
412+# along with this program. If not, see <http://www.gnu.org/licenses/>.
413+#
414+##############################################################################
415+
416+from openerp.osv import orm
417+from openerp.tools.translate import _
418+from openerp import netsvc
419+
420+
421+class PurchaseOrder(orm.Model):
422+ _inherit = 'purchase.order'
423+
424+ def reset_invoice_method_wizard(self, cr, uid, ids, context=None):
425+ wizard_id = self.pool['purchase.reset.invoice_method'].create(
426+ cr, uid, {'order_id': ids[0]}, context=context)
427+ return {
428+ 'name': _('Reset invoice method'),
429+ 'view_type': 'form',
430+ 'view_mode': 'form',
431+ 'res_model': 'purchase.reset.invoice_method',
432+ 'context': context,
433+ 'type': 'ir.actions.act_window',
434+ 'nodestroy': True,
435+ 'target': 'new',
436+ 'res_id': wizard_id,
437+ }
438+
439+ def reset_invoice_method(
440+ self, cr, uid, ids, new_invoice_method, context=None):
441+ """
442+ Reset invoicing method of a purchase order. Clean up any draft
443+ invoices, reset picking invoice state and rest the invoicing
444+ path of the workflow. The affected invoices may be composed from
445+ other orders as well. If these invoices were derived from
446+ pickings, we need to reset the invoice state of these pickings as
447+ well.
448+
449+ This will fail if any of the draft invoices has already been
450+ confirmed, as OpenERP does not allow unlinking these.
451+ """
452+
453+ if len(ids) > 1:
454+ raise orm.except_orm(
455+ _('Error'),
456+ _('Please convert a single order at once.'))
457+
458+ wf_service = netsvc.LocalService("workflow")
459+ order = self.browse(cr, uid, ids[0], context=context)
460+ old_invoice_method = order.invoice_method
461+ if old_invoice_method == new_invoice_method:
462+ raise orm.except_orm(
463+ _('Error'),
464+ _('The new invoice method is the same as the old.'))
465+
466+ if order.invoice_ids:
467+ for invoice in order.invoice_ids:
468+ if invoice.state != 'draft':
469+ raise orm.except_orm(
470+ _('Error'),
471+ _('This order has an invoice which is not in draft '
472+ 'state. Cannot reset the invoice method'))
473+
474+ if old_invoice_method != 'picking':
475+ continue
476+
477+ # Track pickings and reset invoice state of foreign
478+ # pickings
479+ for inv_line in invoice.invoice_line:
480+ if not inv_line.invoiced_stock_move_id:
481+ raise orm.except_orm(
482+ _('Error'),
483+ _('This order has an old invoice created from a '
484+ 'picking, but without the picking reference '
485+ 'registered.'))
486+ picking = inv_line.invoiced_stock_move_id.picking_id
487+ if (picking.invoice_state == 'invoiced' and
488+ picking not in order.picking_ids):
489+ picking.write({'invoice_state': '2binvoiced'})
490+ picking.refresh()
491+
492+ # Reset invoice state of purchase lines
493+ order_line_ids = self.pool['purchase.order.line'].search(
494+ cr, uid, [
495+ ('invoice_lines.invoice_id', 'in',
496+ [inv.id for inv in order.invoice_ids])],
497+ context=context)
498+ self.pool['purchase.order.line'].write(
499+ cr, uid, order_line_ids,
500+ {'invoiced': False}, context=context)
501+
502+ for invoice in order.invoice_ids:
503+ wf_service.trg_validate(
504+ uid, 'account.invoice', invoice.id,
505+ 'invoice_cancel', cr)
506+ self.pool['account.invoice'].unlink(
507+ cr, uid, [invoice.id],
508+ context=context)
509+
510+ if order.picking_ids:
511+ # Reset this order's pickings invoice state
512+ state = '2binvoiced' if new_invoice_method == 'picking' else 'none'
513+ self.pool['stock.picking'].write(
514+ cr, uid,
515+ [picking.id for picking in order.picking_ids],
516+ {'invoice_state': state}, context=context)
517+
518+ order.write({'invoice_method': new_invoice_method})
519+
520+ wf_service.trg_validate(
521+ uid, 'purchase.order', order.id,
522+ 'reset_invoice_method_order', cr)
523+ return True
524
525=== added file 'purchase_reset_invoice_method/model/purchase_reset_invoice_method.py'
526--- purchase_reset_invoice_method/model/purchase_reset_invoice_method.py 1970-01-01 00:00:00 +0000
527+++ purchase_reset_invoice_method/model/purchase_reset_invoice_method.py 2014-03-04 16:10:35 +0000
528@@ -0,0 +1,40 @@
529+# -*- coding: utf-8 -*-
530+##############################################################################
531+#
532+# OpenERP, Open Source Management Solution
533+# This module Copyright (C) 2014 Therp BV (<http://therp.nl>).
534+#
535+# This program is free software: you can redistribute it and/or modify
536+# it under the terms of the GNU Affero General Public License as
537+# published by the Free Software Foundation, either version 3 of the
538+# License, or (at your option) any later version.
539+#
540+# This program is distributed in the hope that it will be useful,
541+# but WITHOUT ANY WARRANTY; without even the implied warranty of
542+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
543+# GNU Affero General Public License for more details.
544+#
545+# You should have received a copy of the GNU Affero General Public License
546+# along with this program. If not, see <http://www.gnu.org/licenses/>.
547+#
548+##############################################################################
549+
550+from openerp.osv import orm, fields
551+
552+
553+class PurchaseResetInvoiceMethod(orm.TransientModel):
554+ _name = 'purchase.reset.invoice_method'
555+ _columns = {
556+ 'order_id': fields.many2one(
557+ 'purchase.order', 'Order', readonly=True),
558+ 'invoice_method': fields.selection(
559+ [('manual', 'Based on Purchase Order lines'),
560+ ('order', 'Based on generated draft invoice'),
561+ ('picking', 'Based on incoming shipments')],
562+ 'Invoicing Control'),
563+ }
564+
565+ def do_reset(self, cr, uid, ids, context=None):
566+ wizard = self.browse(cr, uid, ids[0], context=context)
567+ return wizard.order_id.reset_invoice_method(
568+ wizard.invoice_method)
569
570=== added file 'purchase_reset_invoice_method/model/stock_picking.py'
571--- purchase_reset_invoice_method/model/stock_picking.py 1970-01-01 00:00:00 +0000
572+++ purchase_reset_invoice_method/model/stock_picking.py 2014-03-04 16:10:35 +0000
573@@ -0,0 +1,37 @@
574+#-*- coding: utf-8 -*-
575+##############################################################################
576+#
577+# OpenERP, Open Source Management Solution
578+# This module Copyright (C) 2014 Therp BV (<http://therp.nl>).
579+#
580+# This program is free software: you can redistribute it and/or modify
581+# it under the terms of the GNU Affero General Public License as
582+# published by the Free Software Foundation, either version 3 of the
583+# License, or (at your option) any later version.
584+#
585+# This program is distributed in the hope that it will be useful,
586+# but WITHOUT ANY WARRANTY; without even the implied warranty of
587+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
588+# GNU Affero General Public License for more details.
589+#
590+# You should have received a copy of the GNU Affero General Public License
591+# along with this program. If not, see <http://www.gnu.org/licenses/>.
592+#
593+##############################################################################
594+
595+from openerp.osv import orm
596+
597+
598+class Picking(orm.Model):
599+ _inherit = 'stock.picking'
600+
601+ def _invoice_line_hook(self, cr, uid, move_line, invoice_line_id):
602+ """
603+ Deterministically link the invoiced picking to the associated
604+ invoice lines through the stock move.
605+ """
606+ self.pool['account.invoice.line'].write(
607+ cr, uid, [invoice_line_id],
608+ {'invoiced_stock_move_id': move_line.id})
609+ return super(Picking, self)._invoice_line_hook(
610+ cr, uid, move_line, invoice_line_id)
611
612=== added directory 'purchase_reset_invoice_method/tests'
613=== added file 'purchase_reset_invoice_method/tests/__init__.py'
614--- purchase_reset_invoice_method/tests/__init__.py 1970-01-01 00:00:00 +0000
615+++ purchase_reset_invoice_method/tests/__init__.py 2014-03-04 16:10:35 +0000
616@@ -0,0 +1,5 @@
617+from . import purchase_reset_invoice_method
618+
619+fast_suite = [
620+ purchase_reset_invoice_method,
621+]
622
623=== added file 'purchase_reset_invoice_method/tests/purchase_reset_invoice_method.py'
624--- purchase_reset_invoice_method/tests/purchase_reset_invoice_method.py 1970-01-01 00:00:00 +0000
625+++ purchase_reset_invoice_method/tests/purchase_reset_invoice_method.py 2014-03-04 16:10:35 +0000
626@@ -0,0 +1,362 @@
627+# -*- coding: utf-8 -*-
628+##############################################################################
629+#
630+# Copyright (C) 2014 Therp BV (<http://therp.nl>)
631+#
632+# This program is free software: you can redistribute it and/or modify
633+# it under the terms of the GNU Affero General Public License as
634+# published by the Free Software Foundation, either version 3 of the
635+# License, or (at your option) any later version.
636+#
637+# This program is distributed in the hope that it will be useful,
638+# but WITHOUT ANY WARRANTY; without even the implied warranty of
639+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
640+# GNU Affero General Public License for more details.
641+#
642+# You should have received a copy of the GNU Affero General Public License
643+# along with this program. If not, see <http://www.gnu.org/licenses/>.
644+#
645+##############################################################################
646+import time
647+from openerp.tests.common import SingleTransactionCase
648+from openerp import netsvc
649+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATEFMT
650+
651+
652+class TestResetInvoiceMethod(SingleTransactionCase):
653+ wf_service = netsvc.LocalService("workflow")
654+
655+ def setUp(self):
656+ """
657+ Create a supplier and a product.
658+ """
659+ self.supplier_id = self.registry('res.partner').create(
660+ self.cr, self.uid, {'name': 'Supplier', 'supplier': True})
661+ self.product_id = self.registry('product.product').create(
662+ self.cr, self.uid, {
663+ 'name': 'Product test reset invoice method',
664+ 'type': 'product',
665+ 'supply_method': 'buy',
666+ 'standard_price': 1.0,
667+ })
668+
669+ def assert_state(self, po, state):
670+ self.assertTrue(
671+ po.state == state,
672+ 'Purchase order is not in state \'%s\'' % state)
673+
674+ def pay_invoice(self, po):
675+ """
676+ Pay the purchase order's first invoice with a voucher.
677+ This only affects the purchase order workflow in the case
678+ of invoice_method 'order'.
679+ """
680+ reg, cr, uid, = self.registry, self.cr, self.uid
681+ try:
682+ voucher_obj = reg('account.voucher')
683+ except KeyError:
684+ # Voucher module not available
685+ return
686+ voucher_context = {
687+ 'payment_expected_currency': po.invoice_ids[0].currency_id.id,
688+ 'default_partner_id': po.invoice_ids[0].partner_id.id,
689+ 'default_amount': po.invoice_ids[0].residual,
690+ 'default_reference': po.invoice_ids[0].name,
691+ 'invoice_type': po.invoice_ids[0].type,
692+ 'invoice_id': po.invoice_ids[0].id,
693+ 'default_type': 'payment',
694+ 'type': 'payment',
695+ }
696+ journal_id = reg('account.journal').search(
697+ cr, uid, [
698+ ('type', '=', 'bank'),
699+ ('company_id', '=', po.company_id.id)])[0]
700+ voucher_values = {
701+ 'partner_id': po.invoice_ids[0].partner_id.id,
702+ 'journal_id': journal_id,
703+ 'account_id': reg('account.journal').browse(
704+ cr, uid, journal_id).default_credit_account_id.id,
705+ }
706+ voucher_values.update(voucher_obj.onchange_journal(
707+ cr, uid, False, voucher_values['journal_id'],
708+ [], False, po.invoice_ids[0].partner_id.id,
709+ time.strftime(DATEFMT), po.invoice_ids[0].residual,
710+ 'payment', po.invoice_ids[0].currency_id.id,
711+ context=voucher_context)['value'])
712+ voucher_values['line_dr_ids'] = [
713+ (0, 0, line) for line in voucher_values['line_dr_ids']]
714+ voucher_id = voucher_obj.create(
715+ cr, uid, voucher_values, context=voucher_context)
716+ self.wf_service.trg_validate(
717+ uid, 'account.voucher', voucher_id, 'proforma_voucher', cr)
718+
719+ # Invoice has been paid and the purchase order is done
720+ po.refresh()
721+ self.assertTrue(
722+ po.invoice_ids[0].state == 'paid',
723+ 'Did not succeed in paying the invoice with a voucher')
724+ return voucher_id
725+
726+ def create_order(self, invoice_method):
727+ """
728+ Create a purchase order
729+ """
730+ reg, cr, uid, = self.registry, self.cr, self.uid
731+ po_pool = reg('purchase.order')
732+ line_pool = reg('purchase.order.line')
733+ line_values = {
734+ 'product_id': self.product_id,
735+ 'product_qty': 1,
736+ }
737+ line_values.update(
738+ line_pool.onchange_product_id(
739+ cr, uid, False, False, self.product_id,
740+ 1, False, self.supplier_id)['value'])
741+ po_values = {
742+ 'partner_id': self.supplier_id,
743+ 'order_line': [(0, 0, line_values)],
744+ 'invoice_method': invoice_method,
745+ }
746+ po_values.update(po_pool.onchange_dest_address_id(
747+ cr, uid, False, self.supplier_id)['value'])
748+ po_values.update(po_pool.onchange_partner_id(
749+ cr, uid, False, self.supplier_id)['value'])
750+ return po_pool.create(cr, uid, po_values)
751+
752+ def create_invoice_from_pickings(self, picking_ids):
753+ """
754+ Receive a single invoice for all pickings.
755+ """
756+ reg, cr, uid, = self.registry, self.cr, self.uid
757+ invoicing = reg('stock.invoice.onshipping')
758+ invoicing_context = {
759+ 'active_ids': picking_ids,
760+ 'active_id': picking_ids[0],
761+ 'active_model': 'stock.picking',
762+ }
763+ invoicing_id = invoicing.create(
764+ cr, uid, {'group': True}, context=invoicing_context
765+ )
766+ res = invoicing.create_invoice(
767+ cr, uid, [invoicing_id], context=invoicing_context)
768+ return res.items()[0][1]
769+
770+ def test_00_picking_to_order(self):
771+ """
772+ Create two purchase orders with invoice method 'picking'.
773+ After delivery of the pickings, create a single invoice.
774+ Change the invoice method of one of the orders to 'order'
775+ and run through this order's workflow until completed.
776+ """
777+ reg, cr, uid, = self.registry, self.cr, self.uid
778+ po_pool = reg('purchase.order')
779+ po1_id = self.create_order('picking')
780+ po2_id = self.create_order('picking')
781+
782+ # Confirm and receive the purchases
783+ self.wf_service.trg_validate(
784+ uid, 'purchase.order', po1_id, 'purchase_confirm', cr)
785+ self.wf_service.trg_validate(
786+ uid, 'purchase.order', po2_id, 'purchase_confirm', cr)
787+ po1, po2 = po_pool.browse(
788+ cr, uid, [po1_id, po2_id])
789+ self.wf_service.trg_validate(
790+ uid, 'stock.picking', po1.picking_ids[0].id, 'button_done', cr)
791+ self.wf_service.trg_validate(
792+ uid, 'stock.picking', po2.picking_ids[0].id, 'button_done', cr)
793+ self.assert_state(po1, 'approved')
794+ self.assert_state(po2, 'approved')
795+ for picking in po1.picking_ids[0], po2.picking_ids[0]:
796+ self.assertTrue(
797+ picking.state == 'done' and
798+ picking.invoice_state == '2binvoiced',
799+ 'Picking is not ready for invoicing')
800+
801+ invoice_id = self.create_invoice_from_pickings(
802+ [po1.picking_ids[0].id, po2.picking_ids[0].id])
803+
804+ # Reset the first purchase order's invoice method
805+ po1.reset_invoice_method('order')
806+ po1.refresh()
807+ po2.refresh()
808+
809+ # The original draft invoice has been removed
810+ # and the picking's invoice states have been updated
811+ # according to each order's invoice control
812+ self.assertFalse(
813+ reg('account.invoice').search(
814+ cr, uid, [('id', '=', invoice_id)]),
815+ 'Obsolete invoice has not been removed after changing invoice '
816+ 'method')
817+ self.assertTrue(
818+ po1.picking_ids[0].invoice_state == 'none',
819+ 'Picking from order has not been set not to be invoiced')
820+ self.assertTrue(
821+ po2.picking_ids[0].invoice_state == '2binvoiced',
822+ 'Picking from second order has not been reset to be invoiced')
823+
824+ # A new invoice for the first order has been created through
825+ # the workflow. Confirm this invoice.
826+ self.assertTrue(
827+ len(po1.invoice_ids) == 1,
828+ 'Unexpected number of invoices for first order '
829+ '(other than 1): %s' % (len(po1.invoice_ids)))
830+ self.wf_service.trg_validate(
831+ uid, 'account.invoice', po1.invoice_ids[0].id, 'invoice_open', cr)
832+ po1.refresh()
833+ self.assertTrue(
834+ po1.invoice_ids[0].state == 'open',
835+ 'Did not succeed in confirming the generated invoice')
836+
837+ self.pay_invoice(po1)
838+ self.assertTrue(
839+ po1.state == 'done',
840+ 'Purchase order workflow did not complete after paying the '
841+ 'invoice')
842+
843+ def test_01_order_to_picking(self):
844+ """
845+ Create a purchase order with invoice method 'order'.
846+ After confirming the order, change the invoice method
847+ to 'picking' and run through this order's workflow until completed.
848+ """
849+ reg, cr, uid, = self.registry, self.cr, self.uid
850+ po_pool = reg('purchase.order')
851+ po_id = self.create_order('order')
852+
853+ # Confirm and receive the purchases
854+ self.wf_service.trg_validate(
855+ uid, 'purchase.order', po_id, 'purchase_confirm', cr)
856+ po = po_pool.browse(cr, uid, po_id)
857+ self.assertTrue(
858+ len(po.invoice_ids) == 1,
859+ 'Unexpected number of invoices for purchase order '
860+ '(other than 1): %s' % len(po.invoice_ids))
861+
862+ invoice_id = po.invoice_ids[0].id
863+
864+ # Reset the purchase order's invoice method
865+ po.reset_invoice_method('picking')
866+ po.refresh()
867+
868+ # The original draft invoice has been removed
869+ # and the picking's invoice states have been updated
870+ # according to each order's invoice control
871+ self.assertFalse(
872+ reg('account.invoice').search(
873+ cr, uid, [('id', '=', invoice_id)]),
874+ 'Obsolete invoice has not been removed after changing invoice '
875+ 'method')
876+
877+ # Check that the purchase order is not in an invoice exception
878+ # after unlinking the original invoice
879+ self.assert_state(po, 'approved')
880+
881+ self.wf_service.trg_validate(
882+ uid, 'stock.picking', po.picking_ids[0].id, 'button_done', cr)
883+ self.assertTrue(
884+ po.picking_ids[0].state == 'done' and
885+ po.picking_ids[0].invoice_state == '2binvoiced',
886+ 'Picking is not ready for invoicing')
887+ invoice_id = self.create_invoice_from_pickings([po.picking_ids[0].id])
888+ self.wf_service.trg_validate(
889+ uid, 'account.invoice', po.invoice_ids[0].id, 'invoice_open', cr)
890+ po.refresh()
891+ self.assertTrue(
892+ po.invoice_ids[0].state == 'open',
893+ 'Did not succeed in confirming the generated invoice')
894+ self.assertTrue(
895+ po.state == 'done',
896+ 'Purchase order workflow did not complete after paying the '
897+ 'invoice when changing invoice method from order to picking')
898+
899+ def test_02_lines_to_picking(self):
900+ """
901+ Create two orders set to 'manual', and invoice their lines together.
902+ Reset the first order's invoice method to 'picking'. Finish both
903+ orders' workflows.
904+ """
905+ reg, cr, uid, = self.registry, self.cr, self.uid
906+ po_pool = reg('purchase.order')
907+ po_id = self.create_order('manual')
908+ po2_id = self.create_order('manual')
909+
910+ # Confirm and receive the purchases
911+ self.wf_service.trg_validate(
912+ uid, 'purchase.order', po_id, 'purchase_confirm', cr)
913+ self.wf_service.trg_validate(
914+ uid, 'purchase.order', po2_id, 'purchase_confirm', cr)
915+ po = po_pool.browse(cr, uid, po_id)
916+ po2 = po_pool.browse(cr, uid, po2_id)
917+
918+ invoicing_context = {'active_ids': [
919+ line.id for line in po.order_line + po2.order_line]}
920+ reg('purchase.order.line_invoice').makeInvoices(
921+ cr, uid, False, invoicing_context)
922+ po.refresh()
923+
924+ self.assertTrue(
925+ len(po.invoice_ids) == 1,
926+ 'Unexpected number of invoices for purchase order '
927+ '(other than 1): %s' % len(po.invoice_ids))
928+ invoice_id = po.invoice_ids[0].id
929+
930+ # Reset the purchase order's invoice method
931+ po.reset_invoice_method('picking')
932+ po.refresh()
933+ po2.refresh()
934+
935+ # The original draft invoice has been removed
936+ # and the picking's invoice states have been updated
937+ # according to each order's invoice control
938+ self.assertFalse(
939+ reg('account.invoice').search(
940+ cr, uid, [('id', '=', invoice_id)]),
941+ 'Obsolete invoice has not been removed after changing invoice '
942+ 'method')
943+
944+ # Check that the purchase order is not in an invoice exception
945+ # after unlinking the original invoice
946+ self.assert_state(po, 'approved')
947+
948+ self.wf_service.trg_validate(
949+ uid, 'stock.picking', po.picking_ids[0].id, 'button_done', cr)
950+ self.assertTrue(
951+ po.picking_ids[0].state == 'done' and
952+ po.picking_ids[0].invoice_state == '2binvoiced',
953+ 'Picking is not ready for invoicing')
954+ invoice_id = self.create_invoice_from_pickings([po.picking_ids[0].id])
955+ self.wf_service.trg_validate(
956+ uid, 'account.invoice', invoice_id, 'invoice_open', cr)
957+
958+ po.refresh()
959+ self.assertTrue(
960+ po.invoice_ids[0].state == 'open',
961+ 'Did not succeed in confirming the generated invoice')
962+ self.assertTrue(
963+ po.state == 'done',
964+ 'Purchase order workflow did not complete after paying the '
965+ 'invoice when changing invoice method from order to picking')
966+
967+ # Recreate the invoice for the second order and finish
968+ # its workflow with the original 'manual' invoice control
969+ self.assertFalse(
970+ po2.order_line[0].invoiced,
971+ 'Second order\'s line invoice state has not been reset')
972+ invoicing_context = {'active_ids': [
973+ line.id for line in po2.order_line]}
974+ reg('purchase.order.line_invoice').makeInvoices(
975+ cr, uid, False, invoicing_context)
976+ po2.refresh()
977+ self.wf_service.trg_validate(
978+ uid, 'stock.picking', po2.picking_ids[0].id, 'button_done', cr)
979+ self.wf_service.trg_validate(
980+ uid, 'account.invoice', po2.invoice_ids[0].id, 'invoice_open', cr)
981+ po2.refresh()
982+ self.assertTrue(
983+ po2.state == 'done',
984+ 'Purchase order workflow did not complete after paying the '
985+ 'invoice when changing invoice method from order to picking')
986+ self.assertTrue(
987+ po2.order_line[0].invoiced,
988+ 'Second order\'s line is not set to invoiced')
989
990=== added directory 'purchase_reset_invoice_method/view'
991=== added file 'purchase_reset_invoice_method/view/purchase_order.xml'
992--- purchase_reset_invoice_method/view/purchase_order.xml 1970-01-01 00:00:00 +0000
993+++ purchase_reset_invoice_method/view/purchase_order.xml 2014-03-04 16:10:35 +0000
994@@ -0,0 +1,22 @@
995+<?xml version="1.0" encoding="utf-8"?>
996+<openerp>
997+<data>
998+
999+ <record id="purchase_order_form" model="ir.ui.view">
1000+ <field name="name">Add button to reset invoice method</field>
1001+ <field name="model">purchase.order</field>
1002+ <field name="inherit_id" ref="purchase.purchase_order_form" />
1003+ <field name="arch" type="xml">
1004+ <field name="invoice_method" position="replace">
1005+ <group colspan="2" col="3">
1006+ <field name="invoice_method" />
1007+ <button name="reset_invoice_method_wizard"
1008+ string="Reset" type="object"
1009+ attrs="{'invisible': [('state', 'in', ['draft', 'sent', 'done', 'cancel'])]}"/>
1010+ </group>
1011+ </field>
1012+ </field>
1013+ </record>
1014+
1015+</data>
1016+</openerp>
1017
1018=== added file 'purchase_reset_invoice_method/view/purchase_reset_invoice_method.xml'
1019--- purchase_reset_invoice_method/view/purchase_reset_invoice_method.xml 1970-01-01 00:00:00 +0000
1020+++ purchase_reset_invoice_method/view/purchase_reset_invoice_method.xml 2014-03-04 16:10:35 +0000
1021@@ -0,0 +1,28 @@
1022+<?xml version="1.0" encoding="utf-8"?>
1023+<openerp>
1024+<data>
1025+
1026+ <record id="reset_wizard_form_view" model="ir.ui.view">
1027+ <field name="name">Reset invoice method form view</field>
1028+ <field name="model">purchase.reset.invoice_method</field>
1029+ <field name="arch" type="xml">
1030+ <form string="Reset invoice method" version="7.0">
1031+ <group>
1032+ <field name="order_id" />
1033+ <field name="invoice_method" required="1" />
1034+ </group>
1035+ <footer>
1036+ <button name="do_reset" string="Confirm"
1037+ type="object" class="oe_highlight" />
1038+ or
1039+ <button string="Cancel" class="oe_link" special="cancel"/>
1040+ </footer>
1041+ </form>
1042+ </field>
1043+ </record>
1044+
1045+</data>
1046+</openerp>
1047+
1048+
1049+
1050\ No newline at end of file
1051
1052=== added directory 'purchase_reset_invoice_method/workflow'
1053=== added file 'purchase_reset_invoice_method/workflow/purchase.xml'
1054--- purchase_reset_invoice_method/workflow/purchase.xml 1970-01-01 00:00:00 +0000
1055+++ purchase_reset_invoice_method/workflow/purchase.xml 2014-03-04 16:10:35 +0000
1056@@ -0,0 +1,56 @@
1057+<?xml version="1.0" encoding="utf-8"?>
1058+<openerp>
1059+ <data>
1060+
1061+ <record id="act_ignore_exception" model="workflow.activity">
1062+ <field name="wkf_id" ref="purchase.purchase_order"/>
1063+ <field name="name">skip_exception_after_invoice_method_reset</field>
1064+ <field name="kind">function</field>
1065+ <field name="action">write({'state': 'approved'})</field>
1066+ </record>
1067+
1068+ <!--
1069+ Orders with new method other than 'order' skip the invoice
1070+ part of the workflow. This applies to orders with old method
1071+ 'order', without an existing (draft) invoice.
1072+ -->
1073+ <record id="trans_skip_invoice" model="workflow.transition">
1074+ <field name="act_from" ref="purchase.act_invoice" />
1075+ <field name="act_to" ref="purchase.act_invoice_end" />
1076+ <field name="condition">invoice_method != 'order'</field>
1077+ <field name="signal">reset_invoice_method_order</field>
1078+ </record>
1079+
1080+ <!--
1081+ Orders with old method 'order' with an existing draft
1082+ invoice will get an invoice exception when the draft
1083+ invoice is removed. Route the workflow in this case
1084+ passed the activity that resets the state field and
1085+ moves on to the end of the invoice subflow], which is
1086+ skipped for these invoice methods
1087+ -->
1088+ <record id="trans_ignore_exception" model="workflow.transition">
1089+ <field name="act_from" ref="purchase.act_except_invoice" />
1090+ <field name="act_to" ref="act_ignore_exception" />
1091+ <field name="condition">invoice_method != 'order'</field>
1092+ <field name="signal">reset_invoice_method_order</field>
1093+ </record>
1094+
1095+ <record id="trans_skip_invoiced_invoice" model="workflow.transition">
1096+ <field name="act_from" ref="act_ignore_exception" />
1097+ <field name="act_to" ref="purchase.act_invoice_end" />
1098+ </record>
1099+
1100+ <!--
1101+ Orders with new method 'order' need to enter the invoice
1102+ part of the workflow.
1103+ -->
1104+ <record id="trans_goto_invoice" model="workflow.transition">
1105+ <field name="act_from" ref="purchase.act_invoice_end" />
1106+ <field name="act_to" ref="purchase.act_invoice" />
1107+ <field name="condition">invoice_method == 'order'</field>
1108+ <field name="signal">reset_invoice_method_order</field>
1109+ </record>
1110+
1111+ </data>
1112+</openerp>

Subscribers

People subscribed via source and target branches