From 14da89ccc7660e5e48264875a235e08b387e7491 Mon Sep 17 00:00:00 2001
From: Jean-Paul Smets <jp@nexedi.com>
Date: Sun, 4 Jul 2010 13:30:31 +0000
Subject: [PATCH] Changes in this commit are the consequence of discussion
 between JPS and YO on the split of business path into business link and trade
 model path. The notion of "completion date" does not make much sense anymore
 in a context in which Business Link are unrelated to dates. Instead, we
 provide helpers to gather lists and stats of simulation movements of an
 expanded simulation tree. This should be equivalet for usability at user
 level, with appropriate reports. The notion of "expected start date and stop
 date" is kept because it is useful. It is only related to Trade Model Path. A
 mockup (non working) implementation is provided and supports a bit of
 recursion so that it can handle the case of transformations. Various security
 declarations were added. Next commit will make this work if everyone agrees
 on interfaces.

git-svn-id: https://svn.erp5.org/repos/public/erp5/sandbox/amount_generator@36833 20353a03-c40f-0410-a6d1-a30d3c3de9de
---
 product/ERP5/Document/BusinessProcess.py     | 314 +++++++++----------
 product/ERP5/ExplanationCache.py             |  41 ++-
 product/ERP5/PropertySheet/BusinessLink.py   |   5 -
 product/ERP5/PropertySheet/TradeModelPath.py |  52 +++
 product/ERP5/interfaces/business_process.py  | 173 +++++-----
 5 files changed, 332 insertions(+), 253 deletions(-)
 create mode 100644 product/ERP5/PropertySheet/TradeModelPath.py

diff --git a/product/ERP5/Document/BusinessProcess.py b/product/ERP5/Document/BusinessProcess.py
index f0a0b894f3..fe622fe485 100644
--- a/product/ERP5/Document/BusinessProcess.py
+++ b/product/ERP5/Document/BusinessProcess.py
@@ -61,7 +61,7 @@ class BusinessProcess(Path, XMLObject):
 
   Business Process completion, dates, etc. are calculated
   always in the context of an explanation. Sometimes, 
-  it is necessary to ignore certain business path to evaluate
+  it is necessary to ignore certain business link to evaluate
   completion or completion dates. This is very true for Union 
   Business Processes. This is the concept of Business Link closure,
   ie. filtering out all Business Link which are not used in an explanation.
@@ -83,12 +83,12 @@ class BusinessProcess(Path, XMLObject):
   - add security declarations
   - why are we using objectValues in some places ?
   - add a property to rules in order to determine whether dates
-    are provided by the rule or by business path. This is an extension
+    are provided by the rule or by business link / trade model path. This is an extension
     of the idea that root applied rules provide date information.
   - use explanation cache more to optimize speed
   - DateTime must be extended in ERP5 to support  +infinite and -infinite 
     like floating points do
-  - support conversions in business path
+  - support conversions in trade model path
 
   RENAMED:
     getPathValueList -> getBusinessLinkValueList
@@ -115,11 +115,98 @@ class BusinessProcess(Path, XMLObject):
   zope.interface.implements(interfaces.IBusinessProcess,
                             interfaces.IArrowBase)
 
+  # ITradeModelPathProcess implementation
+  security.declareProtected(Permissions.AccessContentsInformation, 'getTradeModelPathValueList')
+  def getTradeModelPathValueList(self, trade_phase=None, context=None, **kw):
+    """Returns all Trade Model Path of the current Business Process which
+    are matching the given trade_phase and the optional context.
+
+    trade_phase -- filter by trade phase
+
+    context -- a context to test each Business Link on
+               and filter out Business Link which do not match
+
+    **kw -- same arguments as those passed to searchValues / contentValues
+    """
+    if trade_phase is None:
+      trade_phase = set()
+    elif not isinstance(trade_phase, (list, tuple)):
+      trade_phase = set((trade_phase,))
+    else:
+      trade_phase = set(trade_phase)
+    result = []
+    if kw.get('portal_type', None) is None:
+      kw['portal_type'] = self.getPortalTradeModelPathTypeList()
+    if kw.get('sort_on', None) is None:
+      kw['sort_on'] = 'int_index'
+    original_path_list = self.objectValues(**kw) # Why Object Values ??? XXX-JPS
+    # Separate the selection of trade model paths into two steps
+    # for easier debugging.
+    # First, collect trade model paths which can be applicable to a given context.
+    path_list = []
+    for path in original_path_list:
+      accept_path = True
+      if trade_phase is not None and not trade_phase.intersection(path.getTradePhaseList()):
+        accept_path = False # Filter our business path which trade phase does not match
+      if accept_path:
+        path_list.append(path)
+    # Then, filter trade model paths by Predicate API.
+    # FIXME: Ideally, we should use the Domain Tool to search business paths,
+    # and avoid using the low level Predicate API. But the Domain Tool does
+    # support the condition above without scripting?
+    for path in path_list:
+      if path.test(context):
+        result.append(path)
+    return result
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getExpectedTradeModelPathStartAndStopDate')
+  def getExpectedTradeModelPathStartAndStopDate(self, explanation, trade_model_path,
+                                                      delay_mode=None):
+    """Returns the expected start and stop dates of given Trade Model Path
+    document in the context of provided explanation.
+
+    explanation -- an Order, Order Line, Delivery or Delivery Line or
+                   Applied Rule which implicitely defines a simulation subtree
+
+    trade_model_path -- a Trade Model Path document
+
+    delay_mode -- optional value to specify calculation mode ('min', 'max')
+                  if no value specified use average delay
+    """
+    if explanation.getPortalType() != 'Applied Rule':
+      raise TypeError('explanation must be an Applied Rule')
+
+    if explanation.getParentValue().getPortalType() == 'Simulation Tool':
+      raise ValueError('explanation must not be a Root Applied Rule')
+
+    trade_date = trade_model_path.getTradeDate()
+    if not trade_date:
+      raise ValueError('a trade_date must be defined on every Trade Model Path')
+
+    reference_date_method_id = trade_model_path.getReferenceDateMethodId()
+    if not reference_date_method_id:
+      raise ValueError('a reference date method must be defined on every Trade Model Path')
+
+    explanation_cache = _getExplanationCache(self, explanation)
+    reference_date = explanation_cache.getReferenceDate(self, trade_date, reference_date_method_id)
+
+    # Computer start_date and stop_date (XXX-JPS this could be cached and accelerated)
+    start_date = reference_date + trade_model_path.getPaymentTerm() # XXX-JPS Until better naming
+    if delay_mode == 'min':
+      delay = trade_model_path.getMinDelay()
+    elif delay_mode == 'max':
+      delay = trade_model_path.getMaxDelay()
+    else:
+      delay = (business_link.getMaxDelay() + trade_model_path.getMinDelay()) / 2.0
+    stop_date = start_date + delay
+        
+    return start_date, stop_date
+
   # IBusinessLinkProcess implementation
   security.declareProtected(Permissions.AccessContentsInformation, 'getBusinessLinkValueList')
   def getBusinessLinkValueList(self, trade_phase=None, context=None,
                                predecessor=None, successor=None, **kw):
