From f42cc5abd0a07dae818b75450ecb441f0ec59b4b Mon Sep 17 00:00:00 2001
From: Nicolas Dumazet <nicolas.dumazet@nexedi.com>
Date: Fri, 25 Jun 2010 10:35:29 +0000
Subject: [PATCH] Optimize isBuildable towards most common case, i.e. case
 where Business Process is in the same direction than the Simulation Tree
 flow.

First look up simulation tree ancestors to see if movements related
to business path ancestors are indeed in this ancestor set.
Most of the time this check is enough. Conveniently, it's also cheap.

And if we're unlucky and have a Business Process going upwards, we
go through descendants of the movement (which is costly) to check
buildability.


git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@36588 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5/Document/SimulationMovement.py | 55 +++++++++++++++------
 1 file changed, 41 insertions(+), 14 deletions(-)

diff --git a/product/ERP5/Document/SimulationMovement.py b/product/ERP5/Document/SimulationMovement.py
index af9bc11907..d5bd6c6d40 100644
--- a/product/ERP5/Document/SimulationMovement.py
+++ b/product/ERP5/Document/SimulationMovement.py
@@ -567,26 +567,53 @@ class SimulationMovement(Movement, PropertyRecordableMixin):
       # first one, can be built
       return True
 
-    simulation_movement_list = business_path.getBusinessPathClosure([self])
-
     # store a causality -> causality_related_movement_list mapping
     causality_dict = dict()
-    for mov in simulation_movement_list:
-      causality_dict.setdefault(mov.getCausality(), []).append(mov)
 
+    # for now only explore ancestors
+    current = self.getParentValue()
+    while True:
+      portal_type = current.getPortalType()
+      if portal_type == "Simulation Movement":
+        causality_dict[current.getCausality()] = current
+      elif portal_type != "Applied Rule":
+        break
+      # XXX or maybe directly go up by two levels?
+      current = current.getParentValue()
+
+    parent_path_list = predecessor.getSuccessorRelatedValueList()
+    remaining_path_list = []
+    for path in parent_path_list:
+      related_simulation = causality_dict.get(path.getRelativeUrl())
+      if related_simulation is None:
+        remaining_path_list.append(path)
+        continue
+      if related_simulation.getDeliveryValue() is None:
+        return False # related movement is not delivered yet
+      if related_simulation.getSimulationState() not in \
+          path.getCompletedStateList():
+        return False
+
+    # in 90% of cases, Business Path goes downward and this is enough
+    if not remaining_path_list:
+      return True
+
+    # But sometimes we have to dig deeper
 
-    for parent_path in predecessor.getSuccessorRelatedValueList():
-      causality = parent_path.getRelativeUrl()
-      related_simulation_list = causality_dict.get(causality, [])
+    # reset dict
+    causality_dict = dict()
 
-      completed_state_list = parent_path.getCompletedStateList()
-      for business_path_movement in related_simulation_list:
-        business_path_movement_delivery = business_path_movement \
-            .getDeliveryValue()
-        if business_path_movement_delivery is None:
-          return False # related movement is not delivered yet
-        if business_path_movement.getSimulationState() not in completed_state_list:
+    # and explore descendants
+    for descendant in business_path._recurseGetValueList(self, 'Simulation Movement'):
+      causality_dict.setdefault(descendant.getCausality(), []).append(descendant)
+    for path in remaining_path_list:
+      completed_state_list = path.getCompletedStateList()
+      for simulation in causality_dict.get(path.getRelativeUrl(), []):
+        if simulation.getDeliveryValue() is None:
+          return False
+        if simulation.getSimulationState() not in completed_state_list:
           return False
+
     return True
 
   def getSolverProcessValueList(self, movement=None, validation_state=None):
-- 
2.30.9