diff --git a/product/ERP5/Document/DeliveryRule.py b/product/ERP5/Document/DeliveryRule.py
index 156c2bdb130c5767328a360e265acf010b316dbf..6fb118b6b8d0fd12a4224d01d1b880c82b21c9e6 100644
--- a/product/ERP5/Document/DeliveryRule.py
+++ b/product/ERP5/Document/DeliveryRule.py
@@ -132,6 +132,8 @@ class DeliveryRule(Rule):
               quantity_unit=deliv_mvt.getQuantityUnit(),
               price=deliv_mvt.getPrice(),
               price_currency=deliv_mvt.getPriceCurrency(),
+              base_contribution_list=deliv_mvt.getBaseContributionList(),
+              base_application_list=deliv_mvt.getBaseApplicationList(),
           )
         elif sim_mvt in existing_movement_list:
           if sim_mvt not in immutable_movement_list:
@@ -158,6 +160,8 @@ class DeliveryRule(Rule):
                 quantity_unit=deliv_mvt.getQuantityUnit(),
                 price=deliv_mvt.getPrice(),
                 price_currency=deliv_mvt.getPriceCurrency(),
+                base_contribution_list=deliv_mvt.getBaseContributionList(),
+                base_application_list=deliv_mvt.getBaseApplicationList(),
                 force_update=1)
           else:
             # modification disallowed, must compensate
diff --git a/product/ERP5/Document/InvoicingRule.py b/product/ERP5/Document/InvoicingRule.py
index e01d1608117d4f11f224774f5c9c6c3c0039d2e5..71ac4125924528e94a4249574135bb4feb65fba2 100644
--- a/product/ERP5/Document/InvoicingRule.py
+++ b/product/ERP5/Document/InvoicingRule.py
@@ -97,6 +97,7 @@ class InvoicingRule(Rule):
         'resource': context_movement.getResource(),
         'variation_category_list': context_movement.getVariationCategoryList(),
         'variation_property_dict': context_movement.getVariationPropertyDict(),
+        'base_contribution_list': context_movement.getBaseContributionList(),
         'aggregate_list': context_movement.getAggregateList(),
         'quantity': context_movement.getCorrectedQuantity(),
         'quantity_unit': context_movement.getQuantityUnit(),
diff --git a/product/ERP5/Document/OrderRule.py b/product/ERP5/Document/OrderRule.py
index 4da3e492c7da5810580100d5384abaec9e9e693c..da8ae1e7cac1ae8725235e437ddb5467ee8ec833 100644
--- a/product/ERP5/Document/OrderRule.py
+++ b/product/ERP5/Document/OrderRule.py
@@ -106,6 +106,9 @@ class OrderRule(DeliveryRule):
           order_movement_dict[order_movement.getPath()] = s_m
       # Create or modify movements
       for movement in order_movement_list:
+        # FIXME: to be improved later
+        if  movement.getPortalType() not in ('Tax Line', ):
+          continue
         related_order = order_movement_dict.get(movement.getPath(), None)
         if related_order is None:
           related_order = movement.getOrderRelatedValue()
@@ -181,6 +184,7 @@ class OrderRule(DeliveryRule):
         'resource',
         'variation_category_list',
         'variation_property_dict',
+        'base_contribution_list',
         'aggregate_list',
         'price',
         'price_currency',
diff --git a/product/ERP5/Document/TaxLine.py b/product/ERP5/Document/TaxLine.py
new file mode 100644
index 0000000000000000000000000000000000000000..616c3b7d6a232ea99dd631f98ce674a56fbc007e
--- /dev/null
+++ b/product/ERP5/Document/TaxLine.py
@@ -0,0 +1,83 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
+#                    Jerome Perrin <jerome@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.
+#
+##############################################################################
+
+from AccessControl import ClassSecurityInfo
+
+from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
+
+from Products.ERP5.Document.DeliveryLine import DeliveryLine
+
+
+class TaxLine(DeliveryLine):
+    """ Tax Line
+    """
+    meta_type = 'ERP5 Tax Line'
+    portal_type = 'Tax Line'
+
+    # Declarative security
+    security = ClassSecurityInfo()
+    security.declareObjectProtected(Permissions.AccessContentsInformation)
+
+    # Declarative interfaces
+    __implements__ = ( Interface.Variated, )
+
+    # Declarative properties
+    property_sheets = ( PropertySheet.Base
+                      , PropertySheet.XMLObject
+                      , PropertySheet.CategoryCore
+                      , PropertySheet.Amount
+                      , PropertySheet.Task
+                      , PropertySheet.Arrow
+                      , PropertySheet.Movement
+                      , PropertySheet.Price
+                      , PropertySheet.VariationRange
+                      , PropertySheet.ItemAggregation
+                      , PropertySheet.Reference
+                      , PropertySheet.SortIndex
+                      )
+
+    security.declareProtected(Permissions.AccessContentsInformation,
+                              'isAccountable')
+    def isAccountable(self):
+      """ """
+      return 1 # XXX not sure
+
+    security.declareProtected(Permissions.AccessContentsInformation,
+                              'hasCellContent')
+    def hasCellContent(self, base_id='movement'):
+      """Tax line does not contain cell
+      """
+      return 0
+
+    security.declareProtected(Permissions.AccessContentsInformation,
+                              'isMovement' )
+    def isMovement(self):
+      """Tax lines are movements
+      """
+      return 1
+
diff --git a/product/ERP5/Document/TaxRule.py b/product/ERP5/Document/TaxRule.py
new file mode 100644
index 0000000000000000000000000000000000000000..48b01d256f73afccc9d2014f1d6b0067c72cfbed
--- /dev/null
+++ b/product/ERP5/Document/TaxRule.py
@@ -0,0 +1,107 @@
+##############################################################################
+#
+# Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
+#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
+#                    Romain Courteaud <romain@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.
+#
+##############################################################################
+
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
+from Products.ERP5.Document.Rule import Rule
+from Products.ERP5.Document.DeliveryRule import DeliveryRule
+
+class TaxRule(DeliveryRule):
+  """
+  """
+  # CMF Type Definition
+  meta_type = 'ERP5 Tax Rule'
+  portal_type = 'Tax Rule'
+
+  # Declarative security
+  security = ClassSecurityInfo()
+  security.declareObjectProtected(Permissions.AccessContentsInformation)
+  
+  __implements__ = ( Interface.Predicate,
+                     Interface.Rule )
+
+  # Default Properties
+  property_sheets = ( PropertySheet.Base
+                    , PropertySheet.XMLObject
+                    , PropertySheet.CategoryCore
+                    , PropertySheet.DublinCore
+                    , PropertySheet.Task
+                    )
+
+  security.declareProtected(Permissions.ModifyPortalContent, 'expand')
+  def expand(self, applied_rule, force=0, **kw):
+    """ """ 
+    movement_type = 'Simulation Movement'
+    immutable_movement_list = []
+
+    parent_simulation_movement = applied_rule.getParentValue()
+    order_movement = parent_simulation_movement.getDefaultOrderValue()
+    
+    order_movement_dict = {}
+    for s_m in applied_rule.objectValues():
+      order_movement_dict.setdefault(s_m.getOrder(), []).append(s_m)
+
+    order_movement_total_price = order_movement.getTotalPrice()
+    parent_simulation_movement_total_price = \
+                    parent_simulation_movement.getTotalPrice()
+
+    # XXX round 
+    if order_movement_total_price != 0 and \
+        parent_simulation_movement_total_price != 0:
+                      
+      ratio = parent_simulation_movement_total_price / \
+                           order_movement_total_price
+      for tax_movement in order_movement\
+                        .DeliveryMovement_getCorrespondingTaxLineList():
+        existing_simulation_movement_list = order_movement_dict.get(
+                                tax_movement.getRelativeUrl(), [])
+
+        property_dict = dict()
+        for prop in ('price', 'base_application_list',
+                     'base_contribution_list', 'resource'):
+          property_dict[prop] = tax_movement.getProperty(prop)
+
+        property_dict['quantity'] = tax_movement.getQuantity() * ratio
+
+        if not existing_simulation_movement_list:
+          applied_rule.newContent(
+                portal_type=movement_type,
+                order_value=tax_movement,
+                order_ratio=1,
+                delivery_ratio=1,
+                deliverable=1,
+                **property_dict )
+        else:
+          for existing_simulation_movement in \
+                existing_simulation_movement_list:
+            existing_simulation_movement.edit(**property_dict)
+
+    # Pass to base class
+    Rule.expand(self, applied_rule, force=force, **kw)
+
diff --git a/product/ERP5/Document/TradeCondition.py b/product/ERP5/Document/TradeCondition.py
index 7ab8a9a507aa5a091671f8371fc4c97176d045bc..df037f9d6c3f5856c9aaf31a60d8b19dd0e9957f 100644
--- a/product/ERP5/Document/TradeCondition.py
+++ b/product/ERP5/Document/TradeCondition.py
@@ -32,8 +32,9 @@ from AccessControl import ClassSecurityInfo
 
 from Products.ERP5Type import Permissions, PropertySheet, Constraint, Interface
 from Products.ERP5.Document.Path import Path
