# -*- coding: utf-8 -*- ############################################################################## # Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved. # Ćukasz Nowak <luke@nexedi.com> # Yusuke Muraoka <yusuke@nexedi.com> # Fabien Morin <fabien@nexedi.com> # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsibility 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 # guarantees 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. # ############################################################################## import unittest from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase from DateTime import DateTime from Products.ERP5Type.tests.utils import createZODBPythonScript, reindex from Products.ERP5Type.tests.utils import todo_erp5 from Products.ERP5.tests.utils import newSimulationExpectedFailure class TestBPMMixin(ERP5TypeTestCase): """Skeletons for tests which depend on BPM""" def getBusinessTemplateList(self): return ('erp5_base', 'erp5_pdm', 'erp5_simulation', 'erp5_trade', 'erp5_accounting', 'erp5_invoicing', 'erp5_simplified_invoicing', 'erp5_core_proxy_field_legacy', 'erp5_configurator_standard_solver', 'erp5_configurator_standard_trade_template', 'erp5_configurator_standard_accounting_template', 'erp5_configurator_standard_invoicing_template', 'erp5_simulation_test') business_process_portal_type = 'Business Process' business_link_portal_type = 'Business Link' trade_model_path_portal_type = 'Trade Model Path' default_business_process = \ 'business_process_module/erp5_default_business_process' normal_resource_use_category_list = ['normal'] invoicing_resource_use_category_list = ['discount', 'tax'] def createCategoriesInCategory(self, category, category_id_list): for category_id in category_id_list: if not category.hasObject(category_id): category.newContent(category_id, title=category_id.replace('_', ' ').title()) @reindex def createCategories(self): category_tool = self.portal.portal_categories self.createCategoriesInCategory(category_tool.base_amount, ['discount', 'tax', 'total_tax', 'total_discount', 'total']) self.createCategoriesInCategory(category_tool.use, self.normal_resource_use_category_list + \ self.invoicing_resource_use_category_list) 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', 'taxed', 'state_a', 'state_b', 'state_c', 'state_d', 'state_e']) self.createCategoriesInCategory(category_tool, ('tax_range', 'tax_share')) self.createCategoriesInCategory(category_tool.tax_range, ('0_200', '200_inf')) self.createCategoriesInCategory(category_tool.tax_share, 'AB') @reindex def createBusinessProcess(self, create_order_to_invoice_path=False, **kw): module = self.portal.getDefaultModule( portal_type=self.business_process_portal_type,) business_process = module.newContent( portal_type=self.business_process_portal_type, specialise=self.default_business_process) self.business_process = business_process business_process._edit(**kw) if create_order_to_invoice_path: self.createTradeModelPath(self.business_process, reference='order_path', trade_phase_value_list=('default/order',)) self.createTradeModelPath(self.business_process, reference='delivery_path', trade_phase_value_list=('default/delivery',), trade_date='trade_phase/default/order') self.createTradeModelPath(self.business_process, reference='invoice_path', trade_phase_value_list=('default/invoicing',), trade_date='trade_phase/default/delivery') self.createTradeModelPath(business_process, reference='default_path', trade_phase_value_list=('default/discount', 'default/tax'), trade_date='trade_phase/default/invoicing') # A trade model path already exist for root simulation movements # (Accounting Transaction Root Simulation Rule). # The ones we are creating are for Invoice Transaction Simulation Rule # so we add a test on the portal type of the input movement. kw = dict(business_process=business_process, trade_phase='default/accounting', trade_date='trade_phase/default/invoicing', membership_criterion_base_category='resource_use', criterion_property_dict={'portal_type': 'Simulation Movement'}) self.createTradeModelPath(reference='acounting_tax1', efficiency=-1, source_value=self.receivable_account, destination_value=self.payable_account, membership_criterion_category='resource_use/use/tax', **kw) self.createTradeModelPath(reference='acounting_tax2', efficiency=1, source_value=self.collected_tax_account, destination_value=self.refundable_tax_account, membership_criterion_category='resource_use/use/tax', **kw) self.createTradeModelPath(reference='acounting_discount1', efficiency=-1, source_value=self.receivable_account, destination_value=self.payable_account, membership_criterion_category='resource_use/use/discount', **kw) self.createTradeModelPath(reference='acounting_discount2', efficiency=1, source_value=self.income_account, destination_value=self.expense_account, membership_criterion_category='resource_use/use/discount', **kw) self.createTradeModelPath(reference='acounting_normal1', efficiency=-1, source_value=self.receivable_account, destination_value=self.payable_account, membership_criterion_category='resource_use/use/normal', **kw) self.createTradeModelPath(reference='acounting_normal2', efficiency=1, source_value=self.income_account, destination_value=self.expense_account, membership_criterion_category='resource_use/use/normal', **kw) return business_process @reindex def createBusinessLink(self, business_process=None, **kw): if business_process is None: business_process = self.createBusinessProcess() if kw.get('reference'): kw.setdefault('id', kw['reference']) business_link = business_process.newContent( portal_type=self.business_link_portal_type, **kw) return business_link def createTradeModelPath(self, business_process=None, criterion_property_dict={}, **kw): if business_process is None: business_process = self.createBusinessProcess() if kw.get('reference') and not kw.get('id'): kw.setdefault('id', kw['reference'] + '_path') trade_model_path = business_process.newContent( portal_type=self.trade_model_path_portal_type, **kw) if criterion_property_dict: trade_model_path._setCriterionPropertyList(tuple(criterion_property_dict)) for property, identity in criterion_property_dict.iteritems(): trade_model_path.setCriterion(property, identity) reference = kw.get('reference', None) if reference is not None: setattr(self, reference, trade_model_path) return trade_model_path def createMovement(self): # returns a movement for testing applied_rule = self.portal.portal_simulation.newContent( portal_type='Applied Rule') return applied_rule.newContent(portal_type='Simulation Movement') def createAndValidateAccount(self, account_id, account_type): account_module = self.portal.account_module account = account_module.newContent(portal_type='Account', title=account_id, account_type=account_type) self.assertNotEqual(None, account.getAccountTypeValue()) account.validate() return account def createAndValidateAccounts(self): self.receivable_account = self.createAndValidateAccount('receivable', 'asset/receivable') self.payable_account = self.createAndValidateAccount('payable', 'liability/payable') self.income_account = self.createAndValidateAccount('income', 'income') self.expense_account = self.createAndValidateAccount('expense', 'expense') self.collected_tax_account = self.createAndValidateAccount( 'collected_tax', 'liability/payable/collected_vat') self.refundable_tax_account = self.createAndValidateAccount( 'refundable_tax', 'asset/receivable/refundable_vat') def afterSetUp(self): self.validateRules() self.createCategories() self.createAndValidateAccounts() self.stepTic() class TestBPMDummyDeliveryMovementMixin(TestBPMMixin): def _createDelivery(self, **kw): return self.folder.newContent(portal_type='Dummy Delivery', **kw) def _createMovement(self, delivery, **kw): return delivery.newContent(portal_type='Dummy Movement', **kw) def getBusinessTemplateList(self): return super(TestBPMDummyDeliveryMovementMixin, self)\ .getBusinessTemplateList() \ + ('erp5_dummy_movement', ) def afterSetUp(self): super(TestBPMDummyDeliveryMovementMixin, self).afterSetUp() if not hasattr(self.portal, 'testing_folder'): self.portal.newContent(portal_type='Folder', id='testing_folder') self.folder = self.portal.testing_folder self.stepTic() def beforeTearDown(self): super(TestBPMDummyDeliveryMovementMixin, self).beforeTearDown() self.portal.deleteContent(id='testing_folder') self.stepTic() completed_state = 'delivered' frozen_state = 'confirmed' completed_state_list = [completed_state] frozen_state_list = [completed_state, frozen_state] def _createOrderedDeliveredInvoicedBusinessProcess(self): # simple business process preparation business_process = self.createBusinessProcess( create_order_to_invoice_path=True) 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 self.order_link = self.createBusinessLink(business_process, successor_value = ordered, trade_phase='default/order', completed_state_list = self.completed_state_list, frozen_state_list = self.frozen_state_list) self.delivery_link = self.createBusinessLink(business_process, predecessor_value = ordered, successor_value = delivered, trade_phase='default/delivery', completed_state_list = self.completed_state_list, frozen_state_list = self.frozen_state_list) self.invoice_link = self.createBusinessLink(business_process, predecessor_value = delivered, successor_value = invoiced, trade_phase='default/invoicing') self.stepTic() def constructSimulationTreeAndDeliveries(self, simulation_depth=None, dummy_split=False): """ Construct a simple simulation tree with deliveries. This is not real simulation tree, we only need the structure, most usual properties are not there (quantities, arrow, etc) simulation_depth : level of simulation where we should stop """ # create order and order line to have starting point for business process self.order = order = self._createDelivery() order_line = self._createMovement(order) if simulation_depth is None: simulation_depth = float('inf') # first level rule with simulation movement self.applied_rule = self.portal.portal_simulation.newContent( portal_type='Applied Rule', causality_value=order) def setTestClassProperty(prefix, property_name, document): if prefix: property_name = "%s_%s" % (prefix, property_name) setattr(self, property_name, document) return document simulation_movement_kw = { 'specialise': self.business_process.getRelativeUrl()} def constructSimulationTree(applied_rule, prefix=None): document = setTestClassProperty(prefix, 'simulation_movement', applied_rule.newContent( portal_type = 'Simulation Movement', delivery_value = order_line, trade_phase='default/order', causality_value_list=[self.order_link, self.order_path], **simulation_movement_kw )) if simulation_depth > 1: # second level rule with simulation movement document = setTestClassProperty(prefix, 'delivery_rule', document.newContent( portal_type='Applied Rule')) document = setTestClassProperty(prefix, 'delivery_simulation_movement', document.newContent( portal_type='Simulation Movement', trade_phase='default/delivery', causality_value_list=[self.delivery_link, self.delivery_path], **simulation_movement_kw)) if simulation_depth > 2: # third level rule with simulation movement document = setTestClassProperty(prefix, 'invoicing_rule', document.newContent( portal_type='Applied Rule')) document = setTestClassProperty(prefix, 'invoicing_simulation_movement', document.newContent( portal_type='Simulation Movement', trade_phase='default/invoicing', causality_value_list=[self.invoice_link, self.invoice_path], **simulation_movement_kw)) constructSimulationTree(self.applied_rule) if dummy_split: constructSimulationTree(self.applied_rule, prefix='split') self.stepTic() class TestBPMImplementation(TestBPMDummyDeliveryMovementMixin): """Business Process implementation tests""" def test_BusinessProcess_getBusinessLinkValueList(self): business_process = self.createBusinessProcess() accounting_business_link = business_process.newContent( portal_type=self.business_link_portal_type, trade_phase='default/accounting') delivery_business_link = business_process.newContent( portal_type=self.business_link_portal_type, trade_phase='default/delivery') accounting_delivery_business_link = business_process.newContent( portal_type=self.business_link_portal_type, trade_phase=('default/accounting', 'default/delivery')) self.stepTic() self.assertSameSet( (accounting_business_link, accounting_delivery_business_link), business_process.getBusinessLinkValueList(trade_phase='default/accounting') ) self.assertSameSet( (delivery_business_link, accounting_delivery_business_link), business_process.getBusinessLinkValueList(trade_phase='default/delivery') ) self.assertSameSet( (accounting_delivery_business_link, delivery_business_link, accounting_business_link), business_process.getBusinessLinkValueList(trade_phase=('default/delivery', 'default/accounting')) ) def test_BusinessLinkStandardCategoryAccessProvider(self): source_node = self.portal.organisation_module.newContent( portal_type='Organisation') source_section_node = self.portal.organisation_module.newContent( portal_type='Organisation') business_link = self.createBusinessLink() business_link.setSourceValue(source_node) business_link.setSourceSectionValue(source_section_node) self.assertEquals([source_node], business_link.getSourceValueList()) self.assertEquals([source_node.getRelativeUrl()], business_link.getSourceList()) self.assertEquals(source_node.getRelativeUrl(), business_link.getSource(default='something')) def test_EmptyBusinessLinkStandardCategoryAccessProvider(self): business_link = self.createBusinessLink() self.assertEquals(None, business_link.getSourceValue()) self.assertEquals(None, business_link.getSource()) self.assertEquals('something', business_link.getSource(default='something')) def test_BusinessPathDynamicCategoryAccessProvider(self): source_node = self.portal.organisation_module.newContent( portal_type='Organisation') source_section_node = self.portal.organisation_module.newContent( portal_type='Organisation') business_path = self.createTradeModelPath() business_path.setSourceMethodId('TradeModelPath_getDefaultSourceList') context_movement = self.createMovement() context_movement.setSourceValue(source_node) context_movement.setSourceSectionValue(source_section_node) self.assertEquals(None, business_path.getSourceValue()) self.assertEquals([source_node.getRelativeUrl()], business_path.getArrowCategoryDict(context=context_movement)['source']) def test_BusinessPathDynamicCategoryAccessProviderBusinessLinkPrecedence(self): movement_node = self.portal.organisation_module.newContent( portal_type='Organisation') path_node = self.portal.organisation_module.newContent( portal_type='Organisation') business_path = self.createTradeModelPath() business_path.setSourceMethodId('TradeModelPath_getDefaultSourceList') business_path.setSourceValue(path_node) context_movement = self.createMovement() context_movement.setSourceValue(movement_node) self.assertEquals(path_node, business_path.getSourceValue()) self.assertEquals([path_node.getRelativeUrl()], business_path.getArrowCategoryDict(context=context_movement)['source']) def test_BusinessPathDynamicCategoryAccessProviderEmptyMovement(self): business_path = self.createTradeModelPath() business_path.setSourceMethodId('TradeModelPath_getDefaultSourceList') context_movement = self.createMovement() self.assertEquals(None, business_path.getSourceValue()) self.assertFalse(business_path.getArrowCategoryDict(context=context_movement).has_key('source')) def test_BusinessState_getRemainingTradePhaseList(self): """ This test case is described for what trade_phase is remaining after the given business link. """ # define business process category_tool = self.getCategoryTool() business_process = self.createBusinessProcess() business_link_order = self.createBusinessLink(business_process, title='order', id='order', trade_phase='default/order') business_link_deliver = self.createBusinessLink(business_process, title='deliver', id='deliver', trade_phase='default/delivery') business_link_invoice = self.createBusinessLink(business_process, title='invoice', id='invoice', trade_phase='default/invoicing') trade_state = category_tool.trade_state business_link_order.setSuccessorValue(trade_state.ordered) business_link_deliver.setPredecessorValue(trade_state.ordered) business_link_deliver.setSuccessorValue(trade_state.delivered) business_link_invoice.setPredecessorValue(trade_state.delivered) business_link_invoice.setSuccessorValue(trade_state.invoiced) trade_phase = category_tool.trade_phase.default self.assertEquals([trade_phase.delivery, trade_phase.invoicing], business_process.getRemainingTradePhaseList( business_process.order)) self.assertEquals([trade_phase.invoicing], business_process.getRemainingTradePhaseList( business_process.deliver)) self.assertEquals([], business_process.getRemainingTradePhaseList( business_process.invoice)) def test_BusinessProcess_getExpectedTradeModelPathStartAndStopDate(self): """ This test case is described for what start/stop date is expected on path by explanation. """ # define business process self._createOrderedDeliveredInvoicedBusinessProcess() base_date = DateTime('2009/04/01 GMT+9') self.constructSimulationTreeAndDeliveries(simulation_depth=1) # Set dates manually since we have dummy simulation self.simulation_movement.edit(start_date=base_date, stop_date=base_date) self.tic() def checkExpectedDates(explanation, start_date, stop_date, delay_mode=None): self.assertEquals( self.business_process.getExpectedTradeModelPathStartAndStopDate( explanation, self.delivery_path, delay_mode=delay_mode), (start_date, stop_date)) # Default behavior, no delay checkExpectedDates(self.order, base_date, base_date) # Update business process in order to introduce delay self.delivery_path.edit(min_delay=1.0, max_delay=3.0) self.constructSimulationTreeAndDeliveries(simulation_depth=2) # Set dates manually since we have dummy simulation self.simulation_movement.edit(start_date=base_date, stop_date=base_date) checkExpectedDates(self.order, base_date, base_date + 2) checkExpectedDates(self.order, base_date, base_date + 1, delay_mode='min') checkExpectedDates(self.order, base_date, base_date + 3, delay_mode='max') checkExpectedDates(self.delivery_simulation_movement.getParentValue(), base_date, base_date + 2) """ XXX More complex scenarios must be tested, like when several path are possible like this : (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 For now the implementation and documentation is not clear enough. """ def test_isBuildable(self): """Test isBuildable for ordered, delivered and invoiced sequence Here Business Process sequence corresponds simulation tree. delivery_path is related to root applied rule, and invoice_path is related to rule below, and invoice_path is after delivery_path """ self._createOrderedDeliveredInvoicedBusinessProcess() self.constructSimulationTreeAndDeliveries(dummy_split=True) self.order.setSimulationState(self.completed_state) self.stepTic() def checkIsBusinessLinkBuildable(explanation, business_link, value): self.assertEquals(self.business_process.isBusinessLinkBuildable( explanation, business_link), value) # in the beginning only order related movements shall be buildable checkIsBusinessLinkBuildable(self.order, self.delivery_link, True) self.assertEquals(self.delivery_simulation_movement.isBuildable(), True) self.assertEquals(self.split_delivery_simulation_movement.isBuildable(), True) checkIsBusinessLinkBuildable(self.order, self.invoice_link, False) self.assertEquals(self.invoicing_simulation_movement.isBuildable(), False) self.assertEquals(self.split_invoicing_simulation_movement.isBuildable(), False) # add delivery delivery = self._createDelivery(causality_value = self.order) delivery_line = self._createMovement(delivery) # relate not split movement with delivery (deliver it) self.delivery_simulation_movement.edit(delivery_value = delivery_line) self.stepTic() # delivery_link (for order) is still buildable, as split movement is not # delivered yet # # invoice_link is not yet buildable, delivery is in inproper simulation # state # # delivery_link (for delivery) is not buildable - delivery is already # built for those movements checkIsBusinessLinkBuildable(self.order, self.delivery_link, True) self.assertEquals(self.split_delivery_simulation_movement.isBuildable(), True) checkIsBusinessLinkBuildable(delivery, self.delivery_link, False) checkIsBusinessLinkBuildable(delivery, self.invoice_link, False) self.assertEquals(self.delivery_simulation_movement.isBuildable(), False) self.assertEquals(self.invoicing_simulation_movement.isBuildable(), False) checkIsBusinessLinkBuildable(self.order, self.invoice_link, False) self.assertEquals(self.split_invoicing_simulation_movement.isBuildable(), False) # put delivery in simulation state configured on path (and this state is # available directly on movements) delivery.setSimulationState(self.completed_state) self.assertEqual(self.completed_state, delivery.getSimulationState()) self.stepTic() # delivery_link (for order) is still buildable, as split movement is not # delivered yet # # invoice_link is not buildable in case of order because delivery_link # is not completed yet. # # invoice link is buildable for delivery because part of tree is buildable # # split movement for invoicing is not buildable - no proper delivery # related for previous path checkIsBusinessLinkBuildable(self.order, self.delivery_link, True) self.assertEquals(self.invoicing_simulation_movement.isBuildable(), True) checkIsBusinessLinkBuildable(delivery, self.invoice_link, True) checkIsBusinessLinkBuildable(self.order, self.invoice_link, False) checkIsBusinessLinkBuildable(delivery, self.invoice_link, True) checkIsBusinessLinkBuildable(delivery, self.delivery_link, False) self.assertEquals(self.delivery_simulation_movement.isBuildable(), False) self.assertEquals(self.split_invoicing_simulation_movement.isBuildable(), False) def test_isCompleted(self): """Test isCompleted for ordered, delivered and invoiced sequence""" self._createOrderedDeliveredInvoicedBusinessProcess() self.constructSimulationTreeAndDeliveries(dummy_split=True) self.assertEqual(self.delivery_link.isCompleted(self.order), False) self.assertEqual(self.delivery_link.isPartiallyCompleted(self.order), False) self.assertEqual(self.invoice_link.isCompleted(self.order), False) self.assertEqual(self.invoice_link.isPartiallyCompleted(self.order), False) # add delivery delivery = self._createDelivery(causality_value = self.order) delivery_line = self._createMovement(delivery) # relate not split movement with delivery (deliver it) self.delivery_simulation_movement.edit(delivery_value = delivery_line) self.stepTic() # nothing changes self.assertEqual(self.delivery_link.isCompleted(self.order), False) self.assertEqual(self.delivery_link.isPartiallyCompleted(self.order), False) self.assertEqual(self.invoice_link.isCompleted(self.order), False) self.assertEqual(self.invoice_link.isPartiallyCompleted(self.order), False) # from delivery point of view everything is same self.assertEqual(self.delivery_link.isCompleted(delivery), False) self.assertEqual(self.delivery_link.isPartiallyCompleted(delivery), False) self.assertEqual(self.invoice_link.isCompleted(delivery), False) self.assertEqual(self.invoice_link.isPartiallyCompleted(delivery), False) # put delivery in simulation state configured on path (and this state is # available directly on movements) delivery.setSimulationState(self.completed_state) self.assertEqual(self.completed_state, delivery.getSimulationState()) self.stepTic() self.assertEqual(self.delivery_link.isCompleted(self.order), False) self.assertEqual(self.delivery_link.isPartiallyCompleted(self.order), True) self.assertEqual(self.invoice_link.isCompleted(self.order), False) self.assertEqual(self.invoice_link.isPartiallyCompleted(self.order), False) self.assertEqual(self.delivery_link.isCompleted(delivery), True) self.assertEqual(self.delivery_link.isPartiallyCompleted(delivery), True) self.assertEqual(self.invoice_link.isCompleted(delivery), False) self.assertEqual(self.invoice_link.isPartiallyCompleted(delivery), False) # and finally deliver everything simulation movement coming from order another_delivery = self._createDelivery() another_delivery_line = self._createMovement(another_delivery) self.split_delivery_simulation_movement.edit( delivery_value=another_delivery_line) another_delivery.setSimulationState(self.completed_state) self.stepTic() self.assertEqual(self.delivery_link.isCompleted(self.order), True) self.assertEqual(self.delivery_link.isPartiallyCompleted(self.order), True) def test_isFrozen_OrderedDeliveredInvoiced(self): """Test isFrozen for ordered, delivered and invoiced sequence""" self._createOrderedDeliveredInvoicedBusinessProcess() self.constructSimulationTreeAndDeliveries(dummy_split=True) self.assertEqual(self.order_link.isFrozen(self.order), False) self.assertEqual(self.delivery_link.isFrozen(self.order), False) self.assertEqual(self.invoice_link.isFrozen(self.order), False) self.assertEqual(self.simulation_movement.isFrozen(), False) self.assertEqual(self.split_simulation_movement.isFrozen(), False) self.order.setSimulationState(self.completed_state) self.stepTic() self.assertEqual(self.order_link.isFrozen(self.order), True) self.assertEqual(self.delivery_link.isFrozen(self.order), False) self.assertEqual(self.simulation_movement.isFrozen(), True) self.assertEqual(self.invoicing_simulation_movement.isFrozen(), False) self.assertEqual(self.split_simulation_movement.isFrozen(), True) self.assertEqual(self.split_invoicing_simulation_movement.isFrozen(), False) # add delivery delivery = self._createDelivery() delivery_line = self._createMovement(delivery) # relate not split movement with delivery (deliver it) self.delivery_simulation_movement.edit(delivery_value = delivery_line) self.stepTic() # nothing changes self.assertEqual(self.delivery_link.isFrozen(self.order), False) self.assertEqual(self.invoice_link.isFrozen(self.order), False) # from delivery point of view everything is same self.assertEqual(self.delivery_link.isFrozen(delivery), False) self.assertEqual(self.invoice_link.isFrozen(delivery), False) self.assertEqual(self.simulation_movement.isFrozen(), True) self.assertEqual(self.invoicing_simulation_movement.isFrozen(), False) self.assertEqual(self.split_simulation_movement.isFrozen(), True) self.assertEqual(self.split_invoicing_simulation_movement.isFrozen(), False) # put delivery in simulation state configured on path (and this state is # available directly on movements) delivery.setSimulationState(self.frozen_state) self.assertEqual(self.frozen_state, delivery.getSimulationState()) self.stepTic() self.assertEqual(self.delivery_link.isFrozen(self.order), False) self.assertEqual(self.invoice_link.isFrozen(self.order), False) self.assertEqual(self.delivery_link.isFrozen(delivery), True) self.assertEqual(self.invoice_link.isFrozen(delivery), False) self.assertEqual(self.delivery_simulation_movement.isFrozen(), True) self.assertEqual(self.invoicing_simulation_movement.isFrozen(), False) self.assertEqual(self.split_simulation_movement.isFrozen(), True) self.assertEqual(self.split_invoicing_simulation_movement.isFrozen(), False) @todo_erp5 def test_payBeforeDelivery(self): # TODO: Implement use cases where business states don't follow the order # of applied rules. # This was tested in draft implementation of BPM # (see testBPMEvaluation in older revisions). raise NotImplementedError def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(TestBPMImplementation)) return suite