Olivier Dony (OpenERP) has proposed merging 
lp:~openerp-dev/openobject-addons/6.0-bug-770243-odo into 
lp:openobject-addons/6.0.

Requested reviews:
  OpenERP Publisher's Warranty Team (openerp-opw)
Related bugs:
  Bug #770243 in OpenERP Addons: "Creating 4000+ locations slow down the mrp 
module installations and confirming sale order."
  https://bugs.launchpad.net/openobject-addons/+bug/770243

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-addons/6.0-bug-770243-odo/+merge/63116

This branch fixes the performance issues that happen during reservation of 
stock.moves for databases with a large number of stock locations.

The locking and reservation system have been streamlined and are a lot more 
efficient in terms of iterations and database queries.

Support team should review and merge this for OPW.

Thanks!
-- 
https://code.launchpad.net/~openerp-dev/openobject-addons/6.0-bug-770243-odo/+merge/63116
Your team OpenERP R&D Team is subscribed to branch 
lp:~openerp-dev/openobject-addons/6.0-bug-770243-odo.
=== modified file 'stock/stock.py'
--- stock/stock.py	2011-05-23 10:13:58 +0000
+++ stock/stock.py	2011-06-01 13:21:39 +0000
@@ -359,29 +359,107 @@
     def _product_virtual_get(self, cr, uid, id, product_ids=False, context=None, states=['done']):
         return self._product_all_get(cr, uid, id, product_ids, context, ['confirmed', 'waiting', 'assigned', 'done'])
 
+    def _try_lock_product_reserve(self, cr, uid, location_ids, product_id, product_qty, context=None):
+        try:
+            # Must lock with a separate select query than the ones used in _product_reserve
+            # because FOR UPDATE can't be used with aggregation/group by's
+            # (i.e. when individual rows aren't identifiable).
+            # We use a SAVEPOINT to be able to rollback this part of the transaction without
+            # failing the whole transaction in case the LOCK cannot be acquired.
+            cr.execute("SAVEPOINT stock_location_product_reserve")
+            # We lock all stock moves in states we are going to consider in the
+            # calculation. By locking all DONE move we prevent other transactions
+            # from reserving the same products, as they won't be allowed to SELECT
+            # them until we're done.
+            cr.execute("""SELECT id FROM stock_move
+                          WHERE product_id=%s
+                          AND (
+                                (location_dest_id IN %s AND state = 'done')
+                                OR
+                                (location_id IN %s AND state in ('done', 'assigned'))
+                               )
+                          FOR UPDATE of stock_move NOWAIT""",
+                       (product_id, location_ids, location_ids), log_exceptions=False)
+        except Exception:
+            # Here it's likely that the FOR UPDATE NOWAIT failed to get the LOCK,
+            # so we ROLLBACK to the SAVEPOINT to restore the transaction to its earlier
+            # state, we return False as if the products were not available, and log it:
+            cr.execute("ROLLBACK TO stock_location_product_reserve")
+            logger = logging.getLogger('stock.location')
+            logger.warn("Failed attempt to reserve %s x product %s, likely due to another transaction already in progress. Next attempt is likely to work. Detailed error available at DEBUG level.", product_qty, product_id)
+            logger.debug("Trace of the failed product reservation attempt: ", exc_info=True)
+            return False
+        return True
+
     def _product_reserve(self, cr, uid, ids, product_id, product_qty, context=None, lock=False):
         """
         Attempt to find a quantity ``product_qty`` (in the product's default uom or the uom passed in ``context``) of product ``product_id``
         in locations with id ``ids`` and their child locations. If ``lock`` is True, the stock.move lines
         of product with id ``product_id`` in the searched location will be write-locked using Postgres's
-        "FOR UPDATE NOWAIT" option until the transaction is committed or rolled back, to prevent reservin
+        "FOR UPDATE NOWAIT" option until the transaction is committed or rolled back, to prevent reserving
         twice the same products.
         If ``lock`` is True and the lock cannot be obtained (because another transaction has locked some of
         the same stock.move lines), a log line will be output and False will be returned, as if there was
         not enough stock.
 
         :param product_id: Id of product to reserve
-        :param product_qty: Quantity of product to reserve (in the product's default uom or the uom passed in ``context``)
+        :param product_qty: Quantity of product to reserve (in the uom passed in ``context``)
         :param lock: if True, the stock.move lines of product with id ``product_id`` in all locations (and children locations) with ``ids`` will
                      be write-locked using postgres's "FOR UPDATE NOWAIT" option until the transaction is committed or rolled back. This is
                      to prevent reserving twice the same products.
-        :param context: optional context dictionary: it a 'uom' key is present it will be used instead of the default product uom to
-                        compute the ``product_qty`` and in the return value.
-        :return: List of tuples in the form (qty, location_id) with the (partial) quantities that can be taken in each location to
-                 reach the requested product_qty (``qty`` is expressed in the default uom of the product), of False if enough
-                 products could not be found, or the lock could not be obtained (and ``lock`` was True).
+        :param context: context dictionary with 'uom' key mapped to the ID of the UoM to use to compute the product quantities
+        :return: List of pairs (qty, location_id) with the (partial) quantities that can be taken in each location to
+                 reach the requested product_qty (expressed in the requested uom), or False if not enough
+                 products could be found, or the lock could not be obtained (and ``lock`` was True).
+                 sum(qty) == ``product_qty``.
         """