-    """Returns all Path of the current BusinessProcess which
+    """Returns all Business Links of the current BusinessProcess which
     are matching the given trade_phase and the optional context.
 
     trade_phase -- filter by trade phase
@@ -145,22 +232,22 @@ class BusinessProcess(Path, XMLObject):
     if kw.get('sort_on', None) is None:
       kw['sort_on'] = 'int_index'
     original_business_link_list = self.objectValues(**kw) # Why Object Values ??? XXX-JPS
-    # Separate the selection of business paths into two steps
+    # Separate the selection of business links into two steps
     # for easier debugging.
-    # First, collect business paths which can be applicable to a given context.
+    # First, collect business links which can be applicable to a given context.
     business_link_list = []
     for business_link in original_business_link_list:
-      accept_path = True
+      accept_link = True
       if predecessor is not None and business_link.getPredecessor() != predecessor:
-        accept_path = False # Filter our business path which predecessor does not match
+        accept_link = False # Filter our business link which predecessor does not match
       if successor is not None and business_link.getSuccessor() != successor:
-        accept_path = False # Filter our business path which predecessor does not match
+        accept_link = False # Filter our business link which predecessor does not match
       if trade_phase is not None and not trade_phase.intersection(business_link.getTradePhaseList()):
-        accept_path = False # Filter our business path which trade phase does not match
-      if accept_path:
+        accept_link = False # Filter our business link which trade phase does not match
+      if accept_link:
         business_link_list.append(business_link)
-    # Then, filter business paths by Predicate API.
-    # FIXME: Ideally, we should use the Domain Tool to search business paths,
+    # Then, filter business links by Predicate API.
+    # FIXME: Ideally, we should use the Domain Tool to search business links,
     # and avoid using the low level Predicate API. But the Domain Tool does
     # support the condition above without scripting?
     for business_link in business_link_list:
@@ -168,6 +255,7 @@ class BusinessProcess(Path, XMLObject):
         result.append(business_link)
     return result
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isBusinessLinkCompleted')
   def isBusinessLinkCompleted(self, explanation, business_link):
     """Returns True if given Business Link document
     is completed in the context of provided explanation.
@@ -182,7 +270,7 @@ class BusinessProcess(Path, XMLObject):
       return False
     predecessor_state = business_link.getPredecessor()
     if not predecessor_state:
-      # This is a root business path, no predecessor
+      # This is a root business links, no predecessor
       # so no need to do any recursion
       return True
     if self.isTradeStateCompleted(explanation, predecessor_state):
@@ -190,16 +278,17 @@ class BusinessProcess(Path, XMLObject):
       # given explanation, return True
       # Please note that this is a specific case for a Business Process
       # built using asUnionBusinessProcess. In such business process
-      # a business path may be completed even if its predecessor state
+      # a business link may be completed even if its predecessor state
       # is not
       return True
     # Build the closure business process which only includes those business 
-    # path wich are directly related to the current business path but DO NOT 
+    # links wich are directly related to the current business link but DO NOT 
     # narrow down the explanation else we might narrow down so much that
     # it becomes an empty set
     closure_process = _getBusinessLinkClosure(self, explanation, business_link)
     return closure_process.isTradeStateCompleted(explanation, predecessor_state)
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isBusinessLinkPartiallyCompleted')
   def isBusinessLinkPartiallyCompleted(self, explanation, business_link):
     """Returns True if given Business Link document
     is partially completed in the context of provided explanation.
@@ -214,7 +303,7 @@ class BusinessProcess(Path, XMLObject):
       return False
     predecessor_state = business_link.getPredecessor()
     if not predecessor_state:
-      # This is a root business path, no predecessor
+      # This is a root business link, no predecessor
       # so no need to do any recursion
       return True
     if self.isTradeStatePartiallyCompleted(explanation, predecessor_state):
@@ -222,94 +311,19 @@ class BusinessProcess(Path, XMLObject):
       # given explanation, return True
       # Please note that this is a specific case for a Business Process
       # built using asUnionBusinessProcess. In such business process
-      # a business path may be partially completed even if its predecessor
+      # a business link may be partially completed even if its predecessor
       # state is not
       return True
     # Build the closure business process which only includes those business 
-    # path wich are directly related to the current business path but DO NOT 
+    # links wich are directly related to the current business link but DO NOT 
     # narrow down the explanation else we might narrow down so much that
     # it becomes an empty set
     closure_process = _getBusinessLinkClosure(explanation, business_link)
     return closure_process.isTradeStatePartiallyCompleted(explanation, 
                                                            predecessor_state)
 
