Commit 9880c119 authored by Jean-Paul Smets's avatar Jean-Paul Smets

Some more ideas where BPM is heading to

git-svn-id: https://svn.erp5.org/repos/public/erp5/sandbox/amount_generator@35585 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent e31f2aef
...@@ -34,42 +34,12 @@ from AccessControl import ClassSecurityInfo ...@@ -34,42 +34,12 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, interfaces from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5.Document.Path import Path from Products.ERP5.Document.Path import Path
from Products.ERP5.Document.Predicate import Predicate from Products.ERP5.Document.Predicate import Predicate
from Products.ERP5.ExplanationCache import _getExplanationCache
import zope.interface import zope.interface
from zLOG import LOG from zLOG import LOG
class ExplanationCache:
"""ExplanationCache provides a central access to
all parameters and values which are needed to process
an explanation. It is based on the idea that a value is calculated
once and once only, as a way to accelerate performance of algorithms
related to an explanation.
'explanation_uid': self._getExplanationUidList(explanation) # XXX-JPS why do we need explanation_uid ? and why a list
'simulation_path': simulation_path,
explanation_uid = self._getExplanationUidList(explanation) # A hint value to reduce the size of the tree
simulation_path = '/erp5/p.../%' # A list of path
"""
def __init__(self, explanation):
"""
"""
self.explanation = explanation
def getRootExplanationUidList(self):
"""
"""
def getSimulationPathPatternList(self):
"""
"""
def _getExplanationCache(explanation):
# XXX-JPS Cache this in a transaction variable or better
return ExplanationCache(explanation)
class BusinessPath(Path, Predicate): class BusinessPath(Path, Predicate):
""" """
The BusinessPath class embeds all information related to The BusinessPath class embeds all information related to
...@@ -175,6 +145,12 @@ class BusinessPath(Path, Predicate): ...@@ -175,6 +145,12 @@ class BusinessPath(Path, Predicate):
#'destination_transport' #'destination_transport'
) )
# Helper Methods
def _getExplanationRelatedSimulationMovementValueList(self, explanation):
explanation_cache = _getExplanationCache(explanation)
return explanation_cache.getBusinessPathRelatedSimulationMovementValueList(self)
# XXX-JPS UNkonwn ?
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getArrowCategoryDict') 'getArrowCategoryDict')
def getArrowCategoryDict(self, context=None, **kw): # XXX-JPS do we need it in API ? def getArrowCategoryDict(self, context=None, **kw): # XXX-JPS do we need it in API ?
...@@ -271,6 +247,22 @@ class BusinessPath(Path, Predicate): ...@@ -271,6 +247,22 @@ class BusinessPath(Path, Predicate):
method_id = self.getCompletionDateMethodId() method_id = self.getCompletionDateMethodId()
method = getattr(movement, method_id) # We wish to raise if it does not exist method = getattr(movement, method_id) # We wish to raise if it does not exist
return method() return method()
def getCompletionDate(self, explanation):
"""Returns the date of completion of the movemnet
based on paremeters of the business path. This complete date can be
the start date, the stop date, the date of a given workflow transition
on the explaining delivery, etc.
XXX - DOC
movement -- a Simulation Movement
"""
date_list = []
for movement in self._getExplanationRelatedSimulationMovementValueList(explanation):
date_list.append(self.getMovementCompletionDate(movement))
return max(date_list)
security.declareProtected(Permissions.AccessContentsInformation, security.declareProtected(Permissions.AccessContentsInformation,
'getExpectedQuantity') 'getExpectedQuantity')
...@@ -363,6 +355,15 @@ class BusinessPath(Path, Predicate): ...@@ -363,6 +355,15 @@ class BusinessPath(Path, Predicate):
return False return False
return True return True
def isDelivered(self, explanation):
"""XXX
"""
for simulation_movement in self._getExplanationRelatedSimulationMovementValueList(
explanation):
if not simulation_movement.getDelivery():
return False
return True
def build(self, explanation): def build(self, explanation):
"""Builds all related movements in the simulation using the builders """Builds all related movements in the simulation using the builders
defined on the Business Path. defined on the Business Path.
......
...@@ -32,6 +32,7 @@ from AccessControl import ClassSecurityInfo ...@@ -32,6 +32,7 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, interfaces from Products.ERP5Type import Permissions, PropertySheet, interfaces
from Products.ERP5Type.XMLObject import XMLObject from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5.Document.Path import Path from Products.ERP5.Document.Path import Path
from Products.ERP5.ExplanationCache import _getExplanationCache, _getBusinessPathClosure
import zope.interface import zope.interface
...@@ -43,6 +44,8 @@ class BusinessProcess(Path, XMLObject): ...@@ -43,6 +44,8 @@ class BusinessProcess(Path, XMLObject):
TODO: TODO:
- finish interface implementation - finish interface implementation
RENAME:
getPathValueList -> getBusinessPathValueList
""" """
meta_type = 'ERP5 Business Process' meta_type = 'ERP5 Business Process'
portal_type = 'Business Process' portal_type = 'Business Process'
...@@ -66,19 +69,23 @@ class BusinessProcess(Path, XMLObject): ...@@ -66,19 +69,23 @@ class BusinessProcess(Path, XMLObject):
zope.interface.implements(interfaces.IBusinessProcess, zope.interface.implements(interfaces.IBusinessProcess,
interfaces.IArrowBase) interfaces.IArrowBase)
# Access to path and states of the business process # IBusinessPathProcess implementation
security.declareProtected(Permissions.AccessContentsInformation, 'getPathValueList') security.declareProtected(Permissions.AccessContentsInformation, 'getBusinessPathValueList')
def getPathValueList(self, trade_phase=None, context=None, **kw): def getBusinessPathValueList(self, trade_phase=None, context=None,
""" predecessor=None, successor=None, **kw):
Returns all Path of the current BusinessProcess which """Returns all Path of the current BusinessProcess which
are matching the given trade_phase and the optional context. are matching the given trade_phase and the optional context.
trade_phase -- filter by trade phase
context -- a context to test each Business Path on
and filter out Business Path which do not match
trade_phase -- a single trade phase category or a list of predecessor -- filter by trade state predecessor
trade phases
context -- the context to search matching predicates for successor -- filter by trade state successor
**kw -- same parameters as for searchValues / contentValues **kw -- same arguments as those passed to searchValues / contentValues
""" """
if trade_phase is None: if trade_phase is None:
trade_phase = set() trade_phase = set()
...@@ -87,14 +94,22 @@ class BusinessProcess(Path, XMLObject): ...@@ -87,14 +94,22 @@ class BusinessProcess(Path, XMLObject):
else: else:
trade_phase = set(trade_phase) trade_phase = set(trade_phase)
result = [] result = []
if kw.get('portal_type', None) is None:
kw['portal_type'] = self.getPortalBusinessPathTypeList()
if kw.get('sort_on', None) is None:
kw['sort_on'] = 'int_index'
original_business_path_list = self.objectValues(**kw), # Why Object Values ??? XXX-JPS
if len(trade_phase) == 0: if len(trade_phase) == 0:
return result return original_business_path_list # If not trade_phase is specified, return all Business Path
# Separate the selection of business paths into twp steps # Separate the selection of business paths into two steps
# for easier debugging. # for easier debugging.
# First, collect business paths which can be applicable to a given context. # First, collect business paths which can be applicable to a given context.
business_path_list = [] business_path_list = []
for business_path in self.objectValues(portal_type='Business Path', for business_path in original_business_path_list:
sort_on='int_index'): if predecessor is not None and business_path.getPredecessor() != predecessor:
break # Filter our business path which predecessor does not match
if successor is not None and business_path.getSuccessor() != successor:
break # Filter our business path which predecessor does not match
if trade_phase.intersection(business_path.getTradePhaseList()): if trade_phase.intersection(business_path.getTradePhaseList()):
business_path_list.append(business_path) business_path_list.append(business_path)
# Then, filter business paths by Predicate API. # Then, filter business paths by Predicate API.
...@@ -106,174 +121,430 @@ class BusinessProcess(Path, XMLObject): ...@@ -106,174 +121,430 @@ class BusinessProcess(Path, XMLObject):
result.append(business_path) result.append(business_path)
return result return result
security.declareProtected(Permissions.AccessContentsInformation, 'getStateValueList') def isBusinessPathCompleted(self, explanation, business_path):
def getStateValueList(self, *args, **kw): """Returns True if given Business Path document
""" is completed in the context of provided explanation.
Returns all states of the business process. The method
**kw parameters follows the API of searchValues / contentValues explanation -- an Order, Order Line, Delivery or Delivery Line which
""" implicitely defines a simulation subtree
# Naive implementation to redo XXX
kw['portal_type'] = "Business Path" business_path -- a Business Path document
return [x for x in [y.getSuccessorValue() for y in \ """
self.contentValues(*args, **kw)] if x is not None] # Return False if Business Path is not completed
if not business_path.isCompleted(explanation):
return False
predecessor_state = business_path.getPredecessor()
if not predecessor_state:
# This is a root business path, no predecessor
# so no need to do any recursion
return True
if self.isTradeStateCompleted(explanation, predecessor_state):
# If predecessor state is globally completed for the
# 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
# 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
# narrow down the explanation else we might narrow down so much that
# it becomes an empty set
closure_process = _getBusinessPathClosure(explanation, business_path)
return closure_process.isTradeStateCompleted(explanation, predecessor_state)
def isBusinessPathPartiallyCompleted(self, explanation, business_path):
"""Returns True if given Business Path document
is partially completed in the context of provided explanation.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
business_path -- a Business Path document
"""
# Return False if Business Path is not partially completed
if not business_path.isPartiallyCompleted(explanation):
return False
predecessor_state = business_path.getPredecessor()
if not predecessor_state:
# This is a root business path, no predecessor
# so no need to do any recursion
return True
if self.isTradeStatePartiallyCompleted(explanation, predecessor_state):
# If predecessor state is globally partially completed for the
# 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
# 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
# narrow down the explanation else we might narrow down so much that
# it becomes an empty set
closure_process = _getBusinessPathClosure(explanation, business_path)
return closure_process.isTradeStatePartiallyCompleted(explanation,
predecessor_state)
def getExpectedBusinessPathCompletionDate(explanation, business_path,
delay_mode=None):
"""Returns the expected completion date of given Business Path document
in the context of provided explanation.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
business_path -- a Business Path document
delay_mode -- optional value to specify calculation mode ('min', 'max')
if no value specified use average delay
"""
closure_process = _getBusinessPathClosure(explanation, business_path)
# XXX use explanatoin cache to optimize
predecessor = business_path.getPredecessor()
if closure_process.isTradeStateRootState(predecessor):
return business_path.getCompletionDate(explanation)
reference_date = closure_process.getExpectedTradeStateCompletionDate(explanation, predecessor)
return reference_date + wait_time + lead_time
# Access to path and states of the business process def getExpectedBusinessPathStartAndStopDate(explanation, business_path,
def isCompleted(self, explanation): delay_mode=None):
""" """Returns the expected start and stop dates of given Business Path
True if all states are completed document in the context of provided explanation.
"""
for state in self.getStateValueList():
if not state.isCompleted(explanation):
return False
return True
def isBuildable(self, explanation):
"""
True if all any path is buildable
"""
return len(self.getBuildablePathValueList(explanation)) != 0
def getBuildablePathValueList(self, explanation): explanation -- an Order, Order Line, Delivery or Delivery Line which
""" implicitely defines a simulation subtree
Returns the list of Business Path which are ready to
be built
"""
return filter(lambda x:x.isBuildable(explanation),
self.objectValues(portal_type='Business Path'))
def getCompletedStateValueList(self, explanation): business_path -- a Business Path document
"""
Returns the list of Business States which are finished
"""
return filter(lambda x:x.isCompleted(explanation), self.getStateValueList())
def getPartiallyCompletedStateValueList(self, explanation): delay_mode -- optional value to specify calculation mode ('min', 'max')
if no value specified use average delay
""" """
Returns the list of Business States which are finished closure_process = _getBusinessPathClosure(explanation, business_path)
# XXX use explanatoin cache to optimize
trade_date = self.getTradeDate()
if trade_date is not None:
reference_date = closure_process.gettExpectedTradePhaseCompletionDate(explanation, trade_date)
else:
predecessor = business_path.getPredecessor() # XXX-JPS they should all have
reference_date = closure_process.getExpectedTradeStateCompletionDate(explanation, predecessor)
start_date = reference_date + wait_time
stop_date = start_date + lead_time # XXX-JPS use appropriate values
return start_date, stop_date
# IBuildableBusinessPathProcess implementation
def getBuildableBusinessPathValueList(self, explanation):
"""Returns the list of Business Path which are buildable
by taking into account trade state dependencies between
Business Path.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
""" """
return filter(lambda x:x.isPartiallyCompleted(explanation), self.getStateValueList()) result = []
for business_path in self.getBusinessPathValueList():
if self.isBusinessPathBuildable(explanation, business_path):
result.append(business_path)
return result
def getPartiallyBuildableBusinessPathValueList(self, explanation):
"""Returns the list of Business Path which are partially buildable
by taking into account trade state dependencies between
Business Path.
def getLatestCompletedStateValue(self, explanation): explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
""" """
Returns the most advanced completed state result = []
for business_path in self.getBusinessPathValueList():
if self.isBusinessPathPartiallyBuildable(explanation, business_path):
result.append(business_path)
return result
def isBusinessPathBuildable(self, explanation, business_path):
"""Returns True if any of the related Simulation Movement
is buildable and if the predecessor trade state is completed.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
business_path -- a Business Path document
"""
# If everything is delivered, no need to build
if business_path.isDelivered(explanation):
return False
# We must take the closure cause only way to combine business process
closure_process = _getBusinessPathClosure(explanation, business_path)
predecessor = business_path.getPredecessor()
return closure_process.isTradeStateCompleted(predecessor)
def isBusinessPathPartiallyBuildable(self, explanation, business_path):
"""Returns True if any of the related Simulation Movement
is buildable and if the predecessor trade state is partially completed.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
business_path -- a Business Path document
"""
# If everything is delivered, no need to build
if business_path.isDelivered(explanation):
return False
# We must take the closure cause only way to combine business process
closure_process = _getBusinessPathClosure(explanation, business_path)
predecessor = business_path.getPredecessor()
return closure_process.isTradeStatePartiallyCompleted(predecessor)
# ITradeStateProcess implementation
def getTradeStateList():
"""Returns list of all trade_state of this Business Process
by looking at successor and predecessor values of contained
Business Path.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
"""
result = set()
for business_path in self.getBusinessPathValueList():
result.add(business_path.getPredecessor())
result.add(business_path.getSuccessor())
return result
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
and which predecessor is the given trade_phase.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
trade_state -- a Trade State category
""" """
for state in self.getCompletedStateValueList(explanation): result = set()
for path in state.getPredecessorRelatedValueList(): for business_path in self.getBusinessPathValueList():
if not path.isCompleted(explanation): if business_path.getPredecessor() == trade_state:
return state result.add(business_path.getSuccessor())
return None return result
def getLatestPartiallyCompletedStateValue(self, explanation): 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
and which sucessor is the given trade_phase.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
trade_state -- a Trade State category
""" """
Returns the most advanced completed state result = set()
for business_path in self.getBusinessPathValueList():
if business_path.getSuccessor() == trade_state:
result.add(business_path.getPredecessor())
return result
def getCompletedTradeStateList(explanation):
"""Returns the list of Trade States which are completed
in the context of given explanation.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
""" """
for state in self.getCompletedStateValueList(explanation): return filter(lambda x:self.isTradeStateCompleted(explanation, x), self.getTradeStateList())
for path in state.getPredecessorRelatedValueList():
if not path.isPartiallyCompleted(explanation): def getPartiallyCompletedTradeStateList(explanation):
return state """Returns the list of Trade States which are partially
return None completed in the context of given explanation.
def getLatestCompletedStateValueList(self, explanation): explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
""" """
Returns the most advanced completed state return filter(lambda x:self.isTradeStatePartiallyCompleted(explanation, x), self.getTradeStateList())
def getLatestCompletedTradeStateList(explanation):
"""Returns the list of completed trade states which predecessor
states are completed and for which no successor state
is completed in the context of given explanation.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
""" """
result = [] result = set()
for state in self.getCompletedStateValueList(explanation): for state in self.getCompletedTradeStateValue(explanation):
for path in state.getPredecessorRelatedValueList(): for business_path in state.getPredecessorRelatedValueList():
if not path.isCompleted(explanation): if not self.isBusinessPathCompleted(explanation, business_path):
result.append(state) result.add(state)
return result return result
def getLatestPartiallyCompletedStateValueList(self, explanation): def getLatestPartiallyCompletedTradeStateList(explanation):
""" """Returns the list of completed trade states which predecessor
Returns the most advanced completed state states are completed and for which no successor state
is partially completed in the context of given explanation.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
""" """
result = [] result = set()
for state in self.getCompletedStateValueList(explanation): for state in self.getCompletedTradeStateValue(explanation):
for path in state.getPredecessorRelatedValueList(): for business_path in state.getPredecessorRelatedValueList():
if not path.isPartiallyCompleted(explanation): if not self.isBusinessPathPartiallyCompleted(explanation, business_path):
result.append(state) result.add(state)
return result return result
def build(self, explanation_relative_url): def isTradeStateCompleted(explanation, trade_state):
""" """Returns True if all predecessor trade states are
Build whatever is buildable completed and if no successor trade state is completed
in the context of given explanation.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
trade_state -- a Trade State category
""" """
explanation = self.restrictedTraverse(explanation_relative_url) for business_path in self.getBusinessPathValueList(successor=trade_state):
for path in self.getBuildablePathValueList(explanation): if not closure_process.isBusinessPathCompleted(explanation, business_path):
path.build(explanation) return False
return True
def isStartDateReferential(self): # XXX - not in interface def isTradeStatePartiallyCompleted(explanation, trade_state):
return self.getReferentialDate() == 'start_date' """Returns True if all predecessor trade states are
completed and if no successor trade state is partially completed
in the context of given explanation.
def isStopDateReferential(self): # XXX - not in interface explanation -- an Order, Order Line, Delivery or Delivery Line which
return self.getReferentialDate() == 'stop_date' implicitely defines a simulation subtree
def getTradePhaseList(self): trade_state -- a Trade State category
""" """
Returns all trade_phase of this business process for business_path in self.getBusinessPathValueList(successor=trade_state):
if not self.isBusinessPathPartiallyCompleted(explanation, business_path):
return False
return True
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 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
""" """
path_list = self.objectValues(portal_type=self.getPortalBusinessPathTypeList()) date_list = []
return filter(None, [path.getTradePhase() for business_path in self.getBusinessPathValueList(successor=trade_state):
for path in path_list]) date_list.append(self.getExpectedBusinessPathCompletionDate(explanation, business_path))
return max(date_list) # XXX-JPS provide -infinite for...
def getRootExplanationPathValue(self): # ITradePhaseProcess implementation
def getTradePhaseList():
"""Returns list of all trade_phase of this Business Process
by looking at trade_phase values of contained Business Path.
""" """
Returns a root path of this business process result = set()
for business_path in self.getBusinessPathValueList():
result.union(business_path.getTradePhaseList())
return result
def getCompletedTradePhaseList(explanation):
"""Returns the list of Trade Phases which are completed
in the context of given explanation.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
""" """
path_list = self.objectValues(portal_type=self.getPortalBusinessPathTypeList()) return filter(lambda x:self.isTradePhaseCompleted(explanation, x), self.getTradePhaseList())
path_list = filter(lambda x: x.isDeliverable(), path_list)
if len(path_list) > 1: def getPartiallyCompletedTradePhaseList(explanation):
raise Exception, "this business process has multi root paths" """Returns the list of Trade Phases which are partially completed
in the context of given explanation.
if len(path_list) == 1:
return path_list[0]
def getHeadPathValueList(self, trade_phase_list=None): explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
""" """
Returns a list of head path(s) of this business process return filter(lambda x:self.isTradePhasePartiallyCompleted(explanation, x), self.getTradePhaseList())
trade_phase_list -- used to filtering, means that discovering def isTradePhaseCompleted(explanation, trade_phase):
a list of head path with the trade_phase_list """Returns True all business path with given trade_phase
applicable to given explanation are completed.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
trade_phase -- a Trade Phase category
""" """
head_path_list = list() for business_path in self.getBusinessPathValueList(trade_phase=trade_phase):
for state in self.getStateValueList(): if not self.isBusinessPathCompleted(explanation, business_path):
if len(state.getSuccessorRelatedValueList()) == 0: return False
head_path_list += state.getPredecessorRelatedValueList() return True
if trade_phase_list is not None: def isTradePhasePartiallyCompleted(explanation, trade_phase):
_set = set(trade_phase_list) """Returns True at least one business path with given trade_phase
_list = list() applicable to given explanation is partially completed
# start to discover a head path with the trade_phase_list from head path(s) of whole or completed.
for path in head_path_list:
_list += self._getHeadPathValueList(path, _set)
head_path_list = map(lambda t: t[0], filter(lambda t: t != (None, None), _list))
return head_path_list explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
def _getHeadPathValueList(self, path, trade_phase_set): trade_phase -- a Trade Phase category
# if the path has target trade_phase, it is a head path. """
_set = set(path.getTradePhaseList()) for business_path in self.getBusinessPathValueList(trade_phase=trade_phase):
if _set & trade_phase_set: if not self.isBusinessPathPartiallyCompleted(explanation, business_path):
return [(path, _set & trade_phase_set)] return False
return True
node = path.getSuccessorValue() def getExpectedTradePhaseCompletionDate(explanation, trade_phase,
if node is None: delay_mode=None):
return [(None, 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.
_list = list() explanation -- an Order, Order Line, Delivery or Delivery Line which
for next_path in node.getPredecessorRelatedValueList(): implicitely defines a simulation subtree
_list += self._getHeadPathValueList(next_path, trade_phase_set)
return _list trade_phase -- a Trade Phase category
def getRemainingTradePhaseList(self, explanation, trade_state, trade_phase_list=None): delay_mode -- optional value to specify calculation mode ('min', 'max')
if no value specified use average delay
""" """
Returns the list of remaining trade phase for this date_list = []
state based on the explanation. for business_path in self.getBusinessPathValueList(trade_phase=trade_phase):
date_list.append(self.getExpectedTradePhaseCompletionDate(explanation, business_path, delay_mode=delay_mode))
return max(date_list)
trade_phase_list -- if provide, the result is filtered by it after collected def getRemainingTradePhaseList(business_path, 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
method is useful mostly for production and MRP to manage a distributed
supply and production chain.
business_path -- a Business Path document
trade_phase_list -- if provided, the result is filtered by it after
being collected - ???? useful ? XXX-JPS ?
NOTE: explanation is not involved here because we consider here that
self is the result of asUnionBusinessProcess and thus only contains
applicable Business Path to a given simulation subtree. Since the list
of remaining trade phases does not depend on exact values in the
simulation, we did not include the explanation. However, this makes the
API less uniform.
""" """
# XXXX REVIEWJPS
remaining_trade_phase_list = [] remaining_trade_phase_list = []
for path in [x for x in self.objectValues(portal_type="Business Path") \ for path in [x for x in self.objectValues(portal_type="Business Path") \
if x.getPredecessorValue() == trade_state]: if x.getPredecessorValue() == trade_state]:
...@@ -297,38 +568,54 @@ class BusinessProcess(Path, XMLObject): ...@@ -297,38 +568,54 @@ class BusinessProcess(Path, XMLObject):
return remaining_trade_phase_list return remaining_trade_phase_list
def isStatePartiallyCompleted(self, explanation, trade_state): # IBusinessProcess global API
""" def isCompleted(explanation):
If all path which reach this state are partially completed """Returns True is all applicable Trade States and Trade Phases
then this state is completed are completed in the context of given explanation.
explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
""" """
for path in [x for x in self.objectValues(portal_type="Business Path") \ for state in self.getTradeStateList():
if x.getSuccessorValue() == trade_state]: if not state.isTradeStateCompleted(explanation):
if not path.isPartiallyCompleted(explanation):
return False return False
return True return True
def getExpectedStateCompletionDate(self, explanation, trade_state, *args, **kwargs): 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 which
implicitely defines a simulation subtree
""" """
Returns the expected completion date for this
state based on the explanation. def isBuildable(explanation):
"""Returns True is one Business Path of this Business Process
is buildable in the context of given explanation.
explanation -- the document explanation -- an Order, Order Line, Delivery or Delivery Line which
implicitely defines a simulation subtree
""" """
# Should be re-calculated? return len(self.getBuildableBusinessPathValueList(explanation)) != 0
# XXX : what is the purpose of the two following lines ? comment it until there is
# good answer def isPartiallyBuildable(explanation):
if 'predecessor_date' in kwargs: """Returns True is one Business Path of this Business Process
del kwargs['predecessor_date'] is partially buildable in the context of given explanation.
successor_list = [x for x in self.objectValues(portal_type="Business Path") \
if x.getSuccessorValue() == trade_state] explanation -- an Order, Order Line, Delivery or Delivery Line which
date_list = self._getExpectedDateList(explanation, implicitely defines a simulation subtree
successor_list, """
self._getExpectedCompletionDate, return len(self.getPartiallyBuildableBusinessPathValueList(explanation)) != 0
*args,
**kwargs) def build(self, explanation):
if len(date_list) > 0: """
return min(date_list) Build whatever is buildable
"""
for business_path in self.getBuildableBusinessPathValueList(explanation):
business_path.build(explanation)
# GARBAGE - XXXXXXXXXXXXXXXXXXXXXXX
def getExpectedStateBeginningDate(self, explanation, trade_state, *args, **kwargs): def getExpectedStateBeginningDate(self, explanation, trade_state, *args, **kwargs):
""" """
...@@ -381,6 +668,75 @@ class BusinessProcess(Path, XMLObject): ...@@ -381,6 +668,75 @@ class BusinessProcess(Path, XMLObject):
return expected_date_list return expected_date_list
def getRootExplanationPathValue(self): # XXX-JPS not in API
"""
Returns a root path of this business process
"""
path_list = self.objectValues(portal_type=self.getPortalBusinessPathTypeList())
path_list = filter(lambda x: x.isDeliverable(), path_list)
if len(path_list) > 1:
raise Exception, "this business process has multi root paths"
if len(path_list) == 1:
return path_list[0]
def getHeadPathValueList(self, trade_phase_list=None): # XXX-JPS not in API
"""
Returns a list of head path(s) of this business process
trade_phase_list -- used to filtering, means that discovering
a list of head path with the trade_phase_list
"""
head_path_list = list()
for state in self.getStateValueList():
if len(state.getSuccessorRelatedValueList()) == 0:
head_path_list += state.getPredecessorRelatedValueList()
if trade_phase_list is not None:
_set = set(trade_phase_list)
_list = list()
# start to discover a head path with the trade_phase_list from head path(s) of whole
for path in head_path_list:
_list += self._getHeadPathValueList(path, _set)
head_path_list = map(lambda t: t[0], filter(lambda t: t != (None, None), _list))
return head_path_list
def _getHeadPathValueList(self, path, trade_phase_set):
# if the path has target trade_phase, it is a head path.
_set = set(path.getTradePhaseList())
if _set & trade_phase_set:
return [(path, _set & trade_phase_set)]
node = path.getSuccessorValue()
if node is None:
return [(None, None)]
_list = list()
for next_path in node.getPredecessorRelatedValueList():
_list += self._getHeadPathValueList(next_path, trade_phase_set)
return _list
def _getExpectedCompletionDate(self, path, *args, **kwargs): def _getExpectedCompletionDate(self, path, *args, **kwargs):
return path.getExpectedStopDate(*args, **kwargs) return path.getExpectedStopDate(*args, **kwargs)
# From Business Path
def _getExplanationUidList(self, explanation):
"""
Helper method to fetch really explanation related movements
""" # XXX-JPS this seems to be doing too much - why do you need many "explanations"
explanation_uid_list = [explanation.getUid()]
# XXX: getCausalityRelatedValueList is oversimplification, assumes
# that documents are sequenced like simulation movements, which
# is wrong
if getattr(explanation, "getCausalityValueList", None) is None:
return explanation_uid_list
for found_explanation in explanation.getCausalityRelatedValueList( # XXX-JPS this also seems exagerated, and breaking the APIs
portal_type=self.getPortalDeliveryTypeList()) + \
explanation.getCausalityValueList(): # Wrong if an item
explanation_uid_list.extend(self._getExplanationUidList(
found_explanation))
return explanation_uid_list
...@@ -51,12 +51,12 @@ class ExplanationCache: ...@@ -51,12 +51,12 @@ class ExplanationCache:
""" """
# Define share properties # Define share properties
self.explanation = explanation self.explanation = explanation
self.portal_catalog = getToolByName(explanation 'portal_catalog') self.portal_catalog = getToolByName(explanation, 'portal_catalog')
self.simulation_movement_cache = {} # Simulation Movement Cache self.simulation_movement_cache = {} # Simulation Movement Cache
self.explanation_uid_cache = [] self.explanation_uid_cache = []
self.explanation_path_pattern_cache = [] self.explanation_path_pattern_cache = []
def getDeliveryMovementList(self): def _getDeliveryMovementList(self):
"""Returns self is explanation is a delivery line """Returns self is explanation is a delivery line
of the list of explanation delivery lines if explanation of the list of explanation delivery lines if explanation
is a delivery is a delivery
...@@ -80,7 +80,7 @@ class ExplanationCache: ...@@ -80,7 +80,7 @@ class ExplanationCache:
return self.explanation_uid_cache return self.explanation_uid_cache
result = set() result = set()
# For each delivery movement # For each delivery movement
for movement in self.getDeliveryMovementList(): for movement in self._getDeliveryMovementList():
# For each simulation movement # For each simulation movement
for simulation_movement in movement.getDeliveryRelatedValueList(): for simulation_movement in movement.getDeliveryRelatedValueList():
result.add(simulation_movement.getExplanationUid()) # XXX-JPS use new API later result.add(simulation_movement.getExplanationUid()) # XXX-JPS use new API later
...@@ -117,7 +117,7 @@ class ExplanationCache: ...@@ -117,7 +117,7 @@ class ExplanationCache:
local_path_dict[simulation_movement_id] = simulation_movement local_path_dict[simulation_movement_id] = simulation_movement
# For each delivery movement # For each delivery movement
for movement in self.getDeliveryMovementList(): for movement in self._getDeliveryMovementList():
# For each simulation movement # For each simulation movement
for simulation_movement in movement.getDeliveryRelatedValueList(): for simulation_movement in movement.getDeliveryRelatedValueList():
updatePathDict(simulation_movement) updatePathDict(simulation_movement)
...@@ -133,7 +133,7 @@ class ExplanationCache: ...@@ -133,7 +133,7 @@ class ExplanationCache:
else: else:
browsePathDict('%s/%s' % (prefix, key), value) # Recursing with string append is slow XXX-JPS browsePathDict('%s/%s' % (prefix, key), value) # Recursing with string append is slow XXX-JPS
browsePathDict('', path_dict) browsePathDict('/', path_dict)
self.explanation_path_pattern_cache = result self.explanation_path_pattern_cache = result
return result return result
...@@ -143,7 +143,7 @@ class ExplanationCache: ...@@ -143,7 +143,7 @@ class ExplanationCache:
""" """
return self.getSimulationMovementList(causality_uid=business_path.getUid()) return self.getSimulationMovementList(causality_uid=business_path.getUid())
def getSimulationMovementList(self, **kw) def getSimulationMovementList(self, **kw):
"""Search Simulation Movements related to our explanation. """Search Simulation Movements related to our explanation.
Cache result so that the second time we saarch for the same Cache result so that the second time we saarch for the same
list we need not involve the catalog again. list we need not involve the catalog again.
...@@ -157,9 +157,48 @@ class ExplanationCache: ...@@ -157,9 +157,48 @@ class ExplanationCache:
**kw) **kw)
return self.simulation_movement_cache[kw_tuple] return self.simulation_movement_cache[kw_tuple]
def geBusinessPathClosure(business_path):
"""Creates a Business Process by filtering out all Business Path
in self which are not related to a simulation movement
which is either or parent or a child of explanation simulations movements
caused by 'business_path'
cache keys: business_path (simple) then path_set
XXX-JPS PSEUDO CODE
"""
new_business_process = BusinessProcess()
accepted_path = []
explanation_cache = _getExplanationCache(explanation)
path_list = explanation_cache.getSimulationPathPatternList()
path_list = map(lambda x:x[0:-1], path_list) # Remove trailing %
path_set = set()
for simulation_movement in business_path.\
_getExplanationRelatedSimulationMovementValueList(explanation):
simulation_path = simulation_movement.getPath()
for path in path_list:
if simulation_path.startswith(path):
path_set.add(path) # This selection path is part of explanation
for business_path in self.getBusinessPathValueList():
if business_path.hasMovementsIn(explanation, path_set):
accepted_path.append(business_path)
new_business_process.addValueList(business_path)
return new_business_process
def _getExplanationCache(explanation): def _getExplanationCache(explanation):
if explanation.isinstance(ExplanationCache):
return explanation
# Return cached value if any # Return cached value if any
tv = getTransactionalVariable(explanation) tv = getTransactionalVariable(explanation)
if tv.get('explanation_cache', None) is None: if tv.get('explanation_cache', None) is None:
tv['explanation_cache'] = ExplanationCache(explanation) tv['explanation_cache'] = ExplanationCache(explanation)
return tv.get('explanation_cache') return tv.get('explanation_cache')
def _getBusinessPathClosure(explanation, business_path):
"""Returns a
"""
explanation_cache = _getExplanationCache(explanation)
return explanation_cache.getBusinessPathClosure(business_path)
...@@ -59,6 +59,15 @@ class IBusinessPath(Interface): ...@@ -59,6 +59,15 @@ class IBusinessPath(Interface):
movement -- a Simulation Movement movement -- a Simulation Movement
""" """
def getCompletionDate(explanation):
"""Returns the date of completion of the movemnet
based on paremeters of the business path. This completion date can be
the start date, the stop date, the date of a given workflow transition
on the explaining delivery, etc.
XXXXXXXXXXXXXXXx
"""
def getExpectedQuantity(amount): def getExpectedQuantity(amount):
"""Returns the new quantity for the provided amount taking """Returns the new quantity for the provided amount taking
into account the efficiency or the quantity defined on the business path. into account the efficiency or the quantity defined on the business path.
...@@ -116,6 +125,10 @@ class IBusinessPath(Interface): ...@@ -116,6 +125,10 @@ class IBusinessPath(Interface):
not yet completed (ex. in 'delivered' state). not yet completed (ex. in 'delivered' state).
""" """
def isDelivered(explanation):
"""XXX
"""
def build(explanation): def build(explanation):
"""Builds all related movements in the simulation using the builders """Builds all related movements in the simulation using the builders
defined on the Business Path defined on the Business Path
......
...@@ -231,7 +231,7 @@ class ITradeStateProcess(Interface): ...@@ -231,7 +231,7 @@ class ITradeStateProcess(Interface):
implicitely defines a simulation subtree implicitely defines a simulation subtree
""" """
def getLatestPartiallyCompletedTradeState(explanation): def getLatestPartiallyCompletedTradeStateList(explanation):
"""Returns the list of completed trade states which predecessor """Returns the list of completed trade states which predecessor
states are completed and for which no successor state states are completed and for which no successor state
is partially completed in the context of given explanation. is partially completed in the context of given explanation.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment