← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~therp-nl/purchase-wkfl/7.0-add_purchase_reset_invoice_method into lp:purchase-wkfl

 

Stefan Rijnhart (Therp) has proposed merging lp:~therp-nl/purchase-wkfl/7.0-add_purchase_reset_invoice_method into lp:purchase-wkfl.

Requested reviews:
  Purchase Core Editors (purchase-core-editors)

For more details, see:
https://code.launchpad.net/~therp-nl/purchase-wkfl/7.0-add_purchase_reset_invoice_method/+merge/209295
-- 
https://code.launchpad.net/~therp-nl/purchase-wkfl/7.0-add_purchase_reset_invoice_method/+merge/209295
Your team Purchase Core Editors is requested to review the proposed merge of lp:~therp-nl/purchase-wkfl/7.0-add_purchase_reset_invoice_method into lp:purchase-wkfl.
=== added directory 'purchase_reset_invoice_method'
=== added file 'purchase_reset_invoice_method/__init__.py'
--- purchase_reset_invoice_method/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/__init__.py	2014-03-04 16:10:35 +0000
@@ -0,0 +1,1 @@
+from . import model

=== added file 'purchase_reset_invoice_method/__openerp__.py'
--- purchase_reset_invoice_method/__openerp__.py	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/__openerp__.py	2014-03-04 16:10:35 +0000
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module 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/>.
+#
+##############################################################################
+{
+    "name": "Reset invoice method of confirmed purchase orders",
+    "version": "0.1",
+    "author": "Therp BV",
+    "category": 'Purchase Management',
+    'description': """
+Description
+===========
+Allow the user to reset the invoice method of confirmed purchase orders.
+By having this option, you don't have to know upfront how your supplier
+is going to invoice you for the orders that you place with them.
+Changing the invoice method will unlink any draft invoices for the order,
+reset the invoice setting of associated stock pickings or order lines
+and update the order workflow.
+
+Known limitations
+=================
+There may not be any invoices for the order which have been confirmed. This
+would normally include any invoices which have been reset to 'draft' state, as
+such invoices cannot be deleted from OpenERP either.
+
+Converting orders with invoicing method 'picking' only works for orders which
+have been only been invoiced after installation of this module, as OpenERP
+does not properly register which invoice line comes from which picking by
+itself.
+    """,
+    'website': 'http://therp.nl',
+    'depends': [
+        'purchase'
+        ],
+    'data': [
+        'view/purchase_reset_invoice_method.xml',
+        'view/purchase_order.xml',
+        'workflow/purchase.xml',
+        ],
+    'installable': True,
+}