-  def getExpectedBusinessLinkCompletionDate(self, explanation, business_link, 
-                                                       delay_mode=None):
-    """Returns the expected completion date of given Business Link document
-    in the context of provided explanation.
-
-    explanation -- an Order, Order Line, Delivery or Delivery Line or
-                   Applied Rule which implicitely defines a simulation subtree
-
-    business_link -- a Business Link document
-
-    delay_mode -- optional value to specify calculation mode ('min', 'max')
-                  if no value specified use average delay
-    """
-    closure_process = _getBusinessLinkClosure(self, explanation, business_link)
-    # XXX use explanation cache to optimize
-    predecessor = business_link.getPredecessor()
-    if closure_process.isInitialTradeState(predecessor):
-      return business_link.getCompletionDate(explanation)
-
-    # Recursively find reference_date
-    reference_date = closure_process.getExpectedTradeStateCompletionDate(explanation, predecessor)
-    start_date = reference_date + business_link.getPaymentTerm() # XXX-JPS Until better naming
-    if delay_mode == 'min':
-      delay = business_link.getMinDelay()
-    elif delay_mode == 'max':
-      delay = business_link.getMaxDelay()
-    else:
-      delay = (business_link.getMaxDelay() + business_link.getMinDelay()) / 2.0
-    stop_date = start_date + delay
-    
-    completion_date_method_id = business_link.getCompletionDateMethodId()
-    if completion_date_method_id == 'getStartDate':
-      return start_date
-    elif completion_date_method_id == 'getStopDate':
-      return stop_date
-    
-    raise ValueError("Business Link does not support %s complete date method" % completion_date_method_id)    
-
-  def getExpectedTradeModelPathStartAndStopDate(self, explanation, trade_model_path,
-                                                         delay_mode=None):
-    """Returns the expected start and stop dates of given Trade Model Path
-    document in the context of provided explanation.
-
-    explanation -- an Order, Order Line, Delivery or Delivery Line or
-                   Applied Rule which implicitely defines a simulation subtree
-
-    trade_model_path -- a Trade Model Path document
-
-    delay_mode -- optional value to specify calculation mode ('min', 'max')
-                  if no value specified use average delay
-    """
-    if explanation.getPortalType() != 'Applied Rule':
-      raise TypeError('explanation must be an Applied Rule as part of expand process')
-    closure_process = _getBusinessLinkClosure(self, explanation, trade_model_path) # XXX-JPS ???
-    # XXX use explanation cache to optimize
-    trade_date = trade_model_path.getTradeDate()
-    if trade_date is not None:
-      trade_phase = trade_date[len('trade_date/'):] # XXX-JPS quite dirty way
-      reference_date = closure_process.getExpectedTradePhaseCompletionDate(explanation, trade_phase)
-    else:
-      predecessor = business_link.getPredecessor() # XXX-JPS all states are supposed to have a predecessor
-      reference_date = closure_process.getExpectedTradeStateCompletionDate(explanation, predecessor)
-
-    # Recursively find reference_date
-    reference_date = closure_process.getExpectedTradeStateCompletionDate(explanation, predecessor)
-    start_date = reference_date + trade_model_path.getPaymentTerm() # XXX-JPS Until better naming
-    if delay_mode == 'min':
-      delay = trade_model_path.getMinDelay()
-    elif delay_mode == 'max':
-      delay = trade_model_path.getMaxDelay()
-    else:
-      delay = (business_link.getMaxDelay() + trade_model_path.getMinDelay()) / 2.0
-    stop_date = start_date + delay
-        
-    return start_date, stop_date
-
   # IBuildableBusinessLinkProcess implementation
+  security.declareProtected(Permissions.AccessContentsInformation, 'getBuildableBusinessLinkValueList')
   def getBuildableBusinessLinkValueList(self, explanation):
     """Returns the list of Business Link which are buildable
     by taking into account trade state dependencies between
@@ -324,6 +338,7 @@ class BusinessProcess(Path, XMLObject):
         result.append(business_link)
     return result
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'getPartiallyBuildableBusinessLinkValueList')
   def getPartiallyBuildableBusinessLinkValueList(self, explanation):
     """Returns the list of Business Link which are partially buildable
     by taking into account trade state dependencies between
@@ -338,6 +353,7 @@ class BusinessProcess(Path, XMLObject):
         result.append(business_link)
     return result
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isBusinessLinkBuildable')
   def isBusinessLinkBuildable(self, explanation, business_link):
     """Returns True if any of the related Simulation Movement
     is buildable and if the predecessor trade state is completed.
@@ -355,6 +371,7 @@ class BusinessProcess(Path, XMLObject):
     predecessor = business_link.getPredecessor()
     return closure_process.isTradeStateCompleted(predecessor)
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isBusinessLinkPartiallyBuildable')
   def isBusinessLinkPartiallyBuildable(self, explanation, business_link):
     """Returns True if any of the related Simulation Movement
     is buildable and if the predecessor trade state is partially completed.
@@ -373,6 +390,7 @@ class BusinessProcess(Path, XMLObject):
     return closure_process.isTradeStatePartiallyCompleted(predecessor)
 
   # ITradeStateProcess implementation
+  security.declareProtected(Permissions.AccessContentsInformation, 'getTradeStateList')
   def getTradeStateList(self):
     """Returns list of all trade_state of this Business Process
     by looking at successor and predecessor values of contained
@@ -387,6 +405,7 @@ class BusinessProcess(Path, XMLObject):
       result.add(business_link.getSuccessor())
     return result
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isInitialTradeState')
   def isInitialTradeState(self, trade_state):
     """Returns True if given 'trade_state' has no successor related
     Business Link.
