Commit ee23dcb3 authored by Yusuke Muraoka's avatar Yusuke Muraoka

added feature(s) for BPM to support:

  - to calculate of expected start/stop date
  - to get remaining trade_phase list

and update some related code:
  - ERP5Site can be get simulation state list from related workflows
  - typo



git-svn-id: https://svn.erp5.org/repos/public/erp5/trunk@27190 20353a03-c40f-0410-a6d1-a30d3c3de9de
parent 74e481c5
......@@ -2,6 +2,7 @@
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@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
......@@ -35,6 +36,11 @@ from Products.ERP5.Document.Path import Path
import zope.interface
class BackTrack(Exception):
"""
This is a utility Exception for tree back tracking.
"""
class BusinessPath(Path):
"""
The BusinessPath class embeds all information related to
......@@ -191,7 +197,7 @@ class BusinessPath(Path):
Not sure if this will exist some day XXX
"""
def _getRelatedSimulationMovementList(explanation):
def _getRelatedSimulationMovementList(self, explanation):
"""
"""
......@@ -203,7 +209,7 @@ class BusinessPath(Path):
Looks at all simulation related movements
and checks the simulation_state of the delivery
"""
acceptable_state_list = self.getCompletedStateIdList() # XXX Missing method / name
acceptable_state_list = self.getCompletedStateList()
for movement in self._getRelatedSimulationMovementList(explanation):
if movement.getSimulationState() not in acceptable_state_list:
return False
......@@ -214,7 +220,7 @@ class BusinessPath(Path):
Looks at all simulation related movements
and checks the simulation_state of the delivery
"""
acceptable_state_list = self.getCompletedStateList() # XXX Missing method / name
acceptable_state_list = self.getCompletedStateList()
for movement in self._getRelatedSimulationMovementList(explanation):
if movement.getSimulationState() in acceptable_state_list:
return True
......@@ -244,7 +250,7 @@ class BusinessPath(Path):
self._getRelatedSimulationMovementList(explanation)) # Another way
# Date calculation
def getExpectedStartDate(self, explanation, predecessor_date=None):
def getExpectedStartDate(self, explanation, predecessor_date=None, *args, **kwargs):
"""
Returns the expected start date for this
path based on the explanation.
......@@ -252,11 +258,35 @@ class BusinessPath(Path):
predecessor_date -- if provided, computes the date base on the
date value provided
"""
return self._getExpectedDate(explanation,
self._getRootExplanationExpectedStartDate,
self._getPredecessorExpectedStartDate,
self._getSuccessorExpectedStartDate,
predecessor_date=predecessor_date,
*args, **kwargs)
def _getRootExplanationExpectedStartDate(self, explanation, *args, **kwargs):
if self.getParentValue().isStartDateReferential():
return explanation.getStartDate()
else:
return self.getExpectedStopDate(explanation, *args, **kwargs)\
- self.getLeadTime()
def _getPredecessorExpectedStartDate(self, explanation, predecessor_date=None, *args, **kwargs):
if predecessor_date is None:
predecessor_date = self.getPredecessorValue().getExpectedCompletionDate()
return predecessor_date + wait_time
def getExpectedStopDate(self, explanation, predecessor_date=None):
node = self.getPredecessorValue()
if node is not None:
predecessor_date = node.getExpectedCompletionDate(explanation, *args, **kwargs)
if predecessor_date is not None:
return predecessor_date + self.getWaitTime()
def _getSuccessorExpectedStartDate(self, explanation, *args, **kwargs):
node = self.getSuccessorValue()
if node is not None:
return node.getExpectedBeginningDate(explanation, *args, **kwargs)\
- self.getLeadTime()
def getExpectedStopDate(self, explanation, predecessor_date=None, *args, **kwargs):
"""
Returns the expected stop date for this
path based on the explanation.
......@@ -264,4 +294,77 @@ class BusinessPath(Path):
predecessor_date -- if provided, computes the date base on the
date value provided
"""
return context.getExpectedStartDate(explanation, predecessor_date=predecessor_date) + lead_time
return self._getExpectedDate(explanation,
self._getRootExplanationExpectedStopDate,
self._getPredecessorExpectedStopDate,
self._getSuccessorExpectedStopDate,
predecessor_date=predecessor_date,
*args, **kwargs)
def _getRootExplanationExpectedStopDate(self, explanation, *args, **kwargs):
if self.getParentValue().isStopDateReferential():
return explanation.getStopDate()
else:
return self.getExpectedStartDate(explanation, *args, **kwargs)\
+ self.getLeadTime()
def _getPredecessorExpectedStopDate(self, explanation, *args, **kwargs):
node = self.getPredecessorValue()
if node is not None:
return node.getExpectedCompletionDate(explanation, *args, **kwargs)\
+ self.getWaitTime() + self.getLeadTime()
def _getSuccessorExpectedStopDate(self, explanation, *args, **kwargs):
node = self.getSuccessorValue()
if node is not None:
return node.getExpectedBeginningDate(explanation, *args, **kwargs)
def _getExpectedDate(self, explanation, root_explanation_method,
predecessor_method, successor_method,
visited=None, *args, **kwargs):
"""
Returns the expected stop date for this
path based on the explanation.
root_explanation_method -- used when the path is root explanation
predecessor_method --- used to get expected date of side of predecessor
successor_method --- used to get expected date of side of successor
visited -- only used to prevent infinite recursion internally
"""
if visited is None:
visited = []
# mark the path as visited
if self not in visited:
visited.append(self)
if self.isDeliverable():
return root_explanation_method(
explanation, visited=visited, *args, **kwargs)
predecessor_expected_date = None
try:
predecessor_expected_date = predecessor_method(
explanation, visited=visited, *args, **kwargs)
except BackTrack:
pass
successor_expected_date = None
try:
successor_expected_date = successor_method(
explanation, visited=visited, *args, **kwargs)
except BackTrack:
pass
if successor_expected_date is not None or \
predecessor_expected_date is not None:
# return minimum expected date but it is not None
if successor_expected_date is None:
return predecessor_expected_date
elif predecessor_expected_date is None:
return successor_expected_date
else:
if predecessor_expected_date < successor_expected_date:
return predecessor_expected_date
else:
return successor_expected_date
......@@ -2,6 +2,7 @@
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@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
......@@ -55,6 +56,7 @@ class BusinessProcess(Path, XMLObject):
, PropertySheet.Folder
, PropertySheet.Comment
, PropertySheet.Arrow
, PropertySheet.BusinessProcess
)
# Access to path and states of the business process
......@@ -177,3 +179,9 @@ class BusinessProcess(Path, XMLObject):
"""
for path in self.getBuildablePathValueList(explanation):
path.build(explanation)
def isStartDateReferential(self):
return self.getReferentialDate() == 'start_date'
def isStopDateReferential(self):
return self.getReferentialDate() == 'stop_date'
......@@ -2,6 +2,7 @@
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
# Jean-Paul Smets-Solanes <jp@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
......@@ -31,6 +32,7 @@ from AccessControl import ClassSecurityInfo
from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
from Products.ERP5Type.XMLObject import XMLObject
from Products.ERP5Type.Document.BusinessPath import BackTrack
class BusinessState(XMLObject):
"""
......@@ -77,14 +79,103 @@ class BusinessState(XMLObject):
return True
# Duration calculation
def getExpectedCompletionDate(self, explanation):
def getExpectedCompletionDate(self, explanation, *args, **kwargs):
"""
Returns the expected completion date for this
state based on the explanation.
explanation -- the document
"""
# Should be re-calculated?
if 'predecessor_date' in kwargs:
del kwargs['predecessor_date']
return min(self._getExpectedDateList(explanation,
self.getSuccessorRelatedValueList(),
self._getExpectedCompletionDate,
*args,
**kwargs))
def _getExpectedCompletionDate(self, path, *args, **kwargs):
return path.getExpectedStopDate(*args, **kwargs)
def getExpectedBeginningDate(self, explanation, *args, **kwargs):
"""
Returns the expected beginning date for this
state based on the explanation.
explanation -- the document
"""
# Should be re-calculated?
if 'predecessor_date' in kwargs:
del kwargs['predecessor_date']
return min(self._getExpectedDateList(explanation,
self.getPredecessorRelatedValueList(),
self._getExpectedBeginningDate,
*args,
**kwargs))
def _getExpectedBeginningDate(self, path, *args, **kwargs):
expected_date = path.getExpectedStartDate(*args, **kwargs)
if expected_date is not None:
return expected_date - path.getWaitTime()
def _getExpectedDateList(self, explanation, path_list, path_method,
visited=None, *args, **kwargs):
"""
getExpected(Beginning/Completion)Date are same structure
expected date of each path should be returned.
explanation -- the document
path_list -- list of target business path
path_method -- used to get expected date on each path
visited -- only used to prevent infinite recursion internally
"""
if visited is None:
visited = []
expected_date_list = []
for path in path_list:
# filter paths without path of root explanation
if path not in visited or path.isDeliverable():
expected_date = path_method(path, explanation, visited=visited, *args, **kwargs)
if expected_date is not None:
expected_date_list.append(expected_date)
# if visiting leaf of tree
if len(expected_date_list) == 0:
raise BackTrack
else:
return expected_date_list
def getExpectedCompletionDuration(self, explanation):
"""
Returns the expected completion duration for this
state.
"""
def getRemainingTradePhaseList(self, explanation, trade_phase_list=None):
"""
Returns the list of remaining trade phase for this
state based on the explanation.
trade_phase_list -- if provide, the result is filtered by it after collected
"""
remaining_trade_phase_list = []
for path in self.getPredecessorRelatedValueList():
if not (path.isCompleted(explanation) or
path.isPartiallyCompleted(explanation)):
remaining_trade_phase_list += path.getTradePhaseValueList()
# collect to successor direction recursively
state = path.getSuccessorValue()
if state is not None:
remaining_trade_phase_list.extend(
state.getRemainingTradePhaseList(explanation, None))
# filter just at once if given
if trade_phase_list is not None:
remaining_trade_phase_list = filter(
lambda x : x.getLogicalPath() in trade_phase_list,
remaining_trade_phase_list)
return remaining_trade_phase_list
......@@ -941,6 +941,28 @@ class ERP5Site(FolderMixIn, CMFSite):
return self._getPortalConfiguration(
'portal_updatable_amortisation_transaction_state_list')
security.declareProtected(Permissions.AccessContentsInformation,
'getPortalGroupedSimulationStateList')
def getPortalGroupedSimulationStateList(self):
"""
Return all states which is related to simulation state workflow and state type
"""
def getStateList():
state_dict = {}
for wf in self.portal_workflow.objectValues():
if getattr(wf, 'variables', None) and \
wf.variables.getStateVar() == 'simulation_state':
if getattr(wf, 'states', None):
for state in wf.states.objectValues():
if getattr(state, 'type_list', None):
state_dict[state.getId()] = None
return tuple(sorted(state_dict.keys()))
getStateList = CachingMethod(getStateList,
id=('getPortalGroupedSimulationStateList'),
cache_factory='erp5_content_medium')
return getStateList()
security.declareProtected(Permissions.AccessContentsInformation,
'getPortalColumnBaseCategoryList')
def getPortalColumnBaseCategoryList(self):
......
......@@ -61,4 +61,11 @@ class IBusinessCompletable(Interface):
'task' is a document which follows the ITaskGetter interface
(getStartDate, getStopDate)
"""
\ No newline at end of file
"""
def getRemainingTradePhaseList(explanation, trade_phase_list=None):
"""
Returns the list of remaining trade phase which to be done on the explanation.
trade_phase_list -- if provide, the result is filtered by it after collected
"""
# -*- coding: utf8 -*-
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
# Łukasz 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
......@@ -54,5 +55,11 @@ class BusinessPath:
'default' : 0.0,
'type' : 'float',
'mode' : 'w' },
{ 'id' : 'completed_state',
'description' : 'Which movement of simulation state can be passed on this path',
'type' : 'lines',
'default' : [],
'multivalued' : 1,
'mode' : 'w' },
)
_categories = ( )
# -*- coding: utf-8 -*-
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
# Łukasz 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 BusinessProcess:
"""
Business Process properties
"""
_properties = (
{ 'id' : 'referential_date',
'description' : 'Which date(e.g. start_date, stop_date) is referential on the Business Process',
'type' : 'string',
'mode' : 'w' },
)
_categories = ( )
This diff is collapsed.
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