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 = ( )
......@@ -2,6 +2,7 @@
##############################################################################
# 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 responsibility of assessing all potential
......@@ -1994,6 +1995,261 @@ class TestBPMImplementation(TestBPMMixin):
self.assertEquals(node.getRelativeUrl(),
business_path.getSource(context=context_movement, default='something'))
def test_BusinessState_getRemainingTradePhaseList(self):
"""
This test case is described for what trade_phase is remaining after the state.
In this case, root explanation is path of between "b" and "d", and
path of between "a" and "b" has a condition which simulation state of
explanation must be "ordered" to pass the path. (*1)
But this test case will be passed the condition.
(root explanation)
default/discount default/invoicing default/accounting
a ------------------ b ------------------- d -------------------- e
(cond="ordered") \ /
\ /
default/delivery \ / default/payment
\ /
\ /
\ /
\ /
\ /
\ /
\ /
c
"""
# define business process
business_process = self.createBusinessProcess()
business_path_a_b = self.createBusinessPath(business_process)
business_path_b_c = self.createBusinessPath(business_process)
business_path_b_d = self.createBusinessPath(business_process)
business_path_c_d = self.createBusinessPath(business_process)
business_path_d_e = self.createBusinessPath(business_process)
business_state_a = self.createBusinessState(business_process)
business_state_b = self.createBusinessState(business_process)
business_state_c = self.createBusinessState(business_process)
business_state_d = self.createBusinessState(business_process)
business_state_e = self.createBusinessState(business_process)
business_path_a_b.setPredecessorValue(business_state_a)
business_path_b_c.setPredecessorValue(business_state_b)
business_path_b_d.setPredecessorValue(business_state_b)
business_path_c_d.setPredecessorValue(business_state_c)
business_path_d_e.setPredecessorValue(business_state_d)
business_path_a_b.setSuccessorValue(business_state_b)
business_path_b_c.setSuccessorValue(business_state_c)
business_path_b_d.setSuccessorValue(business_state_d)
business_path_c_d.setSuccessorValue(business_state_d)
business_path_d_e.setSuccessorValue(business_state_e)
# set title for debug
business_path_a_b.edit(title="a_b")
business_path_b_c.edit(title="b_c")
business_path_b_d.edit(title="b_d")
business_path_c_d.edit(title="c_d")
business_path_d_e.edit(title="d_e")
business_state_a.edit(title="a")
business_state_b.edit(title="b")
business_state_c.edit(title="c")
business_state_d.edit(title="d")
business_state_e.edit(title="e")
# set trade_phase
business_path_a_b.edit(trade_phase=['default/discount'],
completed_state=['ordered']) # (*1)
business_path_b_c.edit(trade_phase=['default/delivery'])
business_path_b_d.edit(trade_phase=['default/invoicing'])
business_path_c_d.edit(trade_phase=['default/payment'])
business_path_d_e.edit(trade_phase=['default/accounting'])
# mock order
order = self.portal.sale_order_module.newContent(portal_type="Sale Order")
order_line = order.newContent(portal_type="Sale Order Line")
# make simulation
order.order()
self.stepTic()
applied_rule = order.getCausalityRelatedValue()
sm = applied_rule.contentValues(portal_type="Simulation Movement")[0]
sm.edit(causality_value=business_path_a_b)
# make other movements for each business path
applied_rule.newContent(portal_type="Simulation Movement",
causality_value=business_path_b_c,
order_value=order_line)
applied_rule.newContent(portal_type="Simulation Movement",
causality_value=business_path_b_d,
order_value=order_line)
applied_rule.newContent(portal_type="Simulation Movement",
causality_value=business_path_c_d,
order_value=order_line)
applied_rule.newContent(portal_type="Simulation Movement",
causality_value=business_path_d_e,
order_value=order_line)
self.stepTic()
trade_phase = self.portal.portal_categories.trade_phase.default
# assertion which getRemainingTradePhaseList must return category which will be passed
# discount is passed, business_path_a_b is already completed, because simulation state is "ordered"
self.assertEquals(set([trade_phase.delivery,
trade_phase.invoicing,
trade_phase.payment,
trade_phase.accounting]),
set(business_state_a.getRemainingTradePhaseList(order)))
self.assertEquals(set([trade_phase.delivery,
trade_phase.invoicing,
trade_phase.payment,
trade_phase.accounting]),
set(business_state_b.getRemainingTradePhaseList(order)))
self.assertEquals(set([trade_phase.payment,
trade_phase.accounting]),
set(business_state_c.getRemainingTradePhaseList(order)))
self.assertEquals(set([trade_phase.accounting]),
set(business_state_d.getRemainingTradePhaseList(order)))
# when trade_phase_list is defined in arguments, the result is filtered by base category.
self.assertEquals(set([trade_phase.delivery,
trade_phase.accounting]),
set(business_state_a\
.getRemainingTradePhaseList(order,
trade_phase_list=['default/delivery',
'default/accounting'])))
def test_BusinessPath_calculateExpectedDate(self):
"""
This test case is described for what start/stop date is expected on
each path by explanation.
In this case, root explanation is path of between "b" and "d", and
lead time and wait time is set on each path.
("l" is lead time, "w" is wait_time)
Each path must calculate most early day from getting most longest
path in the simulation.
"referential_date" represents for which date have to get of explanation from reality.
(root_explanation)
l:2, w:1 l:3, w:1 l:4, w:2
a ------------ b -------------- d -------------- e
\ /
\ /
l:2, w:1 \ / l:3, w:0
\ /
\ /
\ /
\ /
c
"""
# define business process
business_process = self.createBusinessProcess()
business_path_a_b = self.createBusinessPath(business_process)
business_path_b_c = self.createBusinessPath(business_process)
business_path_b_d = self.createBusinessPath(business_process)
business_path_c_d = self.createBusinessPath(business_process)
business_path_d_e = self.createBusinessPath(business_process)
business_state_a = self.createBusinessState(business_process)
business_state_b = self.createBusinessState(business_process)
business_state_c = self.createBusinessState(business_process)
business_state_d = self.createBusinessState(business_process)
business_state_e = self.createBusinessState(business_process)
business_path_a_b.setPredecessorValue(business_state_a)
business_path_b_c.setPredecessorValue(business_state_b)
business_path_b_d.setPredecessorValue(business_state_b)
business_path_c_d.setPredecessorValue(business_state_c)
business_path_d_e.setPredecessorValue(business_state_d)
business_path_a_b.setSuccessorValue(business_state_b)
business_path_b_c.setSuccessorValue(business_state_c)
business_path_b_d.setSuccessorValue(business_state_d)
business_path_c_d.setSuccessorValue(business_state_d)
business_path_d_e.setSuccessorValue(business_state_e)
business_process.edit(referential_date='stop_date')
business_state_a.edit(title='a')
business_state_b.edit(title='b')
business_state_c.edit(title='c')
business_state_d.edit(title='d')
business_state_e.edit(title='e')
business_path_a_b.edit(title='a_b', lead_time=2, wait_time=1)
business_path_b_c.edit(title='b_c', lead_time=2, wait_time=1)
business_path_b_d.edit(title='b_d', lead_time=3, wait_time=1)
business_path_c_d.edit(title='c_d', lead_time=3, wait_time=0)
business_path_d_e.edit(title='d_e', lead_time=4, wait_time=2)
# root explanation
business_path_b_d.edit(deliverable=True)
self.stepTic()
"""
Basic test, lead time of reality and simulation are consistent.
"""
class Mock:
def __init__(self, date):
self.date = date
def getStartDate(self):
return self.date
def getStopDate(self):
return self.date + 3 # lead time of reality
base_date = DateTime('2009/04/01 GMT+9')
mock = Mock(base_date)
# root explanation.
self.assertEquals(business_path_b_d.getExpectedStartDate(mock), DateTime('2009/04/01 GMT+9'))
self.assertEquals(business_path_b_d.getExpectedStopDate(mock), DateTime('2009/04/04 GMT+9'))
# assertion for each path without root explanation.
self.assertEquals(business_path_a_b.getExpectedStartDate(mock), DateTime('2009/03/27 GMT+9'))
self.assertEquals(business_path_a_b.getExpectedStopDate(mock), DateTime('2009/03/29 GMT+9'))
self.assertEquals(business_path_b_c.getExpectedStartDate(mock), DateTime('2009/03/30 GMT+9'))
self.assertEquals(business_path_b_c.getExpectedStopDate(mock), DateTime('2009/04/01 GMT+9'))
self.assertEquals(business_path_c_d.getExpectedStartDate(mock), DateTime('2009/04/01 GMT+9'))
self.assertEquals(business_path_c_d.getExpectedStopDate(mock), DateTime('2009/04/04 GMT+9'))
self.assertEquals(business_path_d_e.getExpectedStartDate(mock), DateTime('2009/04/06 GMT+9'))
self.assertEquals(business_path_d_e.getExpectedStopDate(mock), DateTime('2009/04/10 GMT+9'))
"""
Test of illegal case, lead time of reality and simulation are inconsistent,
always reality is taken, but it depends on which date(e.g. start_date and stop_date) is referential.
How we know which is referential, currently implementation of it can be known by
BusinessProcess.isStartDateReferential and BusinessProcess.isStopDateReferential.
In this test case, stop_date on business_path_b_d is referential, because business_path_b_d is
root explanation and business_process refer to stop_date as referential.
calculation example(when referential date is 2009/04/06 GMT+9):
start_date of business_path_b_d = referential_date - 3(lead_time of business_path_b_d)
= 2009/04/06 GMT+9 - 3
= 2009/04/03 GMT+9
"""
class Mock:
def __init__(self, date):
self.date = date
def getStartDate(self):
return self.date
def getStopDate(self):
return self.date + 5 # changed
base_date = DateTime('2009/04/01 GMT+9')
mock = Mock(base_date)
self.assertEquals(business_path_b_d.getExpectedStartDate(mock), DateTime('2009/04/03 GMT+9'))
# This is base in this context, because referential_date is 'stop_date'
self.assertEquals(business_path_b_d.getExpectedStopDate(mock), DateTime('2009/04/06 GMT+9'))
# assertion for each path without root explanation.
self.assertEquals(business_path_a_b.getExpectedStartDate(mock), DateTime('2009/03/29 GMT+9'))
self.assertEquals(business_path_a_b.getExpectedStopDate(mock), DateTime('2009/03/31 GMT+9'))
self.assertEquals(business_path_b_c.getExpectedStartDate(mock), DateTime('2009/04/01 GMT+9'))
self.assertEquals(business_path_b_c.getExpectedStopDate(mock), DateTime('2009/04/03 GMT+9'))
self.assertEquals(business_path_c_d.getExpectedStartDate(mock), DateTime('2009/04/03 GMT+9'))
self.assertEquals(business_path_c_d.getExpectedStopDate(mock), DateTime('2009/04/06 GMT+9'))
self.assertEquals(business_path_d_e.getExpectedStartDate(mock), DateTime('2009/04/08 GMT+9'))
self.assertEquals(business_path_d_e.getExpectedStopDate(mock), DateTime('2009/04/12 GMT+9'))
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestBPMSale))
......
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