=== added directory 'purchase_reset_invoice_method/i18n'
=== added file 'purchase_reset_invoice_method/i18n/nl.po'
--- purchase_reset_invoice_method/i18n/nl.po	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/i18n/nl.po	2014-03-04 16:10:35 +0000
@@ -0,0 +1,135 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* purchase_reset_invoice_method
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 7.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2014-03-03 12:37+0000\n"
+"PO-Revision-Date: 2014-03-03 12:37+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: purchase_reset_invoice_method
+#: view:purchase.order:0
+msgid "Reset"
+msgstr "Reset"
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:69
+#, python-format
+msgid "The new invoice method is the same as the old."
+msgstr "De nieuwe factuurcontrole is dezelfde als de oude."
+
+#. module: purchase_reset_invoice_method
+#: view:purchase.reset.invoice_method:0
+msgid "or"
+msgstr "or"
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:90
+#, python-format
+msgid "This order has an old invoice created from a picking, but without the picking reference registered."
+msgstr "This order has an old invoice created from a picking, but without the picking reference registered."
+
+#. module: purchase_reset_invoice_method
+#: code:_description:0
+#: model:ir.model,name:purchase_reset_invoice_method.model_purchase_reset_invoice_method
+#, python-format
+msgid "purchase.reset.invoice_method"
+msgstr "purchase.reset.invoice_method"
+
+#. module: purchase_reset_invoice_method
+#: view:purchase.reset.invoice_method:0
+msgid "Confirm"
+msgstr "OK"
+
+#. module: purchase_reset_invoice_method
+#: selection:purchase.reset.invoice_method,invoice_method:0
+msgid "Based on incoming shipments"
+msgstr "Gebaseerd op inkomende leveringen"
+
+#. module: purchase_reset_invoice_method
+#: field:account.invoice.line,invoiced_stock_move_id:0
+msgid "Invoiced stock move"
+msgstr "Gefactureerd inkomend product"
+
+#. module: purchase_reset_invoice_method
+#: code:_description:0
+#: model:ir.model,name:purchase_reset_invoice_method.model_account_invoice_line
+#, python-format
+msgid "Invoice Line"
+msgstr "Factuurregel"
+
+#. module: purchase_reset_invoice_method
+#: code:_description:0
+#: model:ir.model,name:purchase_reset_invoice_method.model_purchase_order
+#, python-format
+msgid "Purchase Order"
+msgstr "Inkooporder"
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:78
+#, python-format
+msgid "This order has an invoice which is not in draft state. Cannot reset the invoice method"
+msgstr "Deze order heeft een factuur die al bevestigd is, daarom kan de factuurcontrole niet meer gewijzigd worden."
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:62
+#, python-format
+msgid "Please convert a single order at once."
+msgstr "Er kan maar één order tegelijkertijd gewijzigd worden."
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:34
+#: view:purchase.reset.invoice_method:0
+#, python-format
+msgid "Reset invoice method"
+msgstr "Factuurcontrole resetten"
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:61
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:68
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:77
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:89
+#, python-format
+msgid "Error"
+msgstr "Fout"
+
+#. module: purchase_reset_invoice_method
+#: view:purchase.reset.invoice_method:0
+msgid "Cancel"
+msgstr "Annuleer"
+
+#. module: purchase_reset_invoice_method
+#: selection:purchase.reset.invoice_method,invoice_method:0
+msgid "Based on Purchase Order lines"
+msgstr "Gebaseerd op orderregels"
+
+#. module: purchase_reset_invoice_method
+#: field:purchase.reset.invoice_method,invoice_method:0
+msgid "Invoicing Control"
+msgstr "Factuurcontrole"
+
+#. module: purchase_reset_invoice_method
+#: code:_description:0
+#: model:ir.model,name:purchase_reset_invoice_method.model_stock_picking
+#, python-format
+msgid "Picking List"
+msgstr "Verzamellijst"
+
+#. module: purchase_reset_invoice_method
+#: field:purchase.reset.invoice_method,order_id:0
+msgid "Order"
+msgstr "Order"
+
+#. module: purchase_reset_invoice_method
+#: selection:purchase.reset.invoice_method,invoice_method:0
+msgid "Based on generated draft invoice"
+msgstr "Gebaseerd op één factuur voor de hele order"
+

=== added file 'purchase_reset_invoice_method/i18n/purchase_reset_invoice_method.pot'
--- purchase_reset_invoice_method/i18n/purchase_reset_invoice_method.pot	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/i18n/purchase_reset_invoice_method.pot	2014-03-04 16:10:35 +0000
@@ -0,0 +1,128 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+# 	* purchase_reset_invoice_method
+#
+msgid ""
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: view:purchase.order:0
+msgid "Reset"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:69
+#, python-format
+msgid "The new invoice method is the same as the old."
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: view:purchase.reset.invoice_method:0
+msgid "or"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:90
+#, python-format
+msgid ""
+"This order has an old invoice created from a picking, but without the "
+"picking reference registered."
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: code:_description:0
+#: model:ir.model,name:purchase_reset_invoice_method.model_purchase_reset_invoice_method
+#, python-format
+msgid "purchase.reset.invoice_method"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: view:purchase.reset.invoice_method:0
+msgid "Confirm"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: selection:purchase.reset.invoice_method,invoice_method:0
+msgid "Based on incoming shipments"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: field:account.invoice.line,invoiced_stock_move_id:0
+msgid "Invoiced stock move"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: code:_description:0
+#: model:ir.model,name:purchase_reset_invoice_method.model_account_invoice_line
+#, python-format
+msgid "Invoice Line"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: code:_description:0
+#: model:ir.model,name:purchase_reset_invoice_method.model_purchase_order
+#, python-format
+msgid "Purchase Order"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:78
+#, python-format
+msgid ""
+"This order has an invoice which is not in draft state. Cannot reset the "
+"invoice method"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:62
+#, python-format
+msgid "Please convert a single order at once."
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:34
+#: view:purchase.reset.invoice_method:0
+#, python-format
+msgid "Reset invoice method"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:61
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:68
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:77
+#: code:addons/purchase_reset_invoice_method/model/purchase_order.py:89
+#, python-format
+msgid "Error"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: view:purchase.reset.invoice_method:0
+msgid "Cancel"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: selection:purchase.reset.invoice_method,invoice_method:0
+msgid "Based on Purchase Order lines"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: field:purchase.reset.invoice_method,invoice_method:0
+msgid "Invoicing Control"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: code:_description:0
+#: model:ir.model,name:purchase_reset_invoice_method.model_stock_picking
+#, python-format
+msgid "Picking List"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: field:purchase.reset.invoice_method,order_id:0
+msgid "Order"
+msgstr ""
+
+#. module: purchase_reset_invoice_method
+#: selection:purchase.reset.invoice_method,invoice_method:0
+msgid "Based on generated draft invoice"
+msgstr ""