+        if context is None:
+            context = {}
+        location_ids = self.search(cr, uid, [('location_id', 'child_of', ids)])
+        locations_tuple = tuple(location_ids)
+        if lock and not self._try_lock_product_reserve(cr, uid, locations_tuple, product_id, product_qty, context=context):
+            return False
+
+        # Giant query to obtain triplets of (product_uom, product_qty, location_id) summing all relevant
+        # stock moves quantities per location,  with incoming quantities taken positive,
+        # and outgoing taken negative.
+        cr.execute("""SELECT x.product_uom, SUM(x.coeff * x.product_qty) as product_qty, x.loc_id as location_id
+                      FROM (
+                          SELECT 1.0 as coeff, product_uom, location_dest_id as loc_id,
+                                 sum(product_qty) AS product_qty
+                              FROM stock_move
+                              WHERE location_dest_id in %s AND
+                                    location_id != location_dest_id AND
+                                    product_id = %s AND
+                                    state = 'done'
+                              GROUP BY location_dest_id, product_uom
+                          UNION
+                          SELECT -1.0 as coeff, product_uom, location_id as loc_id,
+                                 sum(product_qty) AS product_qty
+                              FROM stock_move
+                              WHERE location_id in %s AND
+                                    location_id != location_dest_id AND
+                                    product_id = %s AND
+                                    state in ('done', 'assigned')
+                              GROUP BY location_id, product_uom
+                      ) AS x
+                      GROUP BY x.loc_id, x.product_uom
+                   """,
+                   (locations_tuple, product_id, locations_tuple, product_id))
+        sum_rows = cr.fetchall()
+
+        qty_by_location = {}
+        ProductUom = self.pool.get('product.uom')
+        target_uom = context.get('uom')
+        # Convert all UoMs into target UoM
+        for uom_id, qty, loc_id in sum_rows:
+            qty_by_location.setdefault(loc_id,0.0)
+            qty_by_location[loc_id] += ProductUom._compute_qty(cr, uid, uom_id, qty, target_uom)
+
+        # to compute final result we handle locations in the
+        # order in which they were returned by the original search().
         result = []
+<<<<<<< TREE
         amount = 0.0
         if context is None:
             context = {}
@@ -460,6 +538,20 @@
                     return result
                 if total <= 0.0:
                     continue
+=======
+        for loc_id in location_ids:
+            if loc_id not in qty_by_location:
+                #skip location without this product
+                continue
+            qty = qty_by_location[loc_id]
+            if qty <= 0.0:
+                continue
+            qty = min(product_qty, qty)
+            result.append((qty, loc_id))
+            product_qty -= qty
+            if product_qty <= 0.0:
+                return result
+>>>>>>> MERGE-SOURCE
         return False
 
 stock_location()

_______________________________________________
Mailing list: https://launchpad.net/~openerp-dev-gtk
Post to     : [email protected]
Unsubscribe : https://launchpad.net/~openerp-dev-gtk
More help   : https://help.launchpad.net/ListHelp

Reply via email to