From d87b9db85dcea529257a33bcd137b8c26b684ec1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9rome=20Perrin?= <jerome@nexedi.com>
Date: Tue, 5 Feb 2013 14:47:29 +0100
Subject: [PATCH] Reimplement fast=True for getTotalPrice/Quantity using
 inventory API

---
 .../erp5_trade/DeliveryLine_zGetTotal.xml     |  77 -------------
 .../erp5_trade/Delivery_zGetTotal.xml         |  90 ----------------
 product/ERP5/Document/Delivery.py             | 101 +++++++++---------
 product/ERP5/Document/DeliveryLine.py         |  34 ++++--
 product/ERP5/Document/Order.py                |   4 +
 product/ERP5/tests/testOrder.py               |  54 +++++++---
 6 files changed, 121 insertions(+), 239 deletions(-)
 delete mode 100644 bt5/erp5_trade/SkinTemplateItem/portal_skins/erp5_trade/DeliveryLine_zGetTotal.xml
 delete mode 100644 bt5/erp5_trade/SkinTemplateItem/portal_skins/erp5_trade/Delivery_zGetTotal.xml

diff --git a/bt5/erp5_trade/SkinTemplateItem/portal_skins/erp5_trade/DeliveryLine_zGetTotal.xml b/bt5/erp5_trade/SkinTemplateItem/portal_skins/erp5_trade/DeliveryLine_zGetTotal.xml
deleted file mode 100644
index 3ca99d5742..0000000000
--- a/bt5/erp5_trade/SkinTemplateItem/portal_skins/erp5_trade/DeliveryLine_zGetTotal.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0"?>
-<ZopeData>
-  <record id="1" aka="AAAAAAAAAAE=">
-    <pickle>
-      <global name="SQL" module="Products.ZSQLMethods.SQL"/>
-    </pickle>
-    <pickle>
-      <dictionary>
-        <item>
-            <key> <string>allow_simple_one_argument_traversal</string> </key>
-            <value>
-              <none/>
-            </value>
-        </item>
-        <item>
-            <key> <string>arguments_src</string> </key>
-            <value> <string>uid</string> </value>
-        </item>
-        <item>
-            <key> <string>cache_time_</string> </key>
-            <value> <int>0</int> </value>
-        </item>
-        <item>
-            <key> <string>class_file_</string> </key>
-            <value> <string></string> </value>
-        </item>
-        <item>
-            <key> <string>class_name_</string> </key>
-            <value> <string></string> </value>
-        </item>
-        <item>
-            <key> <string>connection_hook</string> </key>
-            <value>
-              <none/>
-            </value>
-        </item>
-        <item>
-            <key> <string>connection_id</string> </key>
-            <value> <string>erp5_sql_connection</string> </value>
-        </item>
-        <item>
-            <key> <string>id</string> </key>
-            <value> <string>DeliveryLine_zGetTotal</string> </value>
-        </item>
-        <item>
-            <key> <string>max_cache_</string> </key>
-            <value> <int>100</int> </value>
-        </item>
-        <item>
-            <key> <string>max_rows_</string> </key>
-            <value> <int>1000</int> </value>
-        </item>
-        <item>
-            <key> <string>src</string> </key>
-            <value> <string encoding="cdata"><![CDATA[
-
-SELECT \n
-\tSUM(quantity) AS total_quantity, \n
-\tSUM(quantity * price) AS total_price, \n
-\tAVG(price) AS average_price\n
-FROM catalog, movement\n
-WHERE \n
-\tcatalog.parent_uid = <dtml-sqlvar uid type="int">\n
-AND\t\n
-\tcatalog.uid = movement.uid\n
-
-
-]]></string> </value>
-        </item>
-        <item>
-            <key> <string>title</string> </key>
-            <value> <string></string> </value>
-        </item>
-      </dictionary>
-    </pickle>
-  </record>
-</ZopeData>
diff --git a/bt5/erp5_trade/SkinTemplateItem/portal_skins/erp5_trade/Delivery_zGetTotal.xml b/bt5/erp5_trade/SkinTemplateItem/portal_skins/erp5_trade/Delivery_zGetTotal.xml
deleted file mode 100644
index cdd74f7e91..0000000000
--- a/bt5/erp5_trade/SkinTemplateItem/portal_skins/erp5_trade/Delivery_zGetTotal.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-<?xml version="1.0"?>
-<ZopeData>
-  <record id="1" aka="AAAAAAAAAAE=">
-    <pickle>
-      <global name="SQL" module="Products.ZSQLMethods.SQL"/>
-    </pickle>
-    <pickle>
-      <dictionary>
-        <item>
-            <key> <string>allow_simple_one_argument_traversal</string> </key>
-            <value>
-              <none/>
-            </value>
-        </item>
-        <item>
-            <key> <string>arguments_src</string> </key>
-            <value> <string>from_table_list:list\r\n
-where_expression\r\n
-order_by_expression</string> </value>
-        </item>
-        <item>
-            <key> <string>cache_time_</string> </key>
-            <value> <int>0</int> </value>
-        </item>
-        <item>
-            <key> <string>class_file_</string> </key>
-            <value> <string></string> </value>
-        </item>
-        <item>
-            <key> <string>class_name_</string> </key>
-            <value> <string></string> </value>
-        </item>
-        <item>
-            <key> <string>connection_hook</string> </key>
-            <value>
-              <none/>
-            </value>
-        </item>
-        <item>
-            <key> <string>connection_id</string> </key>
-            <value> <string>erp5_sql_connection</string> </value>
-        </item>
-        <item>
-            <key> <string>id</string> </key>
-            <value> <string>Delivery_zGetTotal</string> </value>
-        </item>
-        <item>
-            <key> <string>max_cache_</string> </key>
-            <value> <int>100</int> </value>
-        </item>
-        <item>
-            <key> <string>max_rows_</string> </key>
-            <value> <int>1000</int> </value>
-        </item>
-        <item>
-            <key> <string>src</string> </key>
-            <value> <string encoding="cdata"><![CDATA[
-
-SELECT\n
-  SUM(movement.quantity) AS inventory,\n
-  SUM(movement.quantity) AS total_quantity,\n
-  SUM(movement.price * movement.quantity) AS total_price,\n
-  AVG(movement.price) AS average_price\n
-\n
-FROM\n
-<dtml-in from_table_list> <dtml-var sequence-item> AS <dtml-var sequence-key><dtml-if sequence-end><dtml-else>,</dtml-if></dtml-in>\n
-\n
-WHERE\n
-  1=1\n
-  <dtml-if where_expression>\n
-    AND <dtml-var where_expression> \n
-  </dtml-if>\n
-  AND catalog.has_cell_content = 0\n
-  AND catalog.portal_type NOT IN ("Container", "Container Line",  "Container Cell", "Simulation Movement")\n
-\n
-<dtml-if order_by_expression>\n
-ORDER BY <dtml-var order_by_expression>\n
-</dtml-if>\n
-
-
-]]></string> </value>
-        </item>
-        <item>
-            <key> <string>title</string> </key>
-            <value> <string></string> </value>
-        </item>
-      </dictionary>
-    </pickle>
-  </record>
-</ZopeData>
diff --git a/product/ERP5/Document/Delivery.py b/product/ERP5/Document/Delivery.py
index 7a71308367..a89e7955b4 100644
--- a/product/ERP5/Document/Delivery.py
+++ b/product/ERP5/Document/Delivery.py
@@ -91,6 +91,7 @@ class Delivery(XMLObject, ImmobilisationDelivery, SimulableMixin,
                                'getTotalPrice')
     def getTotalPrice(self, fast=0, src__=0, base_contribution=None, rounding=False, **kw):
       """ Returns the total price for this order
+
         if the `fast` argument is set to a true value, then it use
         SQLCatalog to compute the price, otherwise it sums the total
         price of objects one by one.
@@ -98,49 +99,51 @@ class Delivery(XMLObject, ImmobilisationDelivery, SimulableMixin,
         So if the order is not in the catalog, getTotalPrice(fast=1)
         will return 0, this is not a bug.
 
-        base_contribution must be a relative url of a category.
+        base_contribution must be a relative url of a category. If passed, then
+        fast parameter is ignored.
       """
       result = None
-      if not fast:
-        kw.setdefault( 'portal_type',
-                       self.getPortalDeliveryMovementTypeList())
-        if base_contribution is None:
-          result = sum([ line.getTotalPrice(fast=0) for line in
-                         self.objectValues(**kw) ])
+      kw.setdefault( 'portal_type',
+                     self.getPortalDeliveryMovementTypeList())
+      if base_contribution is None:
+        if fast:
+          # XXX fast ignores base_contribution for now, but it should be possible
+          # to use a related key
+          kw['section_uid'] = self.getDestinationSectionUid()
+          kw['stock.explanation_uid'] = self.getUid()
+          return self.getPortalObject()\
+            .portal_simulation.getInventoryAssetPrice(**kw)
+
+        result = sum([ line.getTotalPrice(fast=0) for line in
+                       self.objectValues(**kw) ])
+      else:
+        # Find amounts from movements in the delivery.
+        if isinstance(base_contribution, (tuple, list)):
+          base_contribution_list = base_contribution
         else:
-          # Find amounts from movements in the delivery.
-          if isinstance(base_contribution, (tuple, list)):
-            base_contribution_list = base_contribution
-          else:
-            base_contribution_list = (base_contribution,)
-          base_contribution_value_list = []
-          portal_categories = self.portal_categories
-          for relative_url in base_contribution_list:
-            base_contribution_value = portal_categories.getCategoryValue(relative_url)
-            if base_contribution_value is not None:
-              base_contribution_value_list.append(base_contribution_value)
-          if not base_contribution_value_list:
-            # We cannot find any amount so that the result is 0.
-            result = 0
-          else:
+          base_contribution_list = (base_contribution,)
+        base_contribution_value_list = []
+        portal_categories = self.portal_categories
+        for relative_url in base_contribution_list:
+          base_contribution_value = portal_categories.getCategoryValue(relative_url)
+          if base_contribution_value is not None:
+            base_contribution_value_list.append(base_contribution_value)
+        if not base_contribution_value_list:
+          # We cannot find any amount so that the result is 0.
+          result = 0
+        else:
+          matched_movement_list = [
+              movement
+              for movement in self.getMovementList()
+              if set(movement.getBaseContributionValueList()).intersection(base_contribution_value_list)]
+          if rounding:
+            portal_roundings = self.portal_roundings
             matched_movement_list = [
-                movement
-                for movement in self.getMovementList()
-                if set(movement.getBaseContributionValueList()).intersection(base_contribution_value_list)]
-            if rounding:
-              portal_roundings = self.portal_roundings
-              matched_movement_list = [
-                  portal_roundings.getRoundingProxy(movement)
-                  for movement in matched_movement_list]
-            result = sum([movement.getTotalPrice()
-                          for movement in matched_movement_list])
-      else:
-        kw['explanation_uid'] = self.getUid()
-        kw.update(self.portal_catalog.buildSQLQuery(**kw))
-        if src__:
-          return self.Delivery_zGetTotal(src__=1, **kw)
-        aggregate = self.Delivery_zGetTotal(**kw)[0]
-        result = aggregate.total_price or 0
+                portal_roundings.getRoundingProxy(movement)
+                for movement in matched_movement_list]
+          result = sum([movement.getTotalPrice()
+                        for movement in matched_movement_list])
+
       method = self._getTypeBasedMethod('convertTotalPrice')
       if method is not None:
         return method(result)