=== added directory 'purchase_reset_invoice_method/model'
=== added file 'purchase_reset_invoice_method/model/__init__.py'
--- purchase_reset_invoice_method/model/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/model/__init__.py	2014-03-04 16:10:35 +0000
@@ -0,0 +1,4 @@
+from . import account_invoice_line
+from . import stock_picking
+from . import purchase_reset_invoice_method
+from . import purchase_order

=== added file 'purchase_reset_invoice_method/model/account_invoice_line.py'
--- purchase_reset_invoice_method/model/account_invoice_line.py	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/model/account_invoice_line.py	2014-03-04 16:10:35 +0000
@@ -0,0 +1,32 @@
+#-*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2014 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/>.
+#
+##############################################################################
+
+from openerp.osv import orm, fields
+
+
+class InvoiceLine(orm.Model):
+    _inherit = 'account.invoice.line'
+
+    _columns = {
+        'invoiced_stock_move_id': fields.many2one(
+            'stock.move', 'Invoiced stock move',
+            readonly=True),
+        }

=== added file 'purchase_reset_invoice_method/model/purchase_order.py'
--- purchase_reset_invoice_method/model/purchase_order.py	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/model/purchase_order.py	2014-03-04 16:10:35 +0000
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2014 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/>.
+#
+##############################################################################
+
+from openerp.osv import orm
+from openerp.tools.translate import _
+from openerp import netsvc
+
+
+class PurchaseOrder(orm.Model):
+    _inherit = 'purchase.order'
+
+    def reset_invoice_method_wizard(self, cr, uid, ids, context=None):
+        wizard_id = self.pool['purchase.reset.invoice_method'].create(
+            cr, uid, {'order_id': ids[0]}, context=context)
+        return {
+            'name': _('Reset invoice method'),
+            'view_type': 'form',
+            'view_mode': 'form',
+            'res_model': 'purchase.reset.invoice_method',
+            'context': context,
+            'type': 'ir.actions.act_window',
+            'nodestroy': True,
+            'target': 'new',
+            'res_id': wizard_id,
+        }
+
+    def reset_invoice_method(
+            self, cr, uid, ids, new_invoice_method, context=None):
+        """
+        Reset invoicing method of a purchase order. Clean up any draft
+        invoices, reset picking invoice state and rest the invoicing
+        path of the workflow. The affected invoices may be composed from
+        other orders as well. If these invoices were derived from
+        pickings, we need to reset the invoice state of these pickings as
+        well.
+
+        This will fail if any of the draft invoices has already been
+        confirmed, as OpenERP does not allow unlinking these.
+        """
+
+        if len(ids) > 1:
+            raise orm.except_orm(
+                _('Error'),
+                _('Please convert a single order at once.'))
+
+        wf_service = netsvc.LocalService("workflow")
+        order = self.browse(cr, uid, ids[0], context=context)
+        old_invoice_method = order.invoice_method
+        if old_invoice_method == new_invoice_method:
+            raise orm.except_orm(
+                _('Error'),
+                _('The new invoice method is the same as the old.'))
+
+        if order.invoice_ids:
+            for invoice in order.invoice_ids:
+                if invoice.state != 'draft':
+                    raise orm.except_orm(
+                        _('Error'),
+                        _('This order has an invoice which is not in draft '
+                          'state. Cannot reset the invoice method'))
+
+                if old_invoice_method != 'picking':
+                    continue
+
+                # Track pickings and reset invoice state of foreign
+                # pickings
+                for inv_line in invoice.invoice_line:
+                    if not inv_line.invoiced_stock_move_id:
+                        raise orm.except_orm(
+                            _('Error'),
+                            _('This order has an old invoice created from a '
+                              'picking, but without the picking reference '
+                              'registered.'))
+                    picking = inv_line.invoiced_stock_move_id.picking_id
+                    if (picking.invoice_state == 'invoiced' and
+                            picking not in order.picking_ids):
+                        picking.write({'invoice_state': '2binvoiced'})
+                        picking.refresh()
+
+            # Reset invoice state of purchase lines
+            order_line_ids = self.pool['purchase.order.line'].search(
+                cr, uid, [
+                    ('invoice_lines.invoice_id', 'in',
+                     [inv.id for inv in order.invoice_ids])],
+                context=context)
+            self.pool['purchase.order.line'].write(
+                cr, uid, order_line_ids,
+                {'invoiced': False}, context=context)
+
+            for invoice in order.invoice_ids:
+                wf_service.trg_validate(
+                    uid, 'account.invoice', invoice.id,
+                    'invoice_cancel', cr)
+                self.pool['account.invoice'].unlink(
+                    cr, uid, [invoice.id],
+                    context=context)
+
+        if order.picking_ids:
+            # Reset this order's pickings invoice state
+            state = '2binvoiced' if new_invoice_method == 'picking' else 'none'
+            self.pool['stock.picking'].write(
+                cr, uid,
+                [picking.id for picking in order.picking_ids],
+                {'invoice_state': state}, context=context)
+
+        order.write({'invoice_method': new_invoice_method})
+
+        wf_service.trg_validate(
+            uid, 'purchase.order', order.id,
+            'reset_invoice_method_order', cr)
+        return True

