From 5c2458364db3421446e6468c560da4b5167453ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C5=81ukasz=20Nowak?= <luke@nexedi.com>
Date: Thu, 16 Jul 2009 16:07:42 +0000
Subject: [PATCH]  - move _isRelatedWithMovement from SimulationMovement to
 BusinessPath as isMovementRelatedWithMovement, define in interface and use in
 SimulationMovement  - implement proper fetching of all related simulation
 movements to business path and delivery  - implement isBuildable for
 BusinessPath in a way, that partially completion is enough

git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@28100 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5/Document/BusinessPath.py       | 99 ++++++++++++++++++---
 product/ERP5/Document/SimulationMovement.py | 25 +-----
 product/ERP5/interfaces/business_path.py    |  9 ++
 3 files changed, 98 insertions(+), 35 deletions(-)

diff --git a/product/ERP5/Document/BusinessPath.py b/product/ERP5/Document/BusinessPath.py
index 7d6795f4cb..2d4b0c788c 100644
--- a/product/ERP5/Document/BusinessPath.py
+++ b/product/ERP5/Document/BusinessPath.py
@@ -193,7 +193,7 @@ class BusinessPath(Path):
     predecessor = self.getPredecessorValue()
     if predecessor is None:
       return result
-    if predecessor.isCompleted(explanation):
+    if predecessor.isPartiallyCompleted(explanation):
       return result
     return False
 
@@ -267,26 +267,103 @@ class BusinessPath(Path):
         return False
     return True
 
+  def _recurseGetValueList(self, document, portal_type):
+    """Helper method to recurse documents as deep as possible and returns
+       list of document values matching portal_type"""
+    return_list = []
+    for subdocument in document.contentValues():
+      if subdocument.getPortalType() == portal_type:
+        return_list.append(subdocument)
+      return_list.extend(self._recurseGetValueList(subdocument, portal_type))
+    return return_list
+
+  def isMovementRelatedWithMovement(self, movement_value_a, movement_value_b):
+    """Documentation in IBusinessPath"""
+    movement_a_path_list = movement_value_a.getRelativeUrl().split('/')
+    movement_b_path_list = movement_value_b.getRelativeUrl().split('/')
+
+    if len(movement_a_path_list) == len(movement_b_path_list):
+      if movement_value_a == movement_value_b:
+        # same is related
+        return True
+      # same level, cannot be related
+      return False
+
+    index = 0
+    for movement_a_part in movement_a_path_list:
+      try:
+        movement_b_part = movement_b_path_list[index]
+      except IndexError:
+        # so far was good, they are related
+        return True
+      if movement_a_part != movement_b_part:
+        return False
+      index += 1
+    # movement_a_path_list was shorter than movement_b_path_list and matched
+    # so they are related
+    return True
+
+  def _isDeliverySimulationMovementRelated(self, delivery, simulation_movement):
+    """Helper method, which checks if simulation_movement is BPM like related
+       with delivery"""
+    for delivery_simulation_movement in self \
+        ._getDeliverySimulationMovementList(delivery):
+      if self.isMovementRelatedWithMovement(delivery_simulation_movement,
+          simulation_movement):
+        return True
+    return False
+
+  def _getDeliverySimulationMovementList(self, delivery):
+    """Returns list of simulation movements related to delivery by applied rule
+       or delivery's movements"""
+    movement_list = []
+    for applied_rule in delivery.getCausalityRelatedValueList(
+        portal_type='Applied Rule'):
+      movement_list.extend(applied_rule.contentValues(
+        portal_type='Simulation Movement'))
+    for movement in delivery.getMovementList():
+      movement_list.extend(movement.getDeliveryRelatedValueList(
+        portal_type='Simulation Movement'))
+    return movement_list
+
   # IBusinessPath implementation
   security.declareProtected(Permissions.AccessContentsInformation,
       'getRelatedSimulationMovementValueList')
   def getRelatedSimulationMovementValueList(self, explanation):
     """
-      Returns all Simulation Movements related to explanation
+      Returns recursively all Simulation Movements indirectly related to explanation and self
+
+      As business sequence is not related to simulation tree need to built
+      full simulation trees per applied rule
     """
+    # FIXME: Needed better implementation, maybe use catalog?
     simulation_movement_value_list = []