@@ -395,6 +414,7 @@ class BusinessProcess(Path, XMLObject):
     """
     return len(self.getBusinessLinkValueList(successor=trade_state)) == 0
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isFinalTradeState')
   def isFinalTradeState(self, trade_state):
     """Returns True if given 'trade_state' has no predecessor related
     Business Link.
@@ -403,10 +423,11 @@ class BusinessProcess(Path, XMLObject):
     """
     return len(self.getBusinessLinkValueList(predecessor=trade_state)) == 0
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'getSuccessorTradeStateList')
   def getSuccessorTradeStateList(self, explanation, trade_state):
     """Returns the list of successor states in the 
     context of given explanation. This list is built by looking
-    at all successor of business path involved in given explanation
+    at all successor of business link involved in given explanation
     and which predecessor is the given trade_phase.
 
     explanation -- an Order, Order Line, Delivery or Delivery Line or
@@ -420,10 +441,11 @@ class BusinessProcess(Path, XMLObject):
         result.add(business_link.getSuccessor())
     return result
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'getPredecessorTradeStateList')
   def getPredecessorTradeStateList(self, explanation, trade_state):
     """Returns the list of predecessor states in the 
     context of given explanation. This list is built by looking
-    at all predecessor of business path involved in given explanation
+    at all predecessor of business link involved in given explanation
     and which sucessor is the given trade_phase.
 
     explanation -- an Order, Order Line, Delivery or Delivery Line or
@@ -437,6 +459,7 @@ class BusinessProcess(Path, XMLObject):
         result.add(business_link.getPredecessor())
     return result
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'getCompletedTradeStateList')
   def getCompletedTradeStateList(self, explanation):
     """Returns the list of Trade States which are completed
     in the context of given explanation.
@@ -446,6 +469,7 @@ class BusinessProcess(Path, XMLObject):
     """
     return filter(lambda x:self.isTradeStateCompleted(explanation, x), self.getTradeStateList())
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'getPartiallyCompletedTradeStateList')
   def getPartiallyCompletedTradeStateList(self, explanation):
     """Returns the list of Trade States which are partially 
     completed in the context of given explanation.
@@ -455,6 +479,7 @@ class BusinessProcess(Path, XMLObject):
     """
     return filter(lambda x:self.isTradeStatePartiallyCompleted(explanation, x), self.getTradeStateList())
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'getLatestCompletedTradeStateList')
   def getLatestCompletedTradeStateList(self, explanation):
     """Returns the list of completed trade states which predecessor
     states are completed and for which no successor state 
@@ -470,6 +495,7 @@ class BusinessProcess(Path, XMLObject):
           result.add(state)
     return result
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'getLatestPartiallyCompletedTradeStateList')
   def getLatestPartiallyCompletedTradeStateList(self, explanation):
     """Returns the list of completed trade states which predecessor
     states are completed and for which no successor state 
@@ -485,6 +511,7 @@ class BusinessProcess(Path, XMLObject):
           result.add(state)
     return result
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isTradeStateCompleted')
   def isTradeStateCompleted(self, explanation, trade_state):
     """Returns True if all predecessor trade states are
     completed and if no successor trade state is completed
@@ -500,6 +527,7 @@ class BusinessProcess(Path, XMLObject):
         return False
     return True      
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isTradeStatePartiallyCompleted')
   def isTradeStatePartiallyCompleted(self, explanation, trade_state):
     """Returns True if all predecessor trade states are
     completed and if no successor trade state is partially completed
@@ -515,25 +543,8 @@ class BusinessProcess(Path, XMLObject):
         return False
     return True      
 
-  def getExpectedTradeStateCompletionDate(self, explanation, trade_state,
-                                                         delay_mode=None):
-    """Returns the date at which the give trade state is expected
-    to be completed in the context of given explanation.
-
-    explanation -- an Order, Order Line, Delivery or Delivery Line or
-                   Applied Rule which implicitely defines a simulation subtree
-
-    trade_state -- a Trade State category
-
-    delay_mode -- optional value to specify calculation mode ('min', 'max')
-                  if no value specified use average delay
-    """
-    date_list = []
-    for business_link in self.getBusinessLinkValueList(successor=trade_state):
-      date_list.append(self.getExpectedBusinessLinkCompletionDate(explanation, business_link))
-    return max(date_list) # XXX-JPS it would be good that date support infinite values
-
   # ITradePhaseProcess implementation
+  security.declareProtected(Permissions.AccessContentsInformation, 'getTradePhaseList')
   def getTradePhaseList(self):
     """Returns list of all trade_phase of this Business Process
     by looking at trade_phase values of contained Business Link.
@@ -543,6 +554,7 @@ class BusinessProcess(Path, XMLObject):
       result = result.union(business_link.getTradePhaseList())
     return result
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'getCompletedTradePhaseList')
   def getCompletedTradePhaseList(self, explanation):
     """Returns the list of Trade Phases which are completed
     in the context of given explanation.
@@ -552,6 +564,7 @@ class BusinessProcess(Path, XMLObject):
     """
     return filter(lambda x:self.isTradePhaseCompleted(explanation, x), self.getTradePhaseList())
     
+  security.declareProtected(Permissions.AccessContentsInformation, 'getPartiallyCompletedTradePhaseList')
   def getPartiallyCompletedTradePhaseList(self, explanation):
     """Returns the list of Trade Phases which are partially completed
     in the context of given explanation. 
@@ -561,8 +574,9 @@ class BusinessProcess(Path, XMLObject):
     """
     return filter(lambda x:self.isTradePhasePartiallyCompleted(explanation, x), self.getTradePhaseList())
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isTradePhaseCompleted')
   def isTradePhaseCompleted(self, explanation, trade_phase):
-    """Returns True all business path with given trade_phase
+    """Returns True all business link with given trade_phase
     applicable to given explanation are completed.
 
     explanation -- an Order, Order Line, Delivery or Delivery Line or