=== added file 'purchase_reset_invoice_method/model/purchase_reset_invoice_method.py'
--- purchase_reset_invoice_method/model/purchase_reset_invoice_method.py	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/model/purchase_reset_invoice_method.py	2014-03-04 16:10:35 +0000
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2014 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/>.
+#
+##############################################################################
+
+from openerp.osv import orm, fields
+
+
+class PurchaseResetInvoiceMethod(orm.TransientModel):
+    _name = 'purchase.reset.invoice_method'
+    _columns = {
+        'order_id': fields.many2one(
+            'purchase.order', 'Order', readonly=True),
+        'invoice_method': fields.selection(
+            [('manual', 'Based on Purchase Order lines'),
+             ('order', 'Based on generated draft invoice'),
+             ('picking', 'Based on incoming shipments')],
+            'Invoicing Control'),
+        }
+
+    def do_reset(self, cr, uid, ids, context=None):
+        wizard = self.browse(cr, uid, ids[0], context=context)
+        return wizard.order_id.reset_invoice_method(
+            wizard.invoice_method)

=== added file 'purchase_reset_invoice_method/model/stock_picking.py'
--- purchase_reset_invoice_method/model/stock_picking.py	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/model/stock_picking.py	2014-03-04 16:10:35 +0000
@@ -0,0 +1,37 @@
+#-*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    This module Copyright (C) 2014 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/>.
+#
+##############################################################################
+
+from openerp.osv import orm
+
+
+class Picking(orm.Model):
+    _inherit = 'stock.picking'
+
+    def _invoice_line_hook(self, cr, uid, move_line, invoice_line_id):
+        """
+        Deterministically link the invoiced picking to the associated
+        invoice lines through the stock move.
+        """
+        self.pool['account.invoice.line'].write(
+            cr, uid, [invoice_line_id],
+            {'invoiced_stock_move_id': move_line.id})
+        return super(Picking, self)._invoice_line_hook(
+            cr, uid, move_line, invoice_line_id)

=== added directory 'purchase_reset_invoice_method/tests'
=== added file 'purchase_reset_invoice_method/tests/__init__.py'
--- purchase_reset_invoice_method/tests/__init__.py	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/tests/__init__.py	2014-03-04 16:10:35 +0000
@@ -0,0 +1,5 @@
+from . import purchase_reset_invoice_method
+
+fast_suite = [
+    purchase_reset_invoice_method,
+]