@@ -164,6 +167,7 @@ class Delivery(XMLObject, ImmobilisationDelivery, SimulableMixin,
                               'getTotalQuantity')
     def getTotalQuantity(self, fast=0, src__=0, **kw):
       """ Returns the total quantity of this order.
+
         if the `fast` argument is set to a true value, then it use
         SQLCatalog to compute the quantity, otherwise it sums the total
         quantity of objects one by one.
@@ -171,17 +175,14 @@ class Delivery(XMLObject, ImmobilisationDelivery, SimulableMixin,
         So if the order is not in the catalog, getTotalQuantity(fast=1)
         will return 0, this is not a bug.
       """
-      if not fast :
-        kw.setdefault('portal_type',
-                      self.getPortalDeliveryMovementTypeList())
-        return sum([ line.getTotalQuantity(fast=0) for line in
-                        self.objectValues(**kw) ])
-      kw['explanation_uid'] = self.getUid()
-      kw.update(self.portal_catalog.buildSQLQuery(**kw))
-      if src__:
-        return self.Delivery_zGetTotal(src__=1, **kw)
-      aggregate = self.Delivery_zGetTotal(**kw)[0]
-      return aggregate.total_quantity or 0
+      kw.setdefault('portal_type',
+                    self.getPortalDeliveryMovementTypeList())
+      if fast:
+        kw['section_uid'] = self.getDestinationSectionUid()
+        kw['stock.explanation_uid'] = self.getUid()
+        return self.getPortalObject().portal_simulation.getInventory(**kw)
+      return sum([ line.getTotalQuantity(fast=0) for line in
+                      self.objectValues(**kw) ])
 
     security.declareProtected(Permissions.AccessContentsInformation,
                               'getDeliveryUid')
