← Back to team overview

openerp-community-reviewer team mailing list archive

[Merge] lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-date-constraint into lp:stock-logistic-warehouse

 

Loïc Bellier - Numérigraphe has proposed merging lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-date-constraint into lp:stock-logistic-warehouse.

Requested reviews:
  Stock and Logistic Core Editors (stock-logistic-core-editors)

For more details, see:
https://code.launchpad.net/~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-date-constraint/+merge/210632

This proposal blocks the stock moves if move date is before the latest inventory date.

-- 
https://code.launchpad.net/~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-date-constraint/+merge/210632
Your team Stock and Logistic Core Editors is requested to review the proposed merge of lp:~numerigraphe-team/stock-logistic-warehouse/7.0-inventory-date-constraint into lp:stock-logistic-warehouse.
=== added directory 'stock_inventory_date_constraint'
=== added file 'stock_inventory_date_constraint/__init__.py'
--- stock_inventory_date_constraint/__init__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_date_constraint/__init__.py	2014-03-12 15:28:57 +0000
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2011 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+import stock

=== added file 'stock_inventory_date_constraint/__openerp__.py'
--- stock_inventory_date_constraint/__openerp__.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_date_constraint/__openerp__.py	2014-03-12 15:28:57 +0000
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    This module is copyright (C) 2011 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+{
+    "name": "Refuse stock move happening before the date of any inventory done",
+    "version": "1.0",
+    "depends": ["stock"],
+    "author": u"Numérigraphe",
+    "category": "Stock / Inventory",
+    "description": """
+        The goal of the module is to refuse stock move happening before the date of any inventory done
+        for a product/lot/location.
+
+        This module corrects the following bug / feature (#588154):
+            "I setup a physical inventory with date 2010/05/19. Stock is 5 for product x
+            When I have an internal move for 1 piece of product x with date 2010/05/01, 
+            the current calculated stock at 2010/06/01 is 4 which seems to me incorrect as the current stock 
+            should reflect the physical inventory on 2010/05/19 and be 5."
+        url : https://bugs.launchpad.net/openobject-addons/+bug/588154
+    """,
+    "test": ["test/stock_inventory_date.yml"],
+    "demo": ["stock_demo.xml"]
+}

