# -*- 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 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
import transaction

from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from AccessControl.SecurityManagement import newSecurityManager
from DateTime import DateTime

from Products.ERP5Type.tests.Sequence import SequenceList
from Products.CMFCore.utils import getToolByName
from Products.ERP5Type.tests.utils import reindex

# XXX TODO:
#  * move test.* methods to other classes, group by testing area
#  * subclass TestBPMMixin from TestInvoiceMixin and refactor methods and
#    style

class TestBPMMixin(ERP5TypeTestCase):
  """Skeletons for tests for ERP5 BPM"""

  def getBusinessTemplateList(self):
    return ('erp5_base', 'erp5_pdm', 'erp5_trade', 'erp5_accounting',
      'erp5_invoicing', 'erp5_mrp', 'erp5_bpm')

  default_discount_ratio = -0.05 # -5%
  default_tax_ratio = 0.196 # 19,6%

  new_discount_ratio = -0.04 # -4%
  new_tax_ratio = 0.22 # 22%

  node_portal_type = 'Organisation'
  order_date = DateTime()
  default_business_process = \
      'business_process_module/erp5_default_business_process'

  business_process_portal_type = 'Business Process'
  business_path_portal_type = 'Business Path'
  business_state_portal_type = 'Business State'

  modified_order_line_price_ratio = 2.0
  modified_invoice_line_quantity_ratio = modified_order_line_quantity_ratio \
      = 2.5

  modified_packing_list_line_quantity_ratio = 0.5

  base_unit_quantity = 0.01

  normal_resource_use_category_list = ['normal']
  invoicing_resource_use_category_list = ['discount', 'tax']

  def setUpOnce(self):
    self.portal = self.getPortalObject()
    self.validateRules()

  def createCategoriesInCategory(self, category, category_id_list):
    for category_id in category_id_list:
      if getattr(category,category_id,None) is None:
        category.newContent(portal_type='Category', id = category_id,
            title = category_id)

  @reindex
  def createCategories(self):
    category_tool = getToolByName(self.portal, 'portal_categories')
    self.createCategoriesInCategory(category_tool.base_amount, ['discount',
      'tax'])
    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'])

  @reindex
  def createBusinessProcess(self):
    module = self.portal.getDefaultModule(
        portal_type=self.business_process_portal_type)
    return module.newContent(portal_type=self.business_process_portal_type)

  def stepCreateBusinessProcess(self, sequence=None, **kw):
    sequence.edit(business_process=self.createBusinessProcess())

  @reindex
  def createBusinessPath(self, business_process=None):
    if business_process is None:
      business_process = self.portal.business_process_module.newContent(
        portal_type=self.business_process_portal_type)
    business_path = business_process.newContent(
      portal_type=self.business_path_portal_type)
    return business_path

  def stepCreateBusinessPath(self, sequence=None, **kw):
    business_process = sequence.get('business_process')
    sequence.edit(business_path=self.createBusinessPath(business_process))

  def stepModifyBusinessPathTaxing(self, sequence=None, **kw):
    predecessor = sequence.get('business_state_invoiced')
    successor = sequence.get('business_state_taxed')
    business_path = sequence.get('business_path')
    self.assertNotEqual(None, predecessor)
    self.assertNotEqual(None, successor)

    business_path.edit(
      predecessor_value = predecessor,
      successor_value = successor,
      trade_phase = 'default/tax'
    )
    sequence.edit(business_path=None, business_path_taxing=business_path)

  def _solveDivergence(self, obj, property, decision, group='line'):
    kw = {'%s_group_listbox' % group:{}}
    for divergence in obj.getDivergenceList():
      if divergence.getProperty('tested_property') != property:
        continue
      sm_url = divergence.getProperty('simulation_movement').getRelativeUrl()
      kw['line_group_listbox']['%s&%s' % (sm_url, property)] = {
        'choice':decision}
    self.portal.portal_workflow.doActionFor(
      obj,
      'solve_divergence_action',
      **kw)

  def stepAcceptDecisionQuantityInvoice(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    self._solveDivergence(invoice, 'quantity', 'accept')

  def stepAdoptPrevisionQuantityInvoice(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    self._solveDivergence(invoice, 'quantity', 'adopt')

  def stepModifyBusinessPathDiscounting(self, sequence=None, **kw):
    predecessor = sequence.get('business_state_invoiced')
    successor = sequence.get('business_state_taxed')
    business_path = sequence.get('business_path')
    self.assertNotEqual(None, predecessor)
    self.assertNotEqual(None, successor)

    business_path.edit(
      predecessor_value = predecessor,
      successor_value = successor,
      trade_phase = 'default/discount'
    )
    sequence.edit(business_path=None, business_path_discounting=business_path)

  @reindex
  def createBusinessState(self, business_process=None):
    if business_process is None:
      business_process = self.portal.business_process_module.newContent(
                           portal_type=self.business_process_portal_type)
    business_path = business_process.newContent(
        portal_type=self.business_state_portal_type)
    return business_path

  def stepCreateBusinessState(self, sequence=None, **kw):
    business_process = sequence.get('business_process')
    sequence.edit(business_state=self.createBusinessState(business_process))

  def stepModifyBusinessStateTaxed(self, sequence=None, **kw):
    business_state = sequence.get('business_state')
    business_state.edit(reference='taxed')
    sequence.edit( business_state=None, business_state_taxed=business_state)

  def stepModifyBusinessStateInvoiced(self, sequence=None,
                sequence_string=None):
    business_state = sequence.get('business_state')
    business_state.edit(reference='invoiced')
    sequence.edit(business_state=None, business_state_invoiced=business_state)

  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')

  @reindex
  def setSystemPreference(self):
    preference_tool = getToolByName(self.portal, 'portal_preferences')
    system_preference_list = preference_tool.contentValues(
        portal_type='System Preference')
    if len(system_preference_list) > 1:
      raise AttributeError('More than one System Preference, cannot test')
    if len(system_preference_list) == 0:
      system_preference = preference_tool.newContent(
          portal_type='System Preference')
    else:
      system_preference = system_preference_list[0]
    system_preference.edit(
      preferred_invoicing_resource_use_category_list = \
          self.invoicing_resource_use_category_list,
      preferred_normal_resource_use_category_list = \
          self.normal_resource_use_category_list,
      priority = 1,

    )

    if system_preference.getPreferenceState() == 'disabled':
      system_preference.enable()

  @reindex
  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 createInvoiceTransationRule(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')

    itr = self.portal.portal_rules.newContent(
                        portal_type='Invoice Transaction Rule',
                        reference='default_invoice_transaction_rule',
                        id='test_invoice_transaction_rule',
                        title='Transaction Rule',
                        test_method_id=
                        'SimulationMovement_testInvoiceTransactionRule',
                        version=100)
    predicate = itr.newContent(portal_type='Predicate',)
    predicate.edit(
            string_index='use',
            title='tax',
            int_index=1,
            membership_criterion_base_category='resource_use',
            membership_criterion_category='resource_use/use/tax')
    predicate = itr.newContent(portal_type='Predicate',)
    predicate.edit(
            string_index='use',
            title='discount',
            int_index=2,
            membership_criterion_base_category='resource_use',
            membership_criterion_category='resource_use/use/discount')
    predicate = itr.newContent(portal_type='Predicate',)
    predicate.edit(
            string_index='use',
            title='normal',
            int_index=3,
            membership_criterion_base_category='resource_use',
            membership_criterion_category='resource_use/use/normal')
    transaction.commit()
    self.tic()
    accounting_rule_cell_list = itr.contentValues(
                            portal_type='Accounting Rule Cell')
    self.assertEquals(3, len(accounting_rule_cell_list))
    tax_rule_cell = itr._getOb("movement_0")
    self.assertEquals(tax_rule_cell.getTitle(), 'tax')
    tax_rule_cell.newContent(
                         portal_type='Accounting Transaction Line',
                         source_value=self.receivable_account,
                         destination_value=self.payable_account,
                         quantity=-1)
    tax_rule_cell.newContent(
                         portal_type='Accounting Transaction Line',
                         source_value=self.collected_tax_account,
                         destination_value=self.refundable_tax_account,
                         quantity=1)

    discount_rule_cell = itr._getOb("movement_1")
    self.assertEquals(discount_rule_cell.getTitle(), 'discount')
    discount_rule_cell.newContent(
                         portal_type='Accounting Transaction Line',
                         source_value=self.receivable_account,
                         destination_value=self.payable_account,
                         quantity=-1)
    discount_rule_cell.newContent(
                         portal_type='Accounting Transaction Line',
                         source_value=self.income_account,
                         destination_value=self.expense_account,
                         quantity=1)

    normal_rule_cell = itr._getOb("movement_2")
    self.assertEquals(normal_rule_cell.getTitle(), 'normal')
    normal_rule_cell.newContent(
                         portal_type='Accounting Transaction Line',
                         source_value=self.receivable_account,
                         destination_value=self.payable_account,
                         quantity=-1)
    normal_rule_cell.newContent(
                         portal_type='Accounting Transaction Line',
                         source_value=self.income_account,
                         destination_value=self.expense_account,
                         quantity=1)

    itr.validate()

  @reindex
  def afterSetUp(self):
    self.createCategories()
    self.setSystemPreference()
    self.createInvoiceTransationRule()

  @reindex
  def beforeTearDown(self):
    self.portal.portal_rules.manage_delObjects(
        ids=['test_invoice_transaction_rule'])

  def stepCreateSource(self, sequence=None, **kw):
    module = self.portal.getDefaultModule(portal_type=self.node_portal_type)
    node = module.newContent(portal_type=self.node_portal_type)
    sequence.edit(source = node)

  def stepCreateSourceSection(self, sequence=None, **kw):
    module = self.portal.getDefaultModule(portal_type=self.node_portal_type)
    node = module.newContent(portal_type=self.node_portal_type)
    sequence.edit(source_section = node)

  def stepCreateDestination(self, sequence=None, **kw):
    module = self.portal.getDefaultModule(portal_type=self.node_portal_type)
    node = module.newContent(portal_type=self.node_portal_type)
    sequence.edit(destination = node)

  def stepCreateDestinationSection(self, sequence=None, **kw):
    module = self.portal.getDefaultModule(portal_type=self.node_portal_type)
    node = module.newContent(portal_type=self.node_portal_type)
    sequence.edit(destination_section = node)

  def createOrder(self):
    module = self.portal.getDefaultModule(portal_type=self.order_portal_type)
    return module.newContent(portal_type=self.order_portal_type)

  def stepCreateOrder(self, sequence=None, **kw):
    sequence.edit(order = self.createOrder())

  def stepSpecialiseOrderTradeCondition(self, sequence=None, **kw):
    order = sequence.get('order')
    trade_condition = sequence.get('trade_condition')

    order.edit(specialise_value = trade_condition)

  def stepSpecialiseInvoiceTradeCondition(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    trade_condition = sequence.get('trade_condition')

    invoice.edit(specialise_value = trade_condition)

  def stepPlanOrder(self, sequence=None, **kw):
    order = sequence.get('order')
    workflow_tool = getToolByName(self.portal, 'portal_workflow')
    workflow_tool.doActionFor(order, 'plan_action')

  def stepStartInvoice(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    workflow_tool = getToolByName(self.portal, 'portal_workflow')
    workflow_tool.doActionFor(invoice, 'start_action')

  def stepStopInvoice(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    workflow_tool = getToolByName(self.portal, 'portal_workflow')
    workflow_tool.doActionFor(invoice, 'stop_action')

  def stepDeliverInvoice(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    workflow_tool = getToolByName(self.portal, 'portal_workflow')
    workflow_tool.doActionFor(invoice, 'deliver_action')

  def stepStartPackingList(self, sequence=None, **kw):
    packing_list = sequence.get('packing_list')
    workflow_tool = getToolByName(self.portal, 'portal_workflow')
    workflow_tool.doActionFor(packing_list, 'start_action')

  def stepStopPackingList(self, sequence=None, **kw):
    packing_list = sequence.get('packing_list')
    workflow_tool = getToolByName(self.portal, 'portal_workflow')
    workflow_tool.doActionFor(packing_list, 'stop_action')

  def stepDeliverPackingList(self, sequence=None, **kw):
    packing_list = sequence.get('packing_list')
    workflow_tool = getToolByName(self.portal, 'portal_workflow')
    workflow_tool.doActionFor(packing_list, 'deliver_action')

  def stepCheckPackingListDiverged(self, sequence=None, **kw):
    packing_list = sequence.get('packing_list')
    self.assertEqual(
      'diverged',
      packing_list.getCausalityState()
    )

  def stepSplitAndDeferPackingList(self, sequence=None, **kw):
    packing_list = sequence.get('packing_list')
    kw = {'listbox':[
      {'listbox_key':line.getRelativeUrl(),
       'choice':'SplitAndDefer'} for line in packing_list.getMovementList() \
      if line.isDivergent()]}
    self.portal.portal_workflow.doActionFor(
      packing_list,
      'split_and_defer_action',
      start_date=packing_list.getStartDate() + 15,
      stop_date=packing_list.getStopDate() + 25,
      **kw)

  def stepDecreasePackingListLineListQuantity(self, sequence=None, **kw):
    packing_list = sequence.get('packing_list')
    for movement in packing_list.getMovementList():
      movement.edit(
        quantity = movement.getQuantity() * \
            self.modified_packing_list_line_quantity_ratio
      )

  def stepPackPackingList(self, sequence=None, **kw):
    packing_list = sequence.get('packing_list')
    if getattr(packing_list,'getContainerState', None) is None:
      return
    if packing_list.getContainerState() == 'packed':
      return

    packing_list.manage_delObjects(ids=[q.getId() for q in
      packing_list.objectValues(portal_type='Container')])
    transaction.commit()
    cntr = packing_list.newContent(portal_type='Container')
    for movement in packing_list.getMovementList(
        portal_type=self.portal.getPortalMovementTypeList()):
      cntr.newContent(
        portal_type='Container Line',
        resource = movement.getResource(),
        quantity = movement.getQuantity())
    transaction.commit()
    self.tic()
    self.assertEqual('packed', packing_list.getContainerState() )

  def stepCheckInvoiceNormalMovements(self, sequence=None, **kw):
    self.logMessage('Assuming, that it is good...')

  def stepCheckInvoiceAccountingMovements(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    currency = sequence.get('price_currency')
    currency_precision = currency.getQuantityPrecision()
    aggregated_amount_list_list = [
      (q.getResourceValue().getUse(), q)
      for q in invoice.getSpecialiseValue().getAggregatedAmountList(invoice)]
    invoice_line_tax = [q[1] for q in aggregated_amount_list_list
        if q[0] == 'tax'][0]
    invoice_line_discount = [q[1] for q in aggregated_amount_list_list
        if q[0] == 'discount'][0]

    movement_list = invoice.getMovementList(
        portal_type=invoice.getPortalAccountingMovementTypeList())
    self.assertEqual(3, len(movement_list))
    income_expense_line = [q for q in movement_list if
        q.getSourceValue().getAccountType() in ['income', 'expense']][0]
    payable_receivable_line = [q for q in movement_list if
        q.getSourceValue().getAccountType() in ['asset/receivable',
          'liability/payable']][0]
    vat_line = [q for q in movement_list if q.getSourceValue() \
        .getAccountType() in ['liability/payable/collected_vat',
          'asset/receivable/refundable_vat']][0]

    rounded_total_price = round(invoice.getTotalPrice(), currency_precision)
    rounded_tax_price = round(invoice_line_tax.getTotalPrice(),
        currency_precision)
    rounded_discount_price = round(invoice_line_discount.getTotalPrice(),
        currency_precision)

    self.assertEqual(abs(payable_receivable_line.getTotalPrice()),
        rounded_total_price + rounded_tax_price + rounded_discount_price)

    self.assertEqual(abs(vat_line.getTotalPrice()),
        rounded_tax_price)

    self.assertEquals(abs(income_expense_line.getTotalPrice()),
        rounded_total_price + rounded_discount_price)

  def stepSetTradeConditionOld(self, sequence=None, **kw):
    trade_condition = sequence.get('trade_condition')

    self.assertNotEqual(None, trade_condition)

    sequence.edit(
      trade_condition = None,
      old_trade_condition = trade_condition
    )

  def stepSetTradeConditionNew(self, sequence=None, **kw):
    trade_condition = sequence.get('trade_condition')

    self.assertNotEqual(None, trade_condition)

    sequence.edit(
      trade_condition = None,
      new_trade_condition = trade_condition
    )

  def stepGetOldTradeCondition(self, sequence=None, **kw):
    trade_condition = sequence.get('old_trade_condition')

    self.assertNotEqual(None, trade_condition)

    sequence.edit(
      trade_condition = trade_condition,
    )

  def stepGetNewTradeCondition(self, sequence=None, **kw):
    trade_condition = sequence.get('new_trade_condition')

    self.assertNotEqual(None, trade_condition)

    sequence.edit(
      trade_condition = trade_condition,
    )

  def stepAcceptDecisionInvoice(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    invoice.portal_workflow.doActionFor(invoice,'accept_decision_action')

  def stepCheckInvoiceCausalityStateSolved(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    self.assertEqual('solved', invoice.getCausalityState(),
      invoice.getDivergenceList())

  def stepCheckInvoiceCausalityStateDiverged(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    self.assertEqual('diverged', invoice.getCausalityState())

  def stepGetInvoice(self, sequence=None, **kw):
    packing_list = sequence.get('packing_list')
    invoice_list = packing_list.getCausalityRelatedValueList(
        portal_type=self.invoice_portal_type)
    self.assertEqual(1, len(invoice_list)) # XXX 1 HC
    sequence.edit(invoice = invoice_list[0])

  def stepSetNewPackingListAsPackingList(self, sequence=None, **kw):
    packing_list = sequence.get('packing_list')
    new_packing_list = sequence.get('new_packing_list')
    sequence.edit(
      packing_list = new_packing_list,
      new_packing_list = None
    )

  def stepGetNewPackingList(self, sequence=None, **kw):
    order = sequence.get('order')
    packing_list = sequence.get('packing_list')
    packing_list_list = order.getCausalityRelatedValueList(
        portal_type=self.packing_list_portal_type)
    self.assertEqual(2, len(packing_list_list)) # XXX 2 HC
    new_packing_list = [q for q in packing_list_list if q != packing_list][0]
    sequence.edit(new_packing_list = new_packing_list)

  def stepGetPackingList(self, sequence=None, **kw):
    order = sequence.get('order')
    packing_list_list = order.getCausalityRelatedValueList(
        portal_type=self.packing_list_portal_type)
    self.assertEqual(1, len(packing_list_list)) # XXX 1 HC
    sequence.edit(packing_list = packing_list_list[0])

  def stepConfirmOrder(self, sequence=None, **kw):
    order = sequence.get('order')
    workflow_tool = getToolByName(self.portal, 'portal_workflow')
    workflow_tool.doActionFor(order, 'confirm_action')

  def getTradeModelSimulationMovementList(self, order_line):
    result_list = []
    for line_simulation_movement in order_line.getOrderRelatedValueList(
        portal_type='Simulation Movement'):
      invoicing_applied_rule = [x for x in
          line_simulation_movement.objectValues()
          if x.getSpecialiseValue().getPortalType() == 'Invoicing Rule'][0]
      invoicing_movement = invoicing_applied_rule.objectValues()[0]
      trade_model_rule = [x for x in invoicing_movement.objectValues()
          if x.getSpecialiseValue().getPortalType() == 'Trade Model Rule'][0]
      result_list.append(trade_model_rule.objectValues())
    return result_list

  def stepCheckOrderTaxNoSimulation(self, sequence=None, **kw):
    order_line_taxed = sequence.get('order_line_taxed')
    for trade_model_simulation_movement_list in \
        self.getTradeModelSimulationMovementList(order_line_taxed):
      self.assertEquals(0, len(trade_model_simulation_movement_list))

  # XXX: Merge: stepCheckOrderLineDiscountedSimulation stepCheckOrderLineTaxedSimulation stepCheckOrderLineDiscountedTaxedSimulation

  def stepCheckOrderLineDiscountedTaxedSimulation(self, sequence=None, **kw):
    order_line = sequence.get('order_line_discounted_taxed')
    business_path_discounting = sequence.get('business_path_discounting')
    business_path_taxing = sequence.get('business_path_taxing')
    price_currency = sequence.get('price_currency')

    service_tax = sequence.get('service_tax')
    service_discount = sequence.get('service_discount')

    self.assertNotEqual(None, business_path_discounting)
    self.assertNotEqual(None, business_path_taxing)
    self.assertNotEqual(None, price_currency)

    for trade_model_simulation_movement_list in \
        self.getTradeModelSimulationMovementList(order_line):

      self.assertEquals(2, len(trade_model_simulation_movement_list))
      trade_model_simulation_movement_discount_complex = [q for q in \
          trade_model_simulation_movement_list \
          if q.getResourceValue() == service_discount][0]

      trade_model_simulation_movement_tax_complex = [q for q in \
          trade_model_simulation_movement_list \
          if q.getResourceValue() == service_tax][0]

      # discount complex
      self.assertEqual(
        trade_model_simulation_movement_discount_complex.getParentValue() \
            .getParentValue().getTotalPrice() * self.default_discount_ratio,
        trade_model_simulation_movement_discount_complex.getTotalPrice()
      )

      self.assertEqual(
        business_path_discounting,
        trade_model_simulation_movement_discount_complex.getCausalityValue()
      )

      self.assertEqual(
        price_currency,
        trade_model_simulation_movement_discount_complex \
            .getPriceCurrencyValue()
      )

      self.assertSameSet(
        ['base_amount/tax'],
        trade_model_simulation_movement_discount_complex \
            .getBaseContributionList()
      )

      self.assertSameSet(
        ['base_amount/discount'],
        trade_model_simulation_movement_discount_complex \
            .getBaseApplicationList()
      )

      self.checkInvoiceTransactionRule(
          trade_model_simulation_movement_discount_complex)

      # TODO:
      #  * trade_phase ???
      #  * arrow
      #  * dates

      # tax complex
      self.assertEqual(
        (trade_model_simulation_movement_tax_complex.getParentValue()\
            .getParentValue().getTotalPrice() + \
            trade_model_simulation_movement_tax_complex.getParentValue()\
            .getParentValue().getTotalPrice() * self.default_discount_ratio) \
            * self.default_tax_ratio,
        trade_model_simulation_movement_tax_complex.getTotalPrice()
      )

      self.assertEqual(
        business_path_taxing,
        trade_model_simulation_movement_tax_complex.getCausalityValue()
      )

      self.assertEqual(
        price_currency,
        trade_model_simulation_movement_tax_complex.getPriceCurrencyValue()
      )

      self.assertSameSet(
        [],
        trade_model_simulation_movement_tax_complex.getBaseContributionList()
      )

      self.assertSameSet(
        ['base_amount/tax'],
        trade_model_simulation_movement_tax_complex.getBaseApplicationList()
      )

      self.checkInvoiceTransactionRule(
        trade_model_simulation_movement_tax_complex)
      # TODO:
      #  * trade_phase ???
      #  * arrow
      #  * dates

  def stepCheckOrderLineDiscountedSimulation(self, sequence=None, **kw):
    order_line = sequence.get('order_line_discounted')
    business_path_discounting = sequence.get('business_path_discounting')
    business_path_taxing = sequence.get('business_path_taxing')
    price_currency = sequence.get('price_currency')

    service_tax = sequence.get('service_tax')
    service_discount = sequence.get('service_discount')

    self.assertNotEqual(None, business_path_discounting)
    self.assertNotEqual(None, business_path_taxing)
    self.assertNotEqual(None, price_currency)

    for trade_model_simulation_movement_list in \
        self.getTradeModelSimulationMovementList(order_line):

      self.assertEquals(2, len(trade_model_simulation_movement_list))
      trade_model_simulation_movement_discount_only = [q for q in \
          trade_model_simulation_movement_list \
          if q.getResourceValue() == service_discount][0]

      trade_model_simulation_movement_tax_only = [q for q in \
          trade_model_simulation_movement_list \
          if q.getResourceValue() == service_tax][0]

      # discount only
      self.assertEqual(
        trade_model_simulation_movement_discount_only.getParentValue()\
            .getParentValue().getTotalPrice() * self.default_discount_ratio,
        trade_model_simulation_movement_discount_only.getTotalPrice()
      )

      self.assertEqual(
        business_path_discounting,
        trade_model_simulation_movement_discount_only.getCausalityValue()
      )

      self.assertEqual(
        price_currency,
        trade_model_simulation_movement_discount_only.getPriceCurrencyValue()
      )

      self.assertSameSet(
        ['base_amount/tax'],
        trade_model_simulation_movement_discount_only \
            .getBaseContributionList()
      )

      self.assertSameSet(
        ['base_amount/discount'],
        trade_model_simulation_movement_discount_only.getBaseApplicationList()
      )

      self.checkInvoiceTransactionRule(
          trade_model_simulation_movement_discount_only)
      # TODO:
      #  * trade_phase ???
      #  * arrow
      #  * dates

      # tax only
      # below tax is applied only to discount part
      self.assertEqual(trade_model_simulation_movement_discount_only. \
          getTotalPrice() * self.default_tax_ratio,
          trade_model_simulation_movement_tax_only.getTotalPrice())

      self.assertEqual(
        business_path_taxing,
        trade_model_simulation_movement_tax_only.getCausalityValue()
      )

      self.assertEqual(
        price_currency,
        trade_model_simulation_movement_tax_only.getPriceCurrencyValue()
      )

      self.assertSameSet(
        [],
        trade_model_simulation_movement_tax_only.getBaseContributionList()
      )

      self.assertSameSet(
        ['base_amount/tax'],
        trade_model_simulation_movement_tax_only.getBaseApplicationList()
      )

      self.checkInvoiceTransactionRule(
          trade_model_simulation_movement_tax_only)

      # TODO:
      #  * trade_phase ???
      #  * arrow
      #  * dates

  def stepCheckOrderLineTaxedSimulation(self, sequence=None, **kw):
    order_line = sequence.get('order_line_taxed')
    business_path = sequence.get('business_path_taxing')
    price_currency = sequence.get('price_currency')
    self.assertNotEqual(None, business_path)
    self.assertNotEqual(None, price_currency)
    for trade_model_simulation_movement_list in \
        self.getTradeModelSimulationMovementList(order_line):
      self.assertEquals(1, len(trade_model_simulation_movement_list))
      trade_model_simulation_movement = \
          trade_model_simulation_movement_list[0]

      self.assertEqual(
        trade_model_simulation_movement.getParentValue().getParentValue() \
            .getTotalPrice() * self.default_tax_ratio,
        trade_model_simulation_movement.getTotalPrice()
      )

      self.assertEqual(
        business_path,
        trade_model_simulation_movement.getCausalityValue()
      )

      self.assertEqual(
        price_currency,
        trade_model_simulation_movement.getPriceCurrencyValue()
      )

      self.assertSameSet(
        [],
        trade_model_simulation_movement.getBaseContributionList()
      )

      self.assertSameSet(
        ['base_amount/tax'],
        trade_model_simulation_movement.getBaseApplicationList()
      )
      self.checkInvoiceTransactionRule(trade_model_simulation_movement)

      # TODO:
      #  * trade_phase ???
      #  * arrow
      #  * dates

  def checkInvoiceTransactionRule(self, trade_model_simulation_movement):
    invoice_transaction_rule_list = trade_model_simulation_movement\
        .objectValues()
    self.assertEquals(1, len(invoice_transaction_rule_list))
    invoice_transaction_rule = invoice_transaction_rule_list[0]
    self.assertEqual('Invoice Transaction Rule',
        invoice_transaction_rule.getSpecialiseValue().getPortalType())

    invoice_transaction_simulation_movement_list = invoice_transaction_rule \
        .objectValues()

    self.assertEqual(2, len(invoice_transaction_simulation_movement_list))

    for movement in invoice_transaction_simulation_movement_list:
      self.assertEqual(abs(movement.getQuantity()),
          abs(trade_model_simulation_movement.getTotalPrice()))

  def stepFillOrder(self, sequence=None, **kw):
    order = sequence.get('order')
    price_currency = sequence.get('price_currency')
    source = sequence.get('source')
    destination = sequence.get('destination')
    source_section = sequence.get('source_section')
    destination_section = sequence.get('destination_section')
    self.assertNotEqual(None, price_currency)
    self.assertNotEqual(None, source)
    self.assertNotEqual(None, destination)
    self.assertNotEqual(None, source_section)
    self.assertNotEqual(None, destination_section)
    order.edit(
        source_value=source,
        destination_value=destination,
        source_section_value=source_section,
        destination_section_value=destination_section,
        start_date=self.order_date,
        price_currency_value = price_currency)

  def createResource(self, portal_type, **kw):
    module = self.portal.getDefaultModule(portal_type=portal_type)
    return module.newContent(portal_type=portal_type, **kw)

  def stepCreatePriceCurrency(self, sequence=None, **kw):
    sequence.edit(price_currency = self.createResource('Currency', \
        title='Currency', base_unit_quantity=self.base_unit_quantity))

  def stepCreateProductTaxed(self, sequence=None, **kw):
    sequence.edit(product_taxed = self.createResource('Product',
      title='Product Taxed',
      base_contribution=['base_amount/tax'],
      use='normal',
    ))

  def stepCreateProductDiscounted(self, sequence=None, **kw):
    sequence.edit(product_discounted = self.createResource('Product',
      title='Product Discounted',
      base_contribution=['base_amount/discount'],
      use='normal',
    ))

  def stepCreateProductDiscountedTaxed(self, sequence=None, **kw):
    sequence.edit(product_discounted_taxed = self.createResource('Product',
      title='Product Discounted & Taxed',
      base_contribution=['base_amount/discount', 'base_amount/tax'],
      use='normal',
    ))

  def stepCreateServiceTax(self, sequence=None, **kw):
    sequence.edit(service_tax = self.createResource('Service',
      title='Tax',
      use='tax',
    ))

  def stepCreateServiceDiscount(self, sequence=None, **kw):
    sequence.edit(service_discount = self.createResource('Service',
      title='Discount',
      use='discount',
    ))

  def createTradeCondition(self):
    module = self.portal.getDefaultModule(
        portal_type=self.trade_condition_portal_type)
    trade_condition = module.newContent(
        portal_type=self.trade_condition_portal_type)
    return trade_condition

  def stepCreateTradeCondition(self, sequence=None, **kw):
    sequence.edit(trade_condition = self.createTradeCondition())

  def stepCreateInvoiceLine(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    invoice_line = invoice.newContent(portal_type=self.invoice_line_portal_type)
    sequence.edit(invoice_line = invoice_line)

  def stepCreateOrderLine(self, sequence=None, **kw):
    order = sequence.get('order')
    order_line = order.newContent(portal_type=self.order_line_portal_type)
    sequence.edit(order_line = order_line)

  def stepGetInvoiceLineDiscounted(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    resource = sequence.get('product_discounted')
    self.assertNotEqual(None, resource)
    sequence.edit(invoice_line_discounted = [m for m in
      invoice.getMovementList() if m.getResourceValue() == resource][0])

  def stepGetInvoiceLineDiscountedTaxed(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    resource = sequence.get('product_discounted_taxed')
    self.assertNotEqual(None, resource)
    sequence.edit(invoice_line_discounted_taxed = [m for m in
      invoice.getMovementList() if m.getResourceValue() == resource][0])

  def stepGetInvoiceLineTaxed(self, sequence=None, **kw):
    invoice = sequence.get('invoice')
    resource = sequence.get('product_taxed')
    self.assertNotEqual(None, resource)
    sequence.edit(invoice_line_taxed = [m for m in
      invoice.getMovementList() if m.getResourceValue() == resource][0])

  def stepModifyQuantityInvoiceLineTaxed(self, sequence=None, **kw):
    invoice_line = sequence.get('invoice_line_taxed')
    invoice_line.edit(
      quantity=invoice_line.getQuantity() * \
          self.modified_invoice_line_quantity_ratio,
    )

  def stepModifyQuantityInvoiceLineDiscounted(self, sequence=None, **kw):
    invoice_line = sequence.get('invoice_line_discounted')
    invoice_line.edit(
      quantity=invoice_line.getQuantity() * \
          self.modified_invoice_line_quantity_ratio,
    )

  def stepModifyQuantityInvoiceLineDiscountedTaxed(self, sequence=None, **kw):
    invoice_line = sequence.get('invoice_line_discounted_taxed')
    invoice_line.edit(
      quantity=invoice_line.getQuantity() * \
          self.modified_invoice_line_quantity_ratio,
    )

  def stepModifyAgainOrderLineTaxed(self, sequence=None, **kw):
    order_line = sequence.get('order_line_taxed')
    order_line.edit(
      price=order_line.getPrice() * self.modified_order_line_price_ratio,
      quantity=order_line.getQuantity() * \
          self.modified_order_line_quantity_ratio,
    )

  def stepModifyAgainOrderLineDiscounted(self, sequence=None, **kw):
    order_line = sequence.get('order_line_discounted')
    order_line.edit(
      price=order_line.getPrice() * self.modified_order_line_price_ratio,
      quantity=order_line.getQuantity() * \
          self.modified_order_line_quantity_ratio,
    )

  def stepModifyAgainOrderLineDiscountedTaxed(self, sequence=None, **kw):
    order_line = sequence.get('order_line_discounted_taxed')
    order_line.edit(
      price=order_line.getPrice() * self.modified_order_line_price_ratio,
      quantity=order_line.getQuantity() * \
          self.modified_order_line_quantity_ratio,
    )

  def stepModifyOrderLineTaxed(self, sequence=None, **kw):
    order_line = sequence.get('order_line')
    resource = sequence.get('product_taxed')
    self.assertNotEqual(None, resource)
    order_line.edit(
      price=1.0,
      quantity=2.0,
      resource_value=resource
    )
    sequence.edit(
      order_line = None,
      order_line_taxed = order_line
    )

  def stepModifyOrderLineDiscounted(self, sequence=None, **kw):
    order_line = sequence.get('order_line')
    resource = sequence.get('product_discounted')
    self.assertNotEqual(None, resource)
    order_line.edit(
      price=3.0,
      quantity=4.0,
      resource_value=resource
    )
    sequence.edit(
      order_line = None,
      order_line_discounted = order_line
    )

  def stepModifyOrderLineDiscountedTaxed(self, sequence=None, **kw):
    order_line = sequence.get('order_line')
    resource = sequence.get('product_discounted_taxed')
    self.assertNotEqual(None, resource)
    order_line.edit(
      price=5.0,
      quantity=6.0,
      resource_value=resource
    )
    sequence.edit(
      order_line = None,
      order_line_discounted_taxed = order_line
    )

  def stepModifyInvoiceLineTaxed(self, sequence=None, **kw):
    invoice_line = sequence.get('invoice_line')
    resource = sequence.get('product_taxed')
    self.assertNotEqual(None, resource)
    invoice_line.edit(
      price=1.0,
      quantity=2.0,
      resource_value=resource
    )
    sequence.edit(
      invoice_line = None,
      invoice_line_taxed = invoice_line
    )

  def stepModifyInvoiceLineDiscounted(self, sequence=None, **kw):
    invoice_line = sequence.get('invoice_line')
    resource = sequence.get('product_discounted')
    self.assertNotEqual(None, resource)
    invoice_line.edit(
      price=3.0,
      quantity=4.0,
      resource_value=resource
    )
    sequence.edit(
      invoice_line = None,
      invoice_line_discounted = invoice_line
    )

  def stepModifyInvoiceLineDiscountedTaxed(self, sequence=None, **kw):
    invoice_line = sequence.get('invoice_line')
    resource = sequence.get('product_discounted_taxed')
    self.assertNotEqual(None, resource)
    invoice_line.edit(
      price=5.0,
      quantity=6.0,
      resource_value=resource
    )
    sequence.edit(
      invoice_line = None,
      invoice_line_discounted_taxed = invoice_line
    )

  def createTradeModelLine(self, document, **kw):
    return document.newContent(
        portal_type='Trade Model Line',
        **kw)

  def stepOrderCreateTradeModelLine(self, sequence=None, **kw):
    order = sequence.get('order')
    sequence.edit(trade_model_line = self.createTradeModelLine(order))

  def stepCreateTradeModelLine(self, sequence=None, **kw):
    trade_condition = sequence.get('trade_condition')
    sequence.edit(
      trade_model_line = self.createTradeModelLine(trade_condition))

  def stepSpecialiseTradeConditionWithBusinessProcess(self, sequence=None,
      **kw):
    business_process = sequence.get('business_process')
    trade_condition = sequence.get('trade_condition')
    self.assertNotEqual(None, business_process)
    trade_condition.setSpecialiseValue(business_process)

  def stepModifyTradeModelLineNewDiscount(self, sequence=None, **kw):
    trade_model_line = sequence.get('trade_model_line')
    service_discount = sequence.get('service_discount')

    trade_model_line.edit(
      price=self.new_discount_ratio,
      base_application='base_amount/discount',
      base_contribution='base_amount/tax',
      trade_phase='default/discount',
      resource_value=service_discount,
    )
    sequence.edit(
      trade_model_line = None,
      trade_model_line_discount = trade_model_line
    )

  def stepModifyTradeModelLineDiscount(self, sequence=None, **kw):
    trade_model_line = sequence.get('trade_model_line')
    service_discount = sequence.get('service_discount')

    trade_model_line.edit(
      price=self.default_discount_ratio,
      base_application='base_amount/discount',
      base_contribution='base_amount/tax',
      trade_phase='default/discount',
      resource_value=service_discount,
    )
    sequence.edit(
      trade_model_line = None,
      trade_model_line_discount = trade_model_line
    )

  def stepModifyTradeModelLineNewTax(self, sequence=None, **kw):
    trade_model_line = sequence.get('trade_model_line')
    service_tax = sequence.get('service_tax')

    trade_model_line.edit(
      price=self.new_tax_ratio,
      base_application='base_amount/tax',
      trade_phase='default/tax',
      resource_value=service_tax,
    )
    sequence.edit(
      trade_model_line = None,
      trade_model_line_tax = trade_model_line
    )

  def stepModifyTradeModelLineTax(self, sequence=None, **kw):
    trade_model_line = sequence.get('trade_model_line')
    service_tax = sequence.get('service_tax')

    trade_model_line.edit(
      price=self.default_tax_ratio,
      base_application='base_amount/tax',
      trade_phase='default/tax',
      resource_value=service_tax,
    )
    sequence.edit(
      trade_model_line = None,
      trade_model_line_tax = trade_model_line
    )

  def stepUpdateAggregatedAmountListOnOrder(self,
      sequence=None, **kw):
    order = sequence.get('order')
    order.Delivery_updateAggregatedAmountList(batch_mode=1)

  def stepCheckOrderLineTaxedAggregatedAmountList(self, sequence=None, **kw):
    order_line = sequence.get('order_line_taxed')
    trade_condition = sequence.get('trade_condition')
    amount_list = trade_condition.getAggregatedAmountList(order_line)

    self.assertEquals(1, len(amount_list))
    tax_amount_list = [q for q in amount_list
        if q.getBaseApplication() == 'base_amount/tax']
    self.assertEquals(1, len(tax_amount_list))

    tax_amount = tax_amount_list[0]

    self.assertSameSet(['base_amount/tax'],
        tax_amount.getBaseApplicationList())
    self.assertSameSet([], tax_amount.getBaseContributionList())

    self.assertEqual(order_line.getTotalPrice() * self.default_tax_ratio,
        tax_amount.getTotalPrice())

  def stepCheckOrderLineDiscountedTaxedAggregatedAmountList(self,
      sequence=None, **kw):
    order_line = sequence.get('order_line_discounted_taxed')
    trade_condition = sequence.get('trade_condition')
    amount_list = trade_condition.getAggregatedAmountList(order_line)

    self.assertEquals(2, len(amount_list))
    tax_amount_list = [q for q in amount_list
        if q.getBaseApplication() == 'base_amount/tax']
    self.assertEquals(1, len(tax_amount_list))
    tax_amount = tax_amount_list[0]

    discount_amount_list = [q for q in amount_list
        if q.getBaseApplication() == 'base_amount/discount']
    self.assertEquals(1, len(discount_amount_list))

    discount_amount = discount_amount_list[0]

    self.assertSameSet(['base_amount/tax'], tax_amount. \
        getBaseApplicationList())
    self.assertSameSet([], tax_amount.getBaseContributionList())

    self.assertSameSet(['base_amount/discount'], discount_amount. \
        getBaseApplicationList())
    self.assertSameSet(['base_amount/tax'], discount_amount. \
        getBaseContributionList())

    self.assertEqual(order_line.getTotalPrice() * \
        self.default_discount_ratio, discount_amount.getTotalPrice())

    self.assertEqual((order_line.getTotalPrice() + discount_amount. \
        getTotalPrice()) * self.default_tax_ratio,
        tax_amount.getTotalPrice())

  def stepCheckOrderLineDiscountedAggregatedAmountList(self, sequence=None,
      **kw):
    order_line = sequence.get('order_line_discounted')
    trade_condition = sequence.get('trade_condition')
    amount_list = trade_condition.getAggregatedAmountList(order_line)

    self.assertEquals(2, len(amount_list))
    tax_amount_list = [q for q in amount_list
        if q.getBaseApplication() == 'base_amount/tax']
    self.assertEquals(1, len(tax_amount_list))
    tax_amount = tax_amount_list[0]

    discount_amount_list = [q for q in amount_list
        if q.getBaseApplication() == 'base_amount/discount']
    self.assertEquals(1, len(discount_amount_list))

    discount_amount = discount_amount_list[0]

    self.assertSameSet(['base_amount/tax'], tax_amount. \
        getBaseApplicationList())
    self.assertSameSet([], tax_amount.getBaseContributionList())

    self.assertSameSet(['base_amount/discount'], discount_amount. \
        getBaseApplicationList())
    self.assertSameSet(['base_amount/tax'], discount_amount. \
        getBaseContributionList())

    self.assertEqual(order_line.getTotalPrice() * \
        self.default_discount_ratio, discount_amount.getTotalPrice())

    # below tax is applied only to discount part
    self.assertEqual(discount_amount.getTotalPrice() * self.default_tax_ratio,
        tax_amount.getTotalPrice())

  def stepCheckOrderComplexTradeConditionAggregatedAmountList(self,
      sequence=None, **kw):
    trade_condition = sequence.get('trade_condition')
    order = sequence.get('order')
    order_line_discounted = sequence.get('order_line_discounted')
    order_line_discounted_taxed = sequence.get('order_line_discounted_taxed')
    order_line_taxed = sequence.get('order_line_taxed')

    amount_list = trade_condition.getAggregatedAmountList(order)
    self.assertEquals(2, len(amount_list))
    discount_amount_list = [q for q in amount_list
        if q.getBaseApplication() == 'base_amount/discount']
    tax_amount_list = [q for q in amount_list
        if q.getBaseApplication() == 'base_amount/tax']

    self.assertEquals(1, len(discount_amount_list))
    self.assertEquals(1, len(tax_amount_list))

    discount_amount = discount_amount_list[0]
    tax_amount = tax_amount_list[0]

    self.assertSameSet(['base_amount/discount'], discount_amount. \
        getBaseApplicationList())

    self.assertSameSet(['base_amount/tax'], discount_amount. \
        getBaseContributionList())

    self.assertSameSet(['base_amount/tax'], tax_amount. \
        getBaseApplicationList())

    self.assertSameSet([], tax_amount.getBaseContributionList())

    self.assertEqual(
      discount_amount.getTotalPrice(),
      (order_line_discounted.getTotalPrice()
        + order_line_discounted_taxed.getTotalPrice() )
      * self.default_discount_ratio
    )

    self.assertEqual(
      tax_amount.getTotalPrice(),
      (order_line_taxed.getTotalPrice()
        + order_line_discounted_taxed.getTotalPrice()
        + discount_amount.getTotalPrice()) * self.default_tax_ratio
    )

class TestBPMTestCases(TestBPMMixin):
  COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING = """
              CreateServiceTax
              CreateServiceDiscount
              CreatePriceCurrency
              CreateProductDiscounted
              CreateProductTaxed
              CreateProductDiscountedTaxed
              CreateSource
              CreateSourceSection
              CreateDestination
              CreateDestinationSection
              Tic
  """

  AGGREGATED_AMOUNT_LIST_CHECK_SEQUENCE_STRING = """
              CheckOrderComplexTradeConditionAggregatedAmountList
              CheckOrderLineTaxedAggregatedAmountList
              CheckOrderLineDiscountedTaxedAggregatedAmountList
              CheckOrderLineDiscountedAggregatedAmountList
  """

  AGGREGATED_AMOUNT_LIST_COMMON_SEQUENCE_STRING = \
      COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING + """
              CreateBusinessProcess
              CreateBusinessState
              ModifyBusinessStateTaxed
              CreateBusinessState
              ModifyBusinessStateInvoiced
              CreateBusinessPath
              ModifyBusinessPathTaxing
              CreateBusinessPath
              ModifyBusinessPathDiscounting
              CreateTradeCondition
              SpecialiseTradeConditionWithBusinessProcess
              CreateTradeModelLine
              ModifyTradeModelLineTax
              CreateTradeModelLine
              ModifyTradeModelLineDiscount
              Tic
              CreateOrder
              SpecialiseOrderTradeCondition
              FillOrder
              Tic
              CreateOrderLine
              ModifyOrderLineTaxed
              CreateOrderLine
              ModifyOrderLineDiscounted
              CreateOrderLine
              ModifyOrderLineDiscountedTaxed
              Tic
  """ + AGGREGATED_AMOUNT_LIST_CHECK_SEQUENCE_STRING

  def test_TradeConditionTradeModelLineCircularComposition(self):
    """
      If Trade Condition is specialised by another Trade Condition they
      Trade Model Lines shall be merged.
    """
    trade_condition_1 = self.createTradeCondition()
    trade_condition_2 = self.createTradeCondition()

    trade_condition_1.setSpecialiseValue(trade_condition_2)
    trade_condition_2.setSpecialiseValue(trade_condition_1)

    from Products.ERP5Type.Document.TradeCondition import CircularException
    self.assertRaises(
      CircularException,
      trade_condition_1.getTradeModelLineComposedList
    )

  def test_TradeConditionTradeModelLineBasicComposition(self):
    """
      If Trade Condition is specialised by another Trade Condition they
      Trade Model Lines shall be merged.
    """
    service_1 = self.createResource('Service')
    service_2 = self.createResource('Service')

    trade_condition_1 = self.createTradeCondition()
    trade_condition_2 = self.createTradeCondition()

    trade_condition_1.setSpecialiseValue(trade_condition_2)

    trade_condition_1_trade_model_line = self.createTradeModelLine(
        trade_condition_1,
        resource_value = service_1)

    trade_condition_2_trade_model_line = self.createTradeModelLine(
        trade_condition_2,
        resource_value = service_2)

    self.assertSameSet(
      [trade_condition_1_trade_model_line,
        trade_condition_2_trade_model_line],
      trade_condition_1.getTradeModelLineComposedList()
    )

  def test_TradeConditionTradeModelLineBasicCompositionWithOrder(self):
    """
      If Trade Condition is specialised by another Trade Condition they
      Trade Model Lines shall be merged.
    """
    service_1 = self.createResource('Service')
    service_2 = self.createResource('Service')
    service_3 = self.createResource('Service')

    trade_condition_1 = self.createTradeCondition()
    trade_condition_2 = self.createTradeCondition()
    order = self.createOrder()

    trade_condition_1.setSpecialiseValue(trade_condition_2)
    order.setSpecialiseValue(trade_condition_1)

    trade_condition_1_trade_model_line = self.createTradeModelLine(
        trade_condition_1,
        resource_value = service_1)

    trade_condition_2_trade_model_line = self.createTradeModelLine(
        trade_condition_2,
        resource_value = service_2)

    order_trade_model_line = self.createTradeModelLine(
        order,
        resource_value = service_3)

    self.assertSameSet(
      [trade_condition_1_trade_model_line, trade_condition_2_trade_model_line],
      trade_condition_1.getTradeModelLineComposedList()
    )

    self.assertSameSet(
      [trade_condition_1_trade_model_line, trade_condition_2_trade_model_line,
        order_trade_model_line],
      trade_condition_1.getTradeModelLineComposedList(context=order)
    )

  def test_TradeConditionTradeModelLineResourceIsShadowingCompositionWithOrder(self):
    """
      If Trade Condition is specialised by another Trade Condition they
      Trade Model Lines shall be merged.
    """
    service_1 = self.createResource('Service')
    service_2 = self.createResource('Service')

    trade_condition_1 = self.createTradeCondition()
    trade_condition_2 = self.createTradeCondition()
    order = self.createOrder()

    trade_condition_1.setSpecialiseValue(trade_condition_2)
    order.setSpecialiseValue(trade_condition_1)

    trade_condition_1_trade_model_line = self.createTradeModelLine(
        trade_condition_1,
        resource_value = service_1)

    trade_condition_2_trade_model_line = self.createTradeModelLine(
        trade_condition_2,
        resource_value = service_2)

    order_trade_model_line = self.createTradeModelLine(
        order,
        resource_value = service_2)

    self.assertSameSet(
      [trade_condition_1_trade_model_line,
        trade_condition_2_trade_model_line],
      trade_condition_1.getTradeModelLineComposedList()
    )

    self.assertSameSet(
      [trade_condition_1_trade_model_line, order_trade_model_line],
      trade_condition_1.getTradeModelLineComposedList(context=order)
    )

  def test_TradeConditionTradeModelLineResourceIsShadowingComposition(self):
    """
      If Trade Condition is specialised by another Trade Condition
      and resource is repeated, only first Trade Model Line shall be returned.
    """
    service = self.createResource('Service')

    trade_condition_1 = self.createTradeCondition()
    trade_condition_2 = self.createTradeCondition()

    trade_condition_1.setSpecialiseValue(trade_condition_2)

    trade_condition_1_trade_model_line = self.createTradeModelLine(
        trade_condition_1,
        resource_value = service)

    trade_condition_2_trade_model_line = self.createTradeModelLine(
        trade_condition_2,
        resource_value = service)

    self.assertSameSet(
      [trade_condition_1_trade_model_line],
      trade_condition_1.getTradeModelLineComposedList()
    )

  def test_getAggregatedAmountList(self):
    """
      Test for case, when discount contributes to tax, and order has mix of contributing lines
    """
    sequence_list = SequenceList()
    sequence_string = self.AGGREGATED_AMOUNT_LIST_COMMON_SEQUENCE_STRING

    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  ORDER_SPECIALISE_AGGREGATED_AMOUNT_COMMON_SEQUENCE_STRING = \
      COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING + """
              CreateBusinessProcess
              CreateBusinessState
              ModifyBusinessStateTaxed
              CreateBusinessState
              ModifyBusinessStateInvoiced
              CreateBusinessPath
              ModifyBusinessPathTaxing
              CreateBusinessPath
              ModifyBusinessPathDiscounting
              CreateTradeCondition
              SpecialiseTradeConditionWithBusinessProcess
              CreateTradeModelLine
              ModifyTradeModelLineTax
              Tic
              CreateOrder
              OrderCreateTradeModelLine
              ModifyTradeModelLineDiscount
              SpecialiseOrderTradeCondition
              FillOrder
              Tic
              CreateOrderLine
              ModifyOrderLineTaxed
              CreateOrderLine
              ModifyOrderLineDiscounted
              CreateOrderLine
              ModifyOrderLineDiscountedTaxed
              Tic
    """ + AGGREGATED_AMOUNT_LIST_CHECK_SEQUENCE_STRING

  def test_getAggregatedAmountListOrderSpecialise(self):
    """
      Test for case, when discount contributes to tax, and order has mix of contributing lines and order itself defines Trade Model Line
    """
    sequence_list = SequenceList()
    sequence_string = self\
        .ORDER_SPECIALISE_AGGREGATED_AMOUNT_COMMON_SEQUENCE_STRING

    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  def test_getAggregatedAmountList_afterUpdateAggregatedAmountList(self):
    """
      Test for case, when discount contributes to tax, and order has mix of contributing lines

      Check if it is stable if updateAggregatedAmountList was invoked.

      Note: This test assumes, that somethings contributes after update, shall
            be rewritten in a way, that adds explicitly movement which shall
            not be aggregated.
    """
    sequence_list = SequenceList()
    sequence_string = self.AGGREGATED_AMOUNT_LIST_COMMON_SEQUENCE_STRING + """
              UpdateAggregatedAmountListOnOrder
              Tic
    """ + self.AGGREGATED_AMOUNT_LIST_CHECK_SEQUENCE_STRING

    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING = """
              CheckOrderLineTaxedSimulation
              CheckOrderLineDiscountedSimulation
              CheckOrderLineDiscountedTaxedSimulation
  """
  TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING = \
      AGGREGATED_AMOUNT_LIST_COMMON_SEQUENCE_STRING + """
              Tic
              PlanOrder
              Tic
  """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING

  def test_TradeModelRuleSimulationExpand(self):
    """Tests tree of simulations from Trade Model Rule"""
    sequence_list = SequenceList()
    sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  def test_TradeModelRuleSimulationReexpand(self):
    """Tests tree of simulations from Trade Model Rule with reexpanding"""
    sequence_list = SequenceList()
    sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING + """
              ModifyAgainOrderLineTaxed
              ModifyAgainOrderLineDiscounted
              ModifyAgainOrderLineDiscountedTaxed
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  TRADE_MODEL_RULE_SIMULATION_ORDER_SPECIALISED_SEQUENCE_STRING = \
      ORDER_SPECIALISE_AGGREGATED_AMOUNT_COMMON_SEQUENCE_STRING + """
              Tic
              PlanOrder
              Tic
  """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING

  def test_TradeModelRuleSimulationExpandOrderSpecialise(self):
    """Tests tree of simulations from Trade Model Rule"""
    sequence_list = SequenceList()
    sequence_string = self \
        .TRADE_MODEL_RULE_SIMULATION_ORDER_SPECIALISED_SEQUENCE_STRING
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  def test_TradeModelRuleSimulationReexpandOrderSpecialise(self):
    """Tests tree of simulations from Trade Model Rule with reexpanding"""
    sequence_list = SequenceList()
    sequence_string = self \
        .TRADE_MODEL_RULE_SIMULATION_ORDER_SPECIALISED_SEQUENCE_STRING+ """
              ModifyAgainOrderLineTaxed
              ModifyAgainOrderLineDiscounted
              ModifyAgainOrderLineDiscountedTaxed
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  def test_TradeModelRuleSimulationWithoutBPM(self):
    """Tests tree of simulations from Trade Model Rule when there is no BPM"""
    sequence_list = SequenceList()
    sequence_string = self.COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING + """
              CreateTradeCondition
              CreateTradeModelLine
              ModifyTradeModelLineTax
              Tic
              CreateOrder
              SpecialiseOrderTradeCondition
              FillOrder
              Tic
              CreateOrderLine
              ModifyOrderLineTaxed
              Tic
              PlanOrder
              Tic
              CheckOrderTaxNoSimulation
    """
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  def test_TradeModelRuleSimulationWithoutTradeCondition(self):
    """Tests tree of simulations from Trade Model Rule when there is no Trade Condition"""
    sequence_list = SequenceList()
    sequence_string = self.COMMON_DOCUMENTS_CREATION_SEQUENCE_STRING + """
              CreateOrder
              FillOrder
              Tic
              CreateOrderLine
              ModifyOrderLineTaxed
              Tic
              PlanOrder
              Tic
              CheckOrderTaxNoSimulation
    """
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  def test_TradeModelRuleSimulationBuildInvoice(self):
    """Check that invoice lines on invoice are correctly set"""
    sequence_list = SequenceList()
    sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING
    sequence_string += """
              ConfirmOrder
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetPackingList
              PackPackingList
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              StartPackingList
              StopPackingList
              DeliverPackingList
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetInvoice
              CheckInvoiceCausalityStateSolved
              CheckInvoiceNormalMovements
    """
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  def test_TradeModelRuleSimulationBuildInvoiceNewTradeCondition(self):
    """Check that after changing trade condition invoice is not diverged"""
    sequence_list = SequenceList()
    sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING
    sequence_string += """
              ConfirmOrder
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetPackingList
              PackPackingList
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              StartPackingList
              StopPackingList
              DeliverPackingList
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetInvoice
              CheckInvoiceCausalityStateSolved
              CheckInvoiceNormalMovements

              SetTradeConditionOld

              CreateTradeCondition
              SpecialiseTradeConditionWithBusinessProcess
              CreateTradeModelLine
              ModifyTradeModelLineNewTax
              CreateTradeModelLine
              ModifyTradeModelLineNewDiscount
              Tic

              SpecialiseInvoiceTradeCondition
              Tic
              CheckInvoiceCausalityStateSolved
    """
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  def test_TradeModelRuleSimulationBuildInvoiceNewInvoiceLineSupport(self):
    """Check how is supported addition of invoice line to invoice build from order"""
    sequence_list = SequenceList()
    sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING
    sequence_string += """
              ConfirmOrder
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetPackingList
              PackPackingList
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              StartPackingList
              StopPackingList
              DeliverPackingList
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetInvoice
              CheckInvoiceCausalityStateSolved
              CheckInvoiceNormalMovements

              CreateInvoiceLine
              ModifyInvoiceLineDiscounted
              CreateInvoiceLine
              ModifyInvoiceLineDiscountedTaxed
              CreateInvoiceLine
              ModifyInvoiceLineTaxed

              Tic

              CheckInvoiceCausalityStateSolved

              StartInvoice
              Tic
              CheckInvoiceCausalityStateSolved
              CheckInvoiceNormalMovements
              CheckInvoiceAccountingMovements
              StopInvoice
              DeliverInvoice
              Tic
    """
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  def test_TradeModelRuleSimulationBuildInvoiceInvoiceLineModifyDivergencyAndSolving(self):
    """Check that after changing invoice line invoice is properly diverged and it is possible to solve"""
    sequence_list = SequenceList()
    sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING
    sequence_string += """
              ConfirmOrder
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetPackingList
              PackPackingList
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              StartPackingList
              StopPackingList
              DeliverPackingList
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetInvoice
              CheckInvoiceCausalityStateSolved
              CheckInvoiceNormalMovements

              GetInvoiceLineDiscounted
              GetInvoiceLineDiscountedTaxed
              GetInvoiceLineTaxed

              ModifyQuantityInvoiceLineDiscounted
              ModifyQuantityInvoiceLineDiscountedTaxed
              ModifyQuantityInvoiceLineTaxed
              Tic
              CheckInvoiceCausalityStateDiverged
              AcceptDecisionQuantityInvoice
              Tic
              CheckInvoiceCausalityStateSolved
              CheckInvoiceNormalMovements
    """
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  def test_TradeModelRuleSimulationBuildInvoiceBuildInvoiceTransactionLines(self):
    """Check that having properly configured invoice transaction rule it invoice transaction lines are nicely generated and have proper amounts"""
    sequence_list = SequenceList()
    sequence_string = self.TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING
    sequence_string += """
              ConfirmOrder
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetPackingList
              PackPackingList
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              StartPackingList
              StopPackingList
              DeliverPackingList
              Tic
    """ + self.AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetInvoice
              CheckInvoiceCausalityStateSolved
              CheckInvoiceNormalMovements

              StartInvoice
              Tic
              CheckInvoiceCausalityStateSolved
              CheckInvoiceNormalMovements
              CheckInvoiceAccountingMovements
              StopInvoice
              DeliverInvoice
              Tic
    """
    sequence_list.addSequenceString(sequence_string)
    sequence_list.play(self)

  PACKING_LIST_SPLIT_INVOICE_BUILD_SEQUENCE_STRING = \
      TRADE_MODEL_RULE_SIMULATION_SEQUENCE_STRING + """
              ConfirmOrder
              Tic
    """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetPackingList
              DecreasePackingListLineListQuantity
              Tic
    """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              CheckPackingListDiverged
              SplitAndDeferPackingList
              Tic
    """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetNewPackingList
              PackPackingList
              Tic
    """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              StartPackingList
              StopPackingList
              DeliverPackingList
              Tic
    """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetInvoice
              CheckInvoiceCausalityStateSolved
              CheckInvoiceNormalMovements

              SetNewPackingListAsPackingList
              PackPackingList
              Tic
              StartPackingList
    """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              StopPackingList
              DeliverPackingList
              Tic
    """ + AGGREGATED_AMOUNT_SIMULATION_CHECK_SEQUENCE_STRING + """
              GetInvoice
              CheckInvoiceCausalityStateSolved
              CheckInvoiceNormalMovements
    """

  def test_TradeModelRuleSimulationPackingListSplitBuildInvoiceBuildDifferentRatio(self):
    """Check building invoice after splitting packing list using different ratio"""
    self.modified_packing_list_line_quantity_ratio = 0.4
    sequence_list = SequenceList()
    sequence_list.addSequenceString(
        self.PACKING_LIST_SPLIT_INVOICE_BUILD_SEQUENCE_STRING)
    sequence_list.play(self)

  def test_TradeModelRuleSimulationPackingListSplitBuildInvoiceBuild(self):
    """Check building invoice after splitting packing list"""
    sequence_list = SequenceList()
    sequence_list.addSequenceString(
        self.PACKING_LIST_SPLIT_INVOICE_BUILD_SEQUENCE_STRING)
    sequence_list.play(self)

class TestBPMSale(TestBPMTestCases):
  invoice_portal_type = 'Sale Invoice Transaction'
  invoice_line_portal_type = 'Invoice Line'
  order_portal_type = 'Sale Order'
  order_line_portal_type = 'Sale Order Line'
  packing_list_portal_type = 'Sale Packing List'
  trade_condition_portal_type = 'Sale Trade Condition'
  trade_model_line_portal_type = 'Trade Model Line'


class TestBPMPurchase(TestBPMTestCases):
  invoice_portal_type = 'Purchase Invoice Transaction'
  invoice_line_portal_type = 'Invoice Line'
  order_portal_type = 'Purchase Order'
  order_line_portal_type = 'Purchase Order Line'
  packing_list_portal_type = 'Purchase Packing List'
  trade_condition_portal_type = 'Purchase Trade Condition'
  trade_model_line_portal_type = 'Trade Model Line'


class TestBPMImplementation(TestBPMMixin):
  """Business Process implementation tests"""
  def test_BusinessProcess_getPathValueList(self):
    business_process = self.createBusinessProcess()

    accounting_business_path = business_process.newContent(
        portal_type=self.business_path_portal_type,
        trade_phase='default/accounting')

    delivery_business_path = business_process.newContent(
        portal_type=self.business_path_portal_type,
        trade_phase='default/delivery')

    accounting_delivery_business_path = business_process.newContent(
        portal_type=self.business_path_portal_type,
        trade_phase=('default/accounting', 'default/delivery'))

    self.stepTic()

    self.assertSameSet(
      (accounting_business_path, accounting_delivery_business_path),
      business_process.getPathValueList(trade_phase='default/accounting')
    )

    self.assertSameSet(
      (delivery_business_path, accounting_delivery_business_path),
      business_process.getPathValueList(trade_phase='default/delivery')
    )

    # XXX: Luke: it is ORing not ANDing?
    self.assertSameSet(
      (accounting_delivery_business_path, delivery_business_path,
        accounting_business_path),
      business_process.getPathValueList(trade_phase=('default/delivery',
        'default/accounting'))
    )

  def test_BusinessPathStandardCategoryAccessProvider(self):
    node = self.portal.organisation_module.newContent(
                    portal_type='Organisation')
    business_path = self.createBusinessPath()
    business_path.setSourceValue(node)
    self.assertEquals(node, business_path.getSourceValue())
    self.assertEquals(node.getRelativeUrl(), business_path.getSource())
    self.assertEquals(node.getRelativeUrl(),
        business_path.getSource(default='something'))

  def test_BuinessPathDynamicCategoryAccessProvider(self):
    node = self.portal.organisation_module.newContent(
                    portal_type='Organisation')
    business_path = self.createBusinessPath()
    business_path.setSourceMethodId('BusinessPath_getDefaultSourceList')

    context_movement = self.createMovement()
    context_movement.setSourceValue(node)
    self.assertEquals(None, business_path.getSourceValue())
    self.assertEquals(node,
                      business_path.getSourceValue(context=context_movement))
    self.assertEquals(node.getRelativeUrl(),
                      business_path.getSource(context=context_movement))
    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))
  suite.addTest(unittest.makeSuite(TestBPMPurchase))
  suite.addTest(unittest.makeSuite(TestBPMImplementation))
  return suite