@@ -575,8 +589,9 @@ class BusinessProcess(Path, XMLObject):
         return False
     return True
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isTradePhasePartiallyCompleted')
   def isTradePhasePartiallyCompleted(self, explanation, trade_phase):
-    """Returns True at least one business path with given trade_phase
+    """Returns True at least one business link with given trade_phase
     applicable to given explanation is partially completed
     or completed.
 
@@ -590,31 +605,12 @@ class BusinessProcess(Path, XMLObject):
         return False
     return True
 
-  def getExpectedTradePhaseCompletionDate(self, explanation, trade_phase,
-                                                       delay_mode=None):
-    """Returns the date at which the give trade phase is expected
-    to be completed in the context of given explanation, taking
-    into account the graph of date constraints defined by business path
-    and business states.
-
-    explanation -- an Order, Order Line, Delivery or Delivery Line or
-                   Applied Rule which implicitely defines a simulation subtree
-
-    trade_phase -- a Trade Phase category
-
-    delay_mode -- optional value to specify calculation mode ('min', 'max')
-                  if no value specified use average delay
-    """
-    date_list = []
-    for business_link in self.getBusinessLinkValueList(trade_phase=trade_phase):
-      date_list.append(self.getExpectedBusinessLinkCompletionDate(explanation, business_link, delay_mode=delay_mode))
-    return max(date_list)
-
+  security.declareProtected(Permissions.AccessContentsInformation, 'getRemainingTradePhaseList')
   def getRemainingTradePhaseList(self, business_link, trade_phase_list=None):
     """Returns the list of remaining trade phases which to be achieved
     as part of a business process. This list is calculated by analysing 
-    the graph of business path and trade states, starting from a given
-    business path. The result if filtered by a list of trade phases. This
+    the graph of business link and trade states, starting from a given
+    business link. The result if filtered by a list of trade phases. This
     method is useful mostly for production and MRP to manage a distributed
     supply and production chain.
 
@@ -633,16 +629,16 @@ class BusinessProcess(Path, XMLObject):
     API less uniform.
     """
     remaining_trade_phase_list = []
-    for path in [x for x in self.objectValues(portal_type="Business Link") \
+    for link in [x for x in self.objectValues(portal_type="Business Link") \
         if x.getPredecessorValue() == trade_state]:
-      # XXX When no simulations related to path, what should path.isCompleted return?
+      # XXX When no simulations related to link, what should link.isCompleted return?
       #     if True we don't have way to add remaining trade phases to new movement
-      if not (path.getRelatedSimulationMovementValueList(explanation) and
-              path.isCompleted(explanation)):
-        remaining_trade_phase_list += path.getTradePhaseValueList()
+      if not (link.getRelatedSimulationMovementValueList(explanation) and
+              link.isCompleted(explanation)):
+        remaining_trade_phase_list += link.getTradePhaseValueList()
 
       # collect to successor direction recursively
-      state = path.getSuccessorValue()
+      state = link.getSuccessorValue()
       if state is not None:
         remaining_trade_phase_list.extend(
           self.getRemainingTradePhaseList(explanation, state, None))
@@ -655,6 +651,7 @@ class BusinessProcess(Path, XMLObject):
 
     return remaining_trade_phase_list
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'getTradePhaseMovementList')
   def getTradePhaseMovementList(self, explanation, amount, trade_phase=None, delay_mode=None):
     """Returns a list of movement with appropriate arrow and dates,
     based on the Business Link definitions, provided 'amount' and optional
@@ -681,7 +678,7 @@ class BusinessProcess(Path, XMLObject):
     result = []
     id_index = 0
     base_id = amount.getId()
-    for business_link in self.getTradePathValueList(context=amount, trade_phase=trade_phase):
+    for business_link in self.getTradeModelPathValueList(context=amount, trade_phase=trade_phase):
       id_index += 1
       movement = newTempMovement(business_link, '%s_%s' % (base_id, id_index))
       kw = self._getPropertyAndCategoryDict(explanation, amount, business_link, delay_mode=delay_mode)
@@ -765,19 +762,20 @@ class BusinessProcess(Path, XMLObject):
         # applied rules which are not root applied rules. 
         # XXX-JPS could be extended with a rule property instead
         # of supports only in root applied rule case
-        start_date, stop_date = self.getExpectedTradePathStartAndStopDate(
+        start_date, stop_date = self.getExpectedTradeModelPathStartAndStopDate(
                                explanation, business_link, delay_mode=delay_mode)
         property_dict['start_date'] = start_date
         property_dict['stop_date'] = stop_date
     else:
       raise TypeError("Explanation must be an Applied Rule in expand process") # Nothing to do
 
-    # Set causality to business path
+    # Set causality to business link
     property_dict['causality'] = business_link.getRelativeUrl() # XXX-JPS Will not work if we do not use real object
 
     return property_dict 
 
   # IBusinessProcess global API
+  security.declareProtected(Permissions.AccessContentsInformation, 'isCompleted')
   def isCompleted(self, explanation):
     """Returns True is all applicable Trade States and Trade Phases
     are completed in the context of given explanation.
@@ -789,24 +787,8 @@ class BusinessProcess(Path, XMLObject):
       if not state.isTradeStateCompleted(explanation):
         return False
     return True
-
-  def getExpectedCompletionDate(self, explanation, delay_mode=None):
-    """Returns the expected date at which all applicable Trade States and
-    Trade Phases are completed in the context of given explanation.
-
-    explanation -- an Order, Order Line, Delivery or Delivery Line or
-                   Applied Rule which implicitely defines a simulation subtree
-    """
-    date_list = []
-    # This implementation looks completely silly in the sense that it does
-    # not try to find a "final" state. However, it has the advantage to support
-    # negative delays in business path and propper optimization of ExplanationCache
-    # and completion methods should actually prevent calculating the same
-    # thing multiple times
-    for trade_state in self.getTradeStateList():
-      date_list.append(self.getExpectedTradeStateCompletionDate(explanation, delay_mode=delay_mode))
-    return max(date_list)
   
+  security.declareProtected(Permissions.AccessContentsInformation, 'isBuildable')
   def isBuildable(self, explanation):
     """Returns True is one Business Link of this Business Process
     is buildable in the context of given explanation.
@@ -816,6 +798,7 @@ class BusinessProcess(Path, XMLObject):
     """
     return len(self.getBuildableBusinessLinkValueList(explanation)) != 0
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'isPartiallyBuildable')
   def isPartiallyBuildable(self, explanation):
     """Returns True is one Business Link of this Business Process
     is partially buildable in the context of given explanation.
@@ -825,6 +808,7 @@ class BusinessProcess(Path, XMLObject):
     """
     return len(self.getPartiallyBuildableBusinessLinkValueList(explanation)) != 0
 
+  security.declareProtected(Permissions.AccessContentsInformation, 'build')
   def build(self, explanation):
     """
       Build whatever is buildable
diff --git a/product/ERP5/ExplanationCache.py b/product/ERP5/ExplanationCache.py
index 21a1db3e0f..d9c98c9ae3 100644
--- a/product/ERP5/ExplanationCache.py
+++ b/product/ERP5/ExplanationCache.py
@@ -203,8 +203,8 @@ class ExplanationCache:
     NOTE: Business Link Closure must be at least as "big" as composed 
     business path. The appropriate calculation is still not clear. 
     Options are:
-      - take all path of composed business path (even not yet expanded)
-      - take all path of composed business path which phase is not yet expanded
+      - take all link of composed business link (even not yet expanded)
+      - take all link of composed business link which phase is not yet expanded
     """
     # Try to return cached value first
     new_business_process = self.closure_cache.get(business_link, None)
@@ -271,6 +271,43 @@ class ExplanationCache:
     self.union_cache = new_business_process
     return new_business_process
 
+  def getReferenceDate(self, business_process, trade_phase, reference_date_method_id, delay_mode=None):
+    """Browse parent similation movements until a movement with
+    appropriate trade_phase is found.
+    """
+    # Find simulation movements with appropriate trade_phase
+    movement_list = self.getSimulationMovementValueList(trade_phase=trade_phase)
+
+    # Case 1: some (parent) simulation movement with appropriate trade phase exists
+    if len(movement_list):
+      # XXX-JPS - for now take arbitrary one
+      # but we should in reality some way to configure this
+      movement = movement_list[0]
+      method = getattr(movement, reference_date_method_id)
+      return method()
+
+    # Case 2: we must recursively find another trade phase
+    # to find the value recursively
+    # XXX-JPS this is only useful for production (MRP) in reality
+    # whenever trade model path define time constraints within the same
+    # movement generator (ie. transformation with multiple phases)
+    path_list = business_process.getTradeModelPathValueList(trade_phase=trade_phase)
+    if not len(path_list):
+      raise ValueError('No Trade Model Path defines a reference data.')
+
+    path = path_list[0] 
+    # XXX-JPS - for now take arbitrary one
+    # but we should in reality some way to configure this
+    start_date, stop_date = business_process.getExpectedTradeModelPathStartAndStopDate(
+                                   self.explanation, path, delay_mode=delay_mode)
+
+    # Create a fake simulation movement and lookup property
+    movement = self.explanation.newContent(portal_type="Simulation Movement", 
+                                           temp_object=True, 
+                                           start_date=start_date, stop_date=stop_date,
+                                           trade_phase=trade_phase, causality=path)
+    method = getattr(movement, reference_date_method_id)
+    return method()
 
 def _getExplanationCache(explanation):
   # Return cached value if any
diff --git a/product/ERP5/PropertySheet/BusinessLink.py b/product/ERP5/PropertySheet/BusinessLink.py
index 503f285fc5..2b29a97abb 100644
--- a/product/ERP5/PropertySheet/BusinessLink.py
+++ b/product/ERP5/PropertySheet/BusinessLink.py
@@ -33,11 +33,6 @@ class BusinessLink:
       Business Link properties
     """
     _properties = (
-        {   'id'          : 'completion_date_method_id',
-            'description' : 'ID of method to get source list of categories',
-            'type'        : 'string',
-            'default'     : 'getStartDate',
-            'mode'        : 'w' },
         {   'id'          : 'completed_state',
             'description' : 'List of states for which related Simulation '
                             'Movement is considered as completed',
diff --git a/product/ERP5/PropertySheet/TradeModelPath.py b/product/ERP5/PropertySheet/TradeModelPath.py
new file mode 100644
index 0000000000..dfdb401512
--- /dev/null
+++ b/product/ERP5/PropertySheet/TradeModelPath.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
+#                    Lukasz Nowak <luke@nexedi.com>
+#                    Yusuke Muraoka <yusuke@nexedi.com>
+#
+# WARNING: This program as such is intended to be used by professional
+# programmers who take the whole responsability of assessing all potential
+# consequences resulting from its eventual inadequacies and bugs
+# End users who are looking for a ready-to-use solution with commercial
+# garantees and support are strongly adviced to contract a Free Software
+# Service Company
+#
+# This program is Free Software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+##############################################################################
+
+class TradeModelPath:
+    """
+      Trade Model Path properties
+    """
+    _properties = (
+        {   'id'          : 'source_method_id',
+            'description' : 'ID of method to get source list of categories',
+            'type'        : 'string',
+            'mode'        : 'w' },
+        {   'id'          : 'destination_method_id',
+            'description' : 'ID of method to get destination list of categories',
+            'type'        : 'string',
+            'mode'        : 'w' },
+        {   'id'          : 'reference_date_method_id',
+            'description' : 'ID of method to get the reference date at the trade_phase defined by trade_date',
+            'type'        : 'string',
+            'default'     : 'getStopDate',
+            'mode'        : 'w' },
+    )
+
+    _categories = ('end_of', # XXX-JPS What is end_of ????
+                   'trade_phase' , 'incoterm') # XXX-JPS why incoterm ?
diff --git a/product/ERP5/interfaces/business_process.py b/product/ERP5/interfaces/business_process.py
index 2a5ac89a08..fac5e7135e 100644
--- a/product/ERP5/interfaces/business_process.py
+++ b/product/ERP5/interfaces/business_process.py
@@ -33,29 +33,39 @@ Products.ERP5.interfaces.business_process
 from zope.interface import Interface
 
 class ITradeModelPathProcess(Interface):
-  """
+  """Trade Model Path Process interface specification
+
+  ITradeModelPathProcess defines Business Process APIs related
+  to Trade Model Path access and to the evaluation of start_date,
+  stop date, quantity shares and arrows of an Amount.
   """
 
