[tryton-debian-vcs] tryton-modules-stock branch debian updated. debian/3.0.1-2-4-gfe1db50

Mathias Behrle tryton-debian-vcs at alioth.debian.org
Tue Apr 22 13:10:38 UTC 2014


The following commit has been merged in the debian branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/tryton-modules-stock.git;a=commitdiff;h=debian/3.0.1-2-4-gfe1db50

commit fe1db50d3a2ea393bf4ecae96714857ad3ab1c15
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Tue Apr 22 15:00:21 2014 +0200

    Bumping minimal required Python version to 2.7.

diff --git a/debian/control b/debian/control
index ba0053e..cdcf41b 100644
--- a/debian/control
+++ b/debian/control
@@ -9,7 +9,7 @@ Standards-Version: 3.9.5
 Homepage: http://www.tryton.org/
 Vcs-Browser: http://anonscm.debian.org/gitweb/?p=tryton/tryton-modules-stock.git
 Vcs-Git: git://anonscm.debian.org/tryton/tryton-modules-stock.git
-X-Python-Version: >= 2.6
+X-Python-Version: >= 2.7
 
 Package: tryton-modules-stock
 Architecture: all
commit bf33b1610793234ddae8267a7309c2bcf04dfec4
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Tue Apr 22 14:46:52 2014 +0200

    Updating copyright.

diff --git a/debian/copyright b/debian/copyright
index 79dc1e6..b9b85af 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -2,9 +2,9 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
 
 Files: *
 Copyright: 2012 Openlabs Technologies & Consulting (P) LTD
-           2008-2013 Cédric Krier
+           2008-2014 Cédric Krier
            2008-2013 Bertrand Chenal
-           2008-2013 B2CK SPRL
+           2008-2014 B2CK SPRL
            2004-2008 Tiny SPRL
 License: GPL-3+
 
commit cc234e8449583df9e07571f65cc4c354369f5369
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Tue Apr 22 14:23:40 2014 +0200

    Merging upstream version 3.2.0.

diff --git a/CHANGELOG b/CHANGELOG
index e2c3071..8f18144 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,13 @@
-Version 3.0.1 - 2014-01-18
+Version 3.2.0 - 2014-04-21
 * Bug fixes (see mercurial logs for details)
+* Add warning for moves without origin
+* Allow to define the effective date of shipments
+* Prevent changing product type if stock moves exist
+* Use new methods to compute quantities in StockMixin._search_quantity()
+* Split products_by_location into two methods on Move:
+  compute_quantities_query and compute_quantities
+* Use origin for inventory moves
+* Allow partial assignation on Supplier Shipment Return
 
 Version 3.0.0 - 2013-10-21
 * Bug fixes (see mercurial logs for details)
diff --git a/COPYRIGHT b/COPYRIGHT
index b9e8d97..06390fa 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -1,7 +1,7 @@
 Copyright (C) 2012 Openlabs Technologies & Consulting (P) LTD.
-Copyright (C) 2008-2013 Cédric Krier.
+Copyright (C) 2008-2014 Cédric Krier.
 Copyright (C) 2008-2013 Bertrand Chenal.
-Copyright (C) 2008-2013 B2CK SPRL.
+Copyright (C) 2008-2014 B2CK SPRL.
 Copyright (C) 2004-2008 Tiny SPRL.
 
 This program is free software: you can redistribute it and/or modify
diff --git a/INSTALL b/INSTALL
index 3902b76..1f11ac2 100644
--- a/INSTALL
+++ b/INSTALL
@@ -4,7 +4,7 @@ Installing trytond_stock
 Prerequisites
 -------------
 
- * Python 2.6 or later (http://www.python.org/)
+ * Python 2.7 or later (http://www.python.org/)
  * trytond (http://www.tryton.org/)
  * python-sql (http://code.google.com/p/python-sql/)
  * trytond_party (http://www.tryton.org/)
diff --git a/MANIFEST.in b/MANIFEST.in
index f040e5e..fb91042 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,6 +1,5 @@
 include INSTALL
 include README
-include TODO
 include COPYRIGHT
 include CHANGELOG
 include LICENSE
diff --git a/PKG-INFO b/PKG-INFO
index be9e89d..a395af4 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,12 +1,12 @@
 Metadata-Version: 1.1
 Name: trytond_stock
-Version: 3.0.1
+Version: 3.2.0
 Summary: Tryton module for stock and inventory
 Home-page: http://www.tryton.org/
 Author: Tryton
-Author-email: UNKNOWN
+Author-email: issue_tracker at tryton.org
 License: GPL-3
-Download-URL: http://downloads.tryton.org/3.0/
+Download-URL: http://downloads.tryton.org/3.2/
 Description: trytond_stock
         =============
         
@@ -43,6 +43,7 @@ Description: trytond_stock
         
           http://www.tryton.org/
         
+Keywords: tryton stock
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Environment :: Plugins
@@ -63,6 +64,5 @@ Classifier: Natural Language :: Russian
 Classifier: Natural Language :: Slovenian
 Classifier: Natural Language :: Spanish
 Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Topic :: Office/Business
diff --git a/inventory.py b/inventory.py
index b4c6218..3c515b3 100644
--- a/inventory.py
+++ b/inventory.py
@@ -21,14 +21,14 @@ class Inventory(Workflow, ModelSQL, ModelView):
         'stock.location', 'Location', required=True,
         domain=[('type', '=', 'storage')], states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('lines'))),
+                Bool(Eval('lines', [0]))),
             },
-        depends=['state', 'lines'])
+        depends=['state'])
     date = fields.Date('Date', required=True, states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('lines'))),
+                Bool(Eval('lines', [0]))),
             },
-        depends=['state', 'lines'])
+        depends=['state'])
     lost_found = fields.Many2One(
         'stock.location', 'Lost and Found', required=True,
         domain=[('type', '=', 'lost_found')], states=STATES, depends=DEPENDS)
@@ -38,9 +38,9 @@ class Inventory(Workflow, ModelSQL, ModelView):
     company = fields.Many2One('company.company', 'Company', required=True,
         states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('lines'))),
+                Bool(Eval('lines', [0]))),
             },
-        depends=['state', 'lines'])
+        depends=['state'])
     state = fields.Selection([
         ('draft', 'Draft'),
         ('done', 'Done'),
@@ -117,7 +117,7 @@ class Inventory(Workflow, ModelSQL, ModelView):
     @Workflow.transition('done')
     def confirm(self, inventories):
         Move = Pool().get('stock.move')
-        move_ids = []
+        moves = []
         for inventory in inventories:
             keys = set()
             for line in inventory.lines:
@@ -126,11 +126,12 @@ class Inventory(Workflow, ModelSQL, ModelView):
                     self.raise_user_error('unique_line',
                         (line.rec_name, inventory.rec_name))
                 keys.add(key)
-                move_id = line.create_move()
-                if move_id:
-                    move_ids.append(move_id)
-        if move_ids:
-            Move.do(Move.browse(move_ids))
+                move = line.get_move()
+                if move:
+                    moves.append(move)
+        if moves:
+            moves = Move.create([m._save_values for m in moves])
+            Move.do(moves)
 
     @classmethod
     @ModelView.button
@@ -158,7 +159,7 @@ class Inventory(Workflow, ModelSQL, ModelView):
             Line.copy(inventory.lines,
                 default={
                     'inventory': new_inventory.id,
-                    'move': None,
+                    'moves': None,
                     })
             cls.complete_lines([new_inventory])
             new_inventories.append(new_inventory)
@@ -230,8 +231,7 @@ class InventoryLine(ModelSQL, ModelView):
         domain=[
             ('type', '=', 'goods'),
             ('consumable', '=', False),
-            ],
-        on_change=['product'])
+            ])
     uom = fields.Function(fields.Many2One('product.uom', 'UOM'), 'get_uom')
     unit_digits = fields.Function(fields.Integer('Unit Digits'),
             'get_unit_digits')
@@ -240,7 +240,7 @@ class InventoryLine(ModelSQL, ModelView):
             depends=['unit_digits'])
     quantity = fields.Float('Quantity', required=True,
         digits=(16, Eval('unit_digits', 2)), depends=['unit_digits'])
-    move = fields.Many2One('stock.move', 'Move', readonly=True)
+    moves = fields.One2Many('stock.move', 'origin', 'Moves', readonly=True)
     inventory = fields.Many2One('stock.inventory', 'Inventory', required=True,
             ondelete='CASCADE')
 
@@ -257,6 +257,10 @@ class InventoryLine(ModelSQL, ModelView):
     def __register__(cls, module_name):
         TableHandler = backend.get('TableHandler')
         cursor = Transaction().cursor
+        pool = Pool()
+        Move = pool.get('stock.move')
+        sql_table = cls.__table__()
+        move_table = Move.__table__()
 
         super(InventoryLine, cls).__register__(module_name)
 
@@ -264,6 +268,17 @@ class InventoryLine(ModelSQL, ModelView):
         # Migration from 2.8: Remove constraint inventory_product_uniq
         table.drop_constraint('inventory_product_uniq')
 
+        # Migration from 3.0: use Move origin
+        if table.column_exist('move'):
+            cursor.execute(*sql_table.select(sql_table.id, sql_table.move,
+                    where=sql_table.move != None))
+            for line_id, move_id in cursor.fetchall():
+                cursor.execute(*move_table.update(
+                        columns=[move_table.origin],
+                        values=['%s,%s' % (cls.__name__, line_id)],
+                        where=move_table.id == move_id))
+            table.drop_column('move')
+
     @staticmethod
     def default_unit_digits():
         return 2
@@ -272,6 +287,7 @@ class InventoryLine(ModelSQL, ModelView):
     def default_expected_quantity():
         return 0.
 
+    @fields.depends('product')
     def on_change_product(self):
         change = {}
         change['unit_digits'] = 2
@@ -297,13 +313,11 @@ class InventoryLine(ModelSQL, ModelView):
     @classmethod
     def cancel_move(cls, lines):
         Move = Pool().get('stock.move')
-        Move.cancel([l.move for l in lines if l.move])
-        Move.delete([l.move for l in lines if l.move])
-        cls.write([l for l in lines if l.move], {
-            'move': None,
-            })
+        moves = [m for l in lines for m in l.moves if l.moves]
+        Move.cancel(moves)
+        Move.delete(moves)
 
-    def _get_move(self):
+    def get_move(self):
         '''
         Return Move instance for the inventory line
         '''
@@ -322,7 +336,7 @@ class InventoryLine(ModelSQL, ModelView):
             (from_location, to_location, delta_qty) = \
                 (to_location, from_location, -delta_qty)
 