diff --git a/product/ERP5/Document/DeliveryLine.py b/product/ERP5/Document/DeliveryLine.py
index 4f40208c95..8e0981b722 100644
--- a/product/ERP5/Document/DeliveryLine.py
+++ b/product/ERP5/Document/DeliveryLine.py
@@ -116,16 +116,23 @@ class DeliveryLine(Movement, XMLObject, XMLMatrix, ImmobilisationMovement):
         if hasLineContent: return sum of lines total price
         if hasCellContent: return sum of cells total price
         else: return quantity * price
-        if fast is argument true, then a SQL method will be used.
-      """
+        if fast argument is true, inventory API will be used.
+      """
+      if fast:
+        kw = {}
+        kw['section_uid'] = self.getDestinationSectionUid()
+        kw['stock.explanation_uid'] = self.getExplanationUid()
+        kw['relative_url'] = ( '%s/%%' % (
+          self.getRelativeUrl().replace('_', '\\_')),
+          self.getRelativeUrl() )
+        kw['only_accountable'] = False
+        return self.getPortalObject().portal_simulation.getInventoryAssetPrice(**kw)
       if self.hasLineContent():
         meta_type = self.meta_type
         return sum(l.getTotalPrice(context=context)
                    for l in self.objectValues() if l.meta_type==meta_type)
       elif not self.hasCellContent(base_id='movement'):
         return Movement._getTotalPrice(self, default=default, context=context)
-      elif fast: # Use MySQL
-        return self.DeliveryLine_zGetTotal()[0].total_price or 0.0
       return sum(cell.getTotalPrice(default=0.0, context=context)
                  for cell in self.getCellValueList())
 
@@ -138,20 +145,27 @@ class DeliveryLine(Movement, XMLObject, XMLMatrix, ImmobilisationMovement):
         if hasLineContent: return sum of lines total quantity
         if hasCellContent: return sum of cells total quantity
         else: return quantity
-        if fast argument is true, then a SQL method will be used.
+        if fast argument is true, inventory API will be used.
       """
+
+      if fast:
+        kw = {}
+        kw['section_uid'] = self.getDestinationSectionUid()
+        kw['stock.explanation_uid'] = self.getExplanationUid()
+        kw['relative_url'] = ( '%s/%%' % (
+          self.getRelativeUrl().replace('_', '\\_')),
+          self.getRelativeUrl() )
+        kw['only_accountable'] = False
+        return self.getPortalObject().portal_simulation.getInventory(**kw)
+
       base_id = 'movement'
       if self.hasLineContent():
         meta_type = self.meta_type
         return sum(l.getTotalQuantity() for l in
             self.objectValues() if l.meta_type==meta_type)
       elif self.hasCellContent(base_id=base_id):
-        if fast : # Use MySQL
-          aggregate = self.DeliveryLine_zGetTotal()[0]
-          return aggregate.total_quantity or 0.0
         return sum([cell.getQuantity() for cell in self.getCellValueList()])
-      else:
-        return self.getQuantity()
+      return self.getQuantity()
 
     security.declareProtected(Permissions.AccessContentsInformation,
                               'hasLineContent')
diff --git a/product/ERP5/Document/Order.py b/product/ERP5/Document/Order.py
index dc34fdc070..1f21e463ec 100644
--- a/product/ERP5/Document/Order.py
+++ b/product/ERP5/Document/Order.py
@@ -72,6 +72,8 @@ class Order(Delivery):
       If base_contribution is passed, the trade model lines will be used to
       include movements that will be generated.
       """
+      if kw.get('fast'):
+        kw['only_accountable'] = False
       rounding = kw.get('rounding')
       if kw.get('base_contribution') is None:
         kw.setdefault('portal_type', self.getPortalOrderMovementTypeList())
@@ -118,6 +120,8 @@ class Order(Delivery):
     def getTotalQuantity(self, **kw) :
       """Returns the total quantity for this Order. """
       kw.setdefault('portal_type', self.getPortalOrderMovementTypeList())
+      if kw.get('fast'):
+        kw['only_accountable'] = False
       return Delivery.getTotalQuantity(self, **kw)
 
     @deprecated