=== added directory 'stock_inventory_date_constraint/i18n'
=== added file 'stock_inventory_date_constraint/i18n/fr.po'
--- stock_inventory_date_constraint/i18n/fr.po	1970-01-01 00:00:00 +0000
+++ stock_inventory_date_constraint/i18n/fr.po	2014-03-12 15:28:57 +0000
@@ -0,0 +1,109 @@
+# Translation of OpenERP Server.
+# This file contains the translation of the following modules:
+#	* stock_inventory_date_constraint
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: OpenERP Server 6.0.4\n"
+"Report-Msgid-Bugs-To: support@xxxxxxxxxxx\n"
+"POT-Creation-Date: 2013-09-25 13:34+0000\n"
+"PO-Revision-Date: 2013-09-25 13:34+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: stock_inventory_date_constraint
+#: code:addons/stock_inventory_date_constraint/stock.py:85
+#: code:addons/stock_inventory_date_constraint/stock.py:135
+#, python-format
+msgid "- %s (ID %d)"
+msgstr "- %s (Id. %d)"
+
+#. module: stock_inventory_date_constraint
+#: constraint:stock.move:0
+msgid "A Physical Inventory is being conducted at this location"
+msgstr "Un inventaire est déjà en cours à cet emplacement"
+
+#. module: stock_inventory_date_constraint
+#: constraint:stock.inventory:0
+msgid "Error! You can not create recursive inventories."
+msgstr "Erreur! Vous ne pouvez pas créer un inventaire récursif."
+
+#. module: stock_inventory_date_constraint
+#: constraint:stock.inventory.line:0
+msgid "Error: duplicates lines"
+msgstr "Erreur: lignes en double"
+
+#. module: stock_inventory_date_constraint
+#: model:ir.model,name:stock_inventory_date_constraint.model_stock_inventory
+msgid "Gestion des stocks"
+msgstr "Gestion des stocks"
+
+#. module: stock_inventory_date_constraint
+#: model:ir.model,name:stock_inventory_date_constraint.model_stock_inventory_line
+msgid "Ligne d'inventaire"
+msgstr "Ligne d'inventaire"
+
+#. module: stock_inventory_date_constraint
+#: model:ir.model,name:stock_inventory_date_constraint.model_stock_move
+msgid "Mouvement de stock"
+msgstr "Mouvement de stock"
+
+#. module: stock_inventory_date_constraint
+#: constraint:stock.move:0
+msgid "One or more lots are awaiting quality control and cannot be moved."
+msgstr "Un ou plusieurs lots sont en attente de contrôle qualité, et ne peuvent pas être déplacés."
+
+#. module: stock_inventory_date_constraint
+#: constraint:stock.inventory:0
+msgid "Other Physical inventories are being conducted using the same Locations."
+msgstr "Erreur: certains emplacements sont déjà dans un autre inventaire."
+
+#. module: stock_inventory_date_constraint
+#: code:addons/stock_inventory_date_constraint/stock.py:89
+#: code:addons/stock_inventory_date_constraint/stock.py:139
+#, python-format
+msgid "The changes cannot be made because they conflict the following Stock Inventories:\n"
+""
+msgstr "Les changements demandés sont impossibles car ils sont en conflit avec les inventaires suivants :\n"
+""
+
+#. module: stock_inventory_date_constraint
+#: code:addons/stock_inventory_date_constraint/stock.py:164
+#, python-format
+msgid "The following lines are duplicates and will be deleted :\n"
+"%s"
+msgstr "Les lignes suivantes sont en double et seront effacées :\n"
+"%s"
+
+#. module: stock_inventory_date_constraint
+#: code:addons/stock_inventory_date_constraint/stock.py:163
+#, python-format
+msgid "Warning : duplicates lines"
+msgstr "Attention : lignes en double"
+
+#. module: stock_inventory_date_constraint
+#: code:addons/stock_inventory_date_constraint/stock.py:88
+#: code:addons/stock_inventory_date_constraint/stock.py:138
+#, python-format
+msgid "Wrong Stock Moves"
+msgstr "Mouvements de stock incorrects"
+
+#. module: stock_inventory_date_constraint
+#: constraint:stock.move:0
+msgid "You must assign a production lot for this product"
+msgstr "Vous devez affecter un lot de fabrication à ce produit."
+
+#. module: stock_inventory_date_constraint
+#: constraint:stock.inventory.line:0
+msgid "You must not create same inventory line Product, Location, Lot on the same date"
+msgstr "Vous ne pouvez pas créer plusieurs lignes d'inventaires pour le même produit, lot, emplacement à la même date"
+
+#. module: stock_inventory_date_constraint
+#: constraint:stock.move:0
+msgid "You try to assign a lot which is not from the same product"
+msgstr "Vous essayez d'affecter un lot qui n'est pas pour ce produit."
+