-        move = Move(
+        return Move(
             from_location=from_location,
             to_location=to_location,
             quantity=delta_qty,
@@ -330,16 +344,8 @@ class InventoryLine(ModelSQL, ModelView):
             uom=self.uom,
             company=self.inventory.company,
             effective_date=self.inventory.date,
+            origin=self,
             )
-        return move
-
-    def create_move(self):
-        '''
-        Create move for an inventory line and return id
-        '''
-        self.move = self._get_move()
-        self.save()
-        return self.move.id if self.move else None
 
     def update_values4complete(self, quantity, uom_id):
         '''
diff --git a/locale/bg_BG.po b/locale/bg_BG.po
index 1fe13bc..c63aaef 100644
--- a/locale/bg_BG.po
+++ b/locale/bg_BG.po
@@ -10,6 +10,11 @@ msgstr ""
 "Не може да променяте мер. ед. на продукт който е свързан с движения на "
 "наличност."
 
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr ""
+
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
 msgstr ""
@@ -45,6 +50,10 @@ msgid "Source and destination location must be different"
 msgstr "Източника и целта на местонахождението трябва да са различни"
 
 msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr ""
+
+msgctxt "error:stock.move:"
 msgid ""
 "You can not delete stock move \"%s\" because it is not in draft or cancelled"
 " state."
@@ -279,9 +288,10 @@ msgctxt "field:stock.inventory.line,inventory:"
 msgid "Inventory"
 msgstr "Инвентаризация"
 
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
-msgstr "Движение"
+#, fuzzy
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
+msgstr "Движения"
 
 msgctxt "field:stock.inventory.line,product:"
 msgid "Product"
@@ -1981,6 +1991,11 @@ msgctxt "view:product.by_location.start:"
 msgid "Product by Location"
 msgstr "Продукт по местонахождение"
 
+#, fuzzy
+msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr "Себестойност"
+
 msgctxt "view:product.product:"
 msgid "Products"
 msgstr "Продукти"
@@ -2177,9 +2192,10 @@ msgctxt "view:stock.shipment.internal:"
 msgid "Internal Shipments"
 msgstr "Вътрешни пратки"
 
+#, fuzzy
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
-msgstr "Изчакващ"
+msgid "Wait"
+msgstr "Очаквано"
 
 msgctxt "view:stock.shipment.out.assign.failed:"
 msgid "Unable to Assign"
@@ -2257,9 +2273,10 @@ msgctxt "view:stock.shipment.out:"
 msgid "Outgoing Moves"
 msgstr "Изходящи движения"
 
+#, fuzzy
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
-msgstr "Изчакващ"
+msgid "Wait"
+msgstr "Очаквано"
 
 msgctxt "wizard_button:product.by_location,start,end:"
 msgid "Cancel"
diff --git a/locale/ca_ES.po b/locale/ca_ES.po
index 856c644..7d47685 100644
--- a/locale/ca_ES.po
+++ b/locale/ca_ES.po
@@ -8,7 +8,14 @@ msgid ""
 " moves."
 msgstr ""
 "No podeu canviar la UdM predeterminada d'un producte que està associat amb "
-"moviments d'existències."
+"moviments d'estoc."
+
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr ""
+"No podeu canviar el tipus d'un producte que està associat amb moviments "
+"d'estoc."
 
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
@@ -33,7 +40,7 @@ msgid ""
 "Location \"%s\" with existing moves cannot be changed to a type that does "
 "not support moves."
 msgstr ""
-"No es pot canviar la ubicació \"%s\" amb moviments existens a un tipus que "
+"No es pot canviar la ubicació \"%s\" amb moviments existents a un tipus que "
 "no suporta moviments."
 
 msgctxt "error:stock.move:"
@@ -49,6 +56,10 @@ msgid "Source and destination location must be different"
 msgstr "Les ubicacions origen i destí han de ser diferents."
 
 msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr "El moviment d'estoc \"%s\" no té origen."
+
+msgctxt "error:stock.move:"
 msgid ""
 "You can not delete stock move \"%s\" because it is not in draft or cancelled"
 " state."
@@ -66,7 +77,7 @@ msgctxt "error:stock.move:"
 msgid "You can not modify stock move \"%s\" because it is in \"Assigned\" state."
 msgstr ""
 "No podeu canviar el moviment d'estoc \"%s\" perquè es troba en l'estat "
-"\"Assignat\"."
+"\"Reservat\"."
 
 msgctxt "error:stock.move:"
 msgid ""
@@ -78,7 +89,7 @@ msgstr ""
 
 msgctxt "error:stock.move:"
 msgid "You can not set stock move \"%s\" to assigned state."
-msgstr "No podeu canviar el moviment d'estoc \"%s\" a estat assignat."
+msgstr "No podeu canviar el moviment d'estoc \"%s\" a estat reservat."
 
 msgctxt "error:stock.move:"
 msgid "You can not set stock move \"%s\" to done state."
@@ -90,11 +101,11 @@ msgstr "No podeu canviar el moviment d'estoc \"%s\" a estat esborrany."
 
 msgctxt "error:stock.period:"
 msgid "You can not close a period in the future or today."
-msgstr "No es pot tancar un període d'avui o proximament."
+msgstr "No es pot tancar un període actual o futur."
 
 msgctxt "error:stock.period:"
 msgid "You can not close a period when there still are assigned moves."
-msgstr "No podeu tancar un període que encara te moviments assignats."
+msgstr "No podeu tancar un període que encara té moviments reservats."
 
 msgctxt "error:stock.shipment.in.return:"
 msgid "Supplier Return Shipment \"%s\" must be cancelled before deletion."
@@ -114,8 +125,8 @@ msgctxt "error:stock.shipment.in:"
 msgid ""
 "Inventory Moves must have the warehouse input location as source location."
 msgstr ""
-"Els moviments d'inventari han de tenir la ubicació d'entrada del magatzem "
-"com a ubicació origen."
+"Els moviments interns han de tenir la ubicació d'entrada del magatzem com a "
+"ubicació origen."
 
 msgctxt "error:stock.shipment.in:"
 msgid "Supplier Shipment \"%s\" must be cancelled before deletion."
@@ -299,9 +310,9 @@ msgctxt "field:stock.inventory.line,inventory:"
 msgid "Inventory"
 msgstr "Inventari"
 
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
-msgstr "Moviment"
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
+msgstr "Moviments"
 
 msgctxt "field:stock.inventory.line,product:"
 msgid "Product"
@@ -673,7 +684,7 @@ msgstr "Moviments d'entrada"
 
 msgctxt "field:stock.shipment.in,inventory_moves:"
 msgid "Inventory Moves"
-msgstr "Moviments d'inventari"
+msgstr "Moviments interns"
 
 msgctxt "field:stock.shipment.in,moves:"
 msgid "Moves"
@@ -905,7 +916,7 @@ msgstr "ID"
 
 msgctxt "field:stock.shipment.out,inventory_moves:"
 msgid "Inventory Moves"
-msgstr "Moviments d'inventari"
+msgstr "Moviments interns"
 
 msgctxt "field:stock.shipment.out,moves:"
 msgid "Moves"
@@ -961,7 +972,7 @@ msgstr "ID"
 
 msgctxt "field:stock.shipment.out.assign.failed,inventory_moves:"
 msgid "Inventory Moves"
-msgstr "Moviments d'inventari"
+msgstr "Moviments interns"
 
 msgctxt "field:stock.shipment.out.return,code:"
 msgid "Code"
@@ -1005,7 +1016,7 @@ msgstr "Moviments d'entrada"
 
 msgctxt "field:stock.shipment.out.return,inventory_moves:"
 msgid "Inventory Moves"
-msgstr "Moviments d'inventari"
+msgstr "Moviments interns"
 
 msgctxt "field:stock.shipment.out.return,moves:"
 msgid "Moves"
@@ -1151,7 +1162,7 @@ msgstr "Crea albarà de devolució"
 
 msgctxt "model:ir.action,name:report_shipment_in_restocking_list"
 msgid "Restocking List"
-msgstr "Llista de renovació d'inventari"
+msgstr "Llista de proveïment"
 
 msgctxt "model:ir.action,name:report_shipment_internal"
 msgid "Internal Shipment"
@@ -1159,7 +1170,7 @@ msgstr "Albarans interns"
 
 msgctxt "model:ir.action,name:report_shipment_out_delivery_note"
 msgid "Delivery Note"
-msgstr "Nota enviament"
+msgstr "Nota d'enviament"
 
 msgctxt "model:ir.action,name:report_shipment_out_picking_list"
 msgid "Picking List"
@@ -1167,7 +1178,7 @@ msgstr "Llista de selecció"
 
 msgctxt "model:ir.action,name:report_shipment_out_return_restocking_list"
 msgid "Restocking List"
-msgstr "Llista de renovació d'inventari"
+msgstr "Llista de proveïment"
 
 msgctxt "model:ir.action,name:wizard_product_by_location"
 msgid "Product by Locations"
@@ -1183,15 +1194,15 @@ msgstr "Productes per ubicació"
 
 msgctxt "model:ir.action,name:wizard_shipment_in_return_assign"
 msgid "Assign Purchase Return Shipment"
-msgstr "Assignar albarà de devolució de compra"
+msgstr "Reserva albarà de devolució de compra"
 
 msgctxt "model:ir.action,name:wizard_shipment_internal_assign"
 msgid "Assign Shipment Internal"
-msgstr "Assignar albarà intern"
+msgstr "Reserva albarà intern"
 
 msgctxt "model:ir.action,name:wizard_shipment_out_assign"
 msgid "Assign Shipment Out"
-msgstr "Assignació d'albarà de sortida"
+msgstr "Reserva albarà de sortida"
 
 msgctxt "model:ir.action.act_window.domain,name:act_inventory_form_domain_all"
 msgid "All"
@@ -1244,7 +1255,7 @@ msgstr "Tots"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_in_return_form_domain_assigned"
 msgid "Assigned"
-msgstr "Assignat"
+msgstr "Reservat"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_in_return_form_domain_draft"
@@ -1254,7 +1265,7 @@ msgstr "Esborrany"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_in_return_form_domain_waiting"
 msgid "Waiting"
-msgstr "Esperant"
+msgstr "En espera"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_internal_form_domain_all"
@@ -1264,7 +1275,7 @@ msgstr "Tots"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_internal_form_domain_assigned"
 msgid "Assigned"
-msgstr "Assignat"
+msgstr "Reservat"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_internal_form_domain_draft"
@@ -1274,7 +1285,7 @@ msgstr "Esborrany"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_internal_form_domain_waiting"
 msgid "Waiting"
-msgstr "Esperant"
+msgstr "En espera"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_form_domain_all"
@@ -1284,7 +1295,7 @@ msgstr "Tots"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_form_domain_assigned"
 msgid "Assigned"
-msgstr "Assignat"
+msgstr "Reservat"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_form_domain_draft"
@@ -1299,7 +1310,7 @@ msgstr "Empaquetat"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_form_domain_waiting"
 msgid "Waiting"
-msgstr "Esperant"
+msgstr "En espera"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_return_form_domain_all"
@@ -1426,7 +1437,7 @@ msgstr "Administració de logística"
 
 msgctxt "model:res.group,name:group_stock_force_assignment"
 msgid "Stock Force Assignment"
-msgstr "Forçar reserves en la logística"
+msgstr "Forçar reserves a logística"
 
 msgctxt "model:stock.configuration,name:"
 msgid "Stock Configuration"
@@ -1434,15 +1445,15 @@ msgstr "Configuració d'estoc"
 
 msgctxt "model:stock.inventory,name:"
 msgid "Stock Inventory"
-msgstr "Inventari d'existència"
+msgstr "Inventari d'estoc"
 
 msgctxt "model:stock.inventory.line,name:"
 msgid "Stock Inventory Line"
-msgstr "Línia d'existència en inventari "
+msgstr "Línia d'inventari d'estoc"
 
 msgctxt "model:stock.location,name:"
 msgid "Stock Location"
-msgstr "Ubicació d'existència"
+msgstr "Ubicació d'estoc"
 
 msgctxt "model:stock.location,name:location_customer"
 msgid "Customer"
@@ -1474,7 +1485,7 @@ msgstr "Magatzem"
 
 msgctxt "model:stock.move,name:"
 msgid "Stock Move"
-msgstr "Moviment d'existències"
+msgstr "Moviment d'estoc"
 
 msgctxt "model:stock.period,name:"
 msgid "Stock Period"
@@ -1482,7 +1493,7 @@ msgstr "Període d'estoc"
 
 msgctxt "model:stock.period.cache,name:"
 msgid "Stock Period Cache"
-msgstr "Període estoc precalculat"
+msgstr "Període d'estoc precalculat"
 
 msgctxt "model:stock.product_quantities_warehouse,name:"
 msgid "Product Quantities By Warehouse"
@@ -1506,7 +1517,7 @@ msgstr "Albarà devolució proveïdor"
 
 msgctxt "model:stock.shipment.in.return.assign.failed,name:"
 msgid "Assign Supplier Return Shipment"
-msgstr "Reservar albarans de devolució de proveïdor"
+msgstr "Reserva albarà de devolució de proveïdor"
 
 msgctxt "model:stock.shipment.internal,name:"
 msgid "Internal Shipment"
@@ -1514,7 +1525,7 @@ msgstr "Albarà intern"
 
 msgctxt "model:stock.shipment.internal.assign.failed,name:"
 msgid "Assign Shipment Internal"
-msgstr "Assignar albarà intern"
+msgstr "Reserva albarà intern"
 
 msgctxt "model:stock.shipment.out,name:"
 msgid "Customer Shipment"
@@ -1522,7 +1533,7 @@ msgstr "Albarà client"
 
 msgctxt "model:stock.shipment.out.assign.failed,name:"
 msgid "Assign Shipment Out"
-msgstr "Assignació d'albarà de sortida"
+msgstr "Reserva albarà de sortida"
 
 msgctxt "model:stock.shipment.out.return,name:"
 msgid "Customer Return Shipment"
@@ -1566,7 +1577,7 @@ msgstr "Referència:"
 
 msgctxt "odt:stock.shipment.in.restocking_list:"
 msgid "Restocking List"
-msgstr "Llista de renovació d'existències"
+msgstr "Llista de proveïment"
 
 msgctxt "odt:stock.shipment.in.restocking_list:"
 msgid "Supplier:"
@@ -1654,7 +1665,7 @@ msgstr "Data:"
 
 msgctxt "odt:stock.shipment.out.delivery_note:"
 msgid "Delivery Note"
-msgstr "Nota enviament"
+msgstr "Nota d'enviament"
 
 msgctxt "odt:stock.shipment.out.delivery_note:"
 msgid "E-Mail:"
@@ -1786,7 +1797,7 @@ msgstr "Referència:"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid "Restocking List"
-msgstr "Llista de renovació d'existències"
+msgstr "Llista de proveïment"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid "To Location"
@@ -1842,7 +1853,7 @@ msgstr "Magatzem"
 
 msgctxt "selection:stock.move,state:"
 msgid "Assigned"
-msgstr "Assignat"
+msgstr "Reservat"
 
 msgctxt "selection:stock.move,state:"
 msgid "Canceled"
@@ -1882,7 +1893,7 @@ msgstr "Rebut"
 
 msgctxt "selection:stock.shipment.in.return,state:"
 msgid "Assigned"
-msgstr "Assignat"
+msgstr "Reservat"
 
 msgctxt "selection:stock.shipment.in.return,state:"
 msgid "Canceled"
@@ -1902,7 +1913,7 @@ msgstr "En espera"
 
 msgctxt "selection:stock.shipment.internal,state:"
 msgid "Assigned"
-msgstr "Assignat"
+msgstr "Reservat"
 
 msgctxt "selection:stock.shipment.internal,state:"
 msgid "Canceled"
@@ -1922,7 +1933,7 @@ msgstr "En espera"
 
 msgctxt "selection:stock.shipment.out,state:"
 msgid "Assigned"
-msgstr "Assignat"
+msgstr "Reservat"
 
 msgctxt "selection:stock.shipment.out,state:"
 msgid "Canceled"
@@ -1962,13 +1973,17 @@ msgstr "Rebut"
 
 msgctxt "view:party.party:"
 msgid "Stock"
-msgstr "Existències"
+msgstr "Logística"
 
 msgctxt "view:product.by_location.start:"
 msgid "Product by Location"
 msgstr "Producte per ubicació"
 
 msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr "Valor de cost"
+
+msgctxt "view:product.product:"
 msgid "Products"
 msgstr "Productes"
 
@@ -2066,7 +2081,7 @@ msgstr "Productes per ubicació"
 
 msgctxt "view:stock.shipment.in.return.assign.failed:"
 msgid "Unable to Assign"
-msgstr "No es poden reservar aquests productes:"
+msgstr "No es pot reservar"
 
 msgctxt "view:stock.shipment.in.return.assign.failed:"
 msgid "Unable to assign those products:"
@@ -2074,7 +2089,7 @@ msgstr "No es poden reservar aquests productes:"
 
 msgctxt "view:stock.shipment.in.return:"
 msgid "Assign"
-msgstr "Assigna"
+msgstr "Reserva"
 
 msgctxt "view:stock.shipment.in.return:"
 msgid "Cancel"
@@ -2114,7 +2129,7 @@ msgstr "Moviments d'entrada"
 
 msgctxt "view:stock.shipment.in:"
 msgid "Inventory Moves"
-msgstr "Moviments d'inventari"
+msgstr "Moviments interns"
 
 msgctxt "view:stock.shipment.in:"
 msgid "Receive"
@@ -2134,7 +2149,7 @@ msgstr "Albarans proveïdors"
 
 msgctxt "view:stock.shipment.internal.assign.failed:"
 msgid "Unable to Assign"
-msgstr "No es poden reservar aquests productes:"
+msgstr "No es pot reservar"
 
 msgctxt "view:stock.shipment.internal.assign.failed:"
 msgid "Unable to assign those products:"
@@ -2142,7 +2157,7 @@ msgstr "No es poden reservar aquests productes:"
 
 msgctxt "view:stock.shipment.internal:"
 msgid "Assign"
-msgstr "Assigna"
+msgstr "Reserva"
 
 msgctxt "view:stock.shipment.internal:"
 msgid "Cancel"
@@ -2165,12 +2180,12 @@ msgid "Internal Shipments"
 msgstr "Albarans interns"
 
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
+msgid "Wait"
 msgstr "En espera"
 
 msgctxt "view:stock.shipment.out.assign.failed:"
 msgid "Unable to Assign"
-msgstr "No es poden reservar aquests productes:"
+msgstr "No es pot reservar"
 
 msgctxt "view:stock.shipment.out.assign.failed:"
 msgid "Unable to assign those products:"
@@ -2202,7 +2217,7 @@ msgstr "Moviments d'entrada"
 
 msgctxt "view:stock.shipment.out.return:"
 msgid "Inventory Moves"
-msgstr "Moviments d'inventari"
+msgstr "Moviments interns"
 
 msgctxt "view:stock.shipment.out.return:"
 msgid "Received"
@@ -2210,7 +2225,7 @@ msgstr "Rebut"
 
 msgctxt "view:stock.shipment.out:"
 msgid "Assign"
-msgstr "Assigna"
+msgstr "Reserva"
 
 msgctxt "view:stock.shipment.out:"
 msgid "Cancel"
@@ -2234,18 +2249,18 @@ msgstr "Esborrany"
 
 msgctxt "view:stock.shipment.out:"
 msgid "Inventory Moves"
-msgstr "Moviments d'inventari"
+msgstr "Moviments interns"
 
 msgctxt "view:stock.shipment.out:"
 msgid "Make shipment"
-msgstr "Realiza enviament"
+msgstr "Realitza enviament"
 
 msgctxt "view:stock.shipment.out:"
 msgid "Outgoing Moves"
 msgstr "Moviments de sortida"
 
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
+msgid "Wait"
 msgstr "En espera"
 
 msgctxt "wizard_button:product.by_location,start,end:"
@@ -2286,7 +2301,7 @@ msgstr "Accepta"
 
 msgctxt "wizard_button:stock.shipment.internal.assign,failed,force:"
 msgid "Force Assign"
-msgstr "Forçar reserva"
+msgstr "Força reserva"
 
 msgctxt "wizard_button:stock.shipment.out.assign,failed,end:"
 msgid "Ok"
diff --git a/locale/cs_CZ.po b/locale/cs_CZ.po
index 4e74862..67511c8 100644
--- a/locale/cs_CZ.po
+++ b/locale/cs_CZ.po
@@ -8,6 +8,11 @@ msgid ""
 " moves."
 msgstr ""
 
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr ""
+
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
 msgstr ""
@@ -43,6 +48,10 @@ msgid "Source and destination location must be different"
 msgstr ""
 
 msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr ""
+
+msgctxt "error:stock.move:"
 msgid ""
 "You can not delete stock move \"%s\" because it is not in draft or cancelled"
 " state."
@@ -277,8 +286,8 @@ msgctxt "field:stock.inventory.line,inventory:"
 msgid "Inventory"
 msgstr ""
 
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
 msgstr ""
 
 msgctxt "field:stock.inventory.line,product:"
@@ -1939,6 +1948,10 @@ msgid "Product by Location"
 msgstr ""
 
 msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr ""
+
+msgctxt "view:product.product:"
 msgid "Products"
 msgstr ""
 
@@ -2135,7 +2148,7 @@ msgid "Internal Shipments"
 msgstr ""
 
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
+msgid "Wait"
 msgstr ""
 
 msgctxt "view:stock.shipment.out.assign.failed:"
@@ -2215,7 +2228,7 @@ msgid "Outgoing Moves"
 msgstr ""
 
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
+msgid "Wait"
 msgstr ""
 
 msgctxt "wizard_button:product.by_location,start,end:"
diff --git a/locale/de_DE.po b/locale/de_DE.po
index 1ddfd41..8618ca7 100644
--- a/locale/de_DE.po
+++ b/locale/de_DE.po
@@ -10,6 +10,13 @@ msgstr ""
 "Die Standardmaßeinheit kann nicht für einen Artikel geändert werden, der "
 "Lagerbewegungen zugeordnet ist."
 
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr ""
+"Für Artikel, die Lagerbewegungen zugeordnet sind, kann der Typ nicht "
+"geändert werden."
+
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
 msgstr "Anzahl auf der Zeile muss einen positiven Wert aufweisen."
@@ -51,6 +58,10 @@ msgid "Source and destination location must be different"
 msgstr "Herkunfts- und Bestimmungsort müssen unterschiedlich sein"
 
 msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr "Die Lagerbewegung \"%s\" hat keinen Ursprung."
+
+msgctxt "error:stock.move:"
 msgid ""
 "You can not delete stock move \"%s\" because it is not in draft or cancelled"
 " state."
@@ -311,9 +322,9 @@ msgctxt "field:stock.inventory.line,inventory:"
 msgid "Inventory"
 msgstr "Lagerbestand"
 
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
-msgstr "Bewegung"
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
+msgstr "Lagerbewegungen"
 
 msgctxt "field:stock.inventory.line,product:"
 msgid "Product"
@@ -329,7 +340,7 @@ msgstr "Name"
 
 msgctxt "field:stock.inventory.line,unit_digits:"
 msgid "Unit Digits"
-msgstr "Anzahl Stellen"
+msgstr "Nachkommastellen"
 
 msgctxt "field:stock.inventory.line,uom:"
 msgid "UOM"
@@ -433,7 +444,7 @@ msgstr "Unternehmen"
 
 msgctxt "field:stock.move,cost_price:"
 msgid "Cost Price"
-msgstr "Einkaufspreis"
+msgstr "Kostenpreis"
 
 msgctxt "field:stock.move,create_date:"
 msgid "Create Date"
@@ -501,7 +512,7 @@ msgstr "Zu Lagerort"
 
 msgctxt "field:stock.move,unit_digits:"
 msgid "Unit Digits"
-msgstr "Anzahl Stellen"
+msgstr "Nachkommastellen"
 
 msgctxt "field:stock.move,unit_price:"
 msgid "Unit Price"
@@ -1097,15 +1108,15 @@ msgstr "Bestandskorrekturen"
 
 msgctxt "model:ir.action,name:act_location_form"
 msgid "Locations"
-msgstr "Orte"
+msgstr "Lagerorte"
 
 msgctxt "model:ir.action,name:act_location_quantity_tree"
 msgid "Locations"
-msgstr "Orte"
+msgstr "Lagerorte"
 
 msgctxt "model:ir.action,name:act_location_tree"
 msgid "Locations"
-msgstr "Orte"
+msgstr "Lagerorte"
 
 msgctxt "model:ir.action,name:act_move_form"
 msgid "Moves"
@@ -1448,7 +1459,7 @@ msgstr "Lager Lagerbestand"
 
 msgctxt "model:stock.inventory.line,name:"
 msgid "Stock Inventory Line"
-msgstr "Lager Lagerbestand Position"
+msgstr "Lagerbestandsposition"
 
 msgctxt "model:stock.location,name:"
 msgid "Stock Location"
@@ -1979,6 +1990,10 @@ msgid "Product by Location"
 msgstr "Artikel nach Lagerort"
 
 msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr "Kosten"
+
+msgctxt "view:product.product:"
 msgid "Products"
 msgstr "Artikel"
 
@@ -2175,8 +2190,8 @@ msgid "Internal Shipments"
 msgstr "Interne Lieferposten"
 
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
-msgstr "Wartend"
+msgid "Wait"
+msgstr "Warten"
 
 msgctxt "view:stock.shipment.out.assign.failed:"
 msgid "Unable to Assign"
@@ -2255,8 +2270,8 @@ msgid "Outgoing Moves"
 msgstr "Ausgehende Bewegungen"
 
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
-msgstr "Wartend"
+msgid "Wait"
+msgstr "Warten"
 
 msgctxt "wizard_button:product.by_location,start,end:"
 msgid "Cancel"
diff --git a/locale/es_AR.po b/locale/es_AR.po
index d3b2834..63029e0 100644
--- a/locale/es_AR.po
+++ b/locale/es_AR.po
@@ -10,6 +10,13 @@ msgstr ""
 "No puede cambiar la UdM por defecto de un producto que está asociado con "
 "movimientos de existencias."
 
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr ""
+"No puede cambiar el tipo de un producto que está asociado con movimientos de"
+" existencias."
+
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
 msgstr "La cantidad de la línea debe ser positiva."
@@ -49,6 +56,10 @@ msgid "Source and destination location must be different"
 msgstr "Las ubicaciones origen y destino deben ser distintas."
 
 msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr "El movimiento de existencias «%s» no tiene origen."
+
+msgctxt "error:stock.move:"
 msgid ""
 "You can not delete stock move \"%s\" because it is not in draft or cancelled"
 " state."
@@ -298,9 +309,9 @@ msgctxt "field:stock.inventory.line,inventory:"
 msgid "Inventory"
 msgstr "Inventario"
 
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
-msgstr "Movimiento"
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
+msgstr "Movimientos"
 
 msgctxt "field:stock.inventory.line,product:"
 msgid "Product"
@@ -1969,6 +1980,10 @@ msgid "Product by Location"
 msgstr "Producto por ubicación"
 
 msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr "Valor de costo"
+
+msgctxt "view:product.product:"
 msgid "Products"
 msgstr "Productos"
 
@@ -2165,8 +2180,8 @@ msgid "Internal Shipments"
 msgstr "Remitos internos"
 
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
-msgstr "En espera"
+msgid "Wait"
+msgstr "Espera"
 
 msgctxt "view:stock.shipment.out.assign.failed:"
 msgid "Unable to Assign"
@@ -2245,8 +2260,8 @@ msgid "Outgoing Moves"
 msgstr "Movimientos de salida"
 
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
-msgstr "En espera"
+msgid "Wait"
+msgstr "Espera"
 
 msgctxt "wizard_button:product.by_location,start,end:"
 msgid "Cancel"
diff --git a/locale/es_CO.po b/locale/es_CO.po
index 4a4e37e..42a21e9 100644
--- a/locale/es_CO.po
+++ b/locale/es_CO.po
@@ -10,6 +10,13 @@ msgstr ""
 "No puede cambiar la UdM predeterminada de un producto que está asociado con "
 "movimientos de existencias."
 
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr ""
+"No puede cambiar el tipo de un producto el cual esta asociado a movimientos "
+"de inventarios."
+
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
 msgstr "La cantidad en la línea debe ser positiva."
@@ -25,7 +32,7 @@ msgstr "La línea \"%s\" no es única en el Inventario \"%s\"."
 msgctxt "error:stock.location:"
 msgid "Location \"%(location)s\" must be a child of warehouse \"%(warehouse)s\"."
 msgstr ""
-"La(s) bodega(s) \"%(locations)s\" deben ser hijas del almacén  "
+"La(s) bodega(s) \"%(location)s\" deben ser hijas del almacén  "
 "\"%(warehouse)s\"."
 
 msgctxt "error:stock.location:"
@@ -33,8 +40,8 @@ msgid ""
 "Location \"%s\" with existing moves cannot be changed to a type that does "
 "not support moves."
 msgstr ""
-"Bodega(s) \"%s\" con movimientos realizados no pueden ser cambiadas a un "
-"tipo no soportado."
+"Bodega \"%s\" tiene movimientos y no pueden ser cambiada a un tipo que no "
+"soporta movimientos. "
 
 msgctxt "error:stock.move:"
 msgid "Internal move quantity must be positive"
@@ -49,18 +56,22 @@ msgid "Source and destination location must be different"
 msgstr "Las bodegas origen y destino deben ser distintas"
 
 msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr "El movimiento de invetario \"%s\" no tiene origen."
+
+msgctxt "error:stock.move:"
 msgid ""
 "You can not delete stock move \"%s\" because it is not in draft or cancelled"
 " state."
 msgstr ""
-"No puede borrar movimientos de inventarios \"%s\" porque no estan en estado "
-"borrador o cancelados."
+"No puede borrar los movimientos de inventarios \"%s\" porque no estan en "
+"borrador o anulados."
 
 msgctxt "error:stock.move:"
 msgid "You can not modify move \"%(move)s\" because period \"%(period)s\" is closed."
 msgstr ""
-"Usted no puede modificar movimientos \"%(move)s\" porque el periodo "
-"\"%(period)s\" esta cerrado."
+"No puede modificar movimientos \"%(move)s\" porque el periodo \"%(period)s\""
+" esta cerrado."
 
 msgctxt "error:stock.move:"
 msgid "You can not modify stock move \"%s\" because it is in \"Assigned\" state."
@@ -78,7 +89,7 @@ msgstr ""
 
 msgctxt "error:stock.move:"
 msgid "You can not set stock move \"%s\" to assigned state."
-msgstr "No puede establecer el movimiento de inventarios \"%s\" como asignado."
+msgstr "No puede devolver el movimiento de inventarios \"%s\" como asignado."
 
 msgctxt "error:stock.move:"
 msgid "You can not set stock move \"%s\" to done state."
@@ -90,11 +101,12 @@ msgstr "No puede establecer el movimiento de inventarios \"%s\" como borrador."
 
 msgctxt "error:stock.period:"
 msgid "You can not close a period in the future or today."
-msgstr "No se puede cerrar un período en el futuro o hoy mismo!"
+msgstr "No se puede cerrar un período de hoy o del futuro."
 
 msgctxt "error:stock.period:"
 msgid "You can not close a period when there still are assigned moves."
-msgstr "No se puede cerrar un período cuando existen movimientos asignados!"
+msgstr ""
+"No se puede cerrar un período cuando todavía hay movimientos asignados."
 
 msgctxt "error:stock.shipment.in.return:"
 msgid "Supplier Return Shipment \"%s\" must be cancelled before deletion."
@@ -299,9 +311,9 @@ msgctxt "field:stock.inventory.line,inventory:"
 msgid "Inventory"
 msgstr "Inventario"
 
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
-msgstr "Movimiento"
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
+msgstr "Movimientos"
 
 msgctxt "field:stock.inventory.line,product:"
 msgid "Product"
@@ -321,7 +333,7 @@ msgstr "Decimales de Unidad"
 
 msgctxt "field:stock.inventory.line,uom:"
 msgid "UOM"
-msgstr "UdM"
+msgstr "UDM"
 
 msgctxt "field:stock.inventory.line,write_date:"
 msgid "Write Date"
@@ -421,7 +433,7 @@ msgstr "Compañia"
 
 msgctxt "field:stock.move,cost_price:"
 msgid "Cost Price"
-msgstr "Precio de Costo"
+msgstr "Costo"
 
 msgctxt "field:stock.move,create_date:"
 msgid "Create Date"
@@ -709,7 +721,7 @@ msgstr "Bodega del Proveedor"
 
 msgctxt "field:stock.shipment.in,warehouse:"
 msgid "Warehouse"
-msgstr "Almacén"
+msgstr "Depósito"
 
 msgctxt "field:stock.shipment.in,warehouse_input:"
 msgid "Warehouse Input"
@@ -833,7 +845,7 @@ msgstr "Movimientos"
 
 msgctxt "field:stock.shipment.internal,planned_date:"
 msgid "Planned Date"
-msgstr "Fecha estimada"
+msgstr "Fecha Planeada"
 
 msgctxt "field:stock.shipment.internal,rec_name:"
 msgid "Name"
@@ -905,7 +917,7 @@ msgstr "ID"
 
 msgctxt "field:stock.shipment.out,inventory_moves:"
 msgid "Inventory Moves"
-msgstr "Movimientos de inventario"
+msgstr "Movimientos de Inventario"
 
 msgctxt "field:stock.shipment.out,moves:"
 msgid "Moves"
@@ -921,7 +933,7 @@ msgstr "Movimientos de Salida"
 
 msgctxt "field:stock.shipment.out,planned_date:"
 msgid "Planned Date"
-msgstr "Fecha estimada"
+msgstr "Fecha Planeada"
 
 msgctxt "field:stock.shipment.out,rec_name:"
 msgid "Name"
@@ -937,7 +949,7 @@ msgstr "Estado"
 
 msgctxt "field:stock.shipment.out,warehouse:"
 msgid "Warehouse"
-msgstr "Almacén"
+msgstr "Depósito"
 
 msgctxt "field:stock.shipment.out,warehouse_output:"
 msgid "Warehouse Output"
@@ -1005,7 +1017,7 @@ msgstr "Movimientos de Entrada"
 
 msgctxt "field:stock.shipment.out.return,inventory_moves:"
 msgid "Inventory Moves"
-msgstr "Movimientos de inventario"
+msgstr "Movimientos de Inventario"
 
 msgctxt "field:stock.shipment.out.return,moves:"
 msgid "Moves"
@@ -1017,7 +1029,7 @@ msgstr "Orígenes"
 
 msgctxt "field:stock.shipment.out.return,planned_date:"
 msgid "Planned Date"
-msgstr "Fecha planeada"
+msgstr "Fecha Planeada"
 
 msgctxt "field:stock.shipment.out.return,rec_name:"
 msgid "Name"
@@ -1114,7 +1126,7 @@ msgstr "Productos"
 
 msgctxt "model:ir.action,name:act_shipment_in_form"
 msgid "Supplier Shipments"
-msgstr "Envíos del Proveedor"
+msgstr "Envíos de Proveedores"
 
 msgctxt "model:ir.action,name:act_shipment_in_return_form"
 msgid "Supplier Return Shipments"
@@ -1134,11 +1146,11 @@ msgstr "Envíos a Clientes"
 
 msgctxt "model:ir.action,name:act_shipment_out_form3"
 msgid "Supplier Shipments"
-msgstr "Envíos de Proveedor"
+msgstr "Envíos de Proveedores"
 
 msgctxt "model:ir.action,name:act_shipment_out_return_form"
 msgid "Customer Return Shipments"
-msgstr "Devoluciones a Clientes"
+msgstr "Devoluciones de Clientes"
 
 msgctxt "model:ir.action,name:act_stock_configuration_form"
 msgid "Stock Configuration"
@@ -1253,7 +1265,7 @@ msgstr "Borrador"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_in_return_form_domain_waiting"
 msgid "Waiting"
-msgstr "Esperando"
+msgstr "En Espera"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_internal_form_domain_all"
@@ -1273,7 +1285,7 @@ msgstr "Borrador"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_internal_form_domain_waiting"
 msgid "Waiting"
-msgstr "Esperando"
+msgstr "En Espera"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_form_domain_all"
@@ -1298,7 +1310,7 @@ msgstr "Empacado"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_form_domain_waiting"
 msgid "Waiting"
-msgstr "Esperando"
+msgstr "En Espera"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_return_form_domain_all"
@@ -1333,7 +1345,7 @@ msgstr "Envío a Cliente"
 
 msgctxt "model:ir.sequence,name:sequence_shipment_out_return"
 msgid "Customer Return Shipment"
-msgstr "Envío de Devolución de Cliente"
+msgstr "Devolución de Cliente"
 
 msgctxt "model:ir.sequence.type,name:sequence_type_shipment_in"
 msgid "Supplier Shipment"
@@ -1353,7 +1365,7 @@ msgstr "Envío a Cliente"
 
 msgctxt "model:ir.sequence.type,name:sequence_type_shipment_out_return"
 msgid "Customer Return Shipment"
-msgstr "Envío de Devolución de Cliente"
+msgstr "Devolución de Cliente"
 
 msgctxt "model:ir.ui.menu,name:menu_configuration"
 msgid "Configuration"
@@ -1385,7 +1397,7 @@ msgstr "Informes"
 
 msgctxt "model:ir.ui.menu,name:menu_shipment_in_form"
 msgid "Supplier Shipments"
-msgstr "Envíos del Proveedor"
+msgstr "Envíos de Proveedores"
 
 msgctxt "model:ir.ui.menu,name:menu_shipment_in_return_form"
 msgid "Supplier Return Shipments"
@@ -1397,7 +1409,7 @@ msgstr "Envíos Internos"
 
 msgctxt "model:ir.ui.menu,name:menu_shipment_out_form"
 msgid "Customer Shipments"
-msgstr "Envíos al Cliente"
+msgstr "Envíos a Clientes"
 
 msgctxt "model:ir.ui.menu,name:menu_shipment_out_return_form"
 msgid "Customer Return Shipments"
@@ -1549,7 +1561,7 @@ msgstr "Teléfono:"
 
 msgctxt "odt:stock.shipment.in.restocking_list:"
 msgid "Planned Date:"
-msgstr "Fecha planeada:"
+msgstr "Fecha Planeada:"
 
 msgctxt "odt:stock.shipment.in.restocking_list:"
 msgid "Product"
@@ -1577,11 +1589,11 @@ msgstr "A Bodega"
 
 msgctxt "odt:stock.shipment.in.restocking_list:"
 msgid "VAT Number:"
-msgstr "NIT:"
+msgstr "Número Identificación:"
 
 msgctxt "odt:stock.shipment.in.restocking_list:"
 msgid "Warehouse:"
-msgstr "Almacén:"
+msgstr "Depósito:"
 
 msgctxt "odt:stock.shipment.internal.report:"
 msgid "/"
@@ -1613,7 +1625,7 @@ msgstr "Teléfono:"
 
 msgctxt "odt:stock.shipment.internal.report:"
 msgid "Planned Date:"
-msgstr "Fecha estimada:"
+msgstr "Fecha Planeada:"
 
 msgctxt "odt:stock.shipment.internal.report:"
 msgid "Product"
@@ -1637,7 +1649,7 @@ msgstr "A Bodega:"
 
 msgctxt "odt:stock.shipment.internal.report:"
 msgid "VAT Number:"
-msgstr "NIT:"
+msgstr "Número Identificación:"
 
 msgctxt "odt:stock.shipment.out.delivery_note:"
 msgid "/"
@@ -1681,7 +1693,7 @@ msgstr "Número de Envío:"
 
 msgctxt "odt:stock.shipment.out.delivery_note:"
 msgid "VAT Number:"
-msgstr "NIT:"
+msgstr "Número Identificación:"
 
 msgctxt "odt:stock.shipment.out.picking_list:"
 msgid "/"
@@ -1713,7 +1725,7 @@ msgstr "Lista de selección"
 
 msgctxt "odt:stock.shipment.out.picking_list:"
 msgid "Planned Date:"
-msgstr "Fecha estimada:"
+msgstr "Fecha Planeada:"
 
 msgctxt "odt:stock.shipment.out.picking_list:"
 msgid "Product"
@@ -1733,11 +1745,11 @@ msgstr "A Bodega"
 
 msgctxt "odt:stock.shipment.out.picking_list:"
 msgid "VAT Number:"
-msgstr "NIT:"
+msgstr "Número Identificación:"
 
 msgctxt "odt:stock.shipment.out.picking_list:"
 msgid "Warehouse:"
-msgstr "Almacén:"
+msgstr "Depósito:"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid "/"
@@ -1745,7 +1757,7 @@ msgstr "/"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid ":"
-msgstr ""
+msgstr ":"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid "Code:"
@@ -1769,7 +1781,7 @@ msgstr "Teléfono:"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid "Planned Date:"
-msgstr "Fecha estimada:"
+msgstr "Fecha Planeada:"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid "Product"
@@ -1793,15 +1805,15 @@ msgstr "A Bodega"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid "VAT Number:"
-msgstr "NIT:"
+msgstr "Número Identificación:"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid "Warehouse:"
-msgstr "Almacén:"
+msgstr "Depósito:"
 
 msgctxt "selection:stock.inventory,state:"
 msgid "Canceled"
-msgstr "Cancelado"
+msgstr "Anulado"
 
 msgctxt "selection:stock.inventory,state:"
 msgid "Done"
@@ -1845,7 +1857,7 @@ msgstr "Asignado"
 
 msgctxt "selection:stock.move,state:"
 msgid "Canceled"
-msgstr "Cancelado"
+msgstr "Anulado"
 
 msgctxt "selection:stock.move,state:"
 msgid "Done"
@@ -1865,7 +1877,7 @@ msgstr "Borrador"
 
 msgctxt "selection:stock.shipment.in,state:"
 msgid "Canceled"
-msgstr "Cancelado"
+msgstr "Anulado"
 
 msgctxt "selection:stock.shipment.in,state:"
 msgid "Done"
@@ -1885,7 +1897,7 @@ msgstr "Asignado"
 
 msgctxt "selection:stock.shipment.in.return,state:"
 msgid "Canceled"
-msgstr "Cancelado"
+msgstr "Anulado"
 
 msgctxt "selection:stock.shipment.in.return,state:"
 msgid "Done"
@@ -1897,7 +1909,7 @@ msgstr "Borrador"
 
 msgctxt "selection:stock.shipment.in.return,state:"
 msgid "Waiting"
-msgstr "Esperando"
+msgstr "En Espera"
 
 msgctxt "selection:stock.shipment.internal,state:"
 msgid "Assigned"
@@ -1905,7 +1917,7 @@ msgstr "Asignado"
 
 msgctxt "selection:stock.shipment.internal,state:"
 msgid "Canceled"
-msgstr "Cancelado"
+msgstr "Anulado"
 
 msgctxt "selection:stock.shipment.internal,state:"
 msgid "Done"
@@ -1917,7 +1929,7 @@ msgstr "Borrador"
 
 msgctxt "selection:stock.shipment.internal,state:"
 msgid "Waiting"
-msgstr "Esperando"
+msgstr "En Espera"
 
 msgctxt "selection:stock.shipment.out,state:"
 msgid "Assigned"
@@ -1925,7 +1937,7 @@ msgstr "Asignado"
 
 msgctxt "selection:stock.shipment.out,state:"
 msgid "Canceled"
-msgstr "Cancelado"
+msgstr "Anulado"
 
 msgctxt "selection:stock.shipment.out,state:"
 msgid "Done"
@@ -1941,11 +1953,11 @@ msgstr "Empacado"
 
 msgctxt "selection:stock.shipment.out,state:"
 msgid "Waiting"
-msgstr "Esperando"
+msgstr "En Espera"
 
 msgctxt "selection:stock.shipment.out.return,state:"
 msgid "Canceled"
-msgstr "Cancelado"
+msgstr "Anulado"
 
 msgctxt "selection:stock.shipment.out.return,state:"
 msgid "Done"
@@ -1968,6 +1980,10 @@ msgid "Product by Location"
 msgstr "Producto por Bodega"
 
 msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr "Costo"
+
+msgctxt "view:product.product:"
 msgid "Products"
 msgstr "Productos"
 
@@ -2164,8 +2180,8 @@ msgid "Internal Shipments"
 msgstr "Envíos Internos"
 
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
-msgstr "Esperando"
+msgid "Wait"
+msgstr "Espera"
 
 msgctxt "view:stock.shipment.out.assign.failed:"
 msgid "Unable to Assign"
@@ -2221,7 +2237,7 @@ msgstr "Envío a Cliente"
 
 msgctxt "view:stock.shipment.out:"
 msgid "Customer Shipments"
-msgstr "Envíos a Clientes"
+msgstr "Envíos al Cliente"
 
 msgctxt "view:stock.shipment.out:"
 msgid "Done"
@@ -2244,8 +2260,8 @@ msgid "Outgoing Moves"
 msgstr "Movimientos de Salida"
 
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
-msgstr "Esperando"
+msgid "Wait"
+msgstr "Espera"
 
 msgctxt "wizard_button:product.by_location,start,end:"
 msgid "Cancel"
diff --git a/locale/es_ES.po b/locale/es_ES.po
index bd028a9..560d191 100644
--- a/locale/es_ES.po
+++ b/locale/es_ES.po
@@ -9,6 +9,13 @@ msgid ""
 msgstr ""
 "No puede cambiar la UdM por defecto de un producto con movimientos de stock."
 
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr ""
+"No se puede cambiar el tipo de un producto del cual existen movimientos de "
+"stock."
+
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
 msgstr "La cantidad de la línea debe ser positiva."
@@ -43,7 +50,11 @@ msgstr "La cantidad del movimiento tiene que ser positiva."
 
 msgctxt "error:stock.move:"
 msgid "Source and destination location must be different"
-msgstr "Las ubicaciones orígen y destino deben ser distintas."
+msgstr "Las ubicaciones origen y destino deben ser distintas."
+
+msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr "El movimiento de stock \"%s\" no tiene ningún origen."
 
 msgctxt "error:stock.move:"
 msgid ""
@@ -62,37 +73,37 @@ msgstr ""
 msgctxt "error:stock.move:"
 msgid "You can not modify stock move \"%s\" because it is in \"Assigned\" state."
 msgstr ""
-"No puede establecer el movimiento de stock \"%s\" porque se encuentra en "
-"estado \"Asignado\"."
+"No puede cambiar el movimiento de stock \"%s\" porque se encuentra en estado"
+" \"Reservado\"."
 
 msgctxt "error:stock.move:"
 msgid ""
 "You can not modify stock move \"%s\" because it is in \"Done\" or \"Cancel\""
 " state."
 msgstr ""
-"No puede establecer el movimiento de stock \"%s\" porque se encuentra en "
-"estado \"Realizado\" o \"Cancelado\"."
+"No puede cambiar el movimiento de stock \"%s\" porque se encuentra en estado"
+" \"Realizado\" o \"Cancelado\"."
 
 msgctxt "error:stock.move:"
 msgid "You can not set stock move \"%s\" to assigned state."
-msgstr "No puede establecer el movimiento de stock \"%s\" a estado asignado."
+msgstr "No puede cambiar el movimiento de stock \"%s\" a estado reservado."
 
 msgctxt "error:stock.move:"
 msgid "You can not set stock move \"%s\" to done state."
-msgstr "No puede establecer el movimiento de stock \"%s\" a estado realizado."
+msgstr "No puede cambiar el movimiento de stock \"%s\" a estado realizado."
 
 msgctxt "error:stock.move:"
 msgid "You can not set stock move \"%s\" to draft state."
-msgstr "No puede establecer el movimiento de stock \"%s\" al estado borrador."
+msgstr "No puede cambiar el movimiento de stock \"%s\" a estado borrador."
 
 msgctxt "error:stock.period:"
 msgid "You can not close a period in the future or today."
-msgstr "No puede cerrar un período con fecha futura o de hoy."
+msgstr "No puede cerrar un período actual o futuro."
 
 msgctxt "error:stock.period:"
 msgid "You can not close a period when there still are assigned moves."
 msgstr ""
-"No puede cerrar un período cuando todavía dispone de movimientos asignados."
+"No puede cerrar un período cuando todavía dispone de movimientos reservados."
 
 msgctxt "error:stock.shipment.in.return:"
 msgid "Supplier Return Shipment \"%s\" must be cancelled before deletion."
@@ -109,7 +120,7 @@ msgctxt "error:stock.shipment.in:"
 msgid ""
 "Inventory Moves must have the warehouse input location as source location."
 msgstr ""
-"Los movimientos de inventario deben  indicar una ubicación de entrada como "
+"Los movimientos internos deben  indicar una ubicación de entrada como "
 "origen."
 
 msgctxt "error:stock.shipment.in:"
@@ -292,9 +303,9 @@ msgctxt "field:stock.inventory.line,inventory:"
 msgid "Inventory"
 msgstr "Inventario"
 
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
-msgstr "Movimiento"
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
+msgstr "Movimientos"
 
 msgctxt "field:stock.inventory.line,product:"
 msgid "Product"
@@ -674,7 +685,7 @@ msgstr "Movimientos"
 
 msgctxt "field:stock.shipment.in,origins:"
 msgid "Origins"
-msgstr "Orígen"
+msgstr "Orígenes"
 
 msgctxt "field:stock.shipment.in,planned_date:"
 msgid "Planned Date"
@@ -754,7 +765,7 @@ msgstr "Movimientos"
 
 msgctxt "field:stock.shipment.in.return,origins:"
 msgid "Origins"
-msgstr "Orígen"
+msgstr "Orígenes"
 
 msgctxt "field:stock.shipment.in.return,planned_date:"
 msgid "Planned Date"
@@ -1006,7 +1017,7 @@ msgstr "Movimientos"
 
 msgctxt "field:stock.shipment.out.return,origins:"
 msgid "Origins"
-msgstr "Orígen"
+msgstr "Orígenes"
 
 msgctxt "field:stock.shipment.out.return,planned_date:"
 msgid "Planned Date"
@@ -1144,7 +1155,7 @@ msgstr "Crear albarán de devolución"
 
 msgctxt "model:ir.action,name:report_shipment_in_restocking_list"
 msgid "Restocking List"
-msgstr "Lista reabastecimiento"
+msgstr "Lista de reabastecimiento"
 
 msgctxt "model:ir.action,name:report_shipment_internal"
 msgid "Internal Shipment"
@@ -1160,7 +1171,7 @@ msgstr "Lista recogida"
 
 msgctxt "model:ir.action,name:report_shipment_out_return_restocking_list"
 msgid "Restocking List"
-msgstr "Lista reabastecimiento"
+msgstr "Lista de reabastecimiento"
 
 msgctxt "model:ir.action,name:wizard_product_by_location"
 msgid "Product by Locations"
@@ -1419,7 +1430,7 @@ msgstr "Administración de logística"
 
 msgctxt "model:res.group,name:group_stock_force_assignment"
 msgid "Stock Force Assignment"
-msgstr "Forzar reserva en la logística"
+msgstr "Forzar reserva en logística"
 
 msgctxt "model:stock.configuration,name:"
 msgid "Stock Configuration"
@@ -1475,7 +1486,7 @@ msgstr "Período de stock"
 
 msgctxt "model:stock.period.cache,name:"
 msgid "Stock Period Cache"
-msgstr "Período stock precalculado"
+msgstr "Período de stock precalculado"
 
 msgctxt "model:stock.product_quantities_warehouse,name:"
 msgid "Product Quantities By Warehouse"
@@ -1559,7 +1570,7 @@ msgstr "Referencia:"
 
 msgctxt "odt:stock.shipment.in.restocking_list:"
 msgid "Restocking List"
-msgstr "Lista reabastecimiento"
+msgstr "Lista de reabastecimiento"
 
 msgctxt "odt:stock.shipment.in.restocking_list:"
 msgid "Supplier:"
@@ -1779,7 +1790,7 @@ msgstr "Referencia:"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid "Restocking List"
-msgstr "Lista reabastecimiento"
+msgstr "Lista de reabastecimiento"
 
 msgctxt "odt:stock.shipment.out.return.restocking_list:"
 msgid "To Location"
@@ -1955,13 +1966,17 @@ msgstr "Recibido"
 
 msgctxt "view:party.party:"
 msgid "Stock"
-msgstr "Stock"
+msgstr "Logística"
 
 msgctxt "view:product.by_location.start:"
 msgid "Product by Location"
 msgstr "Productos por ubicación"
 
 msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr "Valor de coste"
+
+msgctxt "view:product.product:"
 msgid "Products"
 msgstr "Productos"
 
@@ -2158,7 +2173,7 @@ msgid "Internal Shipments"
 msgstr "Albaranes internos"
 
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
+msgid "Wait"
 msgstr "En espera"
 
 msgctxt "view:stock.shipment.out.assign.failed:"
@@ -2238,7 +2253,7 @@ msgid "Outgoing Moves"
 msgstr "Movimientos de salida"
 
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
+msgid "Wait"
 msgstr "En espera"
 
 msgctxt "wizard_button:product.by_location,start,end:"
diff --git a/locale/fr_FR.po b/locale/fr_FR.po
index 7a721e8..249d529 100644
--- a/locale/fr_FR.po
+++ b/locale/fr_FR.po
@@ -10,13 +10,20 @@ msgstr ""
 "Vous ne pouvez pas changer l'UDM par défaut pour un produit qui a déjà fait "
 "l'objet de mouvements de stock"
 
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr ""
+"Vous ne pouvez pas changer le type d'un produit qui a déjà fait l'objet de "
+"mouvements de stock"
+
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
 msgstr "La quantité de la ligne doit être positive."
 
 msgctxt "error:stock.inventory:"
 msgid "Inventory \"%s\" must be cancelled before deletion."
-msgstr "l'inventaire \"%s\" doit être annulé avant sa suppression"
+msgstr "L'inventaire \"%s\" doit être annulé avant suppression"
 
 msgctxt "error:stock.inventory:"
 msgid "Line \"%s\" is not unique on Inventory \"%s\"."
@@ -49,6 +56,10 @@ msgid "Source and destination location must be different"
 msgstr "Les emplacements d'origine et de destination doivent être distincts"
 
 msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr "Le mouvement de stock \"%s\" n'a pas d'origine."
+
+msgctxt "error:stock.move:"
 msgid ""
 "You can not delete stock move \"%s\" because it is not in draft or cancelled"
 " state."
@@ -102,8 +113,8 @@ msgstr ""
 msgctxt "error:stock.shipment.in.return:"
 msgid "Supplier Return Shipment \"%s\" must be cancelled before deletion."
 msgstr ""
-"Le retour d'expédition fournisseur \"%s\" doit être annulé pour pouvoir être"
-" supprimé."
+"Le retour d'expédition fournisseur \"%s\" doit être annulé avant "
+"suppression."
 
 msgctxt "error:stock.shipment.in:"
 msgid ""
@@ -122,14 +133,11 @@ msgstr ""
 
 msgctxt "error:stock.shipment.in:"
 msgid "Supplier Shipment \"%s\" must be cancelled before deletion."
-msgstr ""
-"L'expédition fournisseur \"%s\" doit être annulée pour pouvoir être "
-"supprimée."
+msgstr "L'expédition fournisseur \"%s\" doit être annulée avant suppression."
 
 msgctxt "error:stock.shipment.internal:"
 msgid "Internal Shipment \"%s\" must be cancelled before deletion."
-msgstr ""
-"L'expédition interne \"%s\" doit être annulée pour pouvoir être supprimée."
+msgstr "L'expédition interne \"%s\" doit être annulée avant suppression."
 
 msgctxt "error:stock.shipment.out.return.create:"
 msgid "The shipment with code \"%s\" is not yet sent."
@@ -141,15 +149,11 @@ msgstr "Vous ne pouvez pas créer d'expédition retour"
 
 msgctxt "error:stock.shipment.out.return:"
 msgid "Customer Return Shipment \"%s\" must be cancelled before deletion."
-msgstr ""
-"Le retour d'expédition client \"%s\" doit être annulé avant de pouvoir être "
-"supprimé."
+msgstr "Le retour d'expédition client \"%s\" doit être annulé avant suppression."
 
 msgctxt "error:stock.shipment.out:"
 msgid "Customer Shipment \"%s\" must be cancelled before deletion."
-msgstr ""
-"L'expédition cliente \"%s\" doit être annulés avant de pouvoir être "
-"supprimée."
+msgstr "L'expédition cliente \"%s\" doit être annulées avant suppression."
 
 msgctxt "field:party.address,delivery:"
 msgid "Delivery"
@@ -307,9 +311,9 @@ msgctxt "field:stock.inventory.line,inventory:"
 msgid "Inventory"
 msgstr "Inventaire"
 
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
-msgstr "Mouvement"
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
+msgstr "Mouvements"
 
 msgctxt "field:stock.inventory.line,product:"
 msgid "Product"
@@ -1973,6 +1977,10 @@ msgid "Product by Location"
 msgstr "Produits par emplacement"
 
 msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr "Valeur du coût"
+
+msgctxt "view:product.product:"
 msgid "Products"
 msgstr "Produits"
 
@@ -2169,8 +2177,8 @@ msgid "Internal Shipments"
 msgstr "Expéditions internes"
 
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
-msgstr "En attente"
+msgid "Wait"
+msgstr "Attendre"
 
 msgctxt "view:stock.shipment.out.assign.failed:"
 msgid "Unable to Assign"
@@ -2249,8 +2257,8 @@ msgid "Outgoing Moves"
 msgstr "Mouvements sortants"
 
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
-msgstr "En attente"
+msgid "Wait"
+msgstr "Attendre"
 
 msgctxt "wizard_button:product.by_location,start,end:"
 msgid "Cancel"
diff --git a/locale/nl_NL.po b/locale/nl_NL.po
index fcf82c8..344d323 100644
--- a/locale/nl_NL.po
+++ b/locale/nl_NL.po
@@ -8,6 +8,11 @@ msgid ""
 " moves."
 msgstr ""
 
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr ""
+
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
 msgstr ""
@@ -43,6 +48,10 @@ msgid "Source and destination location must be different"
 msgstr ""
 
 msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr ""
+
+msgctxt "error:stock.move:"
 msgid ""
 "You can not delete stock move \"%s\" because it is not in draft or cancelled"
 " state."
@@ -286,9 +295,9 @@ msgid "Inventory"
 msgstr ""
 
 #, fuzzy
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
-msgstr "Boeking"
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
+msgstr "Boekingen"
 
 #, fuzzy
 msgctxt "field:stock.inventory.line,product:"
@@ -2107,6 +2116,10 @@ msgctxt "view:product.by_location.start:"
 msgid "Product by Location"
 msgstr ""
 
+msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr ""
+
 #, fuzzy
 msgctxt "view:product.product:"
 msgid "Products"
@@ -2321,10 +2334,9 @@ msgctxt "view:stock.shipment.internal:"
 msgid "Internal Shipments"
 msgstr ""
 
-#, fuzzy
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
-msgstr "In afwachting"
+msgid "Wait"
+msgstr ""
 
 msgctxt "view:stock.shipment.out.assign.failed:"
 msgid "Unable to Assign"
@@ -2408,10 +2420,9 @@ msgctxt "view:stock.shipment.out:"
 msgid "Outgoing Moves"
 msgstr ""
 
-#, fuzzy
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
-msgstr "In afwachting"
+msgid "Wait"
+msgstr ""
 
 #, fuzzy
 msgctxt "wizard_button:product.by_location,start,end:"
diff --git a/locale/ru_RU.po b/locale/ru_RU.po
index d25fcea..f4e0981 100644
--- a/locale/ru_RU.po
+++ b/locale/ru_RU.po
@@ -10,6 +10,11 @@ msgstr ""
 "Вы не можете изменить единицу измерения по умолчанию для продукта, который "
 "связан со складом перемещения."
 
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr ""
+
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
 msgstr "Количество в строке должно быть положительным."
@@ -49,6 +54,10 @@ msgid "Source and destination location must be different"
 msgstr "Исходное и принимающее местоположения должны быть разными"
 
 msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr ""
+
+msgctxt "error:stock.move:"
 msgid ""
 "You can not delete stock move \"%s\" because it is not in draft or cancelled"
 " state."
@@ -291,9 +300,10 @@ msgctxt "field:stock.inventory.line,inventory:"
 msgid "Inventory"
 msgstr "Инвентаризация"
 
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
-msgstr "Перемещение"
+#, fuzzy
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
+msgstr "Перемещения"
 
 msgctxt "field:stock.inventory.line,product:"
 msgid "Product"
@@ -1960,6 +1970,11 @@ msgctxt "view:product.by_location.start:"
 msgid "Product by Location"
 msgstr "Продукция по местоположению"
 
+#, fuzzy
+msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr "Цена"
+
 msgctxt "view:product.product:"
 msgid "Products"
 msgstr "Продукция"
@@ -2156,9 +2171,10 @@ msgctxt "view:stock.shipment.internal:"
 msgid "Internal Shipments"
 msgstr "Внутренние перемещения"
 
+#, fuzzy
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
-msgstr "Ожидание"
+msgid "Wait"
+msgstr "Ожидают"
 
 msgctxt "view:stock.shipment.out.assign.failed:"
 msgid "Unable to Assign"
@@ -2236,9 +2252,10 @@ msgctxt "view:stock.shipment.out:"
 msgid "Outgoing Moves"
 msgstr "Внешнее перемещение"
 
+#, fuzzy
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
-msgstr "Ожидание"
+msgid "Wait"
+msgstr "Ожидают"
 
 msgctxt "wizard_button:product.by_location,start,end:"
 msgid "Cancel"
diff --git a/locale/sl_SI.po b/locale/sl_SI.po
index bb7bee4..890ee6a 100644
--- a/locale/sl_SI.po
+++ b/locale/sl_SI.po
@@ -10,6 +10,11 @@ msgstr ""
 "Privzete ME za izdelek, ki je povezan s prometom zaloge, ni možno "
 "spreminjati."
 
+msgctxt "error:product.template:"
+msgid ""
+"You cannot change the type for a product which is associated to stock moves."
+msgstr "Vrste izdelka, povezanega s prometom zaloge, ni možno popravljati."
+
 msgctxt "error:stock.inventory.line:"
 msgid "Line quantity must be positive."
 msgstr "Količina postavke mora biti večja od nič."
@@ -47,11 +52,15 @@ msgid "Source and destination location must be different"
 msgstr "Izvorna in ciljna lokacija mora biti različni."
 
 msgctxt "error:stock.move:"
+msgid "The stock move \"%s\" has no origin."
+msgstr "Promet zaloge \"%s\" nima porekla."
+
+msgctxt "error:stock.move:"
 msgid ""
 "You can not delete stock move \"%s\" because it is not in draft or cancelled"
 " state."
 msgstr ""
-"Prometa zaloge \"%s\" ni možno brisati, ker ni v stanju osnutka ali "
+"Prometa zaloge \"%s\" ni možno brisati, ker niso v stanju priprave ali "
 "preklica."
 
 msgctxt "error:stock.move:"
@@ -82,7 +91,7 @@ msgstr "Prometa zaloge \"%s\" ni možno postaviti v stanje \"končano\". "
 
 msgctxt "error:stock.move:"
 msgid "You can not set stock move \"%s\" to draft state."
-msgstr "Prometa zaloge \"%s\" ni možno postaviti v stanje \"osnutek\". "
+msgstr "Prometa zaloge \"%s\" ni možno postaviti v stanje priprave. "
 
 msgctxt "error:stock.period:"
 msgid "You can not close a period in the future or today."
@@ -226,7 +235,7 @@ msgstr "Zapisal"
 
 msgctxt "field:stock.inventory,company:"
 msgid "Company"
-msgstr "Podjetje"
+msgstr "Družba"
 
 msgctxt "field:stock.inventory,create_date:"
 msgid "Create Date"
@@ -292,8 +301,8 @@ msgctxt "field:stock.inventory.line,inventory:"
 msgid "Inventory"
 msgstr "Popis"
 
-msgctxt "field:stock.inventory.line,move:"
-msgid "Move"
+msgctxt "field:stock.inventory.line,moves:"
+msgid "Moves"
 msgstr "Promet"
 
 msgctxt "field:stock.inventory.line,product:"
@@ -410,7 +419,7 @@ msgstr "Zapisal"
 
 msgctxt "field:stock.move,company:"
 msgid "Company"
-msgstr "Podjetje"
+msgstr "Družba"
 
 msgctxt "field:stock.move,cost_price:"
 msgid "Cost Price"
@@ -510,7 +519,7 @@ msgstr "Predpomnjenja"
 
 msgctxt "field:stock.period,company:"
 msgid "Company"
-msgstr "Podjetje"
+msgstr "Družba"
 
 msgctxt "field:stock.period,create_date:"
 msgid "Create Date"
@@ -638,7 +647,7 @@ msgstr "Šifra"
 
 msgctxt "field:stock.shipment.in,company:"
 msgid "Company"
-msgstr "Podjetje"
+msgstr "Družba"
 
 msgctxt "field:stock.shipment.in,contact_address:"
 msgid "Contact Address"
@@ -726,7 +735,7 @@ msgstr "Šifra"
 
 msgctxt "field:stock.shipment.in.return,company:"
 msgid "Company"
-msgstr "Podjetje"
+msgstr "Družba"
 
 msgctxt "field:stock.shipment.in.return,create_date:"
 msgid "Create Date"
@@ -798,7 +807,7 @@ msgstr "Šifra"
 
 msgctxt "field:stock.shipment.internal,company:"
 msgid "Company"
-msgstr "Podjetje"
+msgstr "Družba"
 
 msgctxt "field:stock.shipment.internal,create_date:"
 msgid "Create Date"
@@ -866,7 +875,7 @@ msgstr "Šifra"
 
 msgctxt "field:stock.shipment.out,company:"
 msgid "Company"
-msgstr "Podjetje"
+msgstr "Družba"
 
 msgctxt "field:stock.shipment.out,create_date:"
 msgid "Create Date"
@@ -962,7 +971,7 @@ msgstr "Šifra"
 
 msgctxt "field:stock.shipment.out.return,company:"
 msgid "Company"
-msgstr "Podjetje"
+msgstr "Družba"
 
 msgctxt "field:stock.shipment.out.return,create_date:"
 msgid "Create Date"
@@ -1046,11 +1055,11 @@ msgstr "Zapisal"
 
 msgctxt "help:party.party,customer_location:"
 msgid "The default destination location when sending products to the party."
-msgstr "Privzeta ciljna lokacija pri odpremi izdelkov k stranki."
+msgstr "Privzeta ciljna lokacija pri odpremi izdelkov k partnerju."
 
 msgctxt "help:party.party,supplier_location:"
 msgid "The default source location when receiving products from the party."
-msgstr "Privzeta izvorna lokacija pri prevzemu izdelkov od stranke."
+msgstr "Privzeta izvorna lokacija pri prevzemu izdelkov od partnerja."
 
 msgctxt "help:product.by_location.start,forecast_date:"
 msgid ""
@@ -1191,7 +1200,7 @@ msgstr "Vse"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_inventory_form_domain_draft"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt "model:ir.action.act_window.domain,name:act_move_form_domain_all"
 msgid "All"
@@ -1220,7 +1229,7 @@ msgstr "Vse"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_in_form_domain_draft"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_in_form_domain_received"
@@ -1240,7 +1249,7 @@ msgstr "Dodeljeno"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_in_return_form_domain_draft"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_in_return_form_domain_waiting"
@@ -1260,7 +1269,7 @@ msgstr "Dodeljeno"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_internal_form_domain_draft"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_internal_form_domain_waiting"
@@ -1280,7 +1289,7 @@ msgstr "Dodeljeno"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_form_domain_draft"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_form_domain_packed"
@@ -1300,7 +1309,7 @@ msgstr "Vse"
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_return_form_domain_draft"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt ""
 "model:ir.action.act_window.domain,name:act_shipment_out_return_form_domain_received"
@@ -1801,7 +1810,7 @@ msgstr "Zaključeno"
 
 msgctxt "selection:stock.inventory,state:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "V pripravi"
 
 msgctxt "selection:stock.location,type:"
 msgid "Customer"
@@ -1845,7 +1854,7 @@ msgstr "Zaključeno"
 
 msgctxt "selection:stock.move,state:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "V pripravi"
 
 msgctxt "selection:stock.period,state:"
 msgid "Closed"
@@ -1853,7 +1862,7 @@ msgstr "Zaprto"
 
 msgctxt "selection:stock.period,state:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "V pripravi"
 
 msgctxt "selection:stock.shipment.in,state:"
 msgid "Canceled"
@@ -1865,7 +1874,7 @@ msgstr "Zaključeno"
 
 msgctxt "selection:stock.shipment.in,state:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "V pripravi"
 
 msgctxt "selection:stock.shipment.in,state:"
 msgid "Received"
@@ -1885,7 +1894,7 @@ msgstr "Zaključeno"
 
 msgctxt "selection:stock.shipment.in.return,state:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "V pripravi"
 
 msgctxt "selection:stock.shipment.in.return,state:"
 msgid "Waiting"
@@ -1905,7 +1914,7 @@ msgstr "Zaključeno"
 
 msgctxt "selection:stock.shipment.internal,state:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "V pripravi"
 
 msgctxt "selection:stock.shipment.internal,state:"
 msgid "Waiting"
@@ -1925,7 +1934,7 @@ msgstr "Zaključeno"
 
 msgctxt "selection:stock.shipment.out,state:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "V pripravi"
 
 msgctxt "selection:stock.shipment.out,state:"
 msgid "Packed"
@@ -1945,7 +1954,7 @@ msgstr "Zaključeno"
 
 msgctxt "selection:stock.shipment.out.return,state:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "V pripravi"
 
 msgctxt "selection:stock.shipment.out.return,state:"
 msgid "Received"
@@ -1960,6 +1969,10 @@ msgid "Product by Location"
 msgstr "Izdelek po lokaciji"
 
 msgctxt "view:product.product:"
+msgid "Cost Value"
+msgstr "Nabavna vrednost"
+
+msgctxt "view:product.product:"
 msgid "Products"
 msgstr "Izdelki"
 
@@ -2033,7 +2046,7 @@ msgstr "Zaključitev"
 
 msgctxt "view:stock.period:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt "view:stock.period:"
 msgid "Period"
@@ -2077,7 +2090,7 @@ msgstr "Zaključeno"
 
 msgctxt "view:stock.shipment.in.return:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt "view:stock.shipment.in.return:"
 msgid "Supplier Return Shipment"
@@ -2145,7 +2158,7 @@ msgstr "Zaključeno"
 
 msgctxt "view:stock.shipment.internal:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt "view:stock.shipment.internal:"
 msgid "Internal Shipment"
@@ -2156,8 +2169,8 @@ msgid "Internal Shipments"
 msgstr "Notranje odpremnice"
 
 msgctxt "view:stock.shipment.internal:"
-msgid "Waiting"
-msgstr "Čakajoče"
+msgid "Wait"
+msgstr "Čakanje"
 
 msgctxt "view:stock.shipment.out.assign.failed:"
 msgid "Unable to Assign"
@@ -2185,7 +2198,7 @@ msgstr "Zaključeno"
 
 msgctxt "view:stock.shipment.out.return:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt "view:stock.shipment.out.return:"
 msgid "Incoming Moves"
@@ -2221,7 +2234,7 @@ msgstr "Zaključeno"
 
 msgctxt "view:stock.shipment.out:"
 msgid "Draft"
-msgstr "Osnutek"
+msgstr "Priprava"
 
 msgctxt "view:stock.shipment.out:"
 msgid "Inventory Moves"
@@ -2236,8 +2249,8 @@ msgid "Outgoing Moves"
 msgstr "Izhodni promet"
 
 msgctxt "view:stock.shipment.out:"
-msgid "Waiting"
-msgstr "Čakajoče"
+msgid "Wait"
+msgstr "Čakanje"
 
 msgctxt "wizard_button:product.by_location,start,end:"
 msgid "Cancel"
diff --git a/location.py b/location.py
index 2d9afdf..4613081 100644
--- a/location.py
+++ b/location.py
@@ -244,8 +244,9 @@ class Location(ModelSQL, ModelView):
         return locations
 
     @classmethod
-    def write(cls, locations, vals):
-        super(Location, cls).write(locations, vals)
+    def write(cls, *args):
+        super(Location, cls).write(*args)
+        locations = sum(args[::2], [])
         cls._set_warehouse_parent(locations)
 
         ids = [l.id for l in locations]
@@ -312,8 +313,9 @@ class Location(ModelSQL, ModelView):
                     default=default)
                 warehouse_locations = Transaction().context.get(
                     'cp_warehouse_locations') or {}
-                cp_warehouse = cls(Transaction().context['cp_warehouse_id'])
                 if location.id in warehouse_locations.values():
+                    cp_warehouse = cls(
+                        Transaction().context['cp_warehouse_id'])
                     for field, loc_id in warehouse_locations.iteritems():
                         if loc_id == location.id:
                             cls.write([cp_warehouse], {
diff --git a/location.xml b/location.xml
index 8d106ec..99c7050 100644
--- a/location.xml
+++ b/location.xml
@@ -86,6 +86,12 @@ this repository contains the full copyright notices and license terms. -->
             <field name="model">stock.location,-1</field>
             <field name="action" ref="wizard_products_by_locations"/>
         </record>
+        <record model="ir.action.keyword"
+                id="act_products_by_locations_keyword2">
+            <field name="keyword">form_relate</field>
+            <field name="model">stock.location,-1</field>
+            <field name="action" ref="wizard_products_by_locations"/>
+        </record>
 
         <record model="ir.model.access" id="access_location">
             <field name="model" search="[('model', '=', 'stock.location')]"/>
diff --git a/move.py b/move.py
index 2d4749c..057da67 100644
--- a/move.py
+++ b/move.py
@@ -4,12 +4,15 @@ import datetime
 import operator
 from decimal import Decimal
 from functools import partial
-from sql import Column
+from sql import Literal, Union, Column
+from sql.aggregate import Sum
+from sql.conditionals import Coalesce
 from sql.operators import Concat
 
 from trytond.model import Workflow, Model, ModelView, ModelSQL, fields
 from trytond import backend
-from trytond.pyson import In, Eval, Not, Equal, If, Get, Bool
+from trytond.pyson import In, Eval, Not, Equal, If, Bool
+from trytond.tools import reduce_ids
 from trytond.transaction import Transaction
 from trytond.pool import Pool
 
@@ -49,7 +52,7 @@ class StockMixin:
         product.
 
         location_ids is the list of IDs of locations to take account to compute
-            the stock. It can't be empty.
+            the stock.
         products restrict the stock computation to the this products (more
             efficient), so it should be the products related to records.
             If it is None all products are used.
@@ -88,50 +91,59 @@ class StockMixin:
         Compute the domain to filter records which validates the domain over
         quantity field.
 
+        The context with keys:
+            stock_skip_warehouse: if set, quantities on a warehouse are no more
+                quantities of all child locations but quantities of the storage
+                zone.
         location_ids is the list of IDs of locations to take account to compute
-            the stock. It can't be empty.
+            the stock.
         grouping defines how stock moves are grouped.
         position defines which field of grouping corresponds to the record
             whose quantity is computed.
         """
         pool = Pool()
-        Product = pool.get('product.product')
+        Location = pool.get('stock.location')
+        Move = pool.get('stock.move')
 
         if not location_ids or not domain:
             return []
 
-        def _search_quantity_eval_domain(line, domain):
-            operator_funcs = {
-                '=': operator.eq,
-                '>=': operator.ge,
-                '>': operator.gt,
-                '<=': operator.le,
-                '<': operator.lt,
-                '!=': operator.ne,
-                'in': lambda v, l: v in l,
-                'not in': lambda v, l: v not in l,
-                }
-
-            field, op, operand = domain
-            value = line.get(field)
-            return operator_funcs[op](value, operand)
+        # Skip warehouse location in favor of their storage location
+        # to compute quantities. Keep track of which ids to remove
+        # and to add after the query.
+        if Transaction().context.get('stock_skip_warehouse'):
+            location_ids = set(location_ids)
+            for location in Location.browse(list(location_ids)):
+                if location.type == 'warehouse':
+                    location_ids.remove(location.id)
+                    location_ids.add(location.storage_location.id)
+            location_ids = list(location_ids)
 
         with Transaction().set_context(cls._quantity_context(name)):
-            pbl = Product.products_by_location(
-                location_ids=location_ids,
-                with_childs=True, grouping=grouping)
+            query = Move.compute_quantities_query(location_ids,
+                with_childs=True, grouping=grouping,
+                grouping_filter=None)
+            having_domain = getattr(cls, name)._field.convert_domain(domain, {
+                    None: (query, {}),
+                    }, cls)
+            # The last column of 'query' is always the quantity for the 'key'.
+            # It is computed with a SUM() aggregator so in having we have to
+            # use the SUM() expression and not the name of column
+            having_domain.left = query.columns[-1].expression
+            if query.having:
+                query.having &= having_domain
+            else:
+                query.having = having_domain
+            quantities = Move.compute_quantities(query, location_ids,
+                with_childs=True, grouping=grouping,
+                grouping_filter=None)
 
-        processed_lines = []
-        for key, quantity in pbl.iteritems():
+        record_ids = []
+        for key, quantity in quantities.iteritems():
             # pbl could return None in some keys
             if key[position] is not None:
-                processed_lines.append({
-                        'record_id': key[position],
-                        name: quantity,
-                        })
+                record_ids.append(key[position])
 
-        record_ids = [line['record_id'] for line in processed_lines
-            if _search_quantity_eval_domain(line, domain)]
         return [('id', 'in', record_ids)]
 
 
@@ -141,23 +153,18 @@ class Move(Workflow, ModelSQL, ModelView):
     _order_name = 'product'
     product = fields.Many2One("product.product", "Product", required=True,
         select=True, states=STATES,
-        on_change=['product', 'currency', 'uom', 'company',
-            'from_location', 'to_location'],
         domain=[('type', '!=', 'service')],
         depends=DEPENDS)
     product_uom_category = fields.Function(
-        fields.Many2One('product.uom.category', 'Product Uom Category',
-            on_change_with=['product']),
+        fields.Many2One('product.uom.category', 'Product Uom Category'),
         'on_change_with_product_uom_category')
     uom = fields.Many2One("product.uom", "Uom", required=True, states=STATES,
         domain=[
             ('category', '=', Eval('product_uom_category')),
             ],
-        on_change=['product', 'currency', 'uom', 'company',
-            'from_location', 'to_location'],
         depends=['state', 'product_uom_category'])
-    unit_digits = fields.Function(fields.Integer('Unit Digits',
-        on_change_with=['uom']), 'on_change_with_unit_digits')
+    unit_digits = fields.Function(fields.Integer('Unit Digits'),
+        'on_change_with_unit_digits')
     quantity = fields.Float("Quantity", required=True,
         digits=(16, Eval('unit_digits', 2)), states=STATES,
         depends=['state', 'unit_digits'])
@@ -181,7 +188,11 @@ class Move(Workflow, ModelSQL, ModelView):
                 | Eval('shipment'))
             }, depends=['state', 'shipment'],
         select=True)
-    effective_date = fields.Date("Effective Date", readonly=True, select=True)
+    effective_date = fields.Date("Effective Date", readonly=True, select=True,
+        states={
+            'required': Eval('state') == 'done',
+            },
+        depends=['state'])
     state = fields.Selection([
         ('draft', 'Draft'),
         ('assigned', 'Assigned'),
@@ -212,8 +223,7 @@ class Move(Workflow, ModelSQL, ModelView):
             'readonly': Not(Equal(Eval('state'), 'draft')),
             },
         depends=['unit_price_required', 'state'])
-    unit_price_required = fields.Function(fields.Boolean('Unit Price Required',
-        on_change_with=['from_location', 'to_location']),
+    unit_price_required = fields.Function(fields.Boolean('Unit Price Required'),
         'on_change_with_unit_price_required')
 
     @classmethod
@@ -251,6 +261,7 @@ class Move(Workflow, ModelSQL, ModelView):
                 'it is in "Assigned" state.'),
             'modify_done_cancel': ('You can not modify stock move "%s" '
                 'because it is in "Done" or "Cancel" state.'),
+            'no_origin': 'The stock move "%s" has no origin.',
             })
         cls._transitions |= set((
                 ('draft', 'assigned'),
@@ -364,11 +375,14 @@ class Move(Workflow, ModelSQL, ModelView):
     def default_unit_digits():
         return 2
 
+    @fields.depends('uom')
     def on_change_with_unit_digits(self, name=None):
         if self.uom:
             return self.uom.digits
         return 2
 
+    @fields.depends('product', 'currency', 'uom', 'company', 'from_location',
+        'to_location')
     def on_change_product(self):
         pool = Pool()
         Uom = pool.get('product.uom')
@@ -397,10 +411,13 @@ class Move(Workflow, ModelSQL, ModelView):
                 res['unit_price'] = unit_price
         return res
 
+    @fields.depends('product')
     def on_change_with_product_uom_category(self, name=None):
         if self.product:
             return self.product.default_uom_category.id
 
+    @fields.depends('product', 'currency', 'uom', 'company', 'from_location',
+        'to_location')
     def on_change_uom(self):
         pool = Pool()
         Uom = pool.get('product.uom')
@@ -421,6 +438,7 @@ class Move(Workflow, ModelSQL, ModelView):
                 res['unit_price'] = unit_price
         return res
 
+    @fields.depends('from_location', 'to_location')
     def on_change_with_unit_price_required(self, name=None):
         if (self.from_location
                 and self.from_location.type in ('supplier', 'production')):
@@ -457,7 +475,7 @@ class Move(Workflow, ModelSQL, ModelView):
     @staticmethod
     def _get_origin():
         'Return list of Model names for origin Reference'
-        return []
+        return ['stock.inventory.line']
 
     @classmethod
     def get_origin(cls):
@@ -565,36 +583,39 @@ class Move(Workflow, ModelSQL, ModelView):
             product.default_uom, round=True)
         return internal_quantity
 
+    def set_effective_date(self):
+        pool = Pool()
+        Date = pool.get('ir.date')
+
+        if not self.effective_date and self.shipment:
+            self.effective_date = self.shipment.effective_date
+        if not self.effective_date:
+            self.effective_date = Date.today()
+
     @classmethod
     @ModelView.button
     @Workflow.transition('draft')
     def draft(cls, moves):
-        pass
+        cls.write(moves, {
+                'effective_date': None,
+                })
 
     @classmethod
     @ModelView.button
     @Workflow.transition('assigned')
     def assign(cls, moves):
-        pool = Pool()
-        Date = pool.get('ir.date')
-
-        today = Date.today()
+        cls.check_origin(moves)
         for move in moves:
-            if not move.effective_date:
-                move.effective_date = today
+            move.set_effective_date()
             move.save()
 
     @classmethod
     @ModelView.button
     @Workflow.transition('done')
     def do(cls, moves):
-        pool = Pool()
-        Date = pool.get('ir.date')
-
-        today = Date.today()
+        cls.check_origin(moves)
         for move in moves:
-            if not move.effective_date:
-                move.effective_date = today
+            move.set_effective_date()
             if (move.from_location.type in ('supplier', 'production')
                     and move.to_location.type == 'storage'
                     and move.product.cost_price_method == 'average'):
@@ -648,31 +669,35 @@ class Move(Workflow, ModelSQL, ModelView):
         return moves
 
     @classmethod
-    def write(cls, moves, vals):
-        vals_set = set(vals)
-        if cls._deny_modify_assigned & vals_set:
-            for move in moves:
-                if move.state == 'assigned':
-                    cls.raise_user_error('modify_assigned', (move.rec_name,))
-        if cls._deny_modify_done_cancel & vals_set:
-            for move in moves:
-                if move.state in ('done', 'cancel'):
-                    cls.raise_user_error('modify_done_cancel',
-                        (move.rec_name,))
-
-        if any(f not in cls._allow_modify_closed_period for f in vals):
-            cls.check_period_closed(moves)
+    def write(cls, *args):
+        actions = iter(args)
+        for moves, values in zip(actions, actions):
+            vals_set = set(values)
+            if cls._deny_modify_assigned & vals_set:
+                for move in moves:
+                    if move.state == 'assigned':
+                        cls.raise_user_error('modify_assigned', move.rec_name)
+            if cls._deny_modify_done_cancel & vals_set:
+                for move in moves:
+                    if move.state in ('done', 'cancel'):
+                        cls.raise_user_error('modify_done_cancel',
+                            (move.rec_name,))
 
-        super(Move, cls).write(moves, vals)
+        super(Move, cls).write(*args)
 
-        for move in moves:
-            internal_quantity = cls._get_internal_quantity(move.quantity,
-                    move.uom, move.product)
-            if (internal_quantity != move.internal_quantity
-                    and internal_quantity != vals.get('internal_quantity')):
-                cls.write([move], {
-                        'internal_quantity': internal_quantity,
-                        })
+        actions = iter(args)
+        for moves, values in zip(actions, actions):
+            if any(f not in cls._allow_modify_closed_period for f in values):
+                cls.check_period_closed(moves)
+            for move in moves:
+                internal_quantity = cls._get_internal_quantity(move.quantity,
+                        move.uom, move.product)
+                if (internal_quantity != move.internal_quantity
+                        and internal_quantity
+                        != values.get('internal_quantity')):
+                    cls.write([move], {
+                            'internal_quantity': internal_quantity,
+                            })
 
     @classmethod
     def delete(cls, moves):
@@ -681,6 +706,24 @@ class Move(Workflow, ModelSQL, ModelView):
                 cls.raise_user_error('del_draft_cancel', (move.rec_name,))
         super(Move, cls).delete(moves)
 
+    @staticmethod
+    def check_origin_types():
+        "Location types to check for origin"
+        return set()
+
+    @classmethod
+    def check_origin(cls, moves, types=None):
+        if types is None:
+            types = cls.check_origin_types()
+        if not types:
+            return
+        for move in moves:
+            if ((move.from_location.type in types
+                        or move.to_location.type in types)
+                    and not move.origin):
+                cls.raise_user_warning('%s.done' % move,
+                    'no_origin', move.rec_name)
+
     def pick_product(self, location_quantities):
         """
         Pick the product across the location. Naive (fast) implementation.
@@ -706,7 +749,7 @@ class Move(Workflow, ModelSQL, ModelView):
         return to_pick
 
     @classmethod
-    def assign_try(cls, moves, grouping=('product',)):
+    def assign_try(cls, moves, with_childs=True, grouping=('product',)):
         '''
         Try to assign moves.
         It will split the moves to assign as much possible.
@@ -720,9 +763,13 @@ class Move(Workflow, ModelSQL, ModelView):
 
         Transaction().cursor.lock(cls._table)
 
-        locations = Location.search([
-                ('parent', 'child_of', [x.from_location.id for x in moves]),
-                ])
+        if with_childs:
+            locations = Location.search([
+                    ('parent', 'child_of',
+                        [x.from_location.id for x in moves]),
+                    ])
+        else:
+            locations = list(set((m.from_location for m in moves)))
         with Transaction().set_context(
                 stock_date_end=Date.today(),
                 stock_assign=True):
@@ -746,9 +793,12 @@ class Move(Workflow, ModelSQL, ModelView):
                 continue
             to_location = move.to_location
             location_qties = {}
-            childs = Location.search([
-                    ('parent', 'child_of', [move.from_location.id]),
-                    ])
+            if with_childs:
+                childs = Location.search([
+                        ('parent', 'child_of', [move.from_location.id]),
+                        ])
+            else:
+                childs = [move.from_location]
             for location in childs:
                 key = get_key(location)
                 if key in pbl:
@@ -789,3 +839,367 @@ class Move(Workflow, ModelSQL, ModelView):
                 pbl[from_key] = pbl.get(from_key, 0.0) - qty_default_uom
                 pbl[to_key] = pbl.get(to_key, 0.0) + qty_default_uom
         return success
+
+    @classmethod
+    def compute_quantities_query(cls, location_ids, with_childs=False,
+            grouping=('product',), grouping_filter=None):
+        """
+        Prepare a query object to compute for each location and product the
+        stock quantity in the default uom of the product.
+
+        The context with keys:
+            stock_date_end: if set the date of the stock computation.
+            stock_date_start: if set return the delta of the stock between the
+                two dates, (ignored if stock_date_end is missing).
+            stock_assign: if set compute also the assigned moves as done.
+            forecast: if set compute the forecast quantity.
+            stock_destinations: A list of location ids. If set, restrict the
+                computation to moves from and to those locations.
+            stock_skip_warehouse: if set, quantities on a warehouse are no more
+                quantities of all child locations but quantities of the storage
+                zone.
+        If with_childs, it computes also for child locations.
+        grouping is a tuple of Move field names and defines how stock moves are
+            grouped.
+        grouping_filter is a tuple of values, for the Move's field at the same
+            position in grouping tuple, used to filter which moves are used to
+            compute quantities. It must be None or have the same number of
+            elements than grouping. If no grouping_filter is provided it
+            returns quantities for all products.
+
+        The query return the location as first column, after the fields in
+            grouping, and the last column is the quantity.
+        """
+        pool = Pool()
+        Rule = pool.get('ir.rule')
+        Location = pool.get('stock.location')
+        Date = pool.get('ir.date')
+        Period = pool.get('stock.period')
+        Move = pool.get('stock.move')
+        Product = pool.get('product.product')
+        Template = pool.get('product.template')
+
+        move = Move.__table__()
+        product = Product.__table__()
+        template = Template.__table__()
+
+        today = Date.today()
+
+        if not location_ids:
+            return None
+        context = Transaction().context.copy()
+
+        for field in grouping:
+            if field not in Move._fields:
+                raise ValueError('"%s" has no field "%s"' % (Move, field))
+        assert grouping_filter is None or len(grouping_filter) == len(grouping)
+
+        move_rule_query = Rule.domain_get('stock.move')
+        if move_rule_query is None:
+            move_rule_query = Literal(True)
+
+        PeriodCache = Period.get_cache(grouping)
+        period = None
+        if PeriodCache:
+            period_cache = PeriodCache.__table__()
+
+        if not context.get('stock_date_end'):
+            context['stock_date_end'] = datetime.date.max
+
+        # date end in the past or today: filter on state done
+        if (context['stock_date_end'] < today
+                or (context['stock_date_end'] == today
+                    and not context.get('forecast'))):
+            state_date_clause = (
+                move.state.in_(['done',
+                        context.get('stock_assign') and 'assigned' or 'done'])
+                & (
+                    (
+                        (move.effective_date == None)
+                        & (move.planned_date <= context['stock_date_end'])
+                        )
+                    | (move.effective_date <= context['stock_date_end'])
+                    )
+                )
+        # future date end: filter move on state done and date
+        # before today, or on all state and date between today and
+        # date_end.
+        else:
+            state_date_clause = (
+                (move.state.in_(['done',
+                            context.get('stock_assign') and 'assigned'
+                            or 'done'])
+                    & (
+                        (
+                            (move.effective_date == None)
+                            & (move.planned_date <= today)
+                            )
+                        | (move.effective_date <= today)
+                        )
+                    )
+                | (move.state.in_(['done', 'assigned', 'draft'])
+                    & (
+                        (
+                            (move.effective_date == None)
+                            & (Coalesce(move.planned_date, datetime.date.max)
+                                <= context['stock_date_end'])
+                            & (Coalesce(move.planned_date, datetime.date.max)
+                                >= today)
+                            )
+                        | (
+                            (move.effective_date <= context['stock_date_end'])
+                            & (move.effective_date >= today)
+                            )
+                        )
+                    )
+                )
+
+        if context.get('stock_date_start'):
+            if context['stock_date_start'] > today:
+                state_date_clause &= (
+                    move.state.in_(['done', 'assigned', 'draft'])
+                    & (
+                        (
+                            (move.effective_date == None)
+                            & (
+                                (move.planned_date >=
+                                    context['stock_date_start'])
+                                | (move.planned_date == None)
+                                )
+                            )
+                        | (move.effective_date >= context['stock_date_start'])
+                        )
+                    )
+            else:
+                state_date_clause &= (
+                    (
+                        move.state.in_(['done', 'assigned', 'draft'])
+                        & (
+                            (
+                                (move.effective_date == None)
+                                & (
+                                    (move.planned_date >= today)
+                                    | (move.planned_date == None)
+                                    )
+                                )
+                            | (move.effective_date >= today)
+                            )
+                        )
+                    | (
+                        move.state.in_(['done',
+                                context.get('stock_assign') and 'assigned'
+                                or 'done'])
+                        & (
+                            (
+                                (move.effective_date == None)
+                                & (
+                                    (
+                                        (move.planned_date >=
+                                            context['stock_date_start'])
+                                        & (move.planned_date < today)
+                                        )
+                                    | (move.planned_date == None)
+                                    )
+                                )
+                            | (
+                                (move.effective_date >=
+                                    context['stock_date_start'])
+                                & (move.effective_date < today)
+                                )
+                            )
+                        )
+                    )
+        elif PeriodCache:
+            with Transaction().set_user(0, set_context=True):
+                periods = Period.search([
+                        ('date', '<', context['stock_date_end']),
+                        ('state', '=', 'closed'),
+                        ], order=[('date', 'DESC')], limit=1)
+            if periods:
+                period, = periods
+                state_date_clause &= (
+                    Coalesce(move.effective_date, move.planned_date,
+                        datetime.date.max) > period.date)
+
+        if with_childs:
+            location_query = Location.search([
+                    ('parent', 'child_of', location_ids),
+                    ], query=True, order=[])
+        else:
+            location_query = location_ids[:]
+
+        from_ = move
+        if PeriodCache:
+            from_period = period_cache
+        if grouping_filter and any(grouping_filter):
+            where = where_period = Literal(True)
+            for fieldname, grouping_ids in zip(grouping, grouping_filter):
+                if not grouping_ids:
+                    continue
+                column = Column(move, fieldname)
+                if PeriodCache:
+                    cache_column = Column(period_cache, fieldname)
+                if isinstance(grouping_ids[0], (int, long, float, Decimal)):
+                    where &= reduce_ids(column, grouping_ids)
+                    if PeriodCache:
+                        where_period &= reduce_ids(cache_column, grouping_ids)
+                else:
+                    where &= column.in_(grouping_ids)
+                    if PeriodCache:
+                        where_period &= cache_column.in_(grouping_ids)
+        else:
+            where = where_period = template.active == True
+            from_ = from_.join(product, condition=move.product == product.id)
+            from_ = from_.join(template,
+                condition=product.template == template.id)
+            if PeriodCache:
+                from_period = from_period.join(product,
+                    condition=period_cache.product == product.id)
+                from_period = from_period.join(template,
+                    condition=product.template == template.id)
+
+        if context.get('stock_destinations'):
+            destinations = context['stock_destinations']
+            dest_clause_from = move.from_location.in_(destinations)
+            dest_clause_to = move.to_location.in_(destinations)
+
+            if PeriodCache:
+                dest_clause_period = period_cache.location.in_(destinations)
+
+        else:
+            dest_clause_from = dest_clause_to = dest_clause_period = \
+                Literal(True)
+
+        # The main select clause is a union between three similar subqueries.
+        # One that sums incoming moves towards locations, one that sums
+        # outgoing moves and one for the period cache.  UNION ALL is used
+        # because we already know that there will be no duplicates.
+        move_keys = [Column(move, key).as_(key) for key in grouping]
+        query = from_.select(move.to_location.as_('location'),
+            Sum(move.internal_quantity).as_('quantity'),
+            *move_keys,
+            where=state_date_clause
+            & where
+            & move.to_location.in_(location_query)
+            & move.id.in_(move_rule_query)
+            & dest_clause_from,
+            group_by=[move.to_location] + move_keys)
+        query = Union(query, from_.select(move.from_location.as_('location'),
+                (-Sum(move.internal_quantity)).as_('quantity'),
+                *move_keys,
+                where=state_date_clause
+                & where
+                & move.from_location.in_(location_query)
+                & move.id.in_(move_rule_query)
+                & dest_clause_to,
+                group_by=[move.from_location] + move_keys),
+            all_=True)
+        if PeriodCache:
+            period_keys = [Column(period_cache, key).as_(key)
+                for key in grouping]
+            query = Union(query, from_period.select(
+                    period_cache.location.as_('location'),
+                    period_cache.internal_quantity.as_('quantity'),
+                    *period_keys,
+                    where=(period_cache.period
+                        == (period.id if period else None))
+                    & where_period
+                    & period_cache.location.in_(location_query)
+                    & dest_clause_period),
+                all_=True)
+        query_keys = [Column(query, key).as_(key) for key in grouping]
+        columns = ([query.location.as_('location')]
+            + query_keys
+            + [Sum(query.quantity).as_('quantity')])
+        query = query.select(*columns,
+            group_by=[query.location] + query_keys)
+        return query
+
+    @classmethod
+    def compute_quantities(cls, query, location_ids, with_childs=False,
+            grouping=('product',), grouping_filter=None):
+        """
+        Executes the supplied query to compute for each location and product
+        the stock quantity in the default uom of the product and rounded to
+        Uom's rounding digits.
+
+        See compute_quantites_query for params explanation.
+
+        Return a dictionary with location id and grouping as key
+            and quantity as value.
+        """
+        pool = Pool()
+        Location = pool.get('stock.location')
+        Product = pool.get('product.product')
+        Uom = pool.get('product.uom')
+
+        assert query is not None, (
+            "Query in Move.compute_quantities() can't be None")
+        assert 'product' in grouping
+
+        cursor = Transaction().cursor
+        cursor.execute(*query)
+        raw_lines = cursor.fetchall()
+
+        product_getter = operator.itemgetter(grouping.index('product') + 1)
+        res_product_ids = set()
+        quantities = {}
+        keys = set()
+        for line in raw_lines:
+            location = line[0]
+            key = tuple(line[1:-1])
+            quantity = line[-1]
+            quantities[(location,) + key] = quantity
+            res_product_ids.add(product_getter(line))
+            keys.add(key)
+
+        # Propagate quantities on from child locations to their parents
+        if with_childs:
+            # Fetch all child locations
+            locations = Location.search([
+                    ('parent', 'child_of', location_ids),
+                    ])
+            # Generate a set of locations without childs and a dict
+            # giving the parent of each location.
+            leafs = set([l.id for l in locations])
+            parent = {}
+            for location in locations:
+                if not location.parent:
+                    continue
+                if location.parent.id in leafs:
+                    leafs.remove(location.parent.id)
+                parent[location.id] = location.parent.id
+            locations = set((l.id for l in locations))
+            while leafs:
+                for l in leafs:
+                    locations.remove(l)
+                    if l not in parent:
+                        continue
+                    for key in keys:
+                        parent_key = (parent[l],) + key
+                        quantities.setdefault(parent_key, 0)
+                        quantities[parent_key] += quantities.get((l,) + key, 0)
+                next_leafs = set(locations)
+                for l in locations:
+                    if l not in parent:
+                        continue
+                    if parent[l] in next_leafs and parent[l] in locations:
+                        next_leafs.remove(parent[l])
+                leafs = next_leafs
+
+            # clean result
+            for key in quantities.keys():
+                location = key[0]
+                if location not in location_ids:
+                    del quantities[key]
+
+        # Round quantities
+        default_uom = dict((p.id, p.default_uom) for p in
+            Product.browse(list(res_product_ids)))
+        for key, quantity in quantities.iteritems():
+            location = key[0]
+            product = product_getter(key)
+            uom = default_uom[product]
+            quantities[key] = Uom.round(quantity, uom.rounding)
+
+        return quantities
diff --git a/party.xml b/party.xml
index 4e32c9a..9892d32 100644
--- a/party.xml
+++ b/party.xml
@@ -6,7 +6,7 @@ this repository contains the full copyright notices and license terms. -->
         <record model="ir.action.act_window" id="act_shipment_out_form2">
             <field name="name">Customer Shipments</field>
             <field name="res_model">stock.shipment.out</field>
-            <field name="domain">[("customer", "=", Eval('active_id'))]</field>
+            <field name="domain">[('customer', 'in', Eval('active_ids'))]</field>
         </record>
         <record model="ir.action.keyword"
                 id="act_open_purchase_keyword1">
@@ -23,7 +23,7 @@ this repository contains the full copyright notices and license terms. -->
         <record model="ir.action.act_window" id="act_shipment_out_form3">
             <field name="name">Supplier Shipments</field>
             <field name="res_model">stock.shipment.in</field>
-            <field name="domain">[("supplier", "=", Eval('active_id'))]</field>
+            <field name="domain">[('supplier', 'in', Eval('active_ids'))]</field>
         </record>
         <record model="ir.action.keyword"
                 id="act_open_purchase_keyword2">
diff --git a/product.py b/product.py
index 4ca8975..9acfc84 100644
--- a/product.py
+++ b/product.py
@@ -2,9 +2,8 @@
 #this repository contains the full copyright notices and license terms.
 import datetime
 from decimal import Decimal
-from operator import itemgetter
-from sql import Literal, Union, Column
-from sql.aggregate import Max, Sum
+from sql import Literal
+from sql.aggregate import Max
 from sql.functions import Now
 from sql.conditionals import Coalesce
 
@@ -12,7 +11,6 @@ from trytond.model import ModelSQL, ModelView, fields
 from trytond.wizard import Wizard, StateView, StateAction, Button
 from trytond.pyson import PYSONEncoder, Eval, Or
 from trytond.transaction import Transaction
-from trytond.tools import reduce_ids
 from trytond.pool import Pool, PoolMeta
 
 from .move import StockMixin
@@ -46,35 +44,41 @@ class Template:
         cls._error_messages.update({
                 'change_default_uom': ('You cannot change the default uom for '
                     'a product which is associated to stock moves.'),
+                'change_type': ('You cannot change the type for a product '
+                    'which is associated to stock moves.'),
                 })
         cls.cost_price.states['required'] = Or(
             cls.cost_price.states.get('required', True),
             Eval('type').in_(['goods', 'assets']))
         cls.cost_price.depends.append('type')
+        cls._modify_no_move = [
+            ('default_uom', 'change_default_uom'),
+            ('type', 'change_type'),
+            ]
 
     @classmethod
-    def write(cls, templates, vals):
+    def check_no_move(cls, templates, error):
         Move = Pool().get('stock.move')
         cursor = Transaction().cursor
-        if not vals.get("default_uom"):
-            super(Template, cls).write(templates, vals)
-            return
-
         for i in range(0, len(templates), cursor.IN_MAX):
             sub_ids = [t.id for t in templates[i:i + cursor.IN_MAX]]
-            templates_to_check = cls.search([
-                    ('id', 'in', sub_ids),
-                    ('default_uom', '!=', vals['default_uom']),
-                    ])
-
-            if templates_to_check:
-                if Move.search([
-                            ('product.template', 'in',
-                                [t.id for t in templates_to_check]),
-                            ], limit=1):
-                    cls.raise_user_error('change_default_uom')
+            moves = Move.search([
+                    ('product.template', 'in', sub_ids),
+                    ],
+                limit=1, order=[])
+            if moves:
+                cls.raise_user_error(error)
 
-        super(Template, cls).write(templates, vals)
+    @classmethod
+    def write(cls, *args):
+        if Transaction().user != 0:
+            actions = iter(args)
+            for templates, values in zip(actions, actions):
+                for field, error in cls._modify_no_move:
+                    if values.get(field):
+                        cls.check_no_move(templates, error)
+                        break
+        super(Template, cls).write(*args)
 
 
 class Product(object, StockMixin):
@@ -122,346 +126,52 @@ class Product(object, StockMixin):
         uom of the product.
 
         The context with keys:
-                stock_date_end: if set the date of the stock computation.
-                stock_date_start: if set return the delta of the stock
-                    between the two dates, (ignored if stock_date_end is
-                    missing).
-                stock_assign: if set compute also the assigned moves as done.
-                forecast: if set compute the forecast quantity.
-                stock_destinations: A list of location ids. If set, restrict
-                    the computation to moves from and to those locations.
-                stock_skip_warehouse: if set, quantities on a warehouse are no
-                    more quantities of all child locations but quantities of
-                    the storage zone.
-        If product_ids is None all products are used.
-        If with_childs, it computes also for child locations.
-        grouping defines how stock moves are grouped.
+            stock_skip_warehouse: if set, quantities on a warehouse are no more
+                quantities of all child locations but quantities of the storage
+                zone.
 
         Return a dictionary with location id and grouping as key
                 and quantity as value.
         """
         pool = Pool()
-        Uom = pool.get('product.uom')
-        Rule = pool.get('ir.rule')
         Location = pool.get('stock.location')
-        Date = pool.get('ir.date')
-        Period = pool.get('stock.period')
         Move = pool.get('stock.move')
-        Product = pool.get('product.product')
-        Template = pool.get('product.template')
-
-        move = Move.__table__()
-        product = Product.__table__()
-        template = Template.__table__()
-
-        today = Date.today()
-
-        if not location_ids:
-            return {}
-        cursor = Transaction().cursor
-        context = Transaction().context.copy()
-
-        for field in grouping:
-            if field not in Move._fields:
-                raise ValueError('"%s" has no field "%s"' % (Move, field))
-        assert 'product' in grouping
 
         # Skip warehouse location in favor of their storage location
         # to compute quantities. Keep track of which ids to remove
         # and to add after the query.
-        location_ids = set(location_ids)
         storage_to_remove = set()
         wh_to_add = {}
-        for location in Location.browse(list(location_ids)):
-            if (location.type == 'warehouse'
-                    and Transaction().context.get('stock_skip_warehouse')):
-                location_ids.remove(location.id)
-                if location.storage_location.id not in location_ids:
-                    storage_to_remove.add(location.storage_location.id)
-                location_ids.add(location.storage_location.id)
-                wh_to_add[location.id] = location.storage_location.id
-        location_ids = list(location_ids)
-
-        move_rule_query = Rule.domain_get('stock.move')
-        if move_rule_query is None:
-            move_rule_query = Literal(True)
-
-        PeriodCache = Period.get_cache(grouping)
-        period = None
-        if PeriodCache:
-            period_cache = PeriodCache.__table__()
-
-        if not context.get('stock_date_end'):
-            context['stock_date_end'] = datetime.date.max
-
-        # date end in the past or today: filter on state done
-        if (context['stock_date_end'] < today
-                or (context['stock_date_end'] == today
-                    and not context.get('forecast'))):
-            state_date_clause = (
-                move.state.in_(['done',
-                        context.get('stock_assign') and 'assigned' or 'done'])
-                & (
-                    (
-                        (move.effective_date == None)
-                        & (move.planned_date <= context['stock_date_end'])
-                        )
-                    | (move.effective_date <= context['stock_date_end'])
-                    )
-                )
-        # future date end: filter move on state done and date
-        # before today, or on all state and date between today and
-        # date_end.
-        else:
-            state_date_clause = (
-                (move.state.in_(['done',
-                            context.get('stock_assign') and 'assigned'
-                            or 'done'])
-                    & (
-                        (
-                            (move.effective_date == None)
-                            & (move.planned_date <= today)
-                            )
-                        | (move.effective_date <= today)
-                        )
-                    )
-                | (move.state.in_(['done', 'assigned', 'draft'])
-                    & (
-                        (
-                            (move.effective_date == None)
-                            & (Coalesce(move.planned_date, datetime.date.max)
-                                <= context['stock_date_end'])
-                            & (Coalesce(move.planned_date, datetime.date.max)
-                                >= today)
-                            )
-                        | (
-                            (move.effective_date <= context['stock_date_end'])
-                            & (move.effective_date >= today)
-                            )
-                        )
-                    )
-                )
-
-        if context.get('stock_date_start'):
-            if context['stock_date_start'] > today:
-                state_date_clause &= (
-                    move.state.in_(['done', 'assigned', 'draft'])
-                    & (
-                        (
-                            (move.effective_date == None)
-                            & (
-                                (move.planned_date >=
-                                    context['stock_date_start'])
-                                | (move.planned_date == None)
-                                )
-                            )
-                        | (move.effective_date >= context['stock_date_start'])
-                        )
-                    )
-            else:
-                state_date_clause &= (
-                    (
-                        move.state.in_(['done', 'assigned', 'draft'])
-                        & (
-                            (
-                                (move.effective_date == None)
-                                & (
-                                    (move.planned_date >= today)
-                                    | (move.planned_date == None)
-                                    )
-                                )
-                            | (move.effective_date >= today)
-                            )
-                        )
-                    | (
-                        move.state.in_(['done',
-                                context.get('stock_assign') and 'assigned'
-                                or 'done'])
-                        & (
-                            (
-                                (move.effective_date == None)
-                                & (
-                                    (
-                                        (move.planned_date >=
-                                            context['stock_date_start'])
-                                        & (move.planned_date < today)
-                                        )
-                                    | (move.planned_date == None)
-                                    )
-                                )
-                            | (
-                                (move.effective_date >=
-                                    context['stock_date_start'])
-                                & (move.effective_date < today)
-                                )
-                            )
-                        )
-                    )
-        elif PeriodCache:
-            with Transaction().set_user(0, set_context=True):
-                periods = Period.search([
-                        ('date', '<', context['stock_date_end']),
-                        ('state', '=', 'closed'),
-                        ], order=[('date', 'DESC')], limit=1)
-            if periods:
-                period, = periods
-                state_date_clause &= (
-                    Coalesce(move.effective_date, move.planned_date,
-                        datetime.date.max) > period.date)
-
-        if with_childs:
-            location_query = Location.search([
-                    ('parent', 'child_of', location_ids),
-                    ], query=True, order=[])
-        else:
-            location_query = location_ids[:]
-
-        from_ = move
-        if PeriodCache:
-            from_period = period_cache
-        if product_ids:
-            where = reduce_ids(move.product, product_ids)
-            if PeriodCache:
-                where_period = reduce_ids(period_cache.product, product_ids)
-        else:
-            where = where_period = template.active == True
-            from_ = from_.join(product, condition=move.product == product.id)
-            from_ = from_.join(template,
-                condition=product.template == template.id)
-            if PeriodCache:
-                from_period = from_period.join(product,
-                    condition=period_cache.product == product.id)
-                from_period = from_period.join(template,
-                    condition=product.template == template.id)
-
-        if context.get('stock_destinations'):
-            destinations = context.get('stock_destinations')
-            dest_clause_from = move.from_location.in_(destinations)
-            dest_clause_to = move.to_location.in_(destinations)
-
-            if PeriodCache:
-                dest_clause_period = period_cache.location.in_(destinations)
-
-        else:
-            dest_clause_from = dest_clause_to = dest_clause_period = \
-                Literal(True)
-
-        # The main select clause is a union between three similar subqueries.
-        # One that sums incoming moves towards locations, one that sums
-        # outgoing moves and one for the period cache.  UNION ALL is used
-        # because we already know that there will be no duplicates.
-        move_keys = [Column(move, key).as_(key) for key in grouping]
-        query = from_.select(move.to_location.as_('location'),
-            Sum(move.internal_quantity).as_('quantity'),
-            *move_keys,
-            where=state_date_clause
-            & where
-            & move.to_location.in_(location_query)
-            & move.id.in_(move_rule_query)
-            & dest_clause_from,
-            group_by=[move.to_location] + move_keys)
-        query = Union(query, from_.select(move.from_location.as_('location'),
-                (-Sum(move.internal_quantity)).as_('quantity'),
-                *move_keys,
-                where=state_date_clause
-                & where
-                & move.from_location.in_(location_query)
-                & move.id.in_(move_rule_query)
-                & dest_clause_to,
-                group_by=[move.from_location] + move_keys),
-            all_=True)
-        if PeriodCache:
-            period_keys = [Column(period_cache, key).as_(key)
-                for key in grouping]
-            query = Union(query, from_period.select(
-                    period_cache.location.as_('location'),
-                    period_cache.internal_quantity.as_('quantity'),
-                    *period_keys,
-                    where=(period_cache.period
-                        == (period.id if period else None))
-                    & where_period
-                    & period_cache.location.in_(location_query)
-                    & dest_clause_period),
-                all_=True)
-        query_keys = [Column(query, key).as_(key) for key in grouping]
-        columns = ([query.location.as_('location')]
-            + query_keys
-            + [Sum(query.quantity).as_('quantity')])
-        query = query.select(*columns,
-            group_by=[query.location] + query_keys)
-        cursor.execute(*query)
-        raw_lines = cursor.fetchall()
-
-        product_getter = itemgetter(grouping.index('product') + 1)
-        res_product_ids = set()
-        res = {}
-        keys = set()
-        for line in raw_lines:
-            location = line[0]
-            key = tuple(line[1:-1])
-            quantity = line[-1]
-            res[(location,) + key] = quantity
-            res_product_ids.add(product_getter(line))
-            keys.add(key)
-
-        # Propagate quantities on from child locations to their parents
-        if with_childs:
-            # Fetch all child locations
-            locations = Location.search([
-                    ('parent', 'child_of', location_ids),
-                    ])
-            # Generate a set of locations without childs and a dict
-            # giving the parent of each location.
-            leafs = set([l.id for l in locations])
-            parent = {}
-            for location in locations:
-                if not location.parent:
-                    continue
-                if location.parent.id in leafs:
-                    leafs.remove(location.parent.id)
-                parent[location.id] = location.parent.id
-            locations = set((l.id for l in locations))
-            while leafs:
-                for l in leafs:
-                    locations.remove(l)
-                    if l not in parent:
-                        continue
-                    for key in keys:
-                        parent_key = (parent[l],) + key
-                        res.setdefault(parent_key, 0)
-                        res[parent_key] += res.get((l,) + key, 0)
-                next_leafs = set(locations)
-                for l in locations:
-                    if l not in parent:
-                        continue
-                    if parent[l] in next_leafs and parent[l] in locations:
-                        next_leafs.remove(parent[l])
-                leafs = next_leafs
-
-            # clean result
-            for key in res.keys():
-                location = key[0]
-                if location not in location_ids:
-                    del res[key]
-
-        # Round quantities
-        default_uom = dict((p.id, p.default_uom) for p in
-            cls.browse(list(res_product_ids)))
-        for key, quantity in res.iteritems():
-            location = key[0]
-            product = product_getter(key)
-            uom = default_uom[product]
-            res[key] = Uom.round(quantity, uom.rounding)
+        if Transaction().context.get('stock_skip_warehouse'):
+            location_ids = set(location_ids)
+            for location in Location.browse(list(location_ids)):
+                if location.type == 'warehouse':
+                    location_ids.remove(location.id)
+                    if location.storage_location.id not in location_ids:
+                        storage_to_remove.add(location.storage_location.id)
+                    location_ids.add(location.storage_location.id)
+                    wh_to_add[location.id] = location.storage_location.id
+            location_ids = list(location_ids)
+
+        grouping_filter = (product_ids,) + tuple(None for k in grouping[1:])
+        query = Move.compute_quantities_query(location_ids, with_childs,
+            grouping=grouping, grouping_filter=grouping_filter)
+        if query is None:
+            return {}
+        quantities = Move.compute_quantities(query, location_ids, with_childs,
+            grouping=grouping, grouping_filter=grouping_filter)
 
         if wh_to_add:
+            if product_ids is None:
+                product_ids = set((p for s, p in quantities))
             for wh, storage in wh_to_add.iteritems():
                 for product in product_ids:
-                    if (storage, product) in res:
-                        res[(wh, product)] = res[(storage, product)]
+                    if (storage, product) in quantities:
+                        quantities[(wh, product)] = quantities[
+                            (storage, product)]
                         if storage in storage_to_remove:
-                            del res[(storage, product)]
-
-        return res
+                            del quantities[(storage, product)]
+        return quantities
 
 
 class ProductByLocationStart(ModelView):
diff --git a/setup.py b/setup.py
index 0ca2154..433d031 100644
--- a/setup.py
+++ b/setup.py
@@ -11,35 +11,52 @@ import ConfigParser
 def read(fname):
     return open(os.path.join(os.path.dirname(__file__), fname)).read()
 
+
+def get_require_version(name):
+    if minor_version % 2:
+        require = '%s >= %s.%s.dev0, < %s.%s'
+    else:
+        require = '%s >= %s.%s, < %s.%s'
+    require %= (name, major_version, minor_version,
+        major_version, minor_version + 1)
+    return require
+
 config = ConfigParser.ConfigParser()
 config.readfp(open('tryton.cfg'))
 info = dict(config.items('tryton'))
 for key in ('depends', 'extras_depend', 'xml'):
     if key in info:
         info[key] = info[key].strip().splitlines()
-major_version, minor_version, _ = info.get('version', '0.0.1').split('.', 2)
+version = info.get('version', '0.0.1')
+major_version, minor_version, _ = version.split('.', 2)
 major_version = int(major_version)
 minor_version = int(minor_version)
+name = 'trytond_stock'
+
+download_url = 'http://downloads.tryton.org/%s.%s/' % (
+    major_version, minor_version)
+if minor_version % 2:
+    version = '%s.%s.dev0' % (major_version, minor_version)
+    download_url = (
+        'hg+http://hg.tryton.org/modules/%s#egg=%s-%s' % (
+            name[8:], name, version))
 
 requires = ['python-sql']
 for dep in info.get('depends', []):
     if not re.match(r'(ir|res|webdav)(\W|$)', dep):
-        requires.append('trytond_%s >= %s.%s, < %s.%s' %
-            (dep, major_version, minor_version, major_version,
-                minor_version + 1))
-requires.append('trytond >= %s.%s, < %s.%s' %
-    (major_version, minor_version, major_version, minor_version + 1))
-tests_require = ['proteus >= %s.%s, < %s.%s' %
-    (major_version, minor_version, major_version, minor_version + 1)]
+        requires.append(get_require_version('trytond_%s' % dep))
+requires.append(get_require_version('trytond'))
+tests_require = [get_require_version('proteus')]
 
-setup(name='trytond_stock',
-    version=info.get('version', '0.0.1'),
+setup(name=name,
+    version=version,
     description='Tryton module for stock and inventory',
     long_description=read('README'),
     author='Tryton',
+    author_email='issue_tracker at tryton.org',
     url='http://www.tryton.org/',
-    download_url=("http://downloads.tryton.org/" +
-        info.get('version', '0.0.1').rsplit('.', 1)[0] + '/'),
+    download_url=download_url,
+    keywords='tryton stock',
     package_dir={'trytond.modules.stock': '.'},
     packages=[
         'trytond.modules.stock',
@@ -70,7 +87,6 @@ setup(name='trytond_stock',
         'Natural Language :: Slovenian',
         'Natural Language :: Spanish',
         'Operating System :: OS Independent',
-        'Programming Language :: Python :: 2.6',
         'Programming Language :: Python :: 2.7',
         'Topic :: Office/Business',
         ],
diff --git a/shipment.py b/shipment.py
index 80c0be1..47a3b38 100644
--- a/shipment.py
+++ b/shipment.py
@@ -40,7 +40,11 @@ class ShipmentIn(Workflow, ModelSQL, ModelView):
     "Supplier Shipment"
     __name__ = 'stock.shipment.in'
     _rec_name = 'code'
-    effective_date = fields.Date('Effective Date', readonly=True)
+    effective_date = fields.Date('Effective Date',
+        states={
+            'readonly': Eval('state').in_(['cancel', 'done']),
+            },
+        depends=['state'])
     planned_date = fields.Date('Planned Date', states={
             'readonly': Not(Equal(Eval('state'), 'draft')),
             }, depends=['state'])
@@ -60,11 +64,11 @@ class ShipmentIn(Workflow, ModelSQL, ModelView):
     supplier = fields.Many2One('party.party', 'Supplier',
         states={
             'readonly': And(Or(Not(Equal(Eval('state'), 'draft')),
-                    Bool(Eval('incoming_moves'))), Bool(Eval('supplier'))),
-            }, on_change=['supplier'], required=True,
-        depends=['state', 'incoming_moves', 'supplier'])
+                    Bool(Eval('incoming_moves', [0]))), Bool(Eval('supplier'))),
+            }, required=True,
+        depends=['state', 'supplier'])
     supplier_location = fields.Function(fields.Many2One('stock.location',
-            'Supplier Location', on_change_with=['supplier']),
+            'Supplier Location'),
         'on_change_with_supplier_location')
     contact_address = fields.Many2One('party.address', 'Contact Address',
         states={
@@ -75,13 +79,13 @@ class ShipmentIn(Workflow, ModelSQL, ModelView):
         required=True, domain=[('type', '=', 'warehouse')],
         states={
             'readonly': Or(In(Eval('state'), ['cancel', 'done']),
-                Bool(Eval('incoming_moves'))),
-            }, depends=['state', 'incoming_moves'])
+                Bool(Eval('incoming_moves', [0]))),
+            }, depends=['state'])
     warehouse_input = fields.Function(fields.Many2One('stock.location',
-            'Warehouse Input', on_change_with=['warehouse']),
+            'Warehouse Input'),
         'on_change_with_warehouse_input')
     warehouse_storage = fields.Function(fields.Many2One('stock.location',
-            'Warehouse Storage', on_change_with=['warehouse']),
+            'Warehouse Storage'),
         'on_change_with_warehouse_storage')
     incoming_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
             'Incoming Moves',
@@ -259,12 +263,14 @@ class ShipmentIn(Workflow, ModelSQL, ModelView):
     def default_company():
         return Transaction().context.get('company')
 
+    @fields.depends('supplier')
     def on_change_supplier(self):
         address = None
         if self.supplier:
             address = self.supplier.address_get()
         return {'contact_address': address.id if address else None}
 
+    @fields.depends('supplier')
     def on_change_with_supplier_location(self, name=None):
         if self.supplier:
             return self.supplier.supplier_location.id
@@ -275,6 +281,7 @@ class ShipmentIn(Workflow, ModelSQL, ModelView):
         if warehouse:
             return cls(warehouse=warehouse).on_change_with_warehouse_input()
 
+    @fields.depends('warehouse')
     def on_change_with_warehouse_input(self, name=None):
         if self.warehouse:
             return self.warehouse.input_location.id
@@ -285,6 +292,7 @@ class ShipmentIn(Workflow, ModelSQL, ModelView):
         if warehouse:
             return cls(warehouse=warehouse).on_change_with_warehouse_storage()
 
+    @fields.depends('warehouse')
     def on_change_with_warehouse_storage(self, name=None):
         if self.warehouse:
             return self.warehouse.storage_location.id
@@ -375,9 +383,9 @@ class ShipmentIn(Workflow, ModelSQL, ModelView):
         return shipments
 
     @classmethod
-    def write(cls, shipments, values):
-        super(ShipmentIn, cls).write(shipments, values)
-        cls._set_move_planned_date(shipments)
+    def write(cls, *args):
+        super(ShipmentIn, cls).write(*args)
+        cls._set_move_planned_date(sum(args[::2], []))
 
     @classmethod
     def copy(cls, shipments, default=None):
@@ -465,7 +473,7 @@ class ShipmentIn(Workflow, ModelSQL, ModelView):
         Move = pool.get('stock.move')
         Date = pool.get('ir.date')
         Move.do([m for s in shipments for m in s.inventory_moves])
-        cls.write(shipments, {
+        cls.write([s for s in shipments if not s.effective_date], {
                 'effective_date': Date.today(),
                 })
 
@@ -474,7 +482,11 @@ class ShipmentInReturn(Workflow, ModelSQL, ModelView):
     "Supplier Return Shipment"
     __name__ = 'stock.shipment.in.return'
     _rec_name = 'code'
-    effective_date = fields.Date('Effective Date', readonly=True)
+    effective_date = fields.Date('Effective Date',
+        states={
+            'readonly': Eval('state').in_(['cancel', 'done']),
+            },
+        depends=['state'])
     planned_date = fields.Date('Planned Date',
         states={
             'readonly': Not(Equal(Eval('state'), 'draft')),
@@ -496,15 +508,15 @@ class ShipmentInReturn(Workflow, ModelSQL, ModelView):
     from_location = fields.Many2One('stock.location', "From Location",
         required=True, states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('moves'))),
+                Bool(Eval('moves', [0]))),
             }, domain=[('type', '=', 'storage')],
-        depends=['state', 'moves'])
+        depends=['state'])
     to_location = fields.Many2One('stock.location', "To Location",
         required=True, states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('moves'))),
+                Bool(Eval('moves', [0]))),
             }, domain=[('type', '=', 'supplier')],
-        depends=['state', 'moves'])
+        depends=['state'])
     moves = fields.One2Many('stock.move', 'shipment', 'Moves',
         states={
             'readonly': And(Or(Not(Equal(Eval('state'), 'draft')),
@@ -672,9 +684,9 @@ class ShipmentInReturn(Workflow, ModelSQL, ModelView):
         return shipments
 
     @classmethod
-    def write(cls, shipments, values):
-        super(ShipmentInReturn, cls).write(shipments, values)
-        cls._set_move_planned_date(shipments)
+    def write(cls, *args):
+        super(ShipmentInReturn, cls).write(*args)
+        cls._set_move_planned_date(sum(args[::2], []))
 
     @classmethod
     def delete(cls, shipments):
@@ -718,7 +730,7 @@ class ShipmentInReturn(Workflow, ModelSQL, ModelView):
         Date = pool.get('ir.date')
 
         Move.do([m for s in shipments for m in s.moves])
-        cls.write(shipments, {
+        cls.write([s for s in shipments if not s.effective_date], {
                 'effective_date': Date.today(),
                 })
 
@@ -738,37 +750,13 @@ class ShipmentInReturn(Workflow, ModelSQL, ModelView):
     @ModelView.button
     def assign_try(cls, shipments):
         pool = Pool()
-        Product = pool.get('product.product')
-        Uom = pool.get('product.uom')
-        Date = pool.get('ir.date')
         Move = pool.get('stock.move')
-
-        Transaction().cursor.lock(Move._table)
-
-        moves = [m for s in shipments for m in s.moves]
-        location_ids = [m.from_location.id for m in moves]
-        with Transaction().set_context(
-                stock_date_end=Date.today(),
-                stock_assign=True):
-            pbl = Product.products_by_location(location_ids=location_ids,
-                product_ids=[m.product.id for m in moves])
-
-        for move in moves:
-            if move.state != 'draft':
-                continue
-            if (move.from_location.id, move.product.id) in pbl:
-                qty_default_uom = pbl[(move.from_location.id, move.product.id)]
-                qty = Uom.compute_qty(move.product.default_uom,
-                    qty_default_uom, move.uom, round=False)
-                if qty < move.quantity:
-                    return False
-                pbl[(move.from_location.id, move.product.id)] = (
-                    pbl[(move.from_location.id, move.product.id)]
-                    - qty_default_uom)
-            else:
-                return False
-        cls.assign(shipments)
-        return True
+        if Move.assign_try([m for s in shipments for m in s.moves],
+                with_childs=False):
+            cls.assign(shipments)
+            return True
+        else:
+            return False
 
     @classmethod
     @ModelView.button
@@ -780,7 +768,11 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
     "Customer Shipment"
     __name__ = 'stock.shipment.out'
     _rec_name = 'code'
-    effective_date = fields.Date('Effective Date', readonly=True)
+    effective_date = fields.Date('Effective Date',
+        states={
+            'readonly': Eval('state').in_(['cancel', 'done']),
+            },
+        depends=['state'])
     planned_date = fields.Date('Planned Date',
         states={
             'readonly': Not(Equal(Eval('state'), 'draft')),
@@ -797,12 +789,11 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
     customer = fields.Many2One('party.party', 'Customer', required=True,
         states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('outgoing_moves'))),
-            }, on_change=['customer'],
-        depends=['state', 'outgoing_moves'])
+                Bool(Eval('outgoing_moves', [0]))),
+            },
+        depends=['state'])
     customer_location = fields.Function(fields.Many2One('stock.location',
-            'Customer Location', on_change_with=['customer']),
-        'on_change_with_customer_location')
+            'Customer Location'), 'on_change_with_customer_location')
     delivery_address = fields.Many2One('party.address',
         'Delivery Address', required=True,
         states={
@@ -816,15 +807,13 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
     warehouse = fields.Many2One('stock.location', "Warehouse", required=True,
         states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('outgoing_moves'))),
+                Bool(Eval('outgoing_moves', [0]))),
             }, domain=[('type', '=', 'warehouse')],
-        depends=['state', 'outgoing_moves'])
+        depends=['state'])
     warehouse_storage = fields.Function(fields.Many2One('stock.location',
-            'Warehouse Storage', on_change_with=['warehouse']),
-        'on_change_with_warehouse_storage')
+            'Warehouse Storage'), 'on_change_with_warehouse_storage')
     warehouse_output = fields.Function(fields.Many2One('stock.location',
-            'Warehouse Output', on_change_with=['warehouse']),
-        'on_change_with_warehouse_output')
+            'Warehouse Output'), 'on_change_with_warehouse_output')
     outgoing_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
             'Outgoing Moves',
             domain=[
@@ -990,12 +979,14 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
     def default_company():
         return Transaction().context.get('company')
 
+    @fields.depends('customer')
     def on_change_customer(self):
         address = None
         if self.customer:
             address = self.customer.address_get(type='delivery')
         return {'delivery_address': address.id if address else None}
 
+    @fields.depends('customer')
     def on_change_with_customer_location(self, name=None):
         if self.customer:
             return self.customer.customer_location.id
@@ -1006,6 +997,7 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
         if warehouse:
             return cls(warehouse=warehouse).on_change_with_warehouse_storage()
 
+    @fields.depends('warehouse')
     def on_change_with_warehouse_storage(self, name=None):
         if self.warehouse:
             return self.warehouse.storage_location.id
@@ -1016,6 +1008,7 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
         if warehouse:
             return cls(warehouse=warehouse).on_change_with_warehouse_output()
 
+    @fields.depends('warehouse')
     def on_change_with_warehouse_output(self, name=None):
         if self.warehouse:
             return self.warehouse.output_location.id
@@ -1086,22 +1079,26 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
             for move in shipment.outgoing_moves:
                 if move.state in ('cancel', 'done'):
                     continue
-                to_create.append({
-                        'from_location': (
-                            move.shipment.warehouse.storage_location.id),
-                        'to_location': move.from_location.id,
-                        'product': move.product.id,
-                        'uom': move.uom.id,
-                        'quantity': move.quantity,
-                        'shipment': str(shipment),
-                        'planned_date': move.planned_date,
-                        'state': 'draft',
-                        'company': move.company.id,
-                        'currency': move.currency.id,
-                        'unit_price': move.unit_price,
-                        })
+                to_create.append(shipment._get_inventory_move(move))
         if to_create:
-            Move.create(to_create)
+            Move.create([m._save_values for m in to_create])
+
+    def _get_inventory_move(self, move):
+        'Return inventory move for the outgoing move'
+        pool = Pool()
+        Move = pool.get('stock.move')
+        return Move(
+            from_location=move.shipment.warehouse.storage_location,
+            to_location=move.from_location,
+            product=move.product,
+            uom=move.uom,
+            quantity=move.quantity,
+            shipment=self,
+            planned_date=move.planned_date,
+            company=move.company,
+            currency=move.currency,
+            unit_price=move.unit_price,
+            )
 
     @classmethod
     @Workflow.transition('assigned')
@@ -1114,9 +1111,33 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
     def pack(cls, shipments):
         pool = Pool()
         Move = pool.get('stock.move')
-        Uom = pool.get('product.uom')
         Move.do([m for s in shipments for m in s.inventory_moves])
+        cls._sync_inventory_to_outgoing(shipments)
+        Move.assign([m for s in shipments for m in s.outgoing_moves])
 
+    def _get_outgoing_move(self, move):
+        'Return outgoing move for the inventory move'
+        pool = Pool()
+        Move = pool.get('stock.move')
+        return Move(
+            from_location=move.to_location,
+            to_location=self.customer.customer_location,
+            product=move.product,
+            uom=move.uom,
+            quantity=move.quantity,
+            shipment=self,
+            planned_date=self.planned_date,
+            company=move.company,
+            currency=move.company.currency,
+            unit_price=move.unit_price,
+            )
+
+    @classmethod
+    def _sync_inventory_to_outgoing(cls, shipments):
+        'Synchronise outgoing moves with inventory moves'
+        pool = Pool()
+        Move = pool.get('stock.move')
+        Uom = pool.get('product.uom')
         for shipment in shipments:
             # Sum all outgoing quantities
             outgoing_qty = {}
@@ -1152,21 +1173,11 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
 
                 unit_price = Uom.compute_price(move.product.default_uom,
                         move.product.list_price, move.uom)
-                to_create.append({
-                        'from_location': move.to_location.id,
-                        'to_location': shipment.customer.customer_location.id,
-                        'product': move.product.id,
-                        'uom': move.uom.id,
-                        'quantity': out_quantity,
-                        'shipment': str(shipment),
-                        'state': 'draft',
-                        'planned_date': shipment.planned_date,
-                        'company': move.company.id,
-                        'currency': move.company.currency.id,
-                        'unit_price': unit_price,
-                        })
+                to_create.append(shipment._get_outgoing_move(move))
+                to_create[-1].quantity = out_quantity
+                to_create[-1].unit_price = unit_price
             if to_create:
-                Move.create(to_create)
+                Move.create([m._save_values for m in to_create])
 
             #Re-read the shipment and remove exceeding quantities
             for move in shipment.outgoing_moves:
@@ -1183,8 +1194,6 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
                             })
                     outgoing_qty[move.product.id] -= removed_qty
 
-        Move.assign([m for s in shipments for m in s.outgoing_moves])
-
     @classmethod
     @ModelView.button
     @Workflow.transition('done')
@@ -1194,7 +1203,7 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
         Date = pool.get('ir.date')
 
         Move.do([m for s in shipments for m in s.outgoing_moves])
-        cls.write(shipments, {
+        cls.write([s for s in shipments if not s.effective_date], {
                 'effective_date': Date.today(),
                 })
 
@@ -1247,9 +1256,9 @@ class ShipmentOut(Workflow, ModelSQL, ModelView):
         return shipments
 
     @classmethod
-    def write(cls, shipments, values):
-        super(ShipmentOut, cls).write(shipments, values)
-        cls._set_move_planned_date(shipments)
+    def write(cls, *args):
+        super(ShipmentOut, cls).write(*args)
+        cls._set_move_planned_date(sum(args[::2], []))
 
     @classmethod
     def copy(cls, shipments, default=None):
@@ -1312,7 +1321,11 @@ class ShipmentOutReturn(Workflow, ModelSQL, ModelView):
     "Customer Return Shipment"
     __name__ = 'stock.shipment.out.return'
     _rec_name = 'code'
-    effective_date = fields.Date('Effective Date', readonly=True)
+    effective_date = fields.Date('Effective Date',
+        states={
+            'readonly': Eval('state').in_(['cancel', 'done']),
+            },
+        depends=['state'])
     planned_date = fields.Date('Planned Date',
         states={
             'readonly': Not(Equal(Eval('state'), 'draft')),
@@ -1329,12 +1342,11 @@ class ShipmentOutReturn(Workflow, ModelSQL, ModelView):
     customer = fields.Many2One('party.party', 'Customer', required=True,
         states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('incoming_moves'))),
-            }, on_change=['customer'],
-        depends=['state', 'incoming_moves'])
+                Bool(Eval('incoming_moves', [0]))),
+            },
+        depends=['state'])
     customer_location = fields.Function(fields.Many2One('stock.location',
-            'Customer Location', on_change_with=['customer']),
-        'on_change_with_customer_location')
+            'Customer Location'), 'on_change_with_customer_location')
     delivery_address = fields.Many2One('party.address',
         'Delivery Address', required=True,
         states={
@@ -1348,15 +1360,13 @@ class ShipmentOutReturn(Workflow, ModelSQL, ModelView):
     warehouse = fields.Many2One('stock.location', "Warehouse", required=True,
         states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('incoming_moves'))),
+                Bool(Eval('incoming_moves', [0]))),
             }, domain=[('type', '=', 'warehouse')],
-        depends=['state', 'incoming_moves'])
+        depends=['state'])
     warehouse_storage = fields.Function(fields.Many2One('stock.location',
-            'Warehouse Storage', on_change_with=['warehouse']),
-        'on_change_with_warehouse_storage')
+            'Warehouse Storage'), 'on_change_with_warehouse_storage')
     warehouse_input = fields.Function(fields.Many2One('stock.location',
-            'Warehouse Input', on_change_with=['warehouse']),
-        'on_change_with_warehouse_input')
+            'Warehouse Input'), 'on_change_with_warehouse_input')
     incoming_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
             'Incoming Moves',
             domain=[
@@ -1492,6 +1502,7 @@ class ShipmentOutReturn(Workflow, ModelSQL, ModelView):
     def default_company():
         return Transaction().context.get('company')
 
+    @fields.depends('customer')
     def on_change_customer(self):
         address = None
         if self.customer:
@@ -1500,6 +1511,7 @@ class ShipmentOutReturn(Workflow, ModelSQL, ModelView):
             'delivery_address': address.id if address else None,
             }
 
+    @fields.depends('customer')
     def on_change_with_customer_location(self, name=None):
         if self.customer:
             return self.customer.customer_location.id
@@ -1510,6 +1522,7 @@ class ShipmentOutReturn(Workflow, ModelSQL, ModelView):
         if warehouse:
             return cls(warehouse=warehouse).on_change_with_warehouse_storage()
 
+    @fields.depends('warehouse')
     def on_change_with_warehouse_storage(self, name=None):
         if self.warehouse:
             return self.warehouse.storage_location.id
@@ -1520,6 +1533,7 @@ class ShipmentOutReturn(Workflow, ModelSQL, ModelView):
         if warehouse:
             return cls(warehouse=warehouse).on_change_with_warehouse_input()
 
+    @fields.depends('warehouse')
     def on_change_with_warehouse_input(self, name=None):
         if self.warehouse:
             return self.warehouse.input_location.id
@@ -1605,9 +1619,9 @@ class ShipmentOutReturn(Workflow, ModelSQL, ModelView):
         return shipments
 
     @classmethod
-    def write(cls, shipments, values):
-        super(ShipmentOutReturn, cls).write(shipments, values)
-        cls._set_move_planned_date(shipments)
+    def write(cls, *args):
+        super(ShipmentOutReturn, cls).write(*args)
+        cls._set_move_planned_date(sum(args[::2], []))
 
     @classmethod
     def copy(cls, shipments, default=None):
@@ -1654,7 +1668,7 @@ class ShipmentOutReturn(Workflow, ModelSQL, ModelView):
         Move = pool.get('stock.move')
         Date = pool.get('ir.date')
         Move.do([m for s in shipments for m in s.inventory_moves])
-        cls.write(shipments, {
+        cls.write([s for s in shipments if not s.effective_date], {
                 'effective_date': Date.today(),
                 })
 
@@ -1752,7 +1766,11 @@ class ShipmentInternal(Workflow, ModelSQL, ModelView):
     "Internal Shipment"
     __name__ = 'stock.shipment.internal'
     _rec_name = 'code'
-    effective_date = fields.Date('Effective Date', readonly=True)
+    effective_date = fields.Date('Effective Date',
+        states={
+            'readonly': Eval('state').in_(['cancel', 'done']),
+            },
+        depends=['state'])
     planned_date = fields.Date('Planned Date',
         states={
             'readonly': Not(Equal(Eval('state'), 'draft')),
@@ -1774,18 +1792,18 @@ class ShipmentInternal(Workflow, ModelSQL, ModelView):
     from_location = fields.Many2One('stock.location', "From Location",
         required=True, states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('moves'))),
+                Bool(Eval('moves', [0]))),
             },
         domain=[
             ('type', 'in', ['storage', 'lost_found']),
-            ], depends=['state', 'moves'])
+            ], depends=['state'])
     to_location = fields.Many2One('stock.location', "To Location",
         required=True, states={
             'readonly': Or(Not(Equal(Eval('state'), 'draft')),
-                Bool(Eval('moves'))),
+                Bool(Eval('moves', [0]))),
             }, domain=[
             ('type', 'in', ['storage', 'lost_found']),
-            ], depends=['state', 'moves'])
+            ], depends=['state'])
     moves = fields.One2Many('stock.move', 'shipment', 'Moves',
         states={
             'readonly': ((Eval('state') != 'draft')
@@ -1972,7 +1990,7 @@ class ShipmentInternal(Workflow, ModelSQL, ModelView):
         Move = pool.get('stock.move')
         Date = pool.get('ir.date')
         Move.do([m for s in shipments for m in s.moves])
-        cls.write(shipments, {
+        cls.write([s for s in shipments if not s.effective_date], {
                 'effective_date': Date.today(),
                 })
 
diff --git a/tests/scenario_stock_average_cost_price.rst b/tests/scenario_stock_average_cost_price.rst
index d638797..3423ecb 100644
--- a/tests/scenario_stock_average_cost_price.rst
+++ b/tests/scenario_stock_average_cost_price.rst
@@ -101,8 +101,8 @@ Make 1 unit of the product available @ 100 ::
 Check Cost Price is 100::
 
     >>> product.reload()
-    >>> product.template.cost_price == Decimal('100')
-    True
+    >>> product.template.cost_price
+    Decimal('100.0000')
 
 Add 1 more unit @ 200::
 
@@ -123,5 +123,5 @@ Add 1 more unit @ 200::
 Check Cost Price Average is 150::
 
     >>> product.reload()
-    >>> product.template.cost_price == Decimal('150')
-    True
+    >>> product.template.cost_price
+    Decimal('150.0000')
diff --git a/tests/scenario_stock_average_cost_price.rst b/tests/scenario_stock_inventory.rst
similarity index 74%
copy from tests/scenario_stock_average_cost_price.rst
copy to tests/scenario_stock_inventory.rst
index d638797..149842c 100644
--- a/tests/scenario_stock_average_cost_price.rst
+++ b/tests/scenario_stock_inventory.rst
@@ -1,5 +1,5 @@
 ========================
-Stock Average Cost Price
+Stock Inventory Scenario
 ========================
 
 =============
@@ -22,8 +22,8 @@ Create database::
 Install stock Module::
 
     >>> Module = Model.get('ir.module.module')
-    >>> modules = Module.find([('name', '=', 'stock')])
-    >>> Module.install([x.id for x in modules], config.context)
+    >>> stock_module, = Module.find([('name', '=', 'stock')])
+    >>> stock_module.click('install')
     >>> Wizard('ir.module.module.install_upgrade').execute('upgrade')
 
 Create company::
@@ -57,6 +57,12 @@ Reload the context::
     >>> User = Model.get('res.user')
     >>> config._context = User.get_preferences(True, config.context)
 
+Get stock locations::
+
+    >>> Location = Model.get('stock.location')
+    >>> supplier_loc, = Location.find([('code', '=', 'SUP')])
+    >>> storage_loc, = Location.find([('code', '=', 'STO')])
+
 Create product::
 
     >>> ProductUom = Model.get('product.uom')
@@ -75,13 +81,7 @@ Create product::
     >>> product.template = template
     >>> product.save()
 
-Get stock locations::
-
-    >>> Location = Model.get('stock.location')
-    >>> supplier_loc, = Location.find([('code', '=', 'SUP')])
-    >>> storage_loc, = Location.find([('code', '=', 'STO')])
-
-Make 1 unit of the product available @ 100 ::
+Fill storage::
 
     >>> StockMove = Model.get('stock.move')
     >>> incoming_move = StockMove()
@@ -96,32 +96,26 @@ Make 1 unit of the product available @ 100 ::
     >>> incoming_move.unit_price = Decimal('100')
     >>> incoming_move.currency = currency
     >>> incoming_move.save()
-    >>> StockMove.do([incoming_move.id], config.context)
+    >>> incoming_move.click('do')
 
-Check Cost Price is 100::
+Create an inventory::
 
-    >>> product.reload()
-    >>> product.template.cost_price == Decimal('100')
+    >>> Inventory = Model.get('stock.inventory')
+    >>> inventory = Inventory()
+    >>> inventory.location = storage_loc
+    >>> inventory.save()
+    >>> inventory.click('complete_lines')
+    >>> line, = inventory.lines
+    >>> line.expected_quantity == 1
     True
-
-Add 1 more unit @ 200::
-
-    >>> incoming_move = StockMove()
-    >>> incoming_move.product = product
-    >>> incoming_move.uom = unit
-    >>> incoming_move.quantity = 1
-    >>> incoming_move.from_location = supplier_loc
-    >>> incoming_move.to_location = storage_loc
-    >>> incoming_move.planned_date = today
-    >>> incoming_move.effective_date = today
-    >>> incoming_move.company = company
-    >>> incoming_move.unit_price = Decimal('200')
-    >>> incoming_move.currency = currency
-    >>> incoming_move.save()
-    >>> StockMove.do([incoming_move.id], config.context)
-
-Check Cost Price Average is 150::
-
-    >>> product.reload()
-    >>> product.template.cost_price == Decimal('150')
+    >>> line.quantity = 2
+    >>> inventory.save()
+    >>> inventory.click('confirm')
+    >>> line.reload()
+    >>> move, = line.moves
+    >>> move.quantity == 1
+    True
+    >>> move.from_location == inventory.lost_found
+    True
+    >>> move.to_location == inventory.location
     True
diff --git a/tests/scenario_stock_shipment_out.rst b/tests/scenario_stock_shipment_out.rst
index d2689cf..144c3c6 100644
--- a/tests/scenario_stock_shipment_out.rst
+++ b/tests/scenario_stock_shipment_out.rst
@@ -13,6 +13,7 @@ Imports::
     >>> from decimal import Decimal
     >>> from proteus import config, Model, Wizard
     >>> today = datetime.date.today()
+    >>> yesterday = today - relativedelta(days=1)
 
 Create database::
 
@@ -35,14 +36,14 @@ Create company::
     >>> company_config = Wizard('company.company.config')
     >>> company_config.execute('company')
     >>> company = company_config.form
-    >>> party = Party(name='OPENLABS')
+    >>> party = Party(name='Dunder Mifflin')
     >>> party.save()
     >>> company.party = party
-    >>> currencies = Currency.find([('code', '=', 'EUR')])
+    >>> currencies = Currency.find([('code', '=', 'USD')])
     >>> if not currencies:
-    ...     currency = Currency(name='Euro', symbol=u'€', code='EUR',
+    ...     currency = Currency(name='U.S. Dollar', symbol='$', code='USD',
     ...         rounding=Decimal('0.01'), mon_grouping='[3, 3, 0]',
-    ...         mon_decimal_point=',')
+    ...         mon_decimal_point='.', mon_thousands_sep=',')
     ...     currency.save()
     ...     CurrencyRate(date=today + relativedelta(month=1, day=1),
     ...         rate=Decimal('1.0'), currency=currency).save()
@@ -158,6 +159,14 @@ Assign the shipment now::
     >>> states.sort()
     >>> states
     [u'assigned', u'draft']
+    >>> effective_dates = [m.effective_date for m in
+    ...     shipment_out.inventory_moves]
+    >>> len(set(effective_dates))
+    2
+    >>> planned_dates = [m.planned_date for m in
+    ...     shipment_out.outgoing_moves]
+    >>> len(set(planned_dates))
+    1
 
 Delete the draft move, assign and pack shipment::
 
@@ -188,6 +197,14 @@ Set the state as Done::
     >>> shipment_out.reload()
     >>> set([m.state for m in shipment_out.outgoing_moves])
     set([u'done'])
+    >>> planned_dates = [m.planned_date for m in
+    ...     shipment_out.outgoing_moves]
+    >>> planned_dates == [today, today]
+    True
+    >>> effective_dates = [m.effective_date for m in
+    ...     shipment_out.outgoing_moves]
+    >>> len(set(effective_dates))
+    1
     >>> len(shipment_out.outgoing_moves)
     2
     >>> len(shipment_out.inventory_moves)
@@ -197,3 +214,56 @@ Set the state as Done::
     >>> sum([m.quantity for m in shipment_out.inventory_moves]) == \
     ...     sum([m.quantity for m in shipment_out.outgoing_moves])
     True
+
+Create Shipment Out with effective date::
+
+    >>> ShipmentOut = Model.get('stock.shipment.out')
+    >>> shipment_out = ShipmentOut()
+    >>> shipment_out.planned_date = yesterday
+    >>> shipment_out.effective_date = yesterday
+    >>> shipment_out.customer = customer
+    >>> shipment_out.warehouse = warehouse_loc
+    >>> shipment_out.company = company
+    >>> move = shipment_out.outgoing_moves.new()
+    >>> move.product = product
+    >>> move.uom =unit
+    >>> move.quantity = 1
+    >>> move.from_location = output_loc
+    >>> move.to_location = customer_loc
+    >>> move.company = company
+    >>> move.unit_price = Decimal('1')
+    >>> move.currency = currency
+    >>> shipment_out.click('wait')
+
+Make 1 unit of the product available::
+
+    >>> incoming_move = StockMove()
+    >>> incoming_move.product = product
+    >>> incoming_move.uom = unit
+    >>> incoming_move.quantity = 1
+    >>> incoming_move.from_location = supplier_loc
+    >>> incoming_move.to_location = storage_loc
+    >>> incoming_move.planned_date = yesterday
+    >>> incoming_move.effective_date = yesterday
+    >>> incoming_move.company = company
+    >>> incoming_move.unit_price = Decimal('1')
+    >>> incoming_move.currency = currency
+    >>> incoming_move.save()
+    >>> StockMove.do([incoming_move.id], config.context)
+
+Finish the shipment::
+
+    >>> ShipmentOut.assign_try([shipment_out.id], config.context)
+    True
+    >>> shipment_out.click('pack')
+    >>> shipment_out.click('done')
+    >>> shipment_out.reload()
+    >>> shipment_out.state
+    u'done'
+    >>> outgoing_move, = shipment_out.outgoing_moves
+    >>> outgoing_move.effective_date == yesterday
+    True
+    >>> inventory_move, = shipment_out.inventory_moves
+    >>> inventory_move.effective_date == yesterday
+    True
+
diff --git a/tests/test_stock.py b/tests/test_stock.py
index f83c075..4f260a9 100644
--- a/tests/test_stock.py
+++ b/tests/test_stock.py
@@ -1,14 +1,5 @@
-#!/usr/bin/env python
 #This file is part of Tryton.  The COPYRIGHT file at the top level of
 #this repository contains the full copyright notices and license terms.
-
-import sys
-import os
-DIR = os.path.abspath(os.path.normpath(os.path.join(__file__,
-    '..', '..', '..', '..', '..', 'trytond')))
-if os.path.isdir(DIR):
-    sys.path.insert(0, os.path.dirname(DIR))
-
 import unittest
 import doctest
 import datetime
@@ -17,15 +8,13 @@ from dateutil.relativedelta import relativedelta
 from functools import partial
 import trytond.tests.test_tryton
 from trytond.tests.test_tryton import POOL, DB_NAME, USER, CONTEXT, test_view,\
-    test_depends
-from trytond.backend.sqlite.database import Database as SQLiteDatabase
+    test_depends, doctest_dropdb
 from trytond.transaction import Transaction
+from trytond.exceptions import UserWarning
 
 
 class StockTestCase(unittest.TestCase):
-    '''
-    Test Stock module.
-    '''
+    'Test Stock module'
 
     def setUp(self):
         trytond.tests.test_tryton.install_module('stock')
@@ -41,21 +30,15 @@ class StockTestCase(unittest.TestCase):
         self.cache = POOL.get('stock.period.cache')
 
     def test0005views(self):
-        '''
-        Test views.
-        '''
+        'Test views'
         test_view('stock')
 
     def test0006depends(self):
-        '''
-        Test depends.
-        '''
+        'Test depends'
         test_depends()
 
     def test0010move_internal_quantity(self):
-        '''
-        Test Move.internal_quantity.
-        '''
+        'Test Move.internal_quantity'
         with Transaction().start(DB_NAME, USER, context=CONTEXT):
             category, = self.category.create([{
                         'name': 'Test Move.internal_quantity',
@@ -76,7 +59,9 @@ class StockTestCase(unittest.TestCase):
                         }])
             supplier, = self.location.search([('code', '=', 'SUP')])
             storage, = self.location.search([('code', '=', 'STO')])
-            company, = self.company.search([('rec_name', '=', 'B2CK')])
+            company, = self.company.search([
+                    ('rec_name', '=', 'Dunder Mifflin'),
+                    ])
             currency = company.currency
             self.user.write([self.user(USER)], {
                 'main_company': company.id,
@@ -112,9 +97,7 @@ class StockTestCase(unittest.TestCase):
                         internal_quantity)
 
     def test0020products_by_location(self):
-        '''
-        Test products_by_location.
-        '''
+        'Test products_by_location'
         with Transaction().start(DB_NAME, USER,
                 context=CONTEXT) as transaction:
             category, = self.category.create([{
@@ -137,7 +120,9 @@ class StockTestCase(unittest.TestCase):
             supplier, = self.location.search([('code', '=', 'SUP')])
             customer, = self.location.search([('code', '=', 'CUS')])
             storage, = self.location.search([('code', '=', 'STO')])
-            company, = self.company.search([('rec_name', '=', 'B2CK')])
+            company, = self.company.search([
+                    ('rec_name', '=', 'Dunder Mifflin'),
+                    ])
             currency = company.currency
             self.user.write([self.user(USER)], {
                 'main_company': company.id,
@@ -280,6 +265,62 @@ class StockTestCase(unittest.TestCase):
                     else:
                         self.assertEqual(product_reloaded.quantity, quantity)
 
+            def tests_product_search_quantity(context, quantity):
+                with transaction.set_context(locations=[storage.id]):
+                    if (not context.get('stock_date_end')
+                            or context['stock_date_end'] > today
+                            or context.get('forecast')):
+                        fname = 'forecast_quantity'
+                    else:
+                        fname = 'quantity'
+                    found_products = self.product.search([
+                            (fname, '=', quantity),
+                            ])
+                    self.assertIn(product, found_products)
+
+                    found_products = self.product.search([
+                            (fname, '!=', quantity),
+                            ])
+                    self.assertNotIn(product, found_products)
+
+                    found_products = self.product.search([
+                            (fname, 'in', (quantity, quantity + 1)),
+                            ])
+                    self.assertIn(product, found_products)
+
+                    found_products = self.product.search([
+                            (fname, 'not in', (quantity, quantity + 1)),
+                            ])
+                    self.assertNotIn(product, found_products)
+
+                    found_products = self.product.search([
+                            (fname, '<', quantity),
+                            ])
+                    self.assertNotIn(product, found_products)
+                    found_products = self.product.search([
+                            (fname, '<', quantity + 1),
+                            ])
+                    self.assertIn(product, found_products)
+
+                    found_products = self.product.search([
+                            (fname, '>', quantity),
+                            ])
+                    self.assertNotIn(product, found_products)
+                    found_products = self.product.search([
+                            (fname, '>', quantity - 1),
+                            ])
+                    self.assertIn(product, found_products)
+
+                    found_products = self.product.search([
+                            (fname, '>=', quantity),
+                            ])
+                    self.assertIn(product, found_products)
+
+                    found_products = self.product.search([
+                            (fname, '<=', quantity),
+                            ])
+                    self.assertIn(product, found_products)
+
             def test_products_by_location():
                 for context, quantity in tests:
                     with transaction.set_context(context):
@@ -289,6 +330,7 @@ class StockTestCase(unittest.TestCase):
                             self.assertEqual(products_by_location(),
                                     {(storage.id, product.id): quantity})
                             tests_product_quantity(context, quantity)
+                            tests_product_search_quantity(context, quantity)
 
             test_products_by_location()
 
@@ -325,10 +367,12 @@ class StockTestCase(unittest.TestCase):
                 self.period.close([period])
                 test_products_by_location()
 
-        # Test with_childs
+        # Test with_childs and stock_skip_warehouse
         with Transaction().start(DB_NAME, USER,
                 context=CONTEXT) as transaction:
-            company, = self.company.search([('rec_name', '=', 'B2CK')])
+            company, = self.company.search([
+                    ('rec_name', '=', 'Dunder Mifflin'),
+                    ])
             self.user.write([self.user(USER)], {
                 'main_company': company.id,
                 'company': company.id,
@@ -350,6 +394,7 @@ class StockTestCase(unittest.TestCase):
             lost_found, = self.location.search([('type', '=', 'lost_found')])
             warehouse, = self.location.search([('type', '=', 'warehouse')])
             storage, = self.location.search([('code', '=', 'STO')])
+            input_, = self.location.search([('code', '=', 'IN')])
             storage1, = self.location.create([{
                         'name': 'Storage 1',
                         'type': 'view',
@@ -375,6 +420,15 @@ class StockTestCase(unittest.TestCase):
                         'planned_date': today,
                         'effective_date': today,
                         'company': company.id,
+                        }, {
+                        'product': product.id,
+                        'uom': unit.id,
+                        'quantity': 1,
+                        'from_location': input_.id,
+                        'to_location': storage.id,
+                        'planned_date': today,
+                        'effective_date': today,
+                        'company': company.id,
                         }])
             self.move.do(moves)
 
@@ -383,10 +437,18 @@ class StockTestCase(unittest.TestCase):
             self.assertEqual(products_by_location[(warehouse.id, product.id)],
                 1)
 
+            with Transaction().set_context(stock_skip_warehouse=True):
+                products_by_location = self.product.products_by_location(
+                    [warehouse.id], [product.id], with_childs=True)
+                products_by_location_all = self.product.products_by_location(
+                    [warehouse.id], None, with_childs=True)
+            self.assertEqual(products_by_location[(warehouse.id, product.id)],
+                2)
+            self.assertEqual(
+                products_by_location_all[(warehouse.id, product.id)], 2)
+
     def test0030period(self):
-        '''
-        Test period.
-        '''
+        'Test period'
         with Transaction().start(DB_NAME, USER,
                 context=CONTEXT) as transaction:
             category, = self.category.create([{
@@ -408,7 +470,9 @@ class StockTestCase(unittest.TestCase):
             supplier, = self.location.search([('code', '=', 'SUP')])
             customer, = self.location.search([('code', '=', 'CUS')])
             storage, = self.location.search([('code', '=', 'STO')])
-            company, = self.company.search([('rec_name', '=', 'B2CK')])
+            company, = self.company.search([
+                    ('rec_name', '=', 'Dunder Mifflin'),
+                    ])
             currency = company.currency
             self.user.write([self.user(USER)], {
                 'main_company': company.id,
@@ -558,18 +622,42 @@ class StockTestCase(unittest.TestCase):
                         }])
             self.assertRaises(Exception, self.period.close, [period])
 
+    def test0040check_origin(self):
+        'Test Move check_origin'
+        with Transaction().start(DB_NAME, USER, context=CONTEXT):
+            uom, = self.uom.search([('name', '=', 'Unit')])
+            template, = self.template.create([{
+                        'name': 'Test Move.check_origin',
+                        'type': 'goods',
+                        'list_price': Decimal(1),
+                        'cost_price': Decimal(0),
+                        'cost_price_method': 'fixed',
+                        'default_uom': uom.id,
+                        }])
+            product, = self.product.create([{
+                        'template': template.id,
+                        }])
+            storage, = self.location.search([('code', '=', 'STO')])
+            customer, = self.location.search([('code', '=', 'CUS')])
+            company, = self.company.search([
+                    ('rec_name', '=', 'Dunder Mifflin'),
+                    ])
 
-def doctest_dropdb(test):
-    '''
-    Remove sqlite memory database
-    '''
-    database = SQLiteDatabase().connect()
-    cursor = database.cursor(autocommit=True)
-    try:
-        database.drop(cursor, ':memory:')
-        cursor.commit()
-    finally:
-        cursor.close()
+            moves = self.move.create([{
+                        'product': product.id,
+                        'uom': uom.id,
+                        'quantity': 1,
+                        'from_location': storage.id,
+                        'to_location': customer.id,
+                        'company': company.id,
+                        'unit_price': Decimal(1),
+                        'currency': company.currency.id,
+                        }])
+
+            self.move.check_origin(moves, set())
+            self.move.check_origin(moves, {'supplier'})
+            self.assertRaises(UserWarning, self.move.check_origin, moves,
+                {'customer'})
 
 
 def suite():
@@ -586,7 +674,8 @@ def suite():
             'scenario_stock_average_cost_price.rst',
             setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8',
             optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+    suite.addTests(doctest.DocFileSuite(
+            'scenario_stock_inventory.rst',
+            setUp=doctest_dropdb, tearDown=doctest_dropdb, encoding='utf-8',
+            optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
     return suite
-
-if __name__ == '__main__':
-    unittest.TextTestRunner(verbosity=2).run(suite())
diff --git a/tryton.cfg b/tryton.cfg
index 651d7df..639b32a 100644
--- a/tryton.cfg
+++ b/tryton.cfg
@@ -1,5 +1,5 @@
 [tryton]
-version=3.0.1
+version=3.2.0
 depends:
     company
     currency
diff --git a/trytond_stock.egg-info/PKG-INFO b/trytond_stock.egg-info/PKG-INFO
index f2715e7..ed043a0 100644
--- a/trytond_stock.egg-info/PKG-INFO
+++ b/trytond_stock.egg-info/PKG-INFO
@@ -1,12 +1,12 @@
 Metadata-Version: 1.1
 Name: trytond-stock
-Version: 3.0.1
+Version: 3.2.0
 Summary: Tryton module for stock and inventory
 Home-page: http://www.tryton.org/
 Author: Tryton
-Author-email: UNKNOWN
+Author-email: issue_tracker at tryton.org
 License: GPL-3
-Download-URL: http://downloads.tryton.org/3.0/
+Download-URL: http://downloads.tryton.org/3.2/
 Description: trytond_stock
         =============
         
@@ -43,6 +43,7 @@ Description: trytond_stock
         
           http://www.tryton.org/
         
+Keywords: tryton stock
 Platform: UNKNOWN
 Classifier: Development Status :: 5 - Production/Stable
 Classifier: Environment :: Plugins
@@ -63,6 +64,5 @@ Classifier: Natural Language :: Russian
 Classifier: Natural Language :: Slovenian
 Classifier: Natural Language :: Spanish
 Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
 Classifier: Topic :: Office/Business
diff --git a/trytond_stock.egg-info/SOURCES.txt b/trytond_stock.egg-info/SOURCES.txt
index 2acd70d..7910ad4 100644
--- a/trytond_stock.egg-info/SOURCES.txt
+++ b/trytond_stock.egg-info/SOURCES.txt
@@ -45,6 +45,7 @@ locale/nl_NL.po
 locale/ru_RU.po
 locale/sl_SI.po
 tests/scenario_stock_average_cost_price.rst
+tests/scenario_stock_inventory.rst
 tests/scenario_stock_shipment_out.rst
 trytond_stock.egg-info/PKG-INFO
 trytond_stock.egg-info/SOURCES.txt
diff --git a/trytond_stock.egg-info/requires.txt b/trytond_stock.egg-info/requires.txt
index f714b59..191e6a7 100644
--- a/trytond_stock.egg-info/requires.txt
+++ b/trytond_stock.egg-info/requires.txt
@@ -1,6 +1,6 @@
 python-sql
-trytond_company >= 3.0, < 3.1
-trytond_currency >= 3.0, < 3.1
-trytond_party >= 3.0, < 3.1
-trytond_product >= 3.0, < 3.1
-trytond >= 3.0, < 3.1
\ No newline at end of file
+trytond_company >= 3.2, < 3.3
+trytond_currency >= 3.2, < 3.3
+trytond_party >= 3.2, < 3.3
+trytond_product >= 3.2, < 3.3
+trytond >= 3.2, < 3.3
\ No newline at end of file
diff --git a/view/inventory_line_form.xml b/view/inventory_line_form.xml
index 161332f..9a94652 100644
--- a/view/inventory_line_form.xml
+++ b/view/inventory_line_form.xml
@@ -2,6 +2,9 @@
 <!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
 this repository contains the full copyright notices and license terms. -->
 <form string="Inventory Line" col="4">
+    <label name="inventory"/>
+    <field name="inventory"/>
+    <newline/>
     <label name="product"/>
     <field name="product"/>
     <label name="uom"/>
@@ -11,9 +14,5 @@ this repository contains the full copyright notices and license terms. -->
     <field name="expected_quantity"/>
     <label name="quantity"/>
     <field name="quantity"/>
-    <label name="move"/>
-    <field name="move"/>
-    <label name="inventory"/>
-    <field name="inventory"/>
-    <field name="unit_digits" invisible="1" colspan="4"/>
+    <field name="moves" colspan="4"/>
 </form>
diff --git a/view/product_tree_qty.xml b/view/product_tree_qty.xml
index ac84701..d8c5b7d 100644
--- a/view/product_tree_qty.xml
+++ b/view/product_tree_qty.xml
@@ -5,8 +5,8 @@ this repository contains the full copyright notices and license terms. -->
     <field name="template"/>
     <field name="code"/>
     <field name="quantity"/>
-    <field name="cost_value"/>
     <field name="forecast_quantity"/>
     <field name="default_uom"/>
+    <field name="cost_value" sum="Cost Value"/>
     <field name="active"/>
 </tree>
diff --git a/view/shipment_internal_form.xml b/view/shipment_internal_form.xml
index 5a8442b..d095731 100644
--- a/view/shipment_internal_form.xml
+++ b/view/shipment_internal_form.xml
@@ -22,7 +22,7 @@ this repository contains the full copyright notices and license terms. -->
     <group col="5" colspan="2" id="buttons">
         <button string="Cancel" name="cancel" icon="tryton-cancel"/>
         <button string="Draft" name="draft"/>
-        <button string="Waiting" name="wait"/>
+        <button string="Wait" name="wait"/>
         <button string="Assign" name="assign_wizard" icon="tryton-go-next"/>
         <button string="Done" name="done" icon="tryton-ok"/>
     </group>
diff --git a/view/shipment_out_form.xml b/view/shipment_out_form.xml
index ac51e97..1d36ae4 100644
--- a/view/shipment_out_form.xml
+++ b/view/shipment_out_form.xml
@@ -32,7 +32,7 @@ this repository contains the full copyright notices and license terms. -->
         <group col="6" colspan="2" id="buttons">
             <button string="Cancel" name="cancel" icon="tryton-cancel"/>
             <button string="Draft" name="draft"/>
-            <button string="Waiting" name="wait"/>
+            <button string="Wait" name="wait"/>
             <button string="Assign" name="assign_wizard"
                 icon="tryton-go-next"/>
             <button string="Make shipment" name="pack" icon="tryton-go-next"/>
-- 
tryton-modules-stock



More information about the tryton-debian-vcs mailing list