-    # first simulation movements related to explanation itself by its applied rule
+    # first tree from root Applied Rules related to delivery itself
     for applied_rule in explanation.getCausalityRelatedValueList(
         portal_type='Applied Rule'):
-      simulation_movement_value_list.extend([x.getObject() for x in
-        applied_rule.contentValues() if x.getCausalityValue() == self])
-    # now simulation movements which were used to build this delivery
+      simulation_movement_value_list.extend(self._recurseGetValueList(
+        applied_rule, 'Simulation Movement'))
+    # now tree from root Applied Rules related to movements used to build delivery
+    root_applied_rule_list = []
     for movement in explanation.getMovementList():
-      simulation_movement_value_list.extend([x.getObject() for x in
-        movement.getDeliveryRelatedValueList(
-          portal_type='Simulation Movement') if x \
-              .getCausalityValue() == self])
-      return simulation_movement_value_list
+      for simulation_movement in movement.getDeliveryRelatedValueList(
+          portal_type='Simulation Movement'):
+        applied_rule = simulation_movement.getRootAppliedRule()
+        if applied_rule not in root_applied_rule_list:
+          root_applied_rule_list.append(
+              simulation_movement.getRootAppliedRule())
+
+    for applied_rule in root_applied_rule_list:
+      simulation_movement_value_list.extend(self._recurseGetValueList(
+        applied_rule, 'Simulation Movement'))
+
+    return [simulation_movement.getObject() for simulation_movement
+          in simulation_movement_value_list
+          # this business path
+          if simulation_movement.getCausalityValue() == self
+          # related with explanation
+          and self._isDeliverySimulationMovementRelated(
+            explanation, simulation_movement)]
 
   def getExpectedStartDate(self, explanation, predecessor_date=None, *args, **kwargs):
     """
diff --git a/product/ERP5/Document/SimulationMovement.py b/product/ERP5/Document/SimulationMovement.py
index c6663dc52a..73277ece3e 100644
--- a/product/ERP5/Document/SimulationMovement.py
+++ b/product/ERP5/Document/SimulationMovement.py
@@ -542,29 +542,6 @@ class SimulationMovement(Movement):
     else:
       return getTreeDelivered(self, ignore_first=ignore_first)
 
-  def _isRelatedWithMovement(self, movement_value):
-    """Checks if self is parent or children to movement_value, so those are related somehow
-
-    As simulation tree is not related to business process, relation can be bidirectional
-    """
-    self_path_list = self.getRelativeUrl().split('/')
-    movement_path_list = movement_value.getRelativeUrl().split('/')
-    
-    if len(self_path_list) == len(movement_path_list):
-      # same level, cannot be related
-      return False
-    
-    index = 0
-    for self_part in self_path_list:
-      try:
-        movement_part = movement_path_list[index]
-      except IndexError:
-        # so far was good, they are related
-        return True
-      if self_part != movement_part:
-        return False
-      index += 1
-
   security.declareProtected(Permissions.AccessContentsInformation,
                             'isBuildable')
   def isBuildable(self):
@@ -584,7 +561,7 @@ class SimulationMovement(Movement):
         for successor_related in predecessor.getSuccessorRelatedValueList():
           for business_path_movement in successor_related \
               .getRelatedSimulationMovementValueList(explanation_value):
-            if self._isRelatedWithMovement(business_path_movement):
+            if successor_related.isMovementRelatedWithMovement(self, business_path_movement):
               business_path_movement_delivery = business_path_movement \
                   .getDeliveryValue()
               if business_path_movement_delivery is None:
diff --git a/product/ERP5/interfaces/business_path.py b/product/ERP5/interfaces/business_path.py
index 08e6ebcc3e..597dbce690 100644
--- a/product/ERP5/interfaces/business_path.py
+++ b/product/ERP5/interfaces/business_path.py
@@ -63,3 +63,12 @@ class IBusinessPath(IBusinessCompletable, IBusinessBuildable):
     explanation - any document related to business path - which bootstraped
                   process or is related to build of one paths
     """
+
+  def isMovementRelatedWithMovement(movement_value_a, movement_value_b)
+    """Checks if self is parent or children to movement_value
+
+    This logic is Business Process specific for Simulation Movements, as
+    sequence of Business Process is not related appearance of Simulation Tree
+
+    movement_value_a, movement_value_b - movements to check relation between
+    """
-- 
2.30.9