-  def getTradeModelPathValueList():
-    """
+  def getTradeModelPathValueList(trade_phase=None, context=None, **kw):
+    """Returns all Trade Model Path of the current Business Process which
+    are matching the given trade_phase and the optional context.
+
+    trade_phase -- filter by trade phase
+
+    context -- a context to test each Business Link on
+               and filter out Business Link which do not match
+
+    **kw -- same arguments as those passed to searchValues / contentValues
     """
 
-  def getExpectedTradeModelPathStartAndStopDate(explanation, business_link,
-                                              delay_mode=None):
-    """Returns the expected start and stop dates of given Business Link
+  def getExpectedTradeModelPathStartAndStopDate(explanation, trade_model_path,
+                                                delay_mode=None):
+    """Returns the expected start and stop dates of given Trade Model Path
     document in the context of provided explanation.
 
     explanation -- an Order, Order Line, Delivery or Delivery Line or
                    Applied Rule which implicitely defines a simulation subtree
 
-    business_link -- a Business Link document
+    trade_model_path -- a Trade Model Path document
 
     delay_mode -- optional value to specify calculation mode ('min', 'max')
                   if no value specified use average delay
     """
 
-
-
 class IBusinessLinkProcess(Interface):
   """Business Link Process interface specification
 
@@ -81,7 +91,8 @@ class IBusinessLinkProcess(Interface):
 
   def getBusinessLinkValueList(trade_phase=None, context=None,
                                predecessor=None, successor=None, **kw):
-    """Returns the list of contained Business Link documents
+    """Returns all Business Links of the current BusinessProcess which
+    are matching the given trade_phase and the optional context.
 
     trade_phase -- filter by trade phase
 
@@ -115,26 +126,11 @@ class IBusinessLinkProcess(Interface):
     business_link -- a Business Link document
     """
 
-  def getExpectedBusinessLinkCompletionDate(explanation, business_link, 
-                                                       delay_mode=None):
-    """Returns the expected completion date of given Business Link document
-    in the context of provided explanation.
-
-    explanation -- an Order, Order Line, Delivery or Delivery Line or
-                   Applied Rule which implicitely defines a simulation subtree
-
-    business_link -- a Business Link document
-
-    delay_mode -- optional value to specify calculation mode ('min', 'max')
-                  if no value specified use average delay
-    """
-
-
 class IBuildableBusinessLinkProcess(Interface):
   """Buildable Business Link Process interface specification
 
   IBuildableBusinessLinkProcess defines an API to build
-  simulation movements related to business pathj in the context
+  simulation movements related to business link in the context
   of a given explanation.
   """
 
@@ -192,11 +188,11 @@ class ITradeStateProcess(Interface):
   ITradeStateProcess defines Business Process APIs related
   to Trade State completion status and expected completion dates.
   ITradeStateProcess APIs recursively browse trade states and business
-  path to provide completion status and expected completion dates.
+  links to provide completion status and expected completion dates.
 
   For example, a complete trade state is a trade state for
   which all predecessor trade states are completed and for
-  which all business path applicable to the given explanation
+  which all business links applicable to the given explanation
   are also completed.
   """
 
@@ -226,7 +222,7 @@ class ITradeStateProcess(Interface):
   def getSuccessorTradeStateList(explanation, trade_state):
     """Returns the list of successor states in the 
     context of given explanation. This list is built by looking
-    at all successor of business path involved in given explanation
+    at all successor of business link involved in given explanation
     and which predecessor is the given trade_phase.
 
     explanation -- an Order, Order Line, Delivery or Delivery Line or
@@ -238,7 +234,7 @@ class ITradeStateProcess(Interface):
   def getPredecessorTradeStateList(explanation, trade_state):
     """Returns the list of predecessor states in the 
     context of given explanation. This list is built by looking
-    at all predecessor of business path involved in given explanation
+    at all predecessor of business link involved in given explanation
     and which sucessor is the given trade_phase.
 
     explanation -- an Order, Order Line, Delivery or Delivery Line or
@@ -303,20 +299,6 @@ class ITradeStateProcess(Interface):
     trade_state -- a Trade State category
     """
 
-  def getExpectedTradeStateCompletionDate(explanation, trade_state,
-                                                         delay_mode=None):
-    """Returns the date at which the give trade state is expected
-    to be completed in the context of given explanation.
-
-    explanation -- an Order, Order Line, Delivery or Delivery Line or
-                   Applied Rule which implicitely defines a simulation subtree
-
-    trade_state -- a Trade State category
-
-    delay_mode -- optional value to specify calculation mode ('min', 'max')
-                  if no value specified use average delay
-    """
-
 class ITradePhaseProcess(Interface):
   """Trade Phase Process interface specification
 
@@ -324,12 +306,12 @@ class ITradePhaseProcess(Interface):
   to Trade Phase completion status and expected completion dates.
   Unlike ITradeStateProcess, ITradePhaseProcess APIs related to completion
   do not take into account relations between trade states and
-  business path.
+  business link.
 
   For example, a completed trade phase is a trade phase for which all
-  business path applicable to the given explanation are completed. 
+  business link applicable to the given explanation are completed. 
   It does not matter whether the predecessor trade state of related
-  business path is completed or not.
+  business link is completed or not.
   """
 
   def getTradePhaseList():