=== added file 'purchase_reset_invoice_method/tests/purchase_reset_invoice_method.py'
--- purchase_reset_invoice_method/tests/purchase_reset_invoice_method.py	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/tests/purchase_reset_invoice_method.py	2014-03-04 16:10:35 +0000
@@ -0,0 +1,362 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    Copyright (C) 2014 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/>.
+#
+##############################################################################
+import time
+from openerp.tests.common import SingleTransactionCase
+from openerp import netsvc
+from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DATEFMT
+
+
+class TestResetInvoiceMethod(SingleTransactionCase):
+    wf_service = netsvc.LocalService("workflow")
+
+    def setUp(self):
+        """
+        Create a supplier and a product.
+        """
+        self.supplier_id = self.registry('res.partner').create(
+            self.cr, self.uid, {'name': 'Supplier', 'supplier': True})
+        self.product_id = self.registry('product.product').create(
+            self.cr, self.uid, {
+                'name': 'Product test reset invoice method',
+                'type': 'product',
+                'supply_method': 'buy',
+                'standard_price': 1.0,
+                })
+
+    def assert_state(self, po, state):
+        self.assertTrue(
+            po.state == state,
+            'Purchase order is not in state \'%s\'' % state)
+
+    def pay_invoice(self, po):
+        """
+        Pay the purchase order's first invoice with a voucher.
+        This only affects the purchase order workflow in the case
+        of invoice_method 'order'.
+        """
+        reg, cr, uid, = self.registry, self.cr, self.uid
+        try:
+            voucher_obj = reg('account.voucher')
+        except KeyError:
+            # Voucher module not available
+            return
+        voucher_context = {
+            'payment_expected_currency': po.invoice_ids[0].currency_id.id,
+            'default_partner_id': po.invoice_ids[0].partner_id.id,
+            'default_amount': po.invoice_ids[0].residual,
+            'default_reference': po.invoice_ids[0].name,
+            'invoice_type': po.invoice_ids[0].type,
+            'invoice_id': po.invoice_ids[0].id,
+            'default_type': 'payment',
+            'type': 'payment',
+            }
+        journal_id = reg('account.journal').search(
+            cr, uid, [
+                ('type', '=', 'bank'),
+                ('company_id', '=', po.company_id.id)])[0]
+        voucher_values = {
+            'partner_id': po.invoice_ids[0].partner_id.id,
+            'journal_id': journal_id,
+            'account_id': reg('account.journal').browse(
+                cr, uid, journal_id).default_credit_account_id.id,
+            }
+        voucher_values.update(voucher_obj.onchange_journal(
+            cr, uid, False, voucher_values['journal_id'],
+            [], False, po.invoice_ids[0].partner_id.id,
+            time.strftime(DATEFMT), po.invoice_ids[0].residual,
+            'payment', po.invoice_ids[0].currency_id.id,
+            context=voucher_context)['value'])
+        voucher_values['line_dr_ids'] = [
+            (0, 0, line) for line in voucher_values['line_dr_ids']]
+        voucher_id = voucher_obj.create(
+            cr, uid, voucher_values, context=voucher_context)
+        self.wf_service.trg_validate(
+            uid, 'account.voucher', voucher_id, 'proforma_voucher', cr)
+
+        # Invoice has been paid and the purchase order is done
+        po.refresh()
+        self.assertTrue(
+            po.invoice_ids[0].state == 'paid',
+            'Did not succeed in paying the invoice with a voucher')
+        return voucher_id
+
+    def create_order(self, invoice_method):
+        """
+        Create a purchase order
+        """
+        reg, cr, uid, = self.registry, self.cr, self.uid
+        po_pool = reg('purchase.order')
+        line_pool = reg('purchase.order.line')
+        line_values = {
+            'product_id': self.product_id,
+            'product_qty': 1,
+            }
+        line_values.update(
+            line_pool.onchange_product_id(
+                cr, uid, False, False, self.product_id,
+                1, False, self.supplier_id)['value'])
+        po_values = {
+            'partner_id': self.supplier_id,
+            'order_line': [(0, 0, line_values)],
+            'invoice_method': invoice_method,
+            }
+        po_values.update(po_pool.onchange_dest_address_id(
+            cr, uid, False, self.supplier_id)['value'])
+        po_values.update(po_pool.onchange_partner_id(
+            cr, uid, False, self.supplier_id)['value'])
+        return po_pool.create(cr, uid, po_values)
+
+    def create_invoice_from_pickings(self, picking_ids):
+        """
+        Receive a single invoice for all pickings.
+        """
+        reg, cr, uid, = self.registry, self.cr, self.uid
+        invoicing = reg('stock.invoice.onshipping')
+        invoicing_context = {
+            'active_ids': picking_ids,
+            'active_id': picking_ids[0],
+            'active_model': 'stock.picking',
+            }
+        invoicing_id = invoicing.create(
+            cr, uid, {'group': True}, context=invoicing_context
+            )
+        res = invoicing.create_invoice(
+            cr, uid, [invoicing_id], context=invoicing_context)
+        return res.items()[0][1]
+
+    def test_00_picking_to_order(self):
+        """
+        Create two purchase orders with invoice method 'picking'.
+        After delivery of the pickings, create a single invoice.
+        Change the invoice method of one of the orders to 'order'
+        and run through this order's workflow until completed.
+        """
+        reg, cr, uid, = self.registry, self.cr, self.uid
+        po_pool = reg('purchase.order')
+        po1_id = self.create_order('picking')
+        po2_id = self.create_order('picking')
+
+        # Confirm and receive the purchases
+        self.wf_service.trg_validate(
+            uid, 'purchase.order', po1_id, 'purchase_confirm', cr)
+        self.wf_service.trg_validate(
+            uid, 'purchase.order', po2_id, 'purchase_confirm', cr)
+        po1, po2 = po_pool.browse(
+            cr, uid, [po1_id, po2_id])
+        self.wf_service.trg_validate(
+            uid, 'stock.picking', po1.picking_ids[0].id, 'button_done', cr)
+        self.wf_service.trg_validate(
+            uid, 'stock.picking', po2.picking_ids[0].id, 'button_done', cr)
+        self.assert_state(po1, 'approved')
+        self.assert_state(po2, 'approved')
+        for picking in po1.picking_ids[0], po2.picking_ids[0]:
+            self.assertTrue(
+                picking.state == 'done' and
+                picking.invoice_state == '2binvoiced',
+                'Picking is not ready for invoicing')
+
+        invoice_id = self.create_invoice_from_pickings(
+            [po1.picking_ids[0].id, po2.picking_ids[0].id])
+
+        # Reset the first purchase order's invoice method
+        po1.reset_invoice_method('order')
+        po1.refresh()
+        po2.refresh()
+
+        # The original draft invoice has been removed
+        # and the picking's invoice states have been updated
+        # according to each order's invoice control
+        self.assertFalse(
+            reg('account.invoice').search(
+                cr, uid, [('id', '=', invoice_id)]),
+            'Obsolete invoice has not been removed after changing invoice '
+            'method')
+        self.assertTrue(
+            po1.picking_ids[0].invoice_state == 'none',
+            'Picking from order has not been set not to be invoiced')
+        self.assertTrue(
+            po2.picking_ids[0].invoice_state == '2binvoiced',
+            'Picking from second order has not been reset to be invoiced')
+
+        # A new invoice for the first order has been created through
+        # the workflow. Confirm this invoice.
+        self.assertTrue(
+            len(po1.invoice_ids) == 1,
+            'Unexpected number of invoices for first order '
+            '(other than 1): %s' % (len(po1.invoice_ids)))
+        self.wf_service.trg_validate(
+            uid, 'account.invoice', po1.invoice_ids[0].id, 'invoice_open', cr)
+        po1.refresh()
+        self.assertTrue(
+            po1.invoice_ids[0].state == 'open',
+            'Did not succeed in confirming the generated invoice')
+
+        self.pay_invoice(po1)
+        self.assertTrue(
+            po1.state == 'done',
+            'Purchase order workflow did not complete after paying the '
+            'invoice')
+
+    def test_01_order_to_picking(self):
+        """
+        Create a purchase order with invoice method 'order'.
+        After confirming the order, change the invoice method
+        to 'picking' and run through this order's workflow until completed.
+        """
+        reg, cr, uid, = self.registry, self.cr, self.uid
+        po_pool = reg('purchase.order')
+        po_id = self.create_order('order')
+
+        # Confirm and receive the purchases
+        self.wf_service.trg_validate(
+            uid, 'purchase.order', po_id, 'purchase_confirm', cr)
+        po = po_pool.browse(cr, uid, po_id)
+        self.assertTrue(
+            len(po.invoice_ids) == 1,
+            'Unexpected number of invoices for purchase order '
+            '(other than 1): %s' % len(po.invoice_ids))
+
+        invoice_id = po.invoice_ids[0].id
+
+        # Reset the purchase order's invoice method
+        po.reset_invoice_method('picking')
+        po.refresh()
+
+        # The original draft invoice has been removed
+        # and the picking's invoice states have been updated
+        # according to each order's invoice control
+        self.assertFalse(
+            reg('account.invoice').search(
+                cr, uid, [('id', '=', invoice_id)]),
+            'Obsolete invoice has not been removed after changing invoice '
+            'method')
+
+        # Check that the purchase order is not in an invoice exception
+        # after unlinking the original invoice
+        self.assert_state(po, 'approved')
+
+        self.wf_service.trg_validate(
+            uid, 'stock.picking', po.picking_ids[0].id, 'button_done', cr)
+        self.assertTrue(
+            po.picking_ids[0].state == 'done' and
+            po.picking_ids[0].invoice_state == '2binvoiced',
+            'Picking is not ready for invoicing')
+        invoice_id = self.create_invoice_from_pickings([po.picking_ids[0].id])
+        self.wf_service.trg_validate(
+            uid, 'account.invoice', po.invoice_ids[0].id, 'invoice_open', cr)
+        po.refresh()
+        self.assertTrue(
+            po.invoice_ids[0].state == 'open',
+            'Did not succeed in confirming the generated invoice')
+        self.assertTrue(
+            po.state == 'done',
+            'Purchase order workflow did not complete after paying the '
+            'invoice when changing invoice method from order to picking')
+
+    def test_02_lines_to_picking(self):
+        """
+        Create two orders set to 'manual', and invoice their lines together.
+        Reset the first order's invoice method to 'picking'. Finish both
+        orders' workflows.
+        """
+        reg, cr, uid, = self.registry, self.cr, self.uid
+        po_pool = reg('purchase.order')
+        po_id = self.create_order('manual')
+        po2_id = self.create_order('manual')
+
+        # Confirm and receive the purchases
+        self.wf_service.trg_validate(
+            uid, 'purchase.order', po_id, 'purchase_confirm', cr)
+        self.wf_service.trg_validate(
+            uid, 'purchase.order', po2_id, 'purchase_confirm', cr)
+        po = po_pool.browse(cr, uid, po_id)
+        po2 = po_pool.browse(cr, uid, po2_id)
+
+        invoicing_context = {'active_ids': [
+            line.id for line in po.order_line + po2.order_line]}
+        reg('purchase.order.line_invoice').makeInvoices(
+            cr, uid, False, invoicing_context)
+        po.refresh()
+
+        self.assertTrue(
+            len(po.invoice_ids) == 1,
+            'Unexpected number of invoices for purchase order '
+            '(other than 1): %s' % len(po.invoice_ids))
+        invoice_id = po.invoice_ids[0].id
+
+        # Reset the purchase order's invoice method
+        po.reset_invoice_method('picking')
+        po.refresh()
+        po2.refresh()
+
+        # The original draft invoice has been removed
+        # and the picking's invoice states have been updated
+        # according to each order's invoice control
+        self.assertFalse(
+            reg('account.invoice').search(
+                cr, uid, [('id', '=', invoice_id)]),
+            'Obsolete invoice has not been removed after changing invoice '
+            'method')
+
+        # Check that the purchase order is not in an invoice exception
+        # after unlinking the original invoice
+        self.assert_state(po, 'approved')
+
+        self.wf_service.trg_validate(
+            uid, 'stock.picking', po.picking_ids[0].id, 'button_done', cr)
+        self.assertTrue(
+            po.picking_ids[0].state == 'done' and
+            po.picking_ids[0].invoice_state == '2binvoiced',
+            'Picking is not ready for invoicing')
+        invoice_id = self.create_invoice_from_pickings([po.picking_ids[0].id])
+        self.wf_service.trg_validate(
+            uid, 'account.invoice', invoice_id, 'invoice_open', cr)
+
+        po.refresh()
+        self.assertTrue(
+            po.invoice_ids[0].state == 'open',
+            'Did not succeed in confirming the generated invoice')
+        self.assertTrue(
+            po.state == 'done',
+            'Purchase order workflow did not complete after paying the '
+            'invoice when changing invoice method from order to picking')
+
+        # Recreate the invoice for the second order and finish
+        # its workflow with the original 'manual' invoice control
+        self.assertFalse(
+            po2.order_line[0].invoiced,
+            'Second order\'s line invoice state has not been reset')
+        invoicing_context = {'active_ids': [
+            line.id for line in po2.order_line]}
+        reg('purchase.order.line_invoice').makeInvoices(
+            cr, uid, False, invoicing_context)
+        po2.refresh()
+        self.wf_service.trg_validate(
+            uid, 'stock.picking', po2.picking_ids[0].id, 'button_done', cr)
+        self.wf_service.trg_validate(
+            uid, 'account.invoice', po2.invoice_ids[0].id, 'invoice_open', cr)
+        po2.refresh()
+        self.assertTrue(
+            po2.state == 'done',
+            'Purchase order workflow did not complete after paying the '
+            'invoice when changing invoice method from order to picking')
+        self.assertTrue(
+            po2.order_line[0].invoiced,
+            'Second order\'s line is not set to invoiced')