=== added file 'stock_inventory_date_constraint/stock.py'
--- stock_inventory_date_constraint/stock.py	1970-01-01 00:00:00 +0000
+++ stock_inventory_date_constraint/stock.py	2014-03-12 15:28:57 +0000
@@ -0,0 +1,170 @@
+# encoding: utf-8
+##############################################################################
+#
+#    This module is copyright (C) 2011 Numérigraphe SARL. All Rights Reserved.
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU 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 General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+
+from openerp.osv import osv, orm
+from openerp.tools.translate import _
+
+
+class StockInventoryCancelable(osv.osv):
+    """Make inventories cancelable despite the constraint on stock move dates""" 
+    _inherit = "stock.inventory"
+
+    def action_cancel_inventory(self, cr, uid, ids, context=None):
+        """Inject a context key to not block the deletion of stock moves"""
+        if context is None:
+            context = {}
+        else:
+            context = context.copy()
+        context['ignore_inventories'] = ids
+        return super(StockInventoryCancelable, self).action_cancel_inventory(
+            cr, uid, ids, context=context)
+
+
+class StockMoveConstraint(osv.osv):
+
+    _inherit = 'stock.move'
+
+    def _past_inventories(self, cr, uid, product_ids, prodlot_ids, location_ids, limit_date, context=None):
+        """Search for inventories already finished after a given date"""
+        # Search for inventory lines with the given location / prodlot / product
+        if context is None:
+            context = {}
+        sil_obj = self.pool.get('stock.inventory.line')
+        sil_ids = sil_obj.search(cr, uid, [('product_id', 'in', product_ids),
+                                           ('prod_lot_id', 'in', prodlot_ids),
+                                           ('location_id', 'in', location_ids),
+                                           ('inventory_id', 'not in', context.get('ignore_inventories', []))],
+                                 context=context)
+        if not sil_ids:
+            return []
+        # Search for inventories dates after the move containing those lines
+        inventory_ids = [i['inventory_id'][0]
+                         for i in sil_obj.read(cr, uid,
+                                               sil_ids, ['inventory_id'],
+                                               context=context)]
+        return self.pool.get('stock.inventory').search(
+                cr, uid, [('id', 'in', inventory_ids),
+                          ('state', '=', 'done'),
+                          ('date', '>=', limit_date)], context=context)
+
+    def create(self, cr, uid, vals, context=None):
+        """Make sure the Stock Move being created doesn't make a finished inventory wrong"""
+        # Take default values into account
+        old_vals = vals
+        vals = self.default_get(cr, uid,
+                                  ['product_id', 'prodlot_id', 'location_id',
+                                   'location_dest_id', 'date'],
+                                  context=context)
+        vals.update(old_vals)
+
+        if vals.get('state') == 'done':
+            inv_ids = self._past_inventories(cr, uid, [vals.get('product_id')],
+                                             [vals.get('prodlot_id')],
+                                             [vals.get('location_id'),
+                                             vals.get('location_dest_id')],
+                                             vals.get('date'), context=context)
+            if inv_ids:
+                # Make a message string with the names of the Inventories
+                inventories = self.pool.get("stock.inventory").browse(cr, uid, inv_ids, context=context)
+                tab_inventories = {i.id: i.name for i in inventories}
+                msg = "\n".join([_("- %s (ID %d)") % (name, i)
+                                for (i, name) in tab_inventories.iteritems()])
+                raise orm.except_orm(
+                    _('Wrong Stock Moves'),
+                    _('The changes cannot be made because they conflict the following Stock Inventories:\n') + msg)
+        return super(StockMoveConstraint, self).create(cr, uid, vals, context=context)
+
+    def write(self, cr, uid, ids, vals, context=None):
+        """Make sure the changes being made to the Stock Moves don't make a finished inventory wrong"""
+        # XXX: the logic here is not 100% proven and may still allow some corner cases
+        # The difficulty is that inventories can be changed by doing or undoing a move, 
+        # changing it's date, product, prodlot etc.
+        inv_ids = []
+        # Nothing to do if we change no value that can affect past inventories
+        if ('state' not in vals
+             and 'date' not in vals
+             and 'locatation_id' not in vals
+             and 'locatation_dest_id' not in vals
+             and 'prodlot_id' not in vals
+             and 'product_id' not in vals):
+            return super(StockMoveConstraint, self).write(cr, uid, ids, vals, context=context)
+
+        if not isinstance(ids, list):
+            ids = [ids]
+        for move in self.browse(cr, uid, ids, context=context):
+            # Decide the limit date of the inventories that could be made wrong, depending on how the Stock Move is being changed
+            new_state = vals.get('state', move.state)
+            if move.state == 'done':
+                if new_state == 'done':
+                    # Get the earliest date from the new and the old date
+                    limit_date = min(move.date, vals['date'])
+                else:
+                    limit_date = move.date
+            else:
+                if new_state == 'done':
+                    limit_date = vals.get('date', move.date)
+                else:
+                    continue
+
+            # Search for inventories conflicting the change
+            inv_ids.extend(self._past_inventories(cr, uid,
+                                      [vals.get('product_id'), move.product_id.id],
+                                      [vals.get('prodlot_id'), move.prodlot_id.id],
+                                      [vals.get('location_id'), move.location_id.id,
+                                       vals.get('location_dest_id'), move.location_dest_id.id],
+                                      limit_date, context=context))
+
+        if inv_ids:
+            # Make a message string with the names of the Inventories
+            inventories = self.pool.get("stock.inventory").browse(cr, uid, inv_ids, context=context)
+            tab_inventories = {i.id: i.name for i in inventories}
+            msg = "\n".join([_("- %s (ID %d)") % (name, i)
+                            for (i, name) in tab_inventories.iteritems()])
+            raise orm.except_orm(
+                _('Wrong Stock Moves'),
+                _('The changes cannot be made because they conflict the following Stock Inventories:\n') + msg)
+        return super(StockMoveConstraint, self).write(cr, uid, ids, vals, context=context)
+
+
+class stock_inventory_line(osv.osv):
+    """ Check if the new line will not be a duplicate line.
+    Look for location_id, product_id and prod_lot_id.
+    """
+    _inherit = 'stock.inventory.line'
+
+    def _check_duplicates_line(self, cr, uid, ids, context=None):
+        """ Check for duplicates lines """
+        message = ''
+        for line in self.browse(cr, uid, ids, context=context):
+            duplicates_count = self.search(cr, uid, [('location_id', '=', line.location_id.id),
+                                                     ('product_id', '=', line.product_id.id),
+                                                     ('prod_lot_id', '=', line.prod_lot_id.id),
+                                                     ('inventory_id', '=', line.inventory_id.id)
+                                                    ], context=context, count=True)
+            if duplicates_count > 1:
+                message = '%s - %s - %s\n' % (line.location_id.name, line.prod_lot_id.name, line.product_id.name)
+
+        if message:
+            raise osv.except_osv(_('Warning : duplicates lines'),
+                                 _('The following lines are duplicates and will be deleted :\n%s') % message)
+        return True
+
+    _constraints = [(_check_duplicates_line, 'Error: duplicates lines',
+                     ['location_id', 'product_id', 'prod_lot_id']), ]