+from Products.ERP5.Document.Delivery import Delivery
 
-class TradeCondition(Path):
+class TradeCondition(Delivery, Path):
     """
       Trade Conditions are used to store the conditions (payment, logistic,...)
       which should be applied (and used in the orders) when two companies make
diff --git a/product/ERP5/ERP5Defaults.py b/product/ERP5/ERP5Defaults.py
index 56b96d0bfd4c47d68eaa06a259a62d539b796017..ba78ec8688eec979a0eb412cb3c5a1f57f28dc9f 100644
--- a/product/ERP5/ERP5Defaults.py
+++ b/product/ERP5/ERP5Defaults.py
@@ -92,6 +92,9 @@ portal_invoice_movement_type_list = (
                       'Pay Sheet Cell',
                       )
 
+portal_tax_movement_type_list = ( 'Tax Line', 'Discount Line',
+                                  'Pay Sheet Line', )
+
 portal_order_movement_type_list = (
                       'Purchase Order Line',
                       'Purchase Order Cell',
diff --git a/product/ERP5/ERP5Site.py b/product/ERP5/ERP5Site.py
index 922c59152a1b52238b15a879d601591293643d80..2ee14682498a6bec932728601b07ad99f3b3a4e7 100644
--- a/product/ERP5/ERP5Site.py
+++ b/product/ERP5/ERP5Site.py
@@ -609,6 +609,16 @@ class ERP5Site(FolderMixIn, CMFSite):
     return self._getPortalGroupedTypeList('invoice_movement') or \
            self._getPortalConfiguration('portal_invoice_movement_type_list')
 
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getPortalTaxMovementTypeList')
+  def getPortalTaxMovementTypeList(self):
+    """
+      Return tax movement types.
+    """
+    return self._getPortalGroupedTypeList('tax_movement') or \
+           self._getPortalConfiguration('portal_tax_movement_type_list')
+
+
   security.declareProtected(Permissions.AccessContentsInformation,
                             'getPortalOrderMovementTypeList')
   def getPortalOrderMovementTypeList(self):
diff --git a/product/ERP5/MovementGroup.py b/product/ERP5/MovementGroup.py
index d7d4b4f6e038054125be8398b0512a942ed7b5a1..cb84c0240fe0498ab71936d72a05666d2fd6c757 100644
--- a/product/ERP5/MovementGroup.py
+++ b/product/ERP5/MovementGroup.py
@@ -1177,6 +1177,18 @@ class RequirementMovementGroup(RootMovementGroup):
   def test(self,movement):
     return self.getRequirementList(movement) == self.requirement_list
 
+class BaseContributionMovementGroup(PropertyMovementGroup):
+  """ Group movements that have the same base contributions."""
+  _property = 'base_contribution_list'
+
+class BaseApplicationMovementGroup(PropertyMovementGroup):
+  """ Group movements that have the same base applications."""
+  _property = 'base_application_list'
+
+class PriceCurrencyMovementGroup(PropertyMovementGroup):
+  """ Group movements that have the same price currency."""
+  _property = 'price_currency'
+
 class QuantityUnitMovementGroup(PropertyMovementGroup):
   """ Group movements that have the same quantity unit."""
   _property = 'quantity_unit'
diff --git a/product/ERP5/PropertySheet/Amount.py b/product/ERP5/PropertySheet/Amount.py
index 08ec38400785f6d373f3e87706a127505b959de0..d6ffd4ca11f0cf228728727c173fc04645105bd5 100644
--- a/product/ERP5/PropertySheet/Amount.py
+++ b/product/ERP5/PropertySheet/Amount.py
@@ -126,6 +126,7 @@ class Amount:
  )
 
   _categories = ('resource', 'quantity_unit',
+                 'base_application', 'base_contribution',
                  # Acquired categories
                  'product_line', )
   
diff --git a/product/ERP5/PropertySheet/Resource.py b/product/ERP5/PropertySheet/Resource.py
index c921e2cefb3c4333f9faed1bee1415cf60164d36..0c032e7b38e9e0a1efb01730b2ffe0b8d9bf727b 100644
--- a/product/ERP5/PropertySheet/Resource.py
+++ b/product/ERP5/PropertySheet/Resource.py
@@ -182,6 +182,7 @@ class Resource:
     _categories = ( 'source', 'destination', 'quantity_unit', 'price_unit',
                     'weight_unit', 'length_unit', 'height_unit', 'width_unit',
                     'volume_unit',
+                    'base_contribution',
                     'price_currency',  'source_price_currency',
                     'destination_price_currency', 'product_line',
                     'industrial_phase')
diff --git a/product/ERP5/tests/testTradeCondition.py b/product/ERP5/tests/testTradeCondition.py
new file mode 100644
index 0000000000000000000000000000000000000000..96d4c32766eb49d4ec87a184c88967fe68acba0e
--- /dev/null
+++ b/product/ERP5/tests/testTradeCondition.py
@@ -0,0 +1,1290 @@
+##############################################################################
+#
+# Copyright (c) 2008 Nexedi SA and Contributors. All Rights Reserved.
+#          Jerome Perrin <jerome@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.
+#
+##############################################################################
+
+import unittest
+
+from DateTime import DateTime
+
+from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
+
+try:
+  from transaction import get as get_transaction
+except ImportError:
+  pass
+
+class TradeConditionTestCase(ERP5TypeTestCase):
+  """Tests for Trade Conditions and Tax
+  """
+  def getBusinessTemplateList(self):
+    return ('erp5_base', 'erp5_pdm', 'erp5_trade', 'erp5_accounting', 'erp5_invoicing')
+
+  def setUp(self):
+    ERP5TypeTestCase.setUp(self)
+    for rule in self.portal.portal_rules.contentValues():
+      if rule.getPortalType() != 'Payment Rule':
+        rule.validate() # XXX this should be enabled !
+    self.base_amount = self.portal.portal_categories.base_amount
+    self.tax = self.portal.tax_module.newContent(
+                                    portal_type='Tax',
+                                    title='Tax')
+    self.client = self.portal.organisation_module.newContent(
+                                    portal_type='Organisation',
+                                    title='Client')
+    self.vendor = self.portal.organisation_module.newContent(
+                                    portal_type='Organisation',
+                                    title='Vendor')
+    self.resource = self.portal.product_module.newContent(
+                                    portal_type='Product',
+                                    title='Resource')
+    self.currency = self.portal.currency_module.newContent(
+                                    portal_type='Currency',
+                                    title='Currency')
+    self.trade_condition_module = self.portal.getDefaultModule(
+                                      self.trade_condition_type)
+    self.trade_condition = self.trade_condition_module.newContent(
+                            portal_type=self.trade_condition_type,
+                            title='Trade Condition')
+    self.order_module = self.portal.getDefaultModule(
+                                      self.order_type)
+    self.order = self.order_module.newContent(
+                            portal_type=self.order_type,
+                            created_by_builder=1,
+                            title='Order')
+
+  def tearDown(self):
+    get_transaction().abort()
+    for module in (self.portal.tax_module,
+                   self.portal.organisation_module,
+                   self.portal.currency_module,
+                   self.portal.product_module,
+                   self.portal.accounting_module,
+                   self.portal.account_module,
+                   self.portal.portal_simulation,
+                   self.trade_condition_module,
+                   self.order_module,
+                   self.portal.portal_categories.base_amount,
+                   self.portal.portal_categories.product_line):
+      module.manage_delObjects(list(module.objectIds()))
+    if 'test_invoice_transaction_rule' in self.portal.portal_rules.objectIds():
+      self.portal.portal_rules.manage_delObjects('test_invoice_transaction_rule')
+    get_transaction().commit()
+    self.tic()
+    ERP5TypeTestCase.tearDown(self)
+
+
+class AccountingBuildTestCase(TradeConditionTestCase):
+  """Same as TradeConditionTestCase, but with a rule to generate
+  accounting.
+  """
+  def setUp(self):
+    TradeConditionTestCase.setUp(self)
+    self.receivable_account = self.portal.account_module.newContent(
+                                    id='receivable',
+                                    title='Receivable',
+                                    account_type='asset/receivable')
+    self.payable_account = self.portal.account_module.newContent(
+                                    id='payable',
+                                    title='Payable',
+                                    account_type='liability/payable')
+    self.income_account = self.portal.account_module.newContent(
+                                    id='income',
+                                    title='Income',
+                                    account_type='income')
+    self.expense_account = self.portal.account_module.newContent(
+                                    id='expense',
+                                    title='Expense',
+                                    account_type='expense')
+    self.collected_tax_account = self.portal.account_module.newContent(
+                                    id='collected_tax',
+                                    title='Collected Tax',
+                                    account_type='liability/payable/collected_vat')
+    self.refundable_tax_account = self.portal.account_module.newContent(
+                                    id='refundable_tax',
+                                    title='Refundable Tax',
+                                    account_type='asset/receivable/refundable_vat')
+
+    for account in self.portal.account_module.contentValues():
+      self.assertNotEquals(account.getAccountTypeValue(), None)
+      account.validate()
+
+    dummy_resource = self.portal.portal_categories.product_line.newContent(
+                                    id='dummy_resource',
+                                    title='Dummy Resource')
+    self.resource.setProductLineValue(dummy_resource)
+    dummy_tax = self.portal.portal_categories.product_line.newContent(
+                                    id='dummy_tax',
+                                    title='Dummy Tax')
+    # FIXME: tax should not have a product line
+    self.tax.setProductLineValue(dummy_tax)
+    
+    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='resource_type',
+            title='Resource Product',
+            int_index=1,
+            membership_criterion_base_category_list=['product_line',],
+            membership_criterion_category_list=['product_line/dummy_resource'],)
+    predicate = itr.newContent(portal_type='Predicate')
+    predicate.edit(
+            string_index='resource_type',
+            title='Resource Tax',
+            int_index=2,
+            membership_criterion_base_category_list=['product_line',],
+            membership_criterion_category_list=['product_line/dummy_tax'],)
+    get_transaction().commit()
+    self.tic()
+    accounting_rule_cell_list = itr.contentValues(
+                            portal_type='Accounting Rule Cell')
+    self.assertEquals(2, len(accounting_rule_cell_list))
+    product_rule_cell = itr._getOb("movement_0")
+    self.assertEquals(product_rule_cell.getTitle(), 'Resource Product')
+    product_rule_cell.newContent(
+                         portal_type='Accounting Transaction Line',
+                         source_value=self.receivable_account,
+                         destination_value=self.payable_account,
+                         quantity=-1)
+    product_rule_cell.newContent(
+                         portal_type='Accounting Transaction Line',
+                         source_value=self.income_account,
+                         destination_value=self.expense_account,
+                         quantity=1)
+    
+    tax_rule_cell = itr._getOb("movement_1")
+    self.assertEquals(tax_rule_cell.getTitle(), 'Resource 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)
+    itr.validate()
+    get_transaction().commit()
+    self.tic()
+
+
+class TestApplyTradeCondition(TradeConditionTestCase):
+  """Tests Applying Trade Conditions
+  """
+  def test_apply_trade_condition_set_categories(self):
+    self.trade_condition.setSourceSectionValue(self.vendor)
+    self.trade_condition.setDestinationSectionValue(self.client)
+    self.trade_condition.setSourceValue(self.vendor)
+    self.trade_condition.setDestinationValue(self.client)
+    self.trade_condition.setPriceCurrencyValue(self.currency)
+    self.order.setSpecialiseValue(self.trade_condition)
+
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+    
+    self.assertEquals(self.vendor, self.order.getSourceSectionValue())
+    self.assertEquals(self.vendor, self.order.getSourceValue())
+    self.assertEquals(self.client, self.order.getDestinationSectionValue())
+    self.assertEquals(self.client, self.order.getDestinationValue())
+    self.assertEquals(self.currency, self.order.getPriceCurrencyValue())
+
+  def test_apply_trade_condition_keep_categories(self):
+    # source section & source are set on the order, not on the TC
+    self.order.setSourceSectionValue(self.vendor)
+    self.order.setSourceValue(self.vendor)
+
+    self.trade_condition.setSourceSectionValue(None)
+    self.trade_condition.setSourceValue(None)
+    self.trade_condition.setDestinationSectionValue(self.client)
+    self.trade_condition.setDestinationValue(self.client)
+    self.trade_condition.setPriceCurrencyValue(self.currency)
+    self.order.setSpecialiseValue(self.trade_condition)
+
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+    
+    # Applying the TC keeps values on the order
+    self.assertEquals(self.vendor, self.order.getSourceSectionValue())
+    self.assertEquals(self.vendor, self.order.getSourceValue())
+    self.assertEquals(self.client, self.order.getDestinationSectionValue())
+    self.assertEquals(self.client, self.order.getDestinationValue())
+    self.assertEquals(self.currency, self.order.getPriceCurrencyValue())
+
+  def test_apply_trade_condition_set_categories_with_hierarchy(self):
+    trade_condition_source = self.trade_condition_module.newContent(
+                            portal_type=self.trade_condition.getPortalType(),
+                            title='Trade Condition Source',
+                            source_value=self.vendor,
+                            source_section_value=self.vendor)
+    trade_condition_dest = self.trade_condition_module.newContent(
+                            portal_type=self.trade_condition.getPortalType(),
+                            title='Trade Condition Destination',
+                            destination_value=self.client,
+                            destination_section_value=self.client,
+                            price_currency_value=self.currency,
+                            # also set a source, it should not be used
+                            source_value=self.client)
+    self.trade_condition.setSpecialiseValueList(
+        (trade_condition_source, trade_condition_dest))
+
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+    
+    self.assertEquals(self.vendor, self.order.getSourceSectionValue())
+    self.assertEquals(self.vendor, self.order.getSourceValue())
+    self.assertEquals(self.client, self.order.getDestinationSectionValue())
+    self.assertEquals(self.client, self.order.getDestinationValue())
+    self.assertEquals(self.currency, self.order.getPriceCurrencyValue())
+
+  def test_apply_trade_condition_copy_subobjects(self):
+    self.trade_condition.setPaymentConditionTradeDate('custom')
+    self.trade_condition.setPaymentConditionPaymentDate(DateTime(2001, 01, 01))
+    self.order.setSpecialiseValue(self.trade_condition)
+
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+    
+    self.assertEquals('custom', self.order.getPaymentConditionTradeDate())
+    self.assertEquals(DateTime(2001, 01, 01),
+                      self.order.getPaymentConditionPaymentDate())
+
+  def test_apply_trade_condition_copy_subobjects_with_hierarchy(self):
+    other_trade_condition = self.trade_condition_module.newContent(
+                            portal_type=self.trade_condition.getPortalType(),
+                            title='Other Trade Condition')
+    other_trade_condition.setPaymentConditionTradeDate('custom')
+    other_trade_condition.setPaymentConditionPaymentDate(
+                                              DateTime(2001, 01, 01))
+
+    self.trade_condition.setSpecialiseValue(other_trade_condition)
+    self.order.setSpecialiseValue(self.trade_condition)
+
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+    
+    self.assertEquals('custom', self.order.getPaymentConditionTradeDate())
+    self.assertEquals(DateTime(2001, 01, 01),
+                      self.order.getPaymentConditionPaymentDate())
+
+  def test_tax_model_line_consistency(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    self.assertEquals([], tax_model_line.checkConsistency())
+    self.assertEquals([], self.trade_condition.checkConsistency())
+  
+  def test_view_tax_model_line(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    # TODO: fail if a field has an error
+    tax_model_line.view()
+    self.trade_condition.TradeCondition_viewTax()
+
+  def test_tax_line_consistency(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    tax_line = self.order.newContent(
+                        portal_type='Tax Line',
+                        resource_value=self.tax,
+                        base_application_value=base_1,
+                        quantity=0,
+                        efficiency=5.5)
+    self.assertEquals([], tax_line.checkConsistency())
+
+  def test_view_tax_line(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    tax_line = self.order.newContent(
+                        portal_type='Tax Line',
+                        resource_value=self.tax,
+                        base_application_value=base_1,
+                        quantity=0,
+                        efficiency=5.5)
+    # TODO: fail if a field has an error
+    tax_line.view()
+    self.order.Delivery_viewTax()
+
+
+class TestTaxLineCalculation(TradeConditionTestCase):
+  """Test calculating Tax Lines.
+  """
+  def test_simple_tax_model_line_calculation(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+
+    # this creates a tax line, with quantity 0, and it will be updated when
+    # needed
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(0, tax_line.getQuantity())
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals(0.2, tax_line.getPrice())
+
+    order_line = self.order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=10,
+                          price=10,)
+
+    # now tax lines are updated
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(100, tax_line.getQuantity())
+    self.assertEquals(0.2, tax_line.getPrice())
+    self.assertEquals(20, tax_line.getTotalPrice())
+    
+  def test_tax_model_line_calculation_with_two_lines(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+
+    # this creates a tax line, with quantity 0, and it will be updated when
+    # needed
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(0, tax_line.getQuantity())
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals(0.2, tax_line.getPrice())
+
+    order_line_1 = self.order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=3,
+                          price=10,)
+    order_line_2 = self.order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=7,
+                          price=10,)
+    
+    # now tax lines are updated
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(100, tax_line.getQuantity())
+    self.assertEquals(0.2, tax_line.getPrice())
+    self.assertEquals(20, tax_line.getTotalPrice())
+    
+    order_line_1_tax_line_list = \
+      order_line_1.DeliveryMovement_getCorrespondingTaxLineList()
+    self.assertEquals(1, len(order_line_1_tax_line_list))
+    tax_line = order_line_1_tax_line_list[0]
+    self.assertEquals(30, tax_line.getQuantity())
+    self.assertEquals(0.2, tax_line.getPrice())
+    self.assertEquals(6, tax_line.getTotalPrice())
+
+    order_line_2_tax_line_list = \
+      order_line_2.DeliveryMovement_getCorrespondingTaxLineList()
+    self.assertEquals(1, len(order_line_2_tax_line_list))
+    tax_line = order_line_2_tax_line_list[0]
+    self.assertEquals(70, tax_line.getQuantity())
+    self.assertEquals(0.2, tax_line.getPrice())
+    self.assertEquals(14, tax_line.getTotalPrice())
+
+  def test_tax_on_tax(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    base_2 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 2')
+    tax2 = self.portal.tax_module.newContent(
+                          portal_type='Tax',
+                          title='Tax 2')
+    self.resource.setBaseContributionValue(base_1)
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  base_contribution_value=base_2,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_2,
+                  float_index=2,
+                  efficiency=0.5,
+                  resource_value=tax2)
+    
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(2, len(tax_line_list))
+    tax_line1 = [tl for tl in tax_line_list if
+                   tl.getResourceValue() == self.tax][0]
+    self.assertEquals(0, tax_line1.getQuantity())
+    self.assertEquals(0.2, tax_line1.getPrice())
+    self.assertEquals(1, tax_line1.getFloatIndex())
+    self.assertEquals([base_1], tax_line1.getBaseApplicationValueList())
+    self.assertEquals([base_2], tax_line1.getBaseContributionValueList())
+
+    tax_line2 = [tl for tl in tax_line_list if
+                   tl.getResourceValue() == tax2][0]
+    self.assertEquals(0, tax_line2.getQuantity())
+    self.assertEquals(0.5, tax_line2.getPrice())
+    self.assertEquals(2, tax_line2.getFloatIndex())
+    self.assertEquals([base_2], tax_line2.getBaseApplicationValueList())
+
+    order_line = self.order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=3,
+                          price=10,)
+    
+    self.assertEquals(30, tax_line1.getQuantity())
+    self.assertEquals((30*0.2), tax_line2.getQuantity())
+    
+    order_line.setQuantity(5)
+    self.assertEquals(50, tax_line1.getQuantity())
+    self.assertEquals((50*0.2), tax_line2.getQuantity())
+    
+
+  def test_update_order_line_quantity_update_tax_line(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+
+    # this creates a tax line, with quantity 0, and it will be updated when
+    # needed
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(0, tax_line.getQuantity())
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals(0.2, tax_line.getPrice())
+
+    order_line = self.order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=10,
+                          price=10,)
+
+    # tax lines are updated
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(100, tax_line.getQuantity())
+    self.assertEquals(0.2, tax_line.getPrice())
+    self.assertEquals(20, tax_line.getTotalPrice())
+    
+    # change the quantity on order_line,
+    order_line.setQuantity(20)
+    # the tax line is updated
+    self.assertEquals(200, tax_line.getQuantity())
+    self.assertEquals(40, tax_line.getTotalPrice())
+
+  def test_order_cell_and_tax_line(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+    # make a resource with size variation
+    self.portal.portal_categories.size.newContent(id='small', title='Small')
+    self.portal.portal_categories.size.newContent(id='big', title='Big')
+    self.resource.setVariationBaseCategoryList(('size',))
+    self.resource.setVariationCategoryList(('size/big', 'size/small'))
+
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+
+    # this creates a tax line, with quantity 0, and it will be updated when
+    # needed
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(0, tax_line.getQuantity())
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals(0.2, tax_line.getPrice())
+
+    order_line = self.order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,)
+    order_line.setVariationCategoryList(('size/big', 'size/small'))
+    order_line.updateCellRange(base_id='movement')
+    cell_red = order_line.newCell('size/big',
+                                  portal_type=self.order_cell_type,
+                                  base_id='movement')
+    cell_red.setMappedValuePropertyList(['quantity', 'price'])
+    cell_red.setPrice(5)
+    cell_red.setQuantity(10)
+    cell_blue = order_line.newCell('size/small',
+                             portal_type=self.order_cell_type,
+                             base_id='movement')
+    cell_blue.setMappedValuePropertyList(['quantity', 'price'])
+    cell_blue.setPrice(2)
+    cell_blue.setQuantity(25)
+    self.assertEquals(100, order_line.getTotalPrice(fast=0))
+    
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(100, tax_line.getQuantity())
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals(0.2, tax_line.getPrice())
+    
+    # TODO: discuss this behaviour, and what about getTotalNetPrice ?
+    #self.assertEquals(120, self.order.getTotalPrice(fast=0))
+
+
+  def test_hierarchical_order_line_and_tax_line(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+
+    # this creates a tax line, with quantity 0, and it will be updated when
+    # needed
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(0, tax_line.getQuantity())
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals(0.2, tax_line.getPrice())
+
+    order_line = self.order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,)
+    suborder_line1 = order_line.newContent(
+                          portal_type=self.order_line_type,
+                          quantity=4,
+                          price=5)
+    suborder_line2 = order_line.newContent(
+                          portal_type=self.order_line_type,
+                          quantity=2,
+                          price=40)
+
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(100, tax_line.getQuantity())
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals(0.2, tax_line.getPrice())
+  
+  def test_base_contribution_pseudo_acquisition(self):
+    base_1 = self.base_amount.newContent(portal_type='Category',
+                                         title='Base 1')
+    self.resource.setBaseContributionValueList((base_1,))
+    line = self.order.newContent(portal_type=self.order_line_type)
+    self.assertEquals([], line.getBaseContributionValueList())
+    line.setResourceValue(self.resource)
+    self.assertEquals([base_1], line.getBaseContributionValueList())
+    line.setBaseContributionValueList([])
+    self.assertEquals([], line.getBaseContributionValueList())
+
+  def test_multiple_order_line_multiple_tax_line(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    base_2 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 2')
+    self.resource.setBaseContributionValueList((base_1, base_2))
+    tax_model_line_1 = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.1,
+                  resource_value=self.tax)
+    tax_model_line_2 = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_2,
+                  float_index=2,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    tax_model_line_1_2 = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value_list=(base_1, base_2),
+                  float_index=3,
+                  efficiency=0.3,
+                  resource_value=self.tax)
+    
+    self.order.Order_applyTradeCondition(self.trade_condition, force=1)
+    line_1 = self.order.newContent(
+                  portal_type=self.order_line_type,
+                  quantity=1, price=1,
+                  resource_value=self.resource,
+                  base_contribution_value_list=(base_1,))
+    # -> tax_model_line_1 and tax_model_line_1_2 are applicable
+    line_2 = self.order.newContent(
+                  portal_type=self.order_line_type,
+                  quantity=2, price=2,
+                  resource_value=self.resource,
+                  base_contribution_value_list=(base_2,))
+    # -> tax_model_line_2 and tax_model_line_1_2 are applicable
+    line_3 = self.order.newContent(
+                  portal_type=self.order_line_type,
+                  quantity=3, price=3,
+                  resource_value=self.resource,
+                  base_contribution_value_list=(base_1, base_2))
+    # -> tax_model_line_1, tax_model_line_2 and tax_model_line_1_2 are applicable
+    #  (but they are not applied twice)
+
+    tax_line_list = self.order.contentValues(portal_type='Tax Line')
+    self.assertEquals(3, len(tax_line_list))
+    tax_line_1 = [x for x in tax_line_list if x.getPrice() == 0.1][0]
+    tax_line_2 = [x for x in tax_line_list if x.getPrice() == 0.2][0]
+    tax_line_3 = [x for x in tax_line_list if x.getPrice() == 0.3][0]
+
+    self.assertEquals(sum([line_1.getTotalPrice(),
+                           line_3.getTotalPrice()]), tax_line_1.getQuantity())
+    self.assertEquals(sum([line_2.getTotalPrice(),
+                           line_3.getTotalPrice()]), tax_line_2.getQuantity())
+    self.assertEquals(sum([line_1.getTotalPrice(),
+                           line_2.getTotalPrice(),
+                           line_3.getTotalPrice()]), tax_line_3.getQuantity())
+
+    # TODO: test DeliveryMovement_getCorrespondingTaxLineList
+    tax_movement_list = line_1.DeliveryMovement_getCorrespondingTaxLineList()
+    self.assertEquals(2, len(tax_movement_list))
+    tax_1_movement = [m for m in tax_movement_list if m.getPrice() == 0.1][0]
+#    self.assertEquals(
+    
+    
+
+class TestTaxLineOrderSimulation(TradeConditionTestCase):
+  """Test Simulation of Tax Lines on Orders
+  """
+  def test_tax_line_simulation(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    order = self.order
+    order.Order_applyTradeCondition(self.trade_condition, force=1)
+    order.setSourceSectionValue(self.vendor)
+    order.setSourceValue(self.vendor)
+    order.setDestinationSectionValue(self.client)
+    order.setDestinationValue(self.client)
+    order.setStartDate(DateTime(2001, 1, 1))
+    order_line = order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=10,
+                          price=10,)
+    order.plan()
+    order.confirm()
+    self.assertEquals('confirmed', order.getSimulationState())
+    get_transaction().commit()
+    self.tic()
+    related_applied_rule_list = order.getCausalityRelatedValueList(
+                                      portal_type='Applied Rule')
+    self.assertEquals(1, len(related_applied_rule_list))
+    root_applied_rule = related_applied_rule_list[0]
+    simulation_movement_list = root_applied_rule.contentValues(
+                                   portal_type='Simulation Movement')
+    self.assertEquals(1, len(simulation_movement_list))
+    level2_applied_rule_list = simulation_movement_list[0].contentValues()
+    self.assertEquals(2, len(level2_applied_rule_list))
+    # first test the invoice movement, they should have base_contribution set
+    # correctly
+    invoice_rule_list = [ar for ar in level2_applied_rule_list if
+             ar.getSpecialiseValue().getPortalType() == 'Invoicing Rule']
+    self.assertEquals(1, len(invoice_rule_list))
+    invoice_simulation_movement_list = invoice_rule_list[0].contentValues()
+    self.assertEquals(1, len(invoice_simulation_movement_list))
+    invoice_simulation_movement = invoice_simulation_movement_list[0]
+    self.assertEquals(self.resource,
+        invoice_simulation_movement.getResourceValue())
+    self.assertEquals([base_1],
+        invoice_simulation_movement.getBaseContributionValueList())
+
+    # now test the tax movement
+    applied_tax_rule_list = [ar for ar in level2_applied_rule_list if
+             ar.getSpecialiseValue().getPortalType() == 'Tax Rule']
+    self.assertEquals(1, len(applied_tax_rule_list))
+    tax_simulation_movement_list = applied_tax_rule_list[0].contentValues()
+    self.assertEquals(1, len(tax_simulation_movement_list))
+    tax_simulation_movement = tax_simulation_movement_list[0]
+
+    self.assertEquals(self.tax, tax_simulation_movement.getResourceValue())
+    self.assertEquals([base_1],
+                      tax_simulation_movement.getBaseApplicationValueList())
+    self.assertEquals(100, tax_simulation_movement.getQuantity())
+    self.assertEquals(0.2, tax_simulation_movement.getPrice())
+    
+    # reexpand and check nothing changed
+    root_applied_rule.expand()
+    applied_tax_rule_list = [ar for ar in level2_applied_rule_list if
+             ar.getSpecialiseValue().getPortalType() == 'Tax Rule']
+    self.assertEquals(1, len(applied_tax_rule_list))
+    tax_simulation_movement_list = applied_tax_rule_list[0].contentValues()
+    self.assertEquals(1, len(tax_simulation_movement_list))
+    tax_simulation_movement = tax_simulation_movement_list[0]
+
+    self.assertEquals(self.tax, tax_simulation_movement.getResourceValue())
+    self.assertEquals([base_1],
+                      tax_simulation_movement.getBaseApplicationValueList())
+    self.assertEquals(100, tax_simulation_movement.getQuantity())
+    self.assertEquals(0.2, tax_simulation_movement.getPrice())
+
+  def test_2_tax_lines_simulation(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    order = self.order
+    order.Order_applyTradeCondition(self.trade_condition, force=1)
+    order.setSourceSectionValue(self.vendor)
+    order.setSourceValue(self.vendor)
+    order.setDestinationSectionValue(self.client)
+    order.setDestinationValue(self.client)
+    order.setStartDate(DateTime(2001, 1, 1))
+    order_line1 = order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=2,
+                          price=15,)
+    order_line2 = order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=7,
+                          price=10,)
+    order.plan()
+    order.confirm()
+    self.assertEquals('confirmed', order.getSimulationState())
+    get_transaction().commit()
+    self.tic()
+    related_applied_rule_list = order.getCausalityRelatedValueList(
+                                      portal_type='Applied Rule')
+    self.assertEquals(1, len(related_applied_rule_list))
+    root_applied_rule = related_applied_rule_list[0]
+    simulation_movement_list = root_applied_rule.contentValues(
+                                   portal_type='Simulation Movement')
+    self.assertEquals(2, len(simulation_movement_list))
+    # line 1
+    line1_simulation_movement_list = [sm for sm in simulation_movement_list
+          if sm.getOrderValue() == order_line1]
+    self.assertEquals(1, len(line1_simulation_movement_list))
+    simulation_movement = line1_simulation_movement_list[0]
+    self.assertEquals(2.0, simulation_movement.getQuantity())
+    applied_tax_rule_list = [ar for ar in simulation_movement.objectValues()
+        if ar.getSpecialiseValue().getPortalType() == 'Tax Rule']
+    self.assertEquals(1, len(applied_tax_rule_list))
+    tax_simulation_movement_list = applied_tax_rule_list[0].contentValues()
+    self.assertEquals(1, len(tax_simulation_movement_list))
+    tax_simulation_movement = tax_simulation_movement_list[0]
+    self.assertEquals(self.tax, tax_simulation_movement.getResourceValue())
+    self.assertEquals([base_1],
+                      tax_simulation_movement.getBaseApplicationValueList())
+    self.assertEquals(30, tax_simulation_movement.getQuantity())
+    self.assertEquals(0.2, tax_simulation_movement.getPrice())
+    
+    # line 2
+    line2_simulation_movement_list = [sm for sm in simulation_movement_list
+          if sm.getOrderValue() == order_line2]
+    self.assertEquals(1, len(line2_simulation_movement_list))
+    simulation_movement = line2_simulation_movement_list[0]
+    self.assertEquals(7., simulation_movement.getQuantity())
+    applied_tax_rule_list = [ar for ar in simulation_movement.objectValues()
+        if ar.getSpecialiseValue().getPortalType() == 'Tax Rule']
+    self.assertEquals(1, len(applied_tax_rule_list))
+    tax_simulation_movement_list = applied_tax_rule_list[0].contentValues()
+    self.assertEquals(1, len(tax_simulation_movement_list))
+    tax_simulation_movement = tax_simulation_movement_list[0]
+    self.assertEquals(self.tax, tax_simulation_movement.getResourceValue())
+    self.assertEquals([base_1],
+                      tax_simulation_movement.getBaseApplicationValueList())
+    self.assertEquals(70, tax_simulation_movement.getQuantity())
+    self.assertEquals(0.2, tax_simulation_movement.getPrice())
+
+
+  def test_tax_line_build(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    order = self.order
+    order.Order_applyTradeCondition(self.trade_condition, force=1)
+    order.setSourceSectionValue(self.vendor)
+    order.setSourceValue(self.vendor)
+    order.setDestinationSectionValue(self.client)
+    order.setDestinationValue(self.client)
+    order.setPriceCurrencyValue(self.currency)
+    order.setStartDate(DateTime(2001, 1, 1))
+    order_line = order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=2,
+                          price=15,)
+    order.plan()
+    order.confirm()
+    self.assertEquals('confirmed', order.getSimulationState())
+    get_transaction().commit()
+    self.tic()
+    related_delivery = order.getCausalityRelatedValue(
+                  portal_type=('Purchase Packing List', 'Sale Packing List'))
+    self.assertNotEquals(related_delivery, None)
+    related_delivery.setReady()
+    related_delivery.start()
+    related_delivery.stop()
+    related_delivery.deliver()
+    self.assertEquals('delivered', related_delivery.getSimulationState())
+    get_transaction().commit()
+    self.tic()
+    
+    related_invoice = related_delivery.getCausalityRelatedValue(
+                  portal_type=('Purchase Invoice Transaction',
+                               'Sale Invoice Transaction'))
+    self.assertNotEquals(related_invoice, None)
+    invoice_line_list = related_invoice.contentValues(
+                  portal_type='Invoice Line')
+    tax_line_list = related_invoice.contentValues(
+                  portal_type='Tax Line')
+
+    self.assertEquals(1, len(invoice_line_list))
+    invoice_line = invoice_line_list[0]
+    self.assertEquals(2, invoice_line.getQuantity())
+    self.assertEquals(15, invoice_line.getPrice())
+    self.assertEquals(self.resource, invoice_line.getResourceValue())
+    self.assertEquals([base_1], invoice_line.getBaseContributionValueList())
+
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(30, tax_line.getQuantity())
+    self.assertEquals(0.2, tax_line.getPrice())
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals([base_1], tax_line.getBaseApplicationValueList())
+    self.assertEquals([], tax_line.getBaseContributionValueList())
+
+    self.assertEquals('solved', related_invoice.getCausalityState())
+
+    # Of course, this invoice does not generate simulation again
+    self.assertEquals([], related_invoice.getCausalityRelatedValueList(
+                                portal_type='Applied Rule'))
+    
+
+  def test_tax_line_merged_build(self):
+    # an order with 2 lines and 1 tax line will later be built in an invoice
+    # with 2 lines and 1 tax line
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+    resource2 = self.portal.product_module.newContent(
+                            portal_type='Product',
+                            title='Resource 2',
+                            base_contribution_value_list=[base_1])
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    order = self.order
+    order.Order_applyTradeCondition(self.trade_condition, force=1)
+    order.setSourceSectionValue(self.vendor)
+    order.setSourceValue(self.vendor)
+    order.setDestinationSectionValue(self.client)
+    order.setDestinationValue(self.client)
+    order.setPriceCurrencyValue(self.currency)
+    order.setStartDate(DateTime(2001, 1, 1))
+    order_line1 = order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=2,
+                          price=15,)
+    order_line2 = order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=resource2,
+                          quantity=7,
+                          price=10,)
+    # check existing tax line
+    tax_line_list = order.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals(2*15 + 7*10, tax_line.getQuantity())
+    self.assertEquals(0.2, tax_line.getPrice())
+
+    order.plan()
+    order.confirm()
+    self.assertEquals('confirmed', order.getSimulationState())
+    get_transaction().commit()
+    self.tic()
+    related_delivery = order.getCausalityRelatedValue(
+                  portal_type=('Purchase Packing List', 'Sale Packing List'))
+    self.assertNotEquals(related_delivery, None)
+    related_delivery.setReady()
+    related_delivery.start()
+    related_delivery.stop()
+    related_delivery.deliver()
+    self.assertEquals('delivered', related_delivery.getSimulationState())
+    get_transaction().commit()
+    self.tic()
+    
+    related_invoice = related_delivery.getCausalityRelatedValue(
+                  portal_type=('Purchase Invoice Transaction',
+                               'Sale Invoice Transaction'))
+    self.assertNotEquals(related_invoice, None)
+    invoice_line_list = related_invoice.contentValues(
+                  portal_type='Invoice Line')
+    tax_line_list = related_invoice.contentValues(
+                  portal_type='Tax Line')
+
+    self.assertEquals(2, len(invoice_line_list))
+
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(100, tax_line.getQuantity())
+    self.assertEquals(0.2, tax_line.getPrice())
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals([base_1], tax_line.getBaseApplicationValueList())
+    self.assertEquals([], tax_line.getBaseContributionValueList())
+
+    self.assertEquals('solved', related_invoice.getCausalityState())
+
+  def test_tax_line_updated_on_invoice_line_change(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    order = self.order
+    order.Order_applyTradeCondition(self.trade_condition, force=1)
+    order.setSourceSectionValue(self.vendor)
+    order.setSourceValue(self.vendor)
+    order.setDestinationSectionValue(self.client)
+    order.setDestinationValue(self.client)
+    order.setPriceCurrencyValue(self.currency)
+    order.setStartDate(DateTime(2001, 1, 1))
+    order_line = order.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=2,
+                          price=15,)
+    order.plan()
+    order.confirm()
+    self.assertEquals('confirmed', order.getSimulationState())
+    get_transaction().commit()
+    self.tic()
+    related_delivery = order.getCausalityRelatedValue(
+                  portal_type=('Purchase Packing List', 'Sale Packing List'))
+    self.assertNotEquals(related_delivery, None)
+    related_delivery.setReady()
+    related_delivery.start()
+    related_delivery.stop()
+    related_delivery.deliver()
+    self.assertEquals('delivered', related_delivery.getSimulationState())
+    get_transaction().commit()
+    self.tic()
+    
+    related_invoice = related_delivery.getCausalityRelatedValue(
+                  portal_type=('Purchase Invoice Transaction',
+                               'Sale Invoice Transaction'))
+    self.assertNotEquals(related_invoice, None)
+    self.assertEquals('solved', related_invoice.getCausalityState())
+    invoice_line_list = related_invoice.contentValues(
+                  portal_type='Invoice Line')
+    tax_line_list = related_invoice.contentValues(
+                  portal_type='Tax Line')
+
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+    self.assertEquals(30, tax_line.getQuantity())
+    self.assertEquals(0.2, tax_line.getPrice())
+    self.assertEquals(self.tax, tax_line.getResourceValue())
+    self.assertEquals([base_1], tax_line.getBaseApplicationValueList())
+    self.assertEquals([], tax_line.getBaseContributionValueList())
+
+    self.assertEquals(1, len(invoice_line_list))
+    invoice_line = invoice_line_list[0]
+    # change a total price on the invoice_line,
+    invoice_line.setQuantity(3)
+    get_transaction().commit()
+    self.tic()
+    # it will be reflected on the tax line
+    self.assertEquals(45, tax_line.getQuantity())
+    self.assertTrue(tax_line.isDivergent())
+    # and the invoice is diverged
+    self.assertEquals('diverged', related_invoice.getCausalityState())
+    
+
+class TestTaxLineInvoiceSimulation(AccountingBuildTestCase):
+  """Test Simulation of Tax Lines on Invoices
+  """
+  def test_tax_line_simulation(self):
+    base_1 = self.base_amount.newContent(
+                          portal_type='Category',
+                          title='Base 1')
+    self.resource.setBaseContributionValue(base_1)
+    tax_model_line = self.trade_condition.newContent(
+                  portal_type='Tax Model Line',
+                  base_application_value=base_1,
+                  float_index=1,
+                  efficiency=0.2,
+                  resource_value=self.tax)
+    
+    invoice = self.order
+    invoice.Order_applyTradeCondition(self.trade_condition, force=1)
+    invoice.setSourceSectionValue(self.vendor)
+    invoice.setSourceValue(self.vendor)
+    invoice.setDestinationSectionValue(self.client)
+    invoice.setDestinationValue(self.client)
+    invoice.setStartDate(DateTime(2001, 1, 1))
+    invoice.setPriceCurrencyValue(self.currency)
+    invoice_line = invoice.newContent(
+                          portal_type=self.order_line_type,
+                          resource_value=self.resource,
+                          quantity=10,
+                          price=10,)
+    tax_line_list = invoice.contentValues(portal_type='Tax Line')
+    self.assertEquals(1, len(tax_line_list))
+    tax_line = tax_line_list[0]
+
+    invoice.plan()
+    invoice.confirm()
+    self.assertEquals('confirmed', invoice.getSimulationState())
+    get_transaction().commit()
+    self.tic()
+    related_applied_rule_list = invoice.getCausalityRelatedValueList(
+                                      portal_type='Applied Rule')
+    self.assertEquals(1, len(related_applied_rule_list))
+    root_applied_rule = related_applied_rule_list[0]
+    simulation_movement_list = root_applied_rule.contentValues(
+                                   portal_type='Simulation Movement')
+    self.assertEquals(2, len(simulation_movement_list))
+    tax_simulation_movement_list = [m for m in simulation_movement_list
+                                    if m.getOrderValue() == tax_line]
+    self.assertEquals(1, len(tax_simulation_movement_list))
+    tax_simulation_movement = tax_simulation_movement_list[0]
+    self.assertEquals([base_1],
+        tax_simulation_movement.getBaseApplicationValueList())
+    self.assertEquals(100, tax_simulation_movement.getQuantity())
+    self.assertEquals(0.2, tax_simulation_movement.getPrice())
+    self.assertEquals(self.currency,
+                      tax_simulation_movement.getPriceCurrencyValue())
+
+    invoice_simulation_movement_list = [m for m in simulation_movement_list
+                                    if m.getOrderValue() == invoice_line]
+    self.assertEquals(1, len(invoice_simulation_movement_list))
+    invoice_simulation_movement = invoice_simulation_movement_list[0]
+    self.assertEquals([base_1],
+        invoice_simulation_movement.getBaseContributionValueList())
+    self.assertEquals(10, invoice_simulation_movement.getQuantity())
+    self.assertEquals(10, invoice_simulation_movement.getPrice())
+    self.assertEquals(self.currency,
+                      invoice_simulation_movement.getPriceCurrencyValue())
+    self.assertEquals(self.resource,
+                      invoice_simulation_movement.getResourceValue())
+    invoice.start()
+    self.assertEquals('started', invoice.getSimulationState())
+    get_transaction().commit()
+    self.tic()
+    accounting_line_list = invoice.getMovementList(
+                            portal_type=('Sale Invoice Transaction Line',
+                                         'Purchase Invoice Transaction Line'))
+    self.assertEquals(3, len(accounting_line_list))
+    receivable_line = [l for l in accounting_line_list if
+                        l.getSourceValue() == self.receivable_account][0]
+    self.assertEquals(self.payable_account,
+                      receivable_line.getDestinationValue())
+    self.assertEquals(120, receivable_line.getSourceDebit())
+    
+    tax_line = [l for l in accounting_line_list if
+                        l.getSourceValue() == self.collected_tax_account][0]
+    self.assertEquals(self.refundable_tax_account,
+                      tax_line.getDestinationValue())
+    self.assertEquals(20, tax_line.getSourceCredit())
+
+    self.assertEquals('solved', invoice.getCausalityState())
+
+
+class DiscountCalculation:
+  """Test Calculating Discount
+  """
+  def test_simple_discount_model_line_calculation(self):
+    discount_line =self.trade_condition.newContent(
+                     portal_type='Discount Model Line')
+
+
+class TestWithSaleOrder:
+  order_type = 'Sale Order'
+  order_line_type = 'Sale Order Line'
+  order_cell_type = 'Sale Order Cell'
+  trade_condition_type = 'Sale Trade Condition'
+
+class TestWithPurchaseOrder:
+  order_type = 'Purchase Order'
+  order_line_type = 'Purchase Order Line'
+  order_cell_type = 'Purchase Order Cell'
+  trade_condition_type = 'Purchase Trade Condition'
+
+class TestWithSaleInvoice:
+  order_type = 'Sale Invoice Transaction'
+  order_line_type = 'Invoice Line'
+  order_cell_type = 'Invoice Cell'
+  trade_condition_type = 'Sale Trade Condition'
+
+class TestWithPurchaseInvoice:
+  order_type = 'Purchase Invoice Transaction'
+  order_line_type = 'Invoice Line'
+  order_cell_type = 'Invoice Cell'
+  trade_condition_type = 'Purchase Trade Condition'
+
+
+class TestApplyTradeConditionSaleOrder(
+      TestApplyTradeCondition, TestWithSaleOrder):
+  pass
+class TestApplyTradeConditionPurchaseOrder(
+      TestApplyTradeCondition, TestWithPurchaseOrder):
+  pass
+
+class TestTaxLineCalculationSaleOrder(
+    TestTaxLineCalculation, TestWithSaleOrder):
+  pass
+class TestTaxLineCalculationPurchaseOrder(
+    TestTaxLineCalculation, TestWithPurchaseOrder):
+  pass
+class TestTaxLineCalculationSaleInvoice(
+    TestTaxLineCalculation, TestWithSaleInvoice):
+  def not_available(self):
+    pass
+  test_hierarchical_order_line_and_tax_line = not_available
+class TestTaxLineCalculationPurchaseInvoice(
+    TestTaxLineCalculation, TestWithPurchaseInvoice):
+  def not_available(self):
+    pass
+  test_hierarchical_order_line_and_tax_line = not_available
+
+class TestTaxLineOrderSimulationSaleOrder(
+      TestTaxLineOrderSimulation, TestWithSaleOrder):
+  pass
+class TestTaxLineOrderSimulationPurchaseOrder(
+      TestTaxLineOrderSimulation, TestWithPurchaseOrder):
+  pass
+
+class TestTaxLineInvoiceSimulationPurchaseInvoice(
+      TestTaxLineInvoiceSimulation, TestWithPurchaseInvoice):
+  pass
+class TestTaxLineInvoiceSimulationSaleInvoice(
+      TestTaxLineInvoiceSimulation, TestWithSaleInvoice):
+  pass
+
+def test_suite():
+  suite = unittest.TestSuite()
+  suite.addTest(unittest.makeSuite(TestApplyTradeConditionSaleOrder))
+  suite.addTest(unittest.makeSuite(TestApplyTradeConditionPurchaseOrder))
+  suite.addTest(unittest.makeSuite(TestTaxLineCalculationSaleOrder))
+  suite.addTest(unittest.makeSuite(TestTaxLineCalculationPurchaseOrder))
+  suite.addTest(unittest.makeSuite(TestTaxLineCalculationSaleInvoice))
+  suite.addTest(unittest.makeSuite(TestTaxLineCalculationPurchaseInvoice))
+  suite.addTest(unittest.makeSuite(TestTaxLineOrderSimulationSaleOrder))
+  suite.addTest(unittest.makeSuite(TestTaxLineOrderSimulationPurchaseOrder))
+  suite.addTest(unittest.makeSuite(TestTaxLineInvoiceSimulationPurchaseInvoice))
+  suite.addTest(unittest.makeSuite(TestTaxLineInvoiceSimulationSaleInvoice))
+  return suite
+