=== added directory 'purchase_reset_invoice_method/view'
=== added file 'purchase_reset_invoice_method/view/purchase_order.xml'
--- purchase_reset_invoice_method/view/purchase_order.xml	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/view/purchase_order.xml	2014-03-04 16:10:35 +0000
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+<data>
+
+    <record id="purchase_order_form" model="ir.ui.view">
+        <field name="name">Add button to reset invoice method</field>
+        <field name="model">purchase.order</field>
+        <field name="inherit_id" ref="purchase.purchase_order_form" />
+        <field name="arch" type="xml">
+            <field name="invoice_method" position="replace">
+                <group colspan="2" col="3">
+                    <field name="invoice_method" />
+                    <button name="reset_invoice_method_wizard"
+                            string="Reset" type="object"
+                            attrs="{'invisible': [('state', 'in', ['draft', 'sent', 'done', 'cancel'])]}"/>
+                </group>
+            </field>
+        </field>
+    </record>
+
+</data>
+</openerp>

=== added file 'purchase_reset_invoice_method/view/purchase_reset_invoice_method.xml'
--- purchase_reset_invoice_method/view/purchase_reset_invoice_method.xml	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/view/purchase_reset_invoice_method.xml	2014-03-04 16:10:35 +0000
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+<data>
+
+    <record id="reset_wizard_form_view" model="ir.ui.view">
+        <field name="name">Reset invoice method form view</field>
+        <field name="model">purchase.reset.invoice_method</field>
+        <field name="arch" type="xml">
+            <form string="Reset invoice method" version="7.0">
+                <group>
+                    <field name="order_id" />
+                    <field name="invoice_method" required="1" />
+                </group>
+                <footer>
+                    <button name="do_reset" string="Confirm"
+                            type="object" class="oe_highlight" />
+                    or
+                    <button string="Cancel" class="oe_link" special="cancel"/>
+                </footer>
+            </form>
+        </field>
+    </record>
+
+</data>
+</openerp>
+
+
+                    
\ No newline at end of file