diff --git a/product/ERP5/tests/testOrder.py b/product/ERP5/tests/testOrder.py
index 06ae817b21..6a87001b49 100644
--- a/product/ERP5/tests/testOrder.py
+++ b/product/ERP5/tests/testOrder.py
@@ -353,19 +353,30 @@ class TestOrderMixin(SubcontentReindexingWrapper):
 
   def stepCheckOrder(self, sequence=None, sequence_list=None, **kw):
     """
-      Check if order was well created
-    """
-    organisation = sequence.get('organisation')
-    project = sequence.get('project')
+      Check if order was well created, either by stepCreateOrder of
+      stepSetOrderProfile
+    """
+    source_organisation = sequence.get('organisation1')
+    if source_organisation is None:
+      source_organisation = sequence.get('organisation')
+    destination_organisation = sequence.get('organisation2')
+    if destination_organisation is None:
+      destination_organisation = sequence.get('organisation')
+    source_project = sequence.get('project1')
+    if source_project is None:
+      source_project = sequence.get('project')
+    destination_project = sequence.get('project2')
+    if destination_project is None:
+      destination_project = sequence.get('project')
     order = sequence.get('order')
     self.assertEquals(self.datetime+10, order.getStartDate())
     self.assertEquals(self.datetime+20, order.getStopDate())
-    self.assertEquals(organisation, order.getSourceValue())
-    self.assertEquals(organisation, order.getDestinationValue())
-    self.assertEquals(organisation, order.getSourceSectionValue())
-    self.assertEquals(organisation, order.getDestinationSectionValue())
-    self.assertEquals(project, order.getSourceProjectValue())
-    self.assertEquals(project, order.getDestinationProjectValue())
+    self.assertEquals(source_organisation, order.getSourceValue())
+    self.assertEquals(destination_organisation, order.getDestinationValue())
+    self.assertEquals(source_organisation, order.getSourceSectionValue())
+    self.assertEquals(destination_organisation, order.getDestinationSectionValue())
+    self.assertEquals(source_project, order.getSourceProjectValue())
+    self.assertEquals(destination_project, order.getDestinationProjectValue())
 
 
   def stepCreateOrderLine(self,sequence=None, sequence_list=None, **kw):
@@ -736,7 +747,7 @@ class TestOrderMixin(SubcontentReindexingWrapper):
     order_line_list = map(lambda x: x.getObject(), order_line_list)
     total_price = 0
     for order_line in order_line_list:
-      total_price += order_line.getTotalPrice(fast=0)
+      total_price += order_line.getTotalPrice()
     self.assertEquals(0, len(portal_catalog(relative_url=order.getRelativeUrl())))
     self.assertEquals(total_price, order.getTotalPrice(fast=0))
     self.assertNotEquals(total_price, 0)
@@ -1148,7 +1159,12 @@ class TestOrderMixin(SubcontentReindexingWrapper):
                                              'adopt_prevision_action')
 
   non_variated_order_creation = '\
+      stepCreateOrganisation1 \
+      stepCreateOrganisation2 \
+      stepCreateProject1 \
+      stepCreateProject2 \
       stepCreateOrder \
+      stepSetOrderProfile \
       stepCreateNotVariatedResource \
       stepCreateOrderLine \
       stepCheckOrderLineEmptyMatrix \
@@ -1158,7 +1174,12 @@ class TestOrderMixin(SubcontentReindexingWrapper):
       '
 
   variated_order_line_creation = '\
+      stepCreateOrganisation1 \
+      stepCreateOrganisation2 \
+      stepCreateProject1 \
+      stepCreateProject2 \
       stepCreateOrder \
+      stepSetOrderProfile \
       stepCreateVariatedResource \
       stepCreateOrderLine \
       '
@@ -1590,7 +1611,12 @@ class TestOrder(TestOrderMixin, ERP5TypeTestCase):
     sequence_list = SequenceList()
     # Test with positive price order line and negative price order line.
     sequence_string = '\
+                      stepCreateOrganisation1 \
+                      stepCreateOrganisation2 \
+                      stepCreateProject1 \
+                      stepCreateProject2 \
                       stepCreateOrder \
+                      stepSetOrderProfile \
                       stepCheckOrderTotalQuantity \
                       stepCreateNotVariatedResource \
                       stepCreateOrderLine \
@@ -2250,11 +2276,15 @@ class TestOrder(TestOrderMixin, ERP5TypeTestCase):
     """
     if not run: return
 
-    portal = self.getPortal()
+    portal = self.portal
     base_id = 'movement'
     order_line_vcl=['size/Baby']
+    section = portal.organisation_module.newContent(
+      portal_type='Organisation')
     order_module = portal.getDefaultModule(portal_type=self.order_portal_type)
     order = order_module.newContent(portal_type=self.order_portal_type,
+                                    destination_section_value=section,
+                                    destination_value=section,
                                     specialise=self.business_process)
     # No line, no movement
     self.assertEquals(order.getTotalQuantity(fast=0), 0)
-- 
2.30.9