##############################################################################
#
# Copyright (c) 2004 Nexedi SARL and Contributors. All Rights Reserved.
#          Sebastien Robin <seb@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################



#
# Skeleton ZopeTestCase
#

#from random import randint

import os, sys
if __name__ == '__main__':
    execfile(os.path.join(sys.path[0], 'framework.py'))

# Needed in order to have a log file inside the current folder
import os
os.environ['EVENT_LOG_FILE'] = os.path.join(os.getcwd(), 'zLOG.log')
os.environ['EVENT_LOG_SEVERITY'] = '-300'

from Testing import ZopeTestCase
from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
from AccessControl.SecurityManagement import newSecurityManager
#from DateTime import DateTime
#from Acquisition import aq_base, aq_inner
from zLOG import LOG
#from Products.ERP5Type.DateUtils import addToDate
#import time
#from Products.ERP5Type import product_path
#from DateTime import DateTime

class TestAccountingRules(ERP5TypeTestCase):
  """
  This should test these functions :
  - in InvoiceRule.py :
    - expand
    - collectSimulationMovements

  - in InvoiceTransactionRule.py :
    - test
    - expand
    - newCellContent
    - updateMatrix
    - getCellByPredicate
  """

  # Different variables used for this test
  run_all_test = 1
  #source_company_id = 'Nexedi'
  #destination_company_id = 'Coramy'
  #component_id = 'brick'
  #sales_order_id = '1'
  #quantity = 10
  #base_price = 0.7832

  def getTitle(self):
    return "Accouting Rules"

  def getBusinessTemplateList(self):
    """
      Return the list of business templates.
    """
    return ('erp5_accounting','erp5_trade', 'erp5_pdm')

  def enableLightInstall(self):
    """
    You can override this. Return if we should do a light install (1) or not (0)
    """
    return 1

  def enableActivityTool(self):
    """
    You can override this. Return if we should create (1) or not (0) an activity tool
    """
    return 1

  def getActivityTool(self):
    return getattr(self.getPortal(), 'portal_activities', None)

  def getRuleTool(self):
    return getattr(self.getPortal(), 'portal_rules', None)

  def getWorkflowTool(self):
    return getattr(self.getPortal(), 'portal_workflow', None)

  def getAccountModule(self):
    return getattr(self.getPortal(), 'account', None)

  def getAccountingModule(self):
    return getattr(self.getPortal(), 'accounting', None)

  def getOrganisationModule(self):
    return getattr(self.getPortal(), 'organisation', None)

  def getProductModule(self):
    return getattr(self.getPortal(), 'product', None)

  def getCurrencyModule(self) :
    return getattr(self.getPortal(), 'currency', None)

  def login(self, quiet=0, run=run_all_test):
    uf = self.getPortal().acl_users
    uf._doAddUser('alex', '', ['Manager'], [])
    user = uf.getUserById('alex').__of__(uf)
    newSecurityManager(None, user)

  def afterSetUp(self, quiet=1, run=1):
    self.login()
    # Must add some accounts, accounting transactions, products, etc.
    account_module = self.getAccountModule()
    self.accounting_module = self.getAccountingModule()
    self.currency_module = self.getCurrencyModule()
    self.organisation_module = self.getOrganisationModule()
    product_module = self.getProductModule()
    self.activity_tool = self.getActivityTool()
    self.catalog_tool = self.getCatalogTool()
    self.category_tool = self.getCategoryTool()
    self.simulation_tool = self.getSimulationTool()
    self.workflow_tool = self.getWorkflowTool()
    self.portal = self.getPortal()
    # flush activities
    get_transaction().commit()
    self.tic()
    # When using light install, only base categories are created
    if len(self.category_tool.region.contentValues()) == 0 :
      self.category_tool.region.newContent(portal_type='Category', id='africa')
      o = self.category_tool.region.newContent(portal_type='Category', id='europe')
      o = o.newContent(portal_type='Category', id='west')
      o.newContent(portal_type='Category', id='france')

      self.category_tool.pcg.newContent(portal_type='Category', id='1')

      self.category_tool.product_line.newContent(portal_type='Category', id='erp5')
      o = self.category_tool.product_line.newContent(portal_type='Category', id='storever')
      o.newContent(portal_type='Category', id='barebone')
      o.newContent(portal_type='Category', id='notebook')
      o.newContent(portal_type='Category', id='openbrick')
    # If currency/EUR already exists, it means that the afterSetUp actions were already commited. Then, we just need to link to them.
    old_euro = getattr( self.currency_module, 'EUR', None)
    if old_euro is not None :
      self.invoice_transaction_rule = getattr(self.getRuleTool(), 'default_invoice_transaction_rule')
      self.predicate_product1 = getattr(self.invoice_transaction_rule, 'product_1')
      self.predicate_product2 = getattr(self.invoice_transaction_rule, 'product_2')
      self.predicate_region1 = getattr(self.invoice_transaction_rule, 'region_1')
      self.predicate_region2 = getattr(self.invoice_transaction_rule, 'region_2')
      self.invoice = getattr(self.accounting_module, 'invoice1')
      return
    # Create some currencies
    euro = self.currency_module.newContent(id='EUR', title='Euro', portal_type='Currency')
    # Create some accounts
    account_module.newContent(id='prestation_service', title='prestation_service', portal_type='Account')
    account_module.newContent(id='creance_client', title='creance_client', portal_type='Account')
    account_module.newContent(id='tva_collectee_196', title='tva_collectee_196', portal_type='Account')
    account_module.newContent(id='account1', title='Account1', portal_type='Account')
    account_module.newContent(id='account2', title='Account2', portal_type='Account')
    account_module.newContent(id='account3', title='Account3', portal_type='Account')
    account_module.newContent(id='account4', title='Account4', portal_type='Account')
    # Create some organisations
    self.organisation1 = self.organisation_module.newContent(id='nexedi', title='Nexedi', portal_type='Organisation')
    self.organisation1.newContent(id='default_address', portal_type='Address', region='europe/west/france')
    self.organisation2 = self.organisation_module.newContent(id='client1', title='Client1', portal_type='Organisation')
    self.organisation2.newContent(id='default_address', portal_type='Address', region='europe/west/france')
    # Create some products
    self.product1 = product_module.newContent(id='product1', title='Product1', product_line='storever/notebook', base_price=3.0)
    self.product2 = product_module.newContent(id='product2', title='Product2', product_line='storever/barebone', base_price=5.0)
    # Create some predicates in the Invoice Transaction Rule
    self.invoice_transaction_rule = getattr(self.getRuleTool(), 'default_invoice_transaction_rule')
    self.invoice_transaction_rule.deleteContent(self.invoice_transaction_rule.contentIds()) # delete anything inside the rule first

    self.predicate_product1 = self.invoice_transaction_rule.newContent(id='product_1', title='product_1', portal_type='Predicate Group', string_index='product', int_index='1', membership_criterion_base_category_list=['product_line',], membership_criterion_category_list=['product_line/storever/notebook'], immediate_reindex=1)
    self.predicate_product2 = self.invoice_transaction_rule.newContent(id='product_2', title='product_2', portal_type='Predicate Group', string_index='product', int_index='2', membership_criterion_base_category_list=['product_line',], membership_criterion_category_list=['product_line/storever/barebone'], immediate_reindex=1)
    self.predicate_region1 = self.invoice_transaction_rule.newContent(id='region_1', title='region_1', portal_type='Predicate Group', string_index='region', int_index='1', membership_criterion_base_category_list=['destination_region',], membership_criterion_category_list=['destination_region/region/europe/west/france'], immediate_reindex=1)
    self.predicate_region2 = self.invoice_transaction_rule.newContent(id='region_2', title='region_2', portal_type='Predicate Group', string_index='region', int_index='2', membership_criterion_base_category_list=['destination_region',], membership_criterion_category_list=['destination_region/region/africa'], immediate_reindex=1)
    # Create some invoices (now that there is nothing harmful inside the rule)
    self.invoice = self.accounting_module.newContent(id='invoice1', portal_type='Sale Invoice Transaction', destination='organisation/client1', destination_section='organisation/client1', resource='currency/EUR')
    invoice_line = self.invoice.newContent(id='1', portal_type='Invoice Line', resource='product/product1', quantity=7.0, price=11.0)
    # flush activities
    get_transaction().commit()
    self.tic()

  def updateInvoiceTransactionRuleMatrix(self) :

    base_id = 'vat_per_region'
    kwd = {'base_id': base_id}

    # update the matrix, generates the accounting rule cells
    self.invoice_transaction_rule.updateMatrix()

    # check the accounting rule cells inside the matrix
    cell_list = self.invoice_transaction_rule.contentValues(filter={'portal_type':'Accounting Rule Cell'})
    self.assertEqual(len(cell_list), 4)

    self.product1_region1_cell = getattr(self.invoice_transaction_rule, 'vat_per_region_0_0', None)
    self.product1_region2_cell = getattr(self.invoice_transaction_rule, 'vat_per_region_0_1', None)
    self.product2_region1_cell = getattr(self.invoice_transaction_rule, 'vat_per_region_1_0', None)
    self.product2_region2_cell = getattr(self.invoice_transaction_rule, 'vat_per_region_1_1', None)

    self.failUnless(self.product1_region1_cell != None)
    self.failUnless(self.product1_region2_cell != None)
    self.failUnless(self.product2_region1_cell != None)
    self.failUnless(self.product2_region2_cell != None)

    self.product1_region1_line1 = getattr(self.product1_region1_cell, 'income', None)
    self.failUnless(self.product1_region1_line1 != None)
    self.product1_region1_line1.edit(title='income', source='account/account1', destination='account/account2', quantity=19.0)
    # flush activities
    get_transaction().commit()
    self.tic()

  def test_01_HasEverything(self, quiet=0, run=run_all_test):
    if not run: return
    if not quiet:
      ZopeTestCase._print('\nTest Has Everything ')
      LOG('Testing... ',0,'testHasEverything')
    self.failUnless(self.getCategoryTool() != None)
    self.failUnless(self.getSimulationTool() != None)
    self.failUnless(self.getTypeTool() != None)
    self.failUnless(self.getSqlConnection() != None)
    self.failUnless(self.getCatalogTool() != None)
    self.failUnless(self.getRuleTool() != None)
    self.failUnless(self.getAccountModule() != None)
    self.failUnless(self.getAccountingModule() != None)
    self.failUnless(self.getOrganisationModule() != None)
    self.failUnless(self.getProductModule() != None)
    self.failUnless(self.getCurrencyModule() != None)

  def test_02_UpdateInvoiceTransactionRuleMatrix(self, quiet=0, run=run_all_test):
    """
    Try to update the matrix after adding some predicates, and check if all objects were created
    """
    if not run: return
    if not quiet:
      message = 'Test Update Invoice Transaction Rule Matrix'
      ZopeTestCase._print('\n%s ' % message)
      LOG('Testing... ',0,message)

    # before the tests, we need to be sure we have four predicates
    self.failUnless(self.predicate_product1 != None)
    self.failUnless(self.predicate_product2 != None)
    self.failUnless(self.predicate_region1 != None)
    self.failUnless(self.predicate_region2 != None)
    predicate_list = self.invoice_transaction_rule.contentValues(filter={'portal_type':'Predicate Group'})
    self.assertEqual(len(predicate_list), 4)

    # first, we check the matrix was initialized correctly (2x2 cells)
    self.updateInvoiceTransactionRuleMatrix()
    cell_list = self.invoice_transaction_rule.contentValues(filter={'portal_type':'Accounting Rule Cell'})
    self.assertEqual(len(cell_list), 4)

    # next, we add a predicate to see if it is still okay (3x2 cells)
    self.predicate_product3 = self.invoice_transaction_rule.newContent(id='product_3', title='product_3', portal_type='Predicate Group', string_index='product', int_index='3', membership_criterion_base_category_list=['product_line',], membership_criterion_category_list=['product_line/storever/openbrick'], immediate_reindex=1)
    self.invoice_transaction_rule.updateMatrix()
    cell_list = self.invoice_transaction_rule.contentValues(filter={'portal_type':'Accounting Rule Cell'})
    self.assertEqual(len(cell_list), 6)

    # then, we remove a predicate and check again (3x1 cells)
    self.invoice_transaction_rule.deleteContent(id='region_2')
    self.invoice_transaction_rule.updateMatrix()
    cell_list = self.invoice_transaction_rule.contentValues(filter={'portal_type':'Accounting Rule Cell'})
    self.assertEqual(len(cell_list), 3)

    # finally, we put back the matrix in the original format (2x2 cells)
    self.invoice_transaction_rule.deleteContent(id='product_3')
    self.predicate_region2 = self.invoice_transaction_rule.newContent(id='region_2', title='region_2', portal_type='Predicate Group', string_index='region', int_index='2', membership_criterion_base_category_list=['region',], membership_criterion_category_list=['region/africa'], immediate_reindex=1)
    self.invoice_transaction_rule.updateMatrix()
    cell_list = self.invoice_transaction_rule.contentValues(filter={'portal_type':'Accounting Rule Cell'})
    self.assertEqual(len(cell_list), 4)


  def test_03_invoiceTransactionRule_getMatchingCell(self, quiet=0, run=run_all_test):
    """
    test InvoiceTransactionRule.getCellByPredicate()
    """
    if not run: return
    if not quiet:
      message = 'Test Invoice Transaction Rule getCellByPredicate '
      ZopeTestCase._print('\n%s ' % message)
      LOG('Testing... ',0,message)
    # before the tests, we must update the matrix
    self.updateInvoiceTransactionRuleMatrix()
    from Products.ERP5Type.Document import newTempMovement    
    movement = newTempMovement(self.invoice, 'test1', resource = 'product/product1',
                                                      destination =  'organisation/nexedi')
    # Make sure acquisition is working for destination_region
    self.assertEqual(movement.getDestinationRegion(), 'region/europe/west/france')    
    # Make sure category is working for destination_region
    self.assertEqual(movement.getProductLine(), 'storever/notebook')    
    # Test cell on movement
    # correct cell
    self.assertEqual(self.product1_region1_cell, self.invoice_transaction_rule._getMatchingCell(movement))
    # no predicate for this category
    movement = newTempMovement(self.invoice, 'test2', resource = 'product/product2',
                                                      destination = 'organisation/nexedi')
    self.assertEqual(self.product2_region1_cell, self.invoice_transaction_rule._getMatchingCell(movement))
    # incorrect category
    movement = newTempMovement(self.invoice, 'test3', product = None, destination = 'organisation/nexedi')
    self.assertEqual(None, self.invoice_transaction_rule._getMatchingCell(movement))
    # incorrect dimension
    movement = newTempMovement(self.invoice, 'test4', pcg = 'pcg/1', destination = 'organisation/nexedi')
    self.assertEqual(None, self.invoice_transaction_rule._getMatchingCell(movement))

  def test_04_invoiceRule_expand(self, quiet=0, run=run_all_test):
    """
    Try to expand an invoice containing Invoice Lines
    """
    if not run: return
    if not quiet:
      message = 'Test Invoice Rule Expand '
      ZopeTestCase._print('\n%s ' % message)
      LOG('Testing... ',0,message)

    # before the tests, we must update the matrix
    self.updateInvoiceTransactionRuleMatrix()

    ####
    # TEST NO 1 : one Invoice Line (quantity * price) * tax == (7 * 11) * 19
    ####
    # the invoice is expanded by the invoice_edit_workflow when it is edited.
    self.invoice.edit(title='Invoice1')

    # flush activities
    get_transaction().commit()
    self.tic()    

    LOG('history', 0, repr(( self.workflow_tool.getHistoryOf('edit_workflow', self.invoice) )))

    # check every level of the simulation
    applied_rule_list = self.simulation_tool.contentValues() # list of Invoice Rules
    self.assertEqual(len(applied_rule_list), 1)

    applied_rule = applied_rule_list[0]
    self.assertEqual(applied_rule.getPortalType(), 'Applied Rule')
    self.assertEqual(applied_rule.getSpecialise(), 'portal_rules/default_invoice_rule')
    self.assertEqual(applied_rule.getCausality(), 'accounting/invoice1')

    movement_list = applied_rule.contentValues() # list of Invoice Lines
    self.assertEqual(len(movement_list), 1)

    # Make sure the movement acquires all properties
    movement = movement_list[0]
    self.assertEqual(movement.getId(), '1')
    self.assertEqual(movement.getPortalType(), 'Simulation Movement')
    self.assertEqual(movement.getDelivery(), 'accounting/invoice1/1')
    self.assertEqual(movement.getResource(), 'product/product1')
    self.assertEqual(movement.getDestination(), 'organisation/client1')
    self.assertEqual(movement.getDestinationSection(), 'organisation/client1')
    self.assertEqual(movement.getProductLine(), 'storever/notebook')
    self.assertEqual(movement.getDestinationRegion(), 'region/europe/west/france')

    # Make sure the invoice_transaction_rule applies to this movement
    self.assertEqual(self.invoice_transaction_rule.test(movement), True)
    # And make sure the first cell applies
    self.assertEqual(self.product1_region1_line1.test(movement), True)
    
    # Make sure expand succeeded
    sub_applied_rule_list = movement.contentValues() # list of Invoice Transaction Rules
    self.assertEqual(len(sub_applied_rule_list), 1)

    sub_applied_rule = sub_applied_rule_list[0]
    self.assertEqual(sub_applied_rule.getId(), 'default_invoice_transaction_rule')
    self.assertEqual(sub_applied_rule.getPortalType(), 'Applied Rule')
    self.assertEqual(sub_applied_rule.getSpecialise(), 'portal_rules/default_invoice_transaction_rule')

    sub_movement_list = sub_applied_rule.contentValues() # list of Sale Invoice Transaction Lines
    self.assertEqual(len(sub_movement_list), 3) # there should be 'income', 'receivable', 'collected_vat'

    for sub_movement in sub_movement_list :
      if sub_movement.getId() not in ('income', 'receivable', 'collected_vat',) :
        self.fail(msg='%s is not a normal Sale Invoice Transaction Line name' % sub_movement)
      self.assertEqual(movement.getPortalType(), 'Simulation Movement')
      if sub_movement.getId() == 'income' :
        self.assertEqual(sub_movement.getSource(), 'account/account1')
        self.assertEqual(sub_movement.getDestination(), 'account/account2')
        self.assertEqual(sub_movement.getQuantity(), (7.0 * 11.0) * 19.0)

    # check if invoice transaction lines are added and correct (outside simulation too)
    invoice_transaction_line = getattr(self.invoice, 'income', None)
    self.failIf(invoice_transaction_line is None)
    self.assertEqual(invoice_transaction_line.getPortalType(), 'Sale Invoice Transaction Line')
    self.assertEqual(invoice_transaction_line.getSource(), 'account/account1')
    self.assertEqual(invoice_transaction_line.getDestination(), 'account/account2')
    self.assertEqual(invoice_transaction_line.getQuantity(), (7.0 * 11.0) * 19.0)

    ####
    # TEST NO 2 : one Invoice Line (quantity * price) * tax == (7 * 11) * 19
    # expand once again and check that everithing is still the same
    ####
    # the invoice is expanded by the invoice_edit_workflow when it is edited.
    self.invoice.edit(title='Invoice1')

    # flush activities
    get_transaction().commit()
    self.tic()

    # check every level of the simulation
    applied_rule_list = self.simulation_tool.contentValues() # list of Invoice Rules
    self.assertEqual(len(applied_rule_list), 1)

    applied_rule = applied_rule_list[0]
    self.assertEqual(applied_rule.getPortalType(), 'Applied Rule')
    self.assertEqual(applied_rule.getSpecialise(), 'portal_rules/default_invoice_rule')
    self.assertEqual(applied_rule.getCausality(), 'accounting/invoice1')

    movement_list = applied_rule.contentValues() # list of Invoice Lines
    self.assertEqual(len(movement_list), 1)

    movement = movement_list[0]
    self.assertEqual(movement.getId(), '1')
    self.assertEqual(movement.getPortalType(), 'Simulation Movement')
    self.assertEqual(movement.getDelivery(), 'accounting/invoice1/1')

    sub_applied_rule_list = movement.contentValues() # list of Invoice Transaction Rules
    self.assertEqual(len(sub_applied_rule_list), 1)

    sub_applied_rule = sub_applied_rule_list[0]
    self.assertEqual(sub_applied_rule.getId(), 'default_invoice_transaction_rule')
    self.assertEqual(sub_applied_rule.getPortalType(), 'Applied Rule')
    self.assertEqual(sub_applied_rule.getSpecialise(), 'portal_rules/default_invoice_transaction_rule')

    sub_movement_list = sub_applied_rule.contentValues() # list of Sale Invoice Transaction Lines
    self.assertEqual(len(sub_movement_list), 3) # there should be 'income', 'receivable', 'collected_vat'

    for sub_movement in sub_movement_list :
      if sub_movement.getId() not in ('income', 'receivable', 'collected_vat',) :
        self.fail(msg='%s is not a normal Sale Invoice Transaction Line name' % sub_movement)
      self.assertEqual(movement.getPortalType(), 'Simulation Movement')
      if sub_movement.getId() == 'income' :
        self.assertEqual(sub_movement.getSource(), 'account/account1')
        self.assertEqual(sub_movement.getDestination(), 'account/account2')
        self.assertEqual(sub_movement.getQuantity(), (7.0 * 11.0) * 19.0)

    # check if invoice transaction lines are added and correct (outside simulation too)
    #self.invoice.buildInvoiceTransactionList()
    invoice_transaction_line = getattr(self.invoice, 'income', None)
    self.failIf(invoice_transaction_line is None)
    self.assertEqual(invoice_transaction_line.getPortalType(), 'Sale Invoice Transaction Line')
    self.assertEqual(invoice_transaction_line.getSource(), 'account/account1')
    self.assertEqual(invoice_transaction_line.getDestination(), 'account/account2')
    self.assertEqual(invoice_transaction_line.getQuantity(), (7.0 * 11.0) * 19.0)

    ####
    # TEST NO 3 : two Invoice Lines (quantity * price) * tax == (7 * 11) * 19 + (13 * 17) * 19
    # add a line with same product_line and test again
    ####
    invoice_line2 = self.invoice.newContent(id='2', portal_type='Invoice Line', resource='product/product1', quantity=13.0, price=17.0)
    # the invoice is expanded by the invoice_edit_workflow when it is edited.
    self.invoice.edit(title='Invoice1')

    # flush activities
    get_transaction().commit()
    self.tic()

    # check every level of the simulation
    applied_rule_list = self.simulation_tool.contentValues() # list of Invoice Rules
    self.assertEqual(len(applied_rule_list), 1)

    applied_rule = applied_rule_list[0]
    self.assertEqual(applied_rule.getPortalType(), 'Applied Rule')
    self.assertEqual(applied_rule.getSpecialise(), 'portal_rules/default_invoice_rule')
    self.assertEqual(applied_rule.getCausality(), 'accounting/invoice1')

    movement_list = applied_rule.contentValues() # list of Invoice Lines
    self.assertEqual(len(movement_list), 2)

    for movement in movement_list :
      movement_id = movement.getId()
      if movement_id not in ('1', '2',) :
        self.fail(msg='%s is not a normal Invoice Line name' % sub_movement)
      self.assertEqual(movement.getPortalType(), 'Simulation Movement')
      self.assertEqual(movement.getDelivery(), 'accounting/invoice1/%s' % movement_id)

      sub_applied_rule_list = movement.contentValues() # list of Invoice Transaction Rules
      self.assertEqual(len(sub_applied_rule_list), 1)

      sub_applied_rule = sub_applied_rule_list[0]
      self.assertEqual(sub_applied_rule.getId(), 'default_invoice_transaction_rule')
      self.assertEqual(sub_applied_rule.getPortalType(), 'Applied Rule')
      self.assertEqual(sub_applied_rule.getSpecialise(), 'portal_rules/default_invoice_transaction_rule')

      sub_movement_list = sub_applied_rule.contentValues() # list of Sale Invoice Transaction Lines
      self.assertEqual(len(sub_movement_list), 3) # there should be 'income', 'receivable', 'collected_vat'

      for sub_movement in sub_movement_list :
        if sub_movement.getId() not in ('income', 'receivable', 'collected_vat',) :
          self.fail(msg='%s is not a normal Sale Invoice Transaction Line name' % sub_movement)
        self.assertEqual(movement.getPortalType(), 'Simulation Movement')
        if sub_movement.getId() == 'income' :
          self.assertEqual(sub_movement.getSource(), 'account/account1')
          self.assertEqual(sub_movement.getDestination(), 'account/account2')
          if movement_id == '1' :
            self.assertEqual(sub_movement.getQuantity(), (7.0 * 11.0) * 19.0)

          elif movement_id == '2' :
            self.assertEqual(sub_movement.getQuantity(), (13.0 * 17.0) * 19.0)

    # check if invoice transaction lines are added and correct (outside simulation too)
    invoice_transaction_line = getattr(self.invoice, 'income', None)
    self.failIf(invoice_transaction_line is None)
    self.assertEqual(invoice_transaction_line.getPortalType(), 'Sale Invoice Transaction Line')
    self.assertEqual(invoice_transaction_line.getSource(), 'account/account1')
    self.assertEqual(invoice_transaction_line.getDestination(), 'account/account2')
    self.assertEqual(invoice_transaction_line.getQuantity(), (7.0 * 11.0 + 13.0 * 17.0) * 19.0)

    ####
    # TEST NO 4 : three Invoice Lines (quantity * price) * tax == (7 * 11) * 19 + (13 * 17) * 19 + (23 * 29) * 31
    ####
    # add a line with different product_line and test again (we first need a line for this one)
    self.product2_region1_line1 = getattr(self.product2_region1_cell, 'income', None)
    self.failUnless(self.product2_region1_line1 != None)
    self.product2_region1_line1.edit(title='income', source='account/account3', destination='account/account4', quantity=31.0)

    invoice_line3 = self.invoice.newContent(id='3', portal_type='Invoice Line', resource='product/product2', quantity=23.0, price=29.0)
    # the invoice is expanded by the invoice_edit_workflow when it is edited.
    self.invoice.edit(title='Invoice1')

    # flush activities
    get_transaction().commit()
    self.tic()

    # check every level of the simulation
    applied_rule_list = self.simulation_tool.contentValues() # list of Invoice Rules
    self.assertEqual(len(applied_rule_list), 1)

    applied_rule = applied_rule_list[0]
    self.assertEqual(applied_rule.getPortalType(), 'Applied Rule')
    self.assertEqual(applied_rule.getSpecialise(), 'portal_rules/default_invoice_rule')
    self.assertEqual(applied_rule.getCausality(), 'accounting/invoice1')

    movement_list = applied_rule.contentValues() # list of Invoice Lines
    self.assertEqual(len(movement_list), 3)

    for movement in movement_list :
      movement_id = movement.getId()
      if movement_id not in ('1', '2', '3',) :
        self.fail(msg='%s is not a normal Invoice Line name' % sub_movement)
      self.assertEqual(movement.getPortalType(), 'Simulation Movement')
      self.assertEqual(movement.getDelivery(), 'accounting/invoice1/%s' % movement_id)

      sub_applied_rule_list = movement.contentValues() # list of Invoice Transaction Rules
      self.assertEqual(len(sub_applied_rule_list), 1)

      sub_applied_rule = sub_applied_rule_list[0]
      self.assertEqual(sub_applied_rule.getId(), 'default_invoice_transaction_rule')
      self.assertEqual(sub_applied_rule.getPortalType(), 'Applied Rule')
      self.assertEqual(sub_applied_rule.getSpecialise(), 'portal_rules/default_invoice_transaction_rule')

      sub_movement_list = sub_applied_rule.contentValues() # list of Sale Invoice Transaction Lines
      self.assertEqual(len(sub_movement_list), 3) # there should be 'income', 'receivable', 'collected_vat'

      for sub_movement in sub_movement_list :
        if sub_movement.getId() not in ('income', 'receivable', 'collected_vat',) :
          self.fail(msg='%s is not a normal Sale Invoice Transaction Line name' % sub_movement)
        self.assertEqual(movement.getPortalType(), 'Simulation Movement')
        if sub_movement.getId() == 'income' :
          if movement_id == '1' :
            self.assertEqual(sub_movement.getSource(), 'account/account1')
            self.assertEqual(sub_movement.getDestination(), 'account/account2')
            self.assertEqual(sub_movement.getQuantity(), (7.0 * 11.0) * 19.0)
          elif movement_id == '2' :
            self.assertEqual(sub_movement.getSource(), 'account/account1')
            self.assertEqual(sub_movement.getDestination(), 'account/account2')
            self.assertEqual(sub_movement.getQuantity(), (13.0 * 17.0) * 19.0)
          elif movement_id == '3' :
            self.assertEqual(sub_movement.getSource(), 'account/account3')
            self.assertEqual(sub_movement.getDestination(), 'account/account4')
            self.assertEqual(sub_movement.getQuantity(), (23.0 * 29.0) * 31.0)            

    # check if invoice transaction lines are added and correct (outside simulation too)
    invoice_transaction_line = getattr(self.invoice, 'income', None)
    if invoice_transaction_line.getSource() != 'account/account1':
      other_invoice_transaction_line = invoice_transaction_line
      invoice_transaction_line = getattr(self.invoice, 'income_1', None)
    else:
      other_invoice_transaction_line = getattr(self.invoice, 'income_1', None)
    self.failIf(invoice_transaction_line is None)
    self.assertEqual(invoice_transaction_line.getPortalType(), 'Sale Invoice Transaction Line')
    self.assertEqual(invoice_transaction_line.getSource(), 'account/account1')
    self.assertEqual(invoice_transaction_line.getDestination(), 'account/account2')
    self.assertEqual(invoice_transaction_line.getQuantity(), (7.0 * 11.0 + 13.0 * 17.0) * 19.0)

    invoice_transaction_line = other_invoice_transaction_line
    self.failIf(invoice_transaction_line is None)
    self.assertEqual(invoice_transaction_line.getPortalType(), 'Sale Invoice Transaction Line')
    self.assertEqual(invoice_transaction_line.getSource(), 'account/account3')
    self.assertEqual(invoice_transaction_line.getDestination(), 'account/account4')
    self.assertEqual(invoice_transaction_line.getQuantity(), (23.0 * 29.0) * 31.0)

if __name__ == '__main__':
    framework()
else:
    import unittest
    def test_suite():
        suite = unittest.TestSuite()
        suite.addTest(unittest.makeSuite(TestAccountingRules))
        return suite