=== added directory 'purchase_reset_invoice_method/workflow'
=== added file 'purchase_reset_invoice_method/workflow/purchase.xml'
--- purchase_reset_invoice_method/workflow/purchase.xml	1970-01-01 00:00:00 +0000
+++ purchase_reset_invoice_method/workflow/purchase.xml	2014-03-04 16:10:35 +0000
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+
+        <record id="act_ignore_exception" model="workflow.activity">
+            <field name="wkf_id" ref="purchase.purchase_order"/>
+            <field name="name">skip_exception_after_invoice_method_reset</field>
+            <field name="kind">function</field>
+            <field name="action">write({'state': 'approved'})</field>
+        </record>
+
+        <!--
+            Orders with new method other than 'order' skip the invoice
+            part of the workflow. This applies to orders with old method
+            'order', without an existing (draft) invoice.
+        -->
+        <record id="trans_skip_invoice" model="workflow.transition">
+            <field name="act_from" ref="purchase.act_invoice" />
+            <field name="act_to" ref="purchase.act_invoice_end" />
+            <field name="condition">invoice_method != 'order'</field>
+            <field name="signal">reset_invoice_method_order</field>
+        </record>
+
+        <!--
+            Orders with old method 'order' with an existing draft
+            invoice will get an invoice exception when the draft
+            invoice is removed. Route the workflow in this case
+            passed the activity that resets the state field and
+            moves on to the end of the invoice subflow], which is
+            skipped for these invoice methods
+        -->
+        <record id="trans_ignore_exception" model="workflow.transition">
+            <field name="act_from" ref="purchase.act_except_invoice" />
+            <field name="act_to" ref="act_ignore_exception" />
+            <field name="condition">invoice_method != 'order'</field>
+            <field name="signal">reset_invoice_method_order</field>
+        </record>
+
+        <record id="trans_skip_invoiced_invoice" model="workflow.transition">
+            <field name="act_from" ref="act_ignore_exception" />
+            <field name="act_to" ref="purchase.act_invoice_end" />
+        </record>
+
+        <!--
+            Orders with new method 'order' need to enter the invoice
+            part of the workflow.
+        -->
+        <record id="trans_goto_invoice" model="workflow.transition">
+            <field name="act_from" ref="purchase.act_invoice_end" />
+            <field name="act_to" ref="purchase.act_invoice" />
+            <field name="condition">invoice_method == 'order'</field>
+            <field name="signal">reset_invoice_method_order</field>
+        </record>
+
+    </data>
+</openerp>


Follow ups