=== added file 'stock_inventory_date_constraint/stock_demo.xml'
--- stock_inventory_date_constraint/stock_demo.xml	1970-01-01 00:00:00 +0000
+++ stock_inventory_date_constraint/stock_demo.xml	2014-03-12 15:28:57 +0000
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data noupdate="0">
+
+        <!-- Record a production lot we can use in the tests. -->
+        <record id="lot_test0" model="stock.production.lot">
+            <field name="product_id" ref="product.product_product_10" />
+        </record>
+
+        <!-- Record inventories we can use in the tests. -->
+        <!-- We need them in the demo data because test data is rolled back 
+            whenever an exception is raised. -->
+        
+        <!-- Record an inventory to make sure the next one will have stock moves posted. -->
+        <record id="stock_inventory_init" model="stock.inventory">
+            <field name="name">Initial Stock situation for testing the date constraint</field>
+            <field name="state">draft</field>
+            <field name="date">2025-01-01 12:05:27</field>
+        </record>
+        <record id="stock_inventory_line" model="stock.inventory.line">
+            <field name="inventory_id" ref="stock_inventory_init" />
+            <field name="product_id" ref="product.product_product_10" />
+            <field name="prod_lot_id" ref="lot_test0"/>
+            <field name="product_uom" ref="product.product_uom_unit" />
+            <field name="product_qty">27.0</field>
+            <field name="location_id" ref="stock.stock_location_components" />
+        </record>
+        <function model="stock.inventory" name="action_confirm">
+            <function
+                eval="[[('id', '=', ref('stock_inventory_init'))]]"
+                model="stock.inventory" name="search" />
+        </function>
+        <function model="stock.inventory" name="action_done">
+            <function
+                eval="[[('id', '=', ref('stock_inventory_init'))]]"
+                model="stock.inventory" name="search" />
+        </function>
+        
+        <!-- The next inventory is dated in the distant future because we want
+            to test it against stock moves made before, but stock.move.action_done forces 
+            the move date to today. -->
+        <record id="stock_inventory_physical0" model="stock.inventory">
+            <field name="name">Stock situation in 2099 for testing the date constraint</field>
+            <field name="date">2099-01-01 12:05:27</field>
+            <field name="state">draft</field>
+        </record>
+        <record id="stock_inventory_line_0" model="stock.inventory.line">
+            <field name="inventory_id" ref="stock_inventory_physical0" />
+            <field name="product_id" ref="product.product_product_10" />
+            <field name="prod_lot_id" ref="lot_test0"/>
+            <field name="product_uom" ref="product.product_uom_unit" />
+            <field name="product_qty">10.0</field>
+            <field name="location_id" ref="stock.stock_location_components" />
+        </record>
+        <function model="stock.inventory" name="action_confirm">
+            <function
+                eval="[[('id', '=', ref('stock_inventory_physical0'))]]"
+                model="stock.inventory" name="search" />
+        </function>
+        <function model="stock.inventory" name="action_done">
+            <function
+                eval="[[('id', '=', ref('stock_inventory_physical0'))]]"
+                model="stock.inventory" name="search" />
+        </function>
+    </data>
+</openerp>