@@ -354,7 +336,7 @@ class ITradePhaseProcess(Interface):
     """
 
   def isTradePhaseCompleted(explanation, trade_phase):
-    """Returns True all business path with given trade_phase
+    """Returns True all business link with given trade_phase
     applicable to given explanation are completed.
 
     explanation -- an Order, Order Line, Delivery or Delivery Line or
@@ -364,7 +346,7 @@ class ITradePhaseProcess(Interface):
     """
 
   def isTradePhasePartiallyCompleted(explanation, trade_phase):
-    """Returns True at least one business path with given trade_phase
+    """Returns True at least one business link with given trade_phase
     applicable to given explanation is partially completed
     or completed.
 
@@ -374,27 +356,11 @@ class ITradePhaseProcess(Interface):
     trade_phase -- a Trade Phase category
     """
 
-  def getExpectedTradePhaseCompletionDate(explanation, trade_phase,
-                                                       delay_mode=None):
-    """Returns the date at which the give trade phase is expected
-    to be completed in the context of given explanation, taking
-    into account the graph of date constraints defined by business path
-    and business states.
-
-    explanation -- an Order, Order Line, Delivery or Delivery Line or
-                   Applied Rule which implicitely defines a simulation subtree
-
-    trade_phase -- a Trade Phase category
-
-    delay_mode -- optional value to specify calculation mode ('min', 'max')
-                  if no value specified use average delay
-    """
-
   def getRemainingTradePhaseList(business_link, trade_phase_list=None):
     """Returns the list of remaining trade phases which to be achieved
     as part of a business process. This list is calculated by analysing 
-    the graph of business path and trade states, starting from a given
-    business path. The result if filtered by a list of trade phases. This
+    the graph of business link and trade states, starting from a given
+    business link. The result if filtered by a list of trade phases. This
     method is useful mostly for production and MRP to manage a distributed
     supply and production chain.
 
@@ -428,8 +394,63 @@ class ITradePhaseProcess(Interface):
                   if no value specified use average delay
     """
 
+class ISimulationMovementProcess(Interface):
+  """Simulation Movemnt Process interface specification
+
+  ISimulationMovementProcess provides help methods to 
+  access simulation movements of an explanation and
+  gather statistics about them. It is useful to find
+  out min dates or max dates related to a business link,
+  to a trade phase, to a trade model path, to a 
+  trade_model_line, etc.
+  """
+
+  def getSimulationMovementList(explanation, trade_phase=None,
+     business_link=None, trade_model_path=None, trade_model_line=None, **kw):
+
+    """Returns a list of movement part of the simulation subtrees
+    defined by explanation and which match provided parameters. This
+    method can be useful for example to list all simulation movements
+    related to a phase such as payment, and inspect them.
+    
+    explanation -- an Order, Order Line, Delivery or Delivery Line or
+                   Applied Rule which implicitely defines a simulation subtree
+
+    trade_phase -- optional Trade Phase category
+
+    business_link -- optional Business Link document
+
+    trade_model_path -- optional Trade Model Path document
+
+    trade_model_line --optional Trade Model Line document
+
+    **kw -- other optional parameters which are passed to Catalog API
+    """
+
+  def getSimulationMovementStat(explanation, trade_phase=None,
+     business_link=None, trade_model_path=None, trade_model_line=None, **kw):
+
+    """Returns statistics for movements part of the simulation subtrees
+    defined by explanation and which match provided parameters. This
+    method can be useful for example to find the max date of simulation movements
+    related to a phase such as payment.
+    
+    explanation -- an Order, Order Line, Delivery or Delivery Line or
+                   Applied Rule which implicitely defines a simulation subtree
+
+    trade_phase -- optional Trade Phase category
+
+    business_link -- optional Business Link document
+
+    trade_model_path -- optional Trade Model Path document
+
+    trade_model_line --optional Trade Model Line document
+
+    **kw -- other optional parameters which are passed to Catalog API
+    """
+
 class IBusinessProcess(ITradeModelPathProcess, IBusinessLinkProcess, IBuildableBusinessLinkProcess,
-                       ITradeStateProcess, ITradePhaseProcess, ):
+                       ITradeStateProcess, ITradePhaseProcess, ISimulationMovementProcess):
   """Business Process interface specification.
 
   Business Process APIs are used to manage the completion status,
@@ -461,14 +482,6 @@ class IBusinessProcess(ITradeModelPathProcess, IBusinessLinkProcess, IBuildableB
                    Applied Rule which implicitely defines a simulation subtree
     """
 
-  def getExpectedCompletionDate(explanation, delay_mode=None):
-    """Returns the expected date at which all applicable Trade States and
-    Trade Phases are completed in the context of given explanation.
-
-    explanation -- an Order, Order Line, Delivery or Delivery Line or
-                   Applied Rule which implicitely defines a simulation subtree
-    """
-
   def build(explanation, include_partially_buildable=False):
     """Build whatever is buildable in the context of given explanation.
 
@@ -476,8 +489,6 @@ class IBusinessProcess(ITradeModelPathProcess, IBusinessLinkProcess, IBuildableB
                    Applied Rule which implicitely defines a simulation subtree
 
     include_partially_buildable -- if set to True, also build partially
-                                   buildable business path. Else
-                                   only build strictly buildable path.
-    """
-
-
+                                   buildable business link. Else
+                                   only build strictly buildable link.
+    """
\ No newline at end of file
-- 
2.30.9