diff --git a/product/ERP5/Document/BusinessPath.py b/product/ERP5/Document/BusinessPath.py index 39b6f9e5bb5cfbe3dab91cffbbe241a849fbc816..94aea11dc3679cefe457eeb6e99ebdf6a81962d6 100644 --- a/product/ERP5/Document/BusinessPath.py +++ b/product/ERP5/Document/BusinessPath.py @@ -251,7 +251,7 @@ class BusinessPath(Path, Predicate): # # Such cases are Business Processes using sequence not related # to simulation tree with much of compensations - if predecessor.isPartiallyCompleted(explanation): + if self.isStatePartiallyCompleted(explanation, predecessor): return result return False @@ -437,14 +437,16 @@ class BusinessPath(Path, Predicate): if predecessor_date is None: node = self.getPredecessorValue() if node is not None: - predecessor_date = node.getExpectedCompletionDate(explanation, *args, **kwargs) + predecessor_date = self.getParentValue().getExpectedStateCompletionDate( + explanation, node, *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: - expected_date = node.getExpectedBeginningDate(explanation, *args, **kwargs) + expected_date = self.getParentValue().getExpectedStateBeginningDate( + explanation, node, *args, **kwargs) if expected_date is not None: return expected_date - self.getLeadTime() @@ -474,14 +476,16 @@ class BusinessPath(Path, Predicate): def _getPredecessorExpectedStopDate(self, explanation, *args, **kwargs): node = self.getPredecessorValue() if node is not None: - expected_date = node.getExpectedCompletionDate(explanation, *args, **kwargs) + expected_date = self.getParentValue().getExpectedStateCompletionDate( + explanation, node, *args, **kwargs) if expected_date is not None: return expected_date + self.getWaitTime() + self.getLeadTime() def _getSuccessorExpectedStopDate(self, explanation, *args, **kwargs): node = self.getSuccessorValue() if node is not None: - return node.getExpectedBeginningDate(explanation, *args, **kwargs) + return self.getParentValue().getExpectedStateBeginningDate( + explanation, node, *args, **kwargs) def _getExpectedDate(self, explanation, root_explanation_method, predecessor_method, successor_method, diff --git a/product/ERP5/Document/BusinessProcess.py b/product/ERP5/Document/BusinessProcess.py index 38fc571c9558da6b8824d17109b74caa9094d871..e3ad02b798b40d0dc0f71674cd1c86bdf622d4f9 100644 --- a/product/ERP5/Document/BusinessProcess.py +++ b/product/ERP5/Document/BusinessProcess.py @@ -256,3 +256,121 @@ class BusinessProcess(Path, XMLObject): for next_path in node.getPredecessorRelatedValueList(): _list += self._getHeadPathValueList(next_path, trade_phase_set) return _list + + def getRemainingTradePhaseList(self, explanation, trade_state, 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 [x for x in self.objectValues(portal_type="Business Path") \ + if x.getPredecessorValue() == trade_state]: + # XXX When no simulations related to path, what should path.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() + + # collect to successor direction recursively + state = path.getSuccessorValue() + if state is not None: + remaining_trade_phase_list.extend( + self.getRemainingTradePhaseList(explanation, state, 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 + + def isStatePartiallyCompleted(self, explanation, trade_state): + """ + If all path which reach this state are partially completed + then this state is completed + """ + for path in [x for x in self.objectValues(portal_type="Business Path") \ + if x.getSuccessorValue() == trade_state]: + if not path.isPartiallyCompleted(explanation): + return False + return True + + def getExpectedStateCompletionDate(self, explanation, trade_state, *args, **kwargs): + """ + Returns the expected completion date for this + state based on the explanation. + + explanation -- the document + """ + # Should be re-calculated? + # XXX : what is the purpose of the two following lines ? comment it until there is + # good answer + if 'predecessor_date' in kwargs: + del kwargs['predecessor_date'] + successor_list = [x for x in self.objectValues(portal_type="Business Path") \ + if x.getSuccessorValue() == trade_state] + date_list = self._getExpectedDateList(explanation, + successor_list, + self._getExpectedCompletionDate, + *args, + **kwargs) + if len(date_list) > 0: + return min(date_list) + + def getExpectedStateBeginningDate(self, explanation, trade_state, *args, **kwargs): + """ + Returns the expected beginning date for this + state based on the explanation. + + explanation -- the document + """ + # Should be re-calculated? + # XXX : what is the purpose of the two following lines ? comment it until there is + # good answer + if 'predecessor_date' in kwargs: + del kwargs['predecessor_date'] + predecessor_list = [x for x in self.objectValues(portal_type="Business Path") \ + if x.getPredecessorValue() == trade_state] + date_list = self._getExpectedDateList(explanation, + predecessor_list, + self._getExpectedBeginningDate, + *args, + **kwargs) + if len(date_list) > 0: + return min(date_list) + + 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) + + return expected_date_list + + def _getExpectedCompletionDate(self, path, *args, **kwargs): + return path.getExpectedStopDate(*args, **kwargs) + diff --git a/product/ERP5/tests/testBPMCore.py b/product/ERP5/tests/testBPMCore.py index d828a3b819d9fc54a3506f15589705ed926931c9..b554a8d0360e30b344856ff39bdffc8215a4a833 100644 --- a/product/ERP5/tests/testBPMCore.py +++ b/product/ERP5/tests/testBPMCore.py @@ -68,6 +68,9 @@ class TestBPMMixin(ERP5TypeTestCase): self.createCategoriesInCategory(category_tool.trade_phase, ['default',]) self.createCategoriesInCategory(category_tool.trade_phase.default, ['accounting', 'delivery', 'invoicing', 'discount', 'tax', 'payment']) + self.createCategoriesInCategory(category_tool.trade_state, + ['ordered', 'invoiced', 'delivered', + 'state_a', 'state_b', 'state_c', 'state_d', 'state_e']) @reindex def createBusinessProcess(self, **kw): @@ -88,14 +91,6 @@ class TestBPMMixin(ERP5TypeTestCase): portal_type=self.business_path_portal_type, **kw) return business_path - @reindex - def createBusinessState(self, business_process=None, **kw): - if business_process is None: - business_process = self.createBusinessProcess() - business_path = business_process.newContent( - portal_type=self.business_state_portal_type, **kw) - return business_path - def createMovement(self): # returns a movement for testing applied_rule = self.portal.portal_simulation.newContent( @@ -348,17 +343,18 @@ class TestBPMImplementation(TestBPMMixin): c """ # define business process + category_tool = self.getCategoryTool() 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_state_a = category_tool.trade_state.state_a + business_state_b = category_tool.trade_state.state_b + business_state_c = category_tool.trade_state.state_c + business_state_d = category_tool.trade_state.state_d + business_state_e = category_tool.trade_state.state_e business_path_a_b.setPredecessorValue(business_state_a) business_path_b_c.setPredecessorValue(business_state_b) business_path_b_d.setPredecessorValue(business_state_b) @@ -376,11 +372,6 @@ class TestBPMImplementation(TestBPMMixin): 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'], @@ -427,23 +418,27 @@ class TestBPMImplementation(TestBPMMixin): trade_phase.invoicing, trade_phase.payment, trade_phase.accounting]), - set(business_state_a.getRemainingTradePhaseList(order))) + set(business_process.getRemainingTradePhaseList(order, + business_state_a))) self.assertEquals(set([trade_phase.delivery, trade_phase.invoicing, trade_phase.payment, trade_phase.accounting]), - set(business_state_b.getRemainingTradePhaseList(order))) + set(business_process.getRemainingTradePhaseList(order, + business_state_b))) self.assertEquals(set([trade_phase.payment, trade_phase.accounting]), - set(business_state_c.getRemainingTradePhaseList(order))) + set(business_process.getRemainingTradePhaseList(order, + business_state_c))) self.assertEquals(set([trade_phase.accounting]), - set(business_state_d.getRemainingTradePhaseList(order))) + set(business_process.getRemainingTradePhaseList(order, + business_state_d))) # 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, + set(business_process\ + .getRemainingTradePhaseList(order, business_state_a, trade_phase_list=['default/delivery', 'default/accounting']))) @@ -473,17 +468,18 @@ class TestBPMImplementation(TestBPMMixin): c """ # define business process + category_tool = self.getCategoryTool() 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_state_a = category_tool.trade_state.state_a + business_state_b = category_tool.trade_state.state_b + business_state_c = category_tool.trade_state.state_c + business_state_d = category_tool.trade_state.state_d + business_state_e = category_tool.trade_state.state_e business_path_a_b.setPredecessorValue(business_state_a) business_path_b_c.setPredecessorValue(business_state_b) business_path_b_d.setPredecessorValue(business_state_b) @@ -496,11 +492,6 @@ class TestBPMImplementation(TestBPMMixin): 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) @@ -635,9 +626,10 @@ class TestBPMDummyDeliveryMovementMixin(TestBPMMixin): def _createOrderedDeliveredInvoicedBusinessProcess(self): # simple business process preparation business_process = self.createBusinessProcess() - ordered = self.createBusinessState(business_process) - delivered = self.createBusinessState(business_process) - invoiced = self.createBusinessState(business_process) + category_tool = self.getCategoryTool() + ordered = category_tool.trade_state.ordered + delivered = category_tool.trade_state.delivered + invoiced = category_tool.trade_state.invoiced # path which is completed, as soon as related simulation movements are in # proper state @@ -660,9 +652,10 @@ class TestBPMDummyDeliveryMovementMixin(TestBPMMixin): def _createOrderedInvoicedDeliveredBusinessProcess(self): business_process = self.createBusinessProcess() - ordered = self.createBusinessState(business_process) - delivered = self.createBusinessState(business_process) - invoiced = self.createBusinessState(business_process) + category_tool = self.getCategoryTool() + ordered = category_tool.trade_state.ordered + delivered = category_tool.trade_state.delivered + invoiced = category_tool.trade_state.invoiced self.order_path = self.createBusinessPath(business_process, successor_value = ordered,