=== added directory 'stock_inventory_date_constraint/test'
=== added file 'stock_inventory_date_constraint/test/stock_inventory_date.yml'
--- stock_inventory_date_constraint/test/stock_inventory_date.yml	1970-01-01 00:00:00 +0000
+++ stock_inventory_date_constraint/test/stock_inventory_date.yml	2014-03-12 15:28:57 +0000
@@ -0,0 +1,133 @@
+-
+  In this file, I check that a stock move with an effective date before an inventory does not modify
+  the available quantity of a product.
+  First I'll check that the quantity in stock is 10.0 after the inventory
+-
+ !python {model: product.product}: |
+  ctx = {'location': [ref('stock.stock_location_components')], 'to_date': '2099-01-01 13:10:34'}
+  products = self.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)
+  assert products[0]['qty_available'] == 10.0, "Quantity is not equal to 10.0 before stock move : %s" % products[0]['qty_available']
+-
+  To check that inventories are not made wrong by stock moves dated ealier,
+  I'll try to create a stock move dated before the inventory, directly in 'done' state.
+  Either this must fail, or we must check that the quantity is still correct.
+-
+  !python {model: stock.move}: |
+    from osv import orm
+
+    try:
+      move_id = self.create(cr, uid, {'product_id': ref('product.product_product_10'),
+                                      'location_id': ref('stock.stock_location_14'),
+                                      'location_dest_id': ref('stock.stock_location_components'),
+                                      'product_qty': 17.0,
+                                      'prodlot_id': ref('lot_test0'),
+                                      'product_uom': ref('product.product_uom_unit'),
+                                      'name': 'Product of test',
+                                      'state': 'done'})
+      # I check the available quantity
+      product_obj = self.pool.get('product.product')
+      ctx = {'location': [ref('stock.stock_location_components')], 'to_date': '2099-01-01 15:10:34'}
+      products = product_obj.read(cr, uid, [ref('product.product_product_10')], ['qty_available'], context=ctx)
+      assert products[0]['qty_available'] == 10.0, "Quantity is not equal to 10 after stock move : %s" % products[0]['qty_available']
+    except orm.except_orm as e:
+      log("Good ! the stock move creation action has failed : %s" % e)
+-
+  To check that confirming a draft move before an inventory does not make the inventory wrong,
+  I create another Stock Move dated today, in 'draft' state.
+-
+  !record {model: stock.move, id: draft_stock_move}:
+    name: Test draft stock move before inventory date
+    location_id: stock.stock_location_components
+    location_dest_id: stock.stock_location_7
+    product_id: product.product_product_10
+    prodlot_id : lot_test0
+    product_qty: 3.0
+    product_uom: product.product_uom_unit
+    state: draft
+-
+  I confirm the Stock Move.
+-
+  !python {model: stock.move}: |
+    self.action_confirm(cr, uid, [ref('draft_stock_move')])
+-
+  I finish the Stock Move.
+  Either this must fail or we must check that the quantity is still correct.
+-
+  !python {model: stock.move}: |
+    from osv import orm
+    try:
+      self.action_done(cr, uid, [ref('draft_stock_move')])
+      # I check the available quantity
+      products = self.pool.get('product.product').read(
+        cr, uid, [ref('product.product_product_10')], ['qty_available'],
+        context={'location': [ref('stock.stock_location_components')],
+                 'to_date': '2099-01-01 15:10:34'})
+      assert products[0]['qty_available'] == 10.0, "Quantity is not equal to 10 after stock move : %s" % products[0]['qty_available']
+    except orm.except_orm as e:
+      log("Good ! The Stock Move could not be 'done' : %s" % e)
+-
+  To check that confirmed Stock Moves can be canceled even before the inventory,
+  I create yet another Stock Move dated today, in 'draft' state.
+-
+  !record {model: stock.move, id: draft_stock_move2}:
+    name: 2nd Test draft stock move before inventory date
+    location_id: stock.stock_location_components
+    location_dest_id: stock.stock_location_7
+    product_id: product.product_product_10
+    prodlot_id : lot_test0
+    product_qty: 3.0
+    product_uom: product.product_uom_unit
+    state: draft
+-
+  I confirm the Stock Move.
+-
+  !python {model: stock.move}: |
+    self.action_confirm(cr, uid, [ref('draft_stock_move2')])
+-
+  I cancel the Stock Move.
+-
+  !python {model: stock.move}: |
+    self.action_cancel(cr, uid, [ref('draft_stock_move2')])
+-
+  To check that stock moves are allowed after the inventory and not inhibited,
+  I create a Stock Move in 'done' state after the inventory.
+-
+  !record {model: stock.move, id: draft_stock_move}:
+    name: Test draft stock move before inventory date
+    date: '2099-06-02 09:53:28'
+    location_id: stock.stock_location_14
+    location_dest_id: stock.stock_location_components
+    product_id: product.product_product_10
+    prodlot_id : lot_test0
+    product_qty: 10.0
+    product_uom: product.product_uom_unit
+    state: done
+-
+  I check the quantity for product is equal to 20.0 after the inventory.
+-
+ !python {model: product.product}: |
+  products = self.read(cr, uid, [ref('product.product_product_10')],['qty_available'],
+                       context={'location': [ref('stock.stock_location_components')],
+                                'to_date': '2099-12-31 19:10:34'})
+  assert products[0]['qty_available'] == 20.0, "Quantity is not equal to 20.0 : %s" % products[0]['qty_available']
+-
+  I try to cancel the first inventory.
+  Either this must fail or we must check that the quantity is still correct.
+-
+  !python {model: stock.inventory}: |
+    from osv import orm
+    try:
+      self.action_cancel_inventory(cr, uid, [ref('stock_inventory_init')])
+      # I check the available quantity
+      products = self.pool.get('product.product').read(
+        cr, uid, [ref('product.product_product_10')], ['qty_available'],
+        context={'location': [ref('stock.stock_location_components')],
+                 'to_date': '2099-12-31 19:10:34'})
+      assert products[0]['qty_available'] == 20.0, "Quantity is not equal to 20 after canceling the inventory : %s" % products[0]['qty_available']
+    except orm.except_orm as e:
+      log("Good ! The Inventory could not be canceled : %s" % e)
+-
+  I check that the last inventory can be canceled
+-
+  !python {model: stock.inventory}: |
+    self.action_cancel_inventory(cr, uid, [ref('stock_inventory_physical0')])


Follow ups