diff --git a/bt5/erp5_accounting/DocumentTemplateItem/portal_components/document.erp5.AccountingTransactionRootSimulationRule.py b/bt5/erp5_accounting/DocumentTemplateItem/portal_components/document.erp5.AccountingTransactionRootSimulationRule.py
index 6d6b5f6e801c5433726c9967dab6cdfc040e9547..46d02b162332475847584001b58b17fa336018ac 100644
--- a/bt5/erp5_accounting/DocumentTemplateItem/portal_components/document.erp5.AccountingTransactionRootSimulationRule.py
+++ b/bt5/erp5_accounting/DocumentTemplateItem/portal_components/document.erp5.AccountingTransactionRootSimulationRule.py
@@ -28,8 +28,8 @@
 from AccessControl import ClassSecurityInfo
 from Products.ERP5Type import Permissions, PropertySheet
 
-from Products.ERP5.mixin.rule import RuleMixin
-from Products.ERP5.mixin.movement_collection_updater import MovementCollectionUpdaterMixin
+from erp5.component.mixin.RuleMixin import RuleMixin
+from erp5.component.mixin.MovementCollectionUpdaterMixin import MovementCollectionUpdaterMixin
 class AccountingTransactionRootSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   """
   Accounting Transaction Root Simulation Rule is a root level rule for
diff --git a/bt5/erp5_accounting/DocumentTemplateItem/portal_components/document.erp5.PaymentSimulationRule.py b/bt5/erp5_accounting/DocumentTemplateItem/portal_components/document.erp5.PaymentSimulationRule.py
index f69cc8cace26cda4945279ff5df2f952d07e0f15..d6d92168e6b47ca05114a843795f5235955d78a2 100644
--- a/bt5/erp5_accounting/DocumentTemplateItem/portal_components/document.erp5.PaymentSimulationRule.py
+++ b/bt5/erp5_accounting/DocumentTemplateItem/portal_components/document.erp5.PaymentSimulationRule.py
@@ -28,11 +28,13 @@
 
 import zope.interface
 from AccessControl import ClassSecurityInfo
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
-from Products.ERP5.mixin.rule import RuleMixin
+from Products.ERP5Type import Permissions, PropertySheet
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin
-from Products.ERP5.mixin.movement_collection_updater import \
-     MovementCollectionUpdaterMixin
+from erp5.component.mixin.MovementCollectionUpdaterMixin import MovementCollectionUpdaterMixin
+from erp5.component.interface.IRule import IRule
+from erp5.component.interface.IDivergenceController import IDivergenceController
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class PaymentSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   """
@@ -48,9 +50,9 @@ class PaymentSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IRule,
-                            interfaces.IDivergenceController,
-                            interfaces.IMovementCollectionUpdater,)
+  zope.interface.implements(IRule,
+                            IDivergenceController,
+                            IMovementCollectionUpdater,)
 
   # Default Properties
   property_sheets = (
diff --git a/bt5/erp5_base/MixinTemplateItem/portal_components/mixin.erp5.BuilderMixin.py b/bt5/erp5_base/MixinTemplateItem/portal_components/mixin.erp5.BuilderMixin.py
index bbe807af93af554b1209358afa8ac3360e7057f4..90f21bc7d13f2ab7d2e84e6ce899d409999741fa 100644
--- a/bt5/erp5_base/MixinTemplateItem/portal_components/mixin.erp5.BuilderMixin.py
+++ b/bt5/erp5_base/MixinTemplateItem/portal_components/mixin.erp5.BuilderMixin.py
@@ -33,7 +33,7 @@ from Products.ERP5Type import Permissions, PropertySheet
 from Products.ERP5Type.XMLObject import XMLObject
 from Products.ERP5Type.Core.Predicate import Predicate
 from Products.ERP5.Document.Amount import Amount
-from Products.ERP5.MovementGroup import MovementGroupNode
+from erp5.component.module.MovementGroup import MovementGroupNode
 from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
 from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
 from Products.ERP5.ExplanationCache import _getExplanationCache
diff --git a/bt5/erp5_commerce_loyalty_program/DocumentTemplateItem/portal_components/document.erp5.LoyaltyTransactionSimulationRule.py b/bt5/erp5_commerce_loyalty_program/DocumentTemplateItem/portal_components/document.erp5.LoyaltyTransactionSimulationRule.py
index 54944b98b2662c02462b0e911868efa520c44cae..72e8179f1a4cac776afe55b816a844c3d6958f1e 100644
--- a/bt5/erp5_commerce_loyalty_program/DocumentTemplateItem/portal_components/document.erp5.LoyaltyTransactionSimulationRule.py
+++ b/bt5/erp5_commerce_loyalty_program/DocumentTemplateItem/portal_components/document.erp5.LoyaltyTransactionSimulationRule.py
@@ -29,12 +29,15 @@
 
 import zope.interface
 from AccessControl import ClassSecurityInfo
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
-from Products.ERP5.mixin.rule import RuleMixin
+from Products.ERP5Type import Permissions, PropertySheet
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin
-from Products.ERP5.mixin.movement_collection_updater import \
+from erp5.component.mixin.MovementCollectionUpdaterMixin import \
      MovementCollectionUpdaterMixin
 from Acquisition import aq_base
+from erp5.component.interface.IRule import IRule
+from erp5.component.interface.IDivergenceController import IDivergenceController
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class LoyaltyTransactionSimulationRule(RuleMixin,MovementCollectionUpdaterMixin):
   """  """
@@ -47,9 +50,9 @@ class LoyaltyTransactionSimulationRule(RuleMixin,MovementCollectionUpdaterMixin)
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IRule,
-                            interfaces.IDivergenceController,
-                            interfaces.IMovementCollectionUpdater,)
+  zope.interface.implements(IRule,
+                            IDivergenceController,
+                            IMovementCollectionUpdater,)
 
   # Default Properties
   property_sheets = (
diff --git a/bt5/erp5_immobilisation/DocumentTemplateItem/portal_components/document.erp5.AmortisationRule.py b/bt5/erp5_immobilisation/DocumentTemplateItem/portal_components/document.erp5.AmortisationRule.py
index 5feb67c616bfba7c38ce40a657f024350aefcbb3..d2f9bbb6c98fa03c016b2badcf4d28d87d9e9a3a 100644
--- a/bt5/erp5_immobilisation/DocumentTemplateItem/portal_components/document.erp5.AmortisationRule.py
+++ b/bt5/erp5_immobilisation/DocumentTemplateItem/portal_components/document.erp5.AmortisationRule.py
@@ -33,7 +33,7 @@ from string import capitalize
 from Products.ERP5Type.DateUtils import centis, getClosestDate, addToDate
 from Products.ERP5Type.DateUtils import getDecimalNumberOfYearsBetween
 from Products.ERP5Type import Permissions
-from Products.ERP5.mixin.rule import RuleMixin
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.CMFCore.utils import getToolByName
 from Products.ERP5.Document.ImmobilisationMovement import NO_CHANGE_METHOD
 
diff --git a/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.InvoiceSimulationRule.py b/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.InvoiceSimulationRule.py
index 436025b9f624557ee6125a8c875a10e486725298..300305b0f9e5ebda54021d37adde66b7830d3979 100644
--- a/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.InvoiceSimulationRule.py
+++ b/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.InvoiceSimulationRule.py
@@ -28,11 +28,14 @@
 
 import zope.interface
 from AccessControl import ClassSecurityInfo
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
-from Products.ERP5.mixin.rule import RuleMixin
+from Products.ERP5Type import Permissions, PropertySheet
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin
-from Products.ERP5.mixin.movement_collection_updater import \
+from erp5.component.mixin.MovementCollectionUpdaterMixin import \
      MovementCollectionUpdaterMixin
+from erp5.component.interface.IRule import IRule
+from erp5.component.interface.IDivergenceController import IDivergenceController
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class InvoiceSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   """
@@ -47,9 +50,9 @@ class InvoiceSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IRule,
-                            interfaces.IDivergenceController,
-                            interfaces.IMovementCollectionUpdater,)
+  zope.interface.implements(IRule,
+                            IDivergenceController,
+                            IMovementCollectionUpdater,)
 
   # Default Properties
   property_sheets = (
diff --git a/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.InvoiceTransactionSimulationRule.py b/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.InvoiceTransactionSimulationRule.py
index c134d989d8e46eb5be16d633312f41bc7d1f2a41..da95e5e2fb60266ab6908242f26afc45f08d8631 100644
--- a/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.InvoiceTransactionSimulationRule.py
+++ b/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.InvoiceTransactionSimulationRule.py
@@ -28,13 +28,15 @@
 
 import zope.interface
 from AccessControl import ClassSecurityInfo
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
-from Products.ERP5.mixin.rule import RuleMixin
+from Products.ERP5Type import Permissions, PropertySheet
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin
-from Products.ERP5.mixin.movement_collection_updater import \
+from erp5.component.mixin.MovementCollectionUpdaterMixin import \
      MovementCollectionUpdaterMixin
 from Products.ERP5.Document.PredicateMatrix import PredicateMatrix
-
+from erp5.component.interface.IRule import IRule
+from erp5.component.interface.IDivergenceController import IDivergenceController
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class InvoiceTransactionSimulationRule(RuleMixin,
     MovementCollectionUpdaterMixin, PredicateMatrix):
@@ -53,9 +55,9 @@ class InvoiceTransactionSimulationRule(RuleMixin,
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IRule,
-                            interfaces.IDivergenceController,
-                            interfaces.IMovementCollectionUpdater,)
+  zope.interface.implements(IRule,
+                            IDivergenceController,
+                            IMovementCollectionUpdater,)
 
   # Default Properties
   property_sheets = (
diff --git a/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.TradeModelSimulationRule.py b/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.TradeModelSimulationRule.py
index 78c8b3d1f851aace2d0a0e2e9f76e89942a2e216..d8415e25198dde257a8d497409438353b7abbe7c 100644
--- a/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.TradeModelSimulationRule.py
+++ b/bt5/erp5_invoicing/DocumentTemplateItem/portal_components/document.erp5.TradeModelSimulationRule.py
@@ -29,11 +29,14 @@
 import zope.interface
 from AccessControl import ClassSecurityInfo
 from Acquisition import aq_base
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
-from Products.ERP5.mixin.rule import RuleMixin
+from Products.ERP5Type import Permissions, PropertySheet
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin
-from Products.ERP5.mixin.movement_collection_updater import \
+from erp5.component.mixin.MovementCollectionUpdaterMixin import \
      MovementCollectionUpdaterMixin
+from erp5.component.interface.IRule import IRule
+from erp5.component.interface.IDivergenceController import IDivergenceController
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class TradeModelSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   """
@@ -48,9 +51,9 @@ class TradeModelSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IRule,
-                            interfaces.IDivergenceController,
-                            interfaces.IMovementCollectionUpdater,)
+  zope.interface.implements(IRule,
+                            IDivergenceController,
+                            IMovementCollectionUpdater,)
 
   # Default Properties
   property_sheets = (
diff --git a/bt5/erp5_mrp/DocumentTemplateItem/portal_components/document.erp5.TransformationSimulationRule.py b/bt5/erp5_mrp/DocumentTemplateItem/portal_components/document.erp5.TransformationSimulationRule.py
index e844625c72c5ddd925ed914af32495731dc9c6ee..aebe1367bb20e25f00f4a85f817231d55508dacf 100644
--- a/bt5/erp5_mrp/DocumentTemplateItem/portal_components/document.erp5.TransformationSimulationRule.py
+++ b/bt5/erp5_mrp/DocumentTemplateItem/portal_components/document.erp5.TransformationSimulationRule.py
@@ -30,9 +30,9 @@ from AccessControl import ClassSecurityInfo
 from Acquisition import aq_base
 from Products.ERP5Type import Permissions, PropertySheet
 from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
-from Products.ERP5.mixin.rule import RuleMixin
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin
-from Products.ERP5.mixin.movement_collection_updater import \
+from erp5.component.mixin.MovementCollectionUpdaterMixin import \
      MovementCollectionUpdaterMixin
 
 class TransformationSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
diff --git a/bt5/erp5_mrp/DocumentTemplateItem/portal_components/document.erp5.TransformationSourcingSimulationRule.py b/bt5/erp5_mrp/DocumentTemplateItem/portal_components/document.erp5.TransformationSourcingSimulationRule.py
index d88b661e999e5b14fbc6308f7571d0521445cb11..901eea2637b9428fecc941b887f5ce494c387297 100644
--- a/bt5/erp5_mrp/DocumentTemplateItem/portal_components/document.erp5.TransformationSourcingSimulationRule.py
+++ b/bt5/erp5_mrp/DocumentTemplateItem/portal_components/document.erp5.TransformationSourcingSimulationRule.py
@@ -29,9 +29,9 @@
 from AccessControl import ClassSecurityInfo
 from Acquisition import aq_base
 from Products.ERP5Type import Permissions, PropertySheet
-from Products.ERP5.mixin.rule import RuleMixin
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin
-from Products.ERP5.mixin.movement_collection_updater import \
+from erp5.component.mixin.MovementCollectionUpdaterMixin import \
      MovementCollectionUpdaterMixin
 
 class TransformationSourcingSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
diff --git a/bt5/erp5_real_time_inventory_accounting/DocumentTemplateItem/portal_components/document.erp5.InventoryAssetPriceAccountingSimulationRule.py b/bt5/erp5_real_time_inventory_accounting/DocumentTemplateItem/portal_components/document.erp5.InventoryAssetPriceAccountingSimulationRule.py
index 03747bbc7e58db0a84e4ff47337d75506a48c407..878b588d983f548d817faabd4a95777912c61016 100644
--- a/bt5/erp5_real_time_inventory_accounting/DocumentTemplateItem/portal_components/document.erp5.InventoryAssetPriceAccountingSimulationRule.py
+++ b/bt5/erp5_real_time_inventory_accounting/DocumentTemplateItem/portal_components/document.erp5.InventoryAssetPriceAccountingSimulationRule.py
@@ -36,7 +36,7 @@ class InventoryAssetPriceAccountingRuleMovementGenerator(InvoiceTransactionRuleM
   meta_type = 'ERP5 Inventory Asset Price Accounting Simulation Rule'
   portal_type = 'Inventory Asset Price Accounting Simulation Rule'
 
-  # XXX: Copy/paste from Products.ERP5.mixin.rule to support Transit use case
+  # XXX: Copy/paste from erp5.component.mixin.RuleMixin to support Transit use case
   def getGeneratedMovementList(self, movement_list=None, rounding=False):
     """
     Returns a list of movements generated by that rule.
diff --git a/bt5/erp5_simulation/DocumentTemplateItem/portal_components/document.erp5.ItemListSplitSolver.py b/bt5/erp5_simulation/DocumentTemplateItem/portal_components/document.erp5.ItemListSplitSolver.py
index 4d166bc47b7f7a895992901c0401c7fc2bbec404..c0dde7d5b8e5ece25a0650cd7019dc2bea24ee49 100644
--- a/bt5/erp5_simulation/DocumentTemplateItem/portal_components/document.erp5.ItemListSplitSolver.py
+++ b/bt5/erp5_simulation/DocumentTemplateItem/portal_components/document.erp5.ItemListSplitSolver.py
@@ -34,7 +34,7 @@ from Products.ERP5Type import Permissions, PropertySheet
 from Products.ERP5Type.XMLObject import XMLObject
 from erp5.component.mixin.SolverMixin import SolverMixin
 from erp5.component.mixin.ConfigurableMixin import ConfigurableMixin
-from Products.ERP5.MovementCollectionDiff import _getPropertyAndCategoryList
+from erp5.component.module.MovementCollectionDiff import _getPropertyAndCategoryList
 from erp5.component.interface.ISolver import ISolver
 from erp5.component.interface.IConfigurable import IConfigurable
 
diff --git a/bt5/erp5_simulation/DocumentTemplateItem/portal_components/document.erp5.QuantitySplitSolver.py b/bt5/erp5_simulation/DocumentTemplateItem/portal_components/document.erp5.QuantitySplitSolver.py
index e32d84f8d7c61323ff8b31ece34f2af2a2c64a0a..b1837e6332e07ecf0ddfa99911960f67a85ba0cb 100644
--- a/bt5/erp5_simulation/DocumentTemplateItem/portal_components/document.erp5.QuantitySplitSolver.py
+++ b/bt5/erp5_simulation/DocumentTemplateItem/portal_components/document.erp5.QuantitySplitSolver.py
@@ -35,7 +35,7 @@ from Products.ERP5Type.XMLObject import XMLObject
 from Products.ERP5Type.Globals import PersistentMapping
 from erp5.component.mixin.SolverMixin import SolverMixin
 from erp5.component.mixin.ConfigurableMixin import ConfigurableMixin
-from Products.ERP5.MovementCollectionDiff import _getPropertyAndCategoryList
+from erp5.component.module.MovementCollectionDiff import _getPropertyAndCategoryList
 from erp5.component.interface.ISolver import ISolver
 from erp5.component.interface.IConfigurable import IConfigurable
 
diff --git a/bt5/erp5_simulation/MixinTemplateItem/portal_components/mixin.erp5.EquivalenceTesterMixin.py b/bt5/erp5_simulation/MixinTemplateItem/portal_components/mixin.erp5.EquivalenceTesterMixin.py
index c535880b12be43cec42ae1f5db0a72b2b41d99c0..3ee94bc00d1b8f8ad53c4997c68ec1f04191fea7 100644
--- a/bt5/erp5_simulation/MixinTemplateItem/portal_components/mixin.erp5.EquivalenceTesterMixin.py
+++ b/bt5/erp5_simulation/MixinTemplateItem/portal_components/mixin.erp5.EquivalenceTesterMixin.py
@@ -30,7 +30,7 @@ import zope.interface
 from AccessControl import ClassSecurityInfo
 from Products.ERP5Type.Globals import InitializeClass
 from Products.ERP5Type import Permissions
-from Products.ERP5Type.DivergenceMessage import DivergenceMessage
+from erp5.component.module.DivergenceMessage import DivergenceMessage
 from Products.ERP5Type.Message import Message
 from Products.PythonScripts.standard import html_quote as h
 from zLOG import LOG, WARNING
diff --git a/bt5/erp5_simulation/ToolComponentTemplateItem/portal_components/tool.erp5.SolverProcessTool.py b/bt5/erp5_simulation/ToolComponentTemplateItem/portal_components/tool.erp5.SolverProcessTool.py
index 1381588488e51d967ffe55a1bf8bb75413d83d0f..738c0b7c02e4486c750021334343e41873f116a0 100644
--- a/bt5/erp5_simulation/ToolComponentTemplateItem/portal_components/tool.erp5.SolverProcessTool.py
+++ b/bt5/erp5_simulation/ToolComponentTemplateItem/portal_components/tool.erp5.SolverProcessTool.py
@@ -31,10 +31,11 @@ import zope.interface
 
 from AccessControl import ClassSecurityInfo
 from Products.ERP5Type.Globals import InitializeClass
-from Products.ERP5Type import Permissions, interfaces
+from Products.ERP5Type import Permissions
 from Products.ERP5Type.Tool.BaseTool import BaseTool
 
 from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
+from erp5.component.interface.IDivergenceController import IDivergenceController
 
 class SolverProcessTool(BaseTool):
   """ Container for solver processes.
@@ -48,7 +49,7 @@ class SolverProcessTool(BaseTool):
   security = ClassSecurityInfo()
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IDivergenceController, )
+  zope.interface.implements(IDivergenceController, )
 
   # IDivergenceController implementation
   security.declareProtected(Permissions.AccessContentsInformation,
diff --git a/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.BusinessProcess.py b/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.BusinessProcess.py
index f6b9a24791a1469214695af63c1e70bf4643803a..4bfd97cc8e1da7336d81f75de4f680d8162fb176 100644
--- a/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.BusinessProcess.py
+++ b/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.BusinessProcess.py
@@ -35,7 +35,7 @@ from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
 from Products.ERP5Type.XMLObject import XMLObject
 from Products.ERP5.Document.Path import Path
 from Products.ERP5.ExplanationCache import _getExplanationCache, _getBusinessLinkClosure
-from Products.ERP5.MovementCollectionDiff import _getPropertyAndCategoryList
+from erp5.component.module.MovementCollectionDiff import _getPropertyAndCategoryList
 from erp5.component.interface.IBusinessProcess import IBusinessProcess
 
 import zope.interface
diff --git a/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.DeliveryRootSimulationRule.py b/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.DeliveryRootSimulationRule.py
index 2cf5e2bac3072502665d8f925483095db1ca743b..0b3d8c551bb5c6b037e00873cd55a18cd4530f30 100644
--- a/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.DeliveryRootSimulationRule.py
+++ b/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.DeliveryRootSimulationRule.py
@@ -28,11 +28,14 @@
 
 import zope.interface
 from AccessControl import ClassSecurityInfo
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
-from Products.ERP5.mixin.rule import RuleMixin
+from Products.ERP5Type import Permissions, PropertySheet
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin
-from Products.ERP5.mixin.movement_collection_updater import \
+from erp5.component.mixin.MovementCollectionUpdaterMixin import \
      MovementCollectionUpdaterMixin
+from erp5.component.interface.IRule import IRule
+from erp5.component.interface.IDivergenceController import IDivergenceController
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class DeliveryRootSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   """
@@ -50,9 +53,9 @@ class DeliveryRootSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IRule,
-                            interfaces.IDivergenceController,
-                            interfaces.IMovementCollectionUpdater,)
+  zope.interface.implements(IRule,
+                            IDivergenceController,
+                            IMovementCollectionUpdater,)
 
   # Default Properties
   property_sheets = (
diff --git a/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.DeliverySimulationRule.py b/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.DeliverySimulationRule.py
index e96933ab543b28251398867b0051740935a7e935..45d9c6fcb727a7394fe830c392dac89f19c86f5b 100644
--- a/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.DeliverySimulationRule.py
+++ b/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.DeliverySimulationRule.py
@@ -28,11 +28,14 @@
 
 import zope.interface
 from AccessControl import ClassSecurityInfo
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
-from Products.ERP5.mixin.rule import RuleMixin
+from Products.ERP5Type import Permissions, PropertySheet
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin
-from Products.ERP5.mixin.movement_collection_updater import \
+from erp5.component.mixin.MovementCollectionUpdaterMixin import \
      MovementCollectionUpdaterMixin
+from erp5.component.interface.IRule import IRule
+from erp5.component.interface.IDivergenceController import IDivergenceController
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class DeliverySimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   """
@@ -50,9 +53,9 @@ class DeliverySimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IRule,
-                            interfaces.IDivergenceController,
-                            interfaces.IMovementCollectionUpdater,)
+  zope.interface.implements(IRule,
+                            IDivergenceController,
+                            IMovementCollectionUpdater,)
 
   # Default Properties
   property_sheets = (
diff --git a/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.OrderRootSimulationRule.py b/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.OrderRootSimulationRule.py
index ce184bc8a2776ac1b0dc319efe4a82f4e836c84e..02d971fa6f270e67f186959d7746925d039e84db 100644
--- a/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.OrderRootSimulationRule.py
+++ b/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.OrderRootSimulationRule.py
@@ -28,11 +28,14 @@
 
 import zope.interface
 from AccessControl import ClassSecurityInfo
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
-from Products.ERP5.mixin.rule import RuleMixin
+from Products.ERP5Type import Permissions, PropertySheet
+from erp5.component.mixin.RuleMixin import RuleMixin
 from Products.ERP5.mixin.movement_generator import MovementGeneratorMixin
-from Products.ERP5.mixin.movement_collection_updater import \
+from erp5.component.mixin.MovementCollectionUpdaterMixin import \
      MovementCollectionUpdaterMixin
+from erp5.component.interface.IRule import IRule
+from erp5.component.interface.IDivergenceController import IDivergenceController
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class OrderRootSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   """
@@ -50,9 +53,9 @@ class OrderRootSimulationRule(RuleMixin, MovementCollectionUpdaterMixin):
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IRule,
-                            interfaces.IDivergenceController,
-                            interfaces.IMovementCollectionUpdater,)
+  zope.interface.implements(IRule,
+                            IDivergenceController,
+                            IMovementCollectionUpdater,)
 
   # Default Properties
   property_sheets = (
diff --git a/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.TradeCondition.py b/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.TradeCondition.py
index da83871cb3b9c151c42e524c72d698742daad8fe..46164729f51f9cb3ba091b93d61848c230ce01f6 100644
--- a/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.TradeCondition.py
+++ b/bt5/erp5_trade/DocumentTemplateItem/portal_components/document.erp5.TradeCondition.py
@@ -40,6 +40,7 @@ from Products.ERP5.mixin.composition import _getEffectiveModel
 from Products.ERP5.Document.MappedValue import MappedValue
 from Products.ERP5.mixin.amount_generator import AmountGeneratorMixin
 from Products.ERP5.mixin.variated import VariatedMixin
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class TradeCondition(MappedValue, AmountGeneratorMixin, VariatedMixin):
   """
@@ -74,7 +75,7 @@ class TradeCondition(MappedValue, AmountGeneratorMixin, VariatedMixin):
 
   zope.interface.implements(interfaces.IAmountGenerator,
                             interfaces.IMovementGenerator,
-                            interfaces.IMovementCollectionUpdater,)
+                            IMovementCollectionUpdater,)
 
 
   # Mapped Value implementation
diff --git a/product/ERP5/TargetSolver/SplitAndDefer.py b/product/ERP5/TargetSolver/SplitAndDefer.py
index 16d5330b3c7ed32534f05d43951914ae69509b2d..a6aed2278cf9d2ad017d10fbd08dacfca366b705 100644
--- a/product/ERP5/TargetSolver/SplitAndDefer.py
+++ b/product/ERP5/TargetSolver/SplitAndDefer.py
@@ -27,7 +27,6 @@
 #
 ##############################################################################
 
-from Products.ERP5.MovementCollectionDiff import _getPropertyAndCategoryList
 from Products.ERP5Type.Globals import PersistentMapping
 from CopyToTarget import CopyToTarget
 from Acquisition import aq_base
@@ -64,6 +63,7 @@ class SplitAndDefer(CopyToTarget):
         split_index += 1
         new_id = "%s_split_%s" % (simulation_movement.getId(), split_index)
       # Adopt different dates for deferred movements
+      from erp5.component.module.MovementCollectionDiff import _getPropertyAndCategoryList
       movement_dict = _getPropertyAndCategoryList(simulation_movement)
       # new properties
       movement_dict.update(
diff --git a/product/ERP5/Tool/SimulationTool.py b/product/ERP5/Tool/SimulationTool.py
deleted file mode 100644
index 85b810016fdc7a205a8ecbfa19494817c5d408e8..0000000000000000000000000000000000000000
--- a/product/ERP5/Tool/SimulationTool.py
+++ /dev/null
@@ -1,3032 +0,0 @@
-##############################################################################
-#
-# 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 Products.CMFCore.utils import getToolByName
-
-from AccessControl import ClassSecurityInfo
-from Products.ERP5Type.Globals import InitializeClass, DTMLFile
-from Products.ERP5Type import Permissions
-from Products.ERP5Type.Tool.BaseTool import BaseTool
-from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
-
-from Products.ERP5 import _dtmldir
-
-from zLOG import LOG, PROBLEM, WARNING, INFO
-
-from Products.ERP5.Capacity.GLPK import solve
-from numpy import zeros, resize
-from DateTime import DateTime
-
-from Products.ERP5 import DeliverySolver
-from Products.ERP5 import TargetSolver
-from Products.PythonScripts.Utility import allow_class
-
-from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery, SimpleQuery
-
-from Shared.DC.ZRDB.Results import Results
-from Products.ERP5Type.Utils import mergeZRDBResults
-from App.Extensions import getBrain
-from MySQLdb import ProgrammingError
-from MySQLdb.constants.ER import NO_SUCH_TABLE
-
-from hashlib import md5
-from warnings import warn
-from cPickle import loads, dumps
-from copy import deepcopy
-
-MYSQL_MIN_DATETIME_RESOLUTION = 1/86400.
-
-class StockOptimisationError(Exception):
-    pass
-
-class SimulationTool(BaseTool):
-    """
-    The SimulationTool implements the ERP5
-    simulation algorithmics.
-
-
-    Examples of applications:
-
-    -
-
-    -
-    ERP5 main purpose:
-
-    -
-
-    -
-
-    """
-    id = 'portal_simulation'
-    meta_type = 'ERP5 Simulation Tool'
-    portal_type = 'Simulation Tool'
-    allowed_types = ( 'ERP5 Applied Rule', )
-
-    # Declarative Security
-    security = ClassSecurityInfo()
-
-    #
-    #   ZMI methods
-    #
-    security.declareProtected( Permissions.ManagePortal, 'manage_overview' )
-    manage_overview = DTMLFile( 'explainSimulationTool', _dtmldir )
-
-    def filtered_meta_types(self, user=None):
-      # Filters the list of available meta types.
-      all = SimulationTool.inheritedAttribute('filtered_meta_types')(self)
-      meta_types = []
-      for meta_type in self.all_meta_types():
-        if meta_type['name'] in self.allowed_types:
-          meta_types.append(meta_type)
-      return meta_types
-
-    def tpValues(self) :
-      """ show the content in the left pane of the ZMI """
-      return self.objectValues()
-
-    security.declarePrivate('manage_afterAdd')
-    def manage_afterAdd(self, item, container) :
-      """Init permissions right after creation.
-
-      Permissions in simulation tool are simple:
-       o Each member can access and create some content.
-       o Only manager can view, because simulation can be seen as
-         sensitive information.
-      """
-      item.manage_permission(Permissions.AddPortalContent,
-            ['Member', 'Author', 'Manager'])
-      item.manage_permission(Permissions.AccessContentsInformation,
-            ['Member', 'Auditor', 'Manager'])
-      item.manage_permission(Permissions.View,
-            ['Manager',])
-      BaseTool.inheritedAttribute('manage_afterAdd')(self, item, container)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'solveDelivery')
-    def solveDelivery(self, delivery, delivery_solver_name, target_solver_name,
-                      additional_parameters=None, **kw):
-      """
-        XXX obsoleted API
-
-        Solves a delivery by calling first DeliverySolver, then TargetSolver
-      """
-      return self._solveMovementOrDelivery(delivery, delivery_solver_name,
-          target_solver_name, delivery=1,
-          additional_parameters=additional_parameters, **kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'solveMovement')
-    def solveMovement(self, movement, delivery_solver_name, target_solver_name,
-                      additional_parameters=None, **kw):
-      """
-        XXX obsoleted API
-
-        Solves a movement by calling first DeliverySolver, then TargetSolver
-      """
-      return self._solveMovementOrDelivery(movement, delivery_solver_name,
-          target_solver_name, movement=1,
-          additional_parameters=additional_parameters, **kw)
-
-    def _solveMovementOrDelivery(self, document, delivery_solver_name,
-                                 target_solver_name, movement=0, delivery=0,
-                                 additional_parameters=None,**kw):
-      """
-        Solves a document by calling first DeliverySolver, then TargetSolver
-      """
-      if movement == delivery:
-        raise ValueError('Parameters movement and delivery have to be'
-                         ' different')
-
-      solve_result = []
-      for solver_name, solver_module in ((delivery_solver_name, DeliverySolver),
-                                         (target_solver_name, TargetSolver)):
-        result = None
-        if solver_name is not None:
-          solver_file_path = "%s.%s" % (solver_module.__name__,
-                                        solver_name)
-          __import__(solver_file_path)
-          solver_file = getattr(solver_module, solver_name)
-          solver_class = getattr(solver_file, solver_name)
-          solver = solver_class(additional_parameters=additional_parameters,
-              **kw)
-
-          if movement:
-            result = solver.solveMovement(document)
-          if delivery:
-            result = solver.solveDelivery(document)
-        solve_result.append(result)
-      return solve_result
-
-    #######################################################
-    # Stock Management
-
-    def _generatePropertyUidList(self, prop, as_text=0):
-      """
-      converts relative_url or text (single element or list or dict)
-        to an object usable by buildSQLQuery
-
-      as_text == 0: tries to lookup an uid from the relative_url
-      as_text == 1: directly passes the argument as text
-      """
-      if prop is None :
-        return []
-      category_tool = getToolByName(self, 'portal_categories')
-      property_uid_list = []
-      if isinstance(prop, str):
-        if not as_text:
-          prop_value = category_tool.getCategoryValue(prop)
-          if prop_value is None:
-            raise ValueError, 'Category %s does not exists' % prop
-          property_uid_list.append(prop_value.getUid())
-        else:
-          property_uid_list.append(prop)
-      elif isinstance(prop, (list, tuple)):
-        for property_item in prop :
-          if not as_text:
-            prop_value = category_tool.getCategoryValue(property_item)
-            if prop_value is None:
-              raise ValueError, 'Category %s does not exists' % property_item
-            property_uid_list.append(prop_value.getUid())
-          else:
-            property_uid_list.append(property_item)
-      elif isinstance(prop, dict):
-        tmp_uid_list = []
-        if isinstance(prop['query'], str):
-          prop['query'] = [prop['query']]
-        for property_item in prop['query'] :
-          if not as_text:
-            prop_value = category_tool.getCategoryValue(property_item)
-            if prop_value is None:
-              raise ValueError, 'Category %s does not exists' % property_item
-            tmp_uid_list.append(prop_value.getUid())
-          else:
-            tmp_uid_list.append(property_item)
-        if tmp_uid_list:
-          property_uid_list = {}
-          property_uid_list['operator'] = prop['operator']
-          property_uid_list['query'] = tmp_uid_list
-      return property_uid_list
-
-    def _getSimulationStateQuery(self, **kw):
-      simulation_state_dict = self._getSimulationStateDict(**kw)
-      return self._buildSimulationStateQuery(simulation_state_dict=simulation_state_dict)
-
-    def _buildSimulationStateQuery(self, simulation_state_dict, table='stock'):
-      simulation_state = simulation_state_dict.get('simulation_state')
-      if simulation_state is not None:
-        return SimpleQuery(**{table + '.simulation_state': simulation_state})
-      input_simulation_state = simulation_state_dict.get('input_simulation_state')
-      if input_simulation_state is not None:
-        simulation_query = ComplexQuery(
-          self._getIncreaseQuery(table, 'quantity', True),
-          SimpleQuery(**{table + '.simulation_state': input_simulation_state}),
-          logical_operator='AND',
-        )
-        output_simulation_state = simulation_state_dict.get('output_simulation_state')
-        if output_simulation_state is not None:
-          simulation_query = ComplexQuery(
-            simulation_query,
-            ComplexQuery(
-              self._getIncreaseQuery(table, 'quantity', False),
-              SimpleQuery(**{table + '.simulation_state': output_simulation_state}),
-              logical_operator='AND',
-            ),
-            logical_operator='OR'
-          )
-        return simulation_query
-
-    def _getSimulationStateDict(self, simulation_state=None, omit_transit=0,
-                                input_simulation_state=None,
-                                output_simulation_state=None,
-                                transit_simulation_state=None,
-                                strict_simulation_state=None):
-      """
-      This method is used in order to give what should be
-      the input_simulation_state or output_simulation_state
-      depending on many parameters
-      """
-      string_or_list = (str, list, tuple)
-      # Simulation States
-      # If strict_simulation_state is set, we directly put it into the dictionary
-      simulation_dict = {}
-      if strict_simulation_state:
-        if isinstance(simulation_state, string_or_list)\
-                and simulation_state:
-           simulation_query = SimpleQuery(
-                   **{'stock.simulation_state': simulation_state})
-      else:
-        # first, we evaluate simulation_state
-        sql_kw = {}
-        if simulation_state and isinstance(simulation_state, string_or_list):
-          if isinstance(simulation_state, str):
-            sql_kw['input_simulation_state'] = [simulation_state]
-            sql_kw['output_simulation_state'] = [simulation_state]
-          else:
-            sql_kw['input_simulation_state'] = simulation_state
-            sql_kw['output_simulation_state'] = simulation_state
-        # then, if omit_transit == 1, we evaluate (simulation_state -
-        # transit_simulation_state) for input_simulation_state
-        if omit_transit:
-          if isinstance(simulation_state, string_or_list)\
-                and simulation_state:
-            if isinstance(transit_simulation_state, string_or_list)\
-                  and transit_simulation_state:
-              # when we know both are usable, we try to calculate
-              # (simulation_state - transit_simulation_state)
-              if isinstance(simulation_state, str):
-                simulation_state = [simulation_state]
-              if isinstance(transit_simulation_state, str) :
-                transit_simulation_state = [transit_simulation_state]
-              delivered_simulation_state_list = []
-              for state in simulation_state :
-                if state not in transit_simulation_state :
-                  delivered_simulation_state_list.append(state)
-              sql_kw['input_simulation_state'] = delivered_simulation_state_list
-
-        # alternatively, the user can directly define input_simulation_state
-        # and output_simulation_state
-        if input_simulation_state and isinstance(input_simulation_state,
-                                                  string_or_list):
-          if isinstance(input_simulation_state, str):
-            input_simulation_state = [input_simulation_state]
-          sql_kw['input_simulation_state'] = input_simulation_state
-        if output_simulation_state and isinstance(output_simulation_state,
-                                                  string_or_list):
-          if isinstance(output_simulation_state, str):
-            output_simulation_state = [output_simulation_state]
-          sql_kw['output_simulation_state'] = output_simulation_state
-        # XXX In this case, we must not set sql_kw[input_simumlation_state] before
-        input_simulation_state = None
-        output_simulation_state = None
-        if sql_kw.has_key('input_simulation_state'):
-          input_simulation_state = sql_kw.get('input_simulation_state')
-        if sql_kw.has_key('output_simulation_state'):
-          output_simulation_state = sql_kw.get('output_simulation_state')
-        if input_simulation_state is not None \
-           or output_simulation_state is not None:
-          sql_kw.pop('input_simulation_state',None)
-          sql_kw.pop('output_simulation_state',None)
-        if input_simulation_state is not None:
-          if output_simulation_state is not None:
-            if input_simulation_state == output_simulation_state:
-              simulation_dict['simulation_state'] = input_simulation_state
-            else:
-              simulation_dict['input_simulation_state'] = input_simulation_state
-              simulation_dict['output_simulation_state'] = output_simulation_state
-          else:
-            simulation_dict['input_simulation_state'] = input_simulation_state
-        elif output_simulation_state is not None:
-          simulation_dict['simulation_state'] = output_simulation_state
-      return simulation_dict
-
-    def _getIncreaseQuery(self, table, column, increase, sql_catalog_id=None):
-      """
-      Returns a Query filtering rows depending on whether they represent an
-      increase or a decrease.
-      table (string)
-        Name of table to use as stock table.
-      column (string)
-        Name of interesting column. Supported values are:
-        - total_price for asset price increase/decrease
-        - quantity for quantity increase (aka input)/decrease (aka output)
-      increase (bool)
-        False: decreasing rows are kept
-        True: increasing rows are kept
-      sql_catalog_id (string or None)
-        Idenfitier of an SQLCatalog object relevant to table, or None for
-        default one.
-      """
-      if column == 'total_price':
-        dedicated_column = 'is_asset_increase'
-      elif column == 'quantity':
-        dedicated_column = 'is_input'
-      else:
-        raise ValueError('Unknown column %r' % (column, ))
-      if self.getPortalObject().portal_catalog.hasColumn(
-            dedicated_column,
-            sql_catalog_id,
-          ):
-        return SimpleQuery(**{dedicated_column: increase})
-      # Dedicated columns are not present, compute on the fly.
-      return ComplexQuery(
-        ComplexQuery(
-          SimpleQuery(comparison_operator='<', **{table + '.' + column: 0}),
-          SimpleQuery(**{table + '.is_cancellation': increase}),
-          logical_operator='AND',
-        ),
-        ComplexQuery(
-          SimpleQuery(comparison_operator='>=', **{table + '.' + column: 0}),
-          SimpleQuery(**{table + '.is_cancellation': not increase}),
-          logical_operator='AND',
-        ),
-        logical_operator='OR',
-      )
-
-    def _generateSQLKeywordDict(self, table='stock', **kw):
-        sql_kw, new_kw = self._generateKeywordDict(**kw)
-        return self._generateSQLKeywordDictFromKeywordDict(table=table,
-                 sql_kw=sql_kw, new_kw=new_kw)
-
-    def _generateSQLKeywordDictFromKeywordDict(self, table='stock', sql_kw={},
-                                               new_kw={}):
-        ctool = getToolByName(self, 'portal_catalog')
-        sql_kw = sql_kw.copy()
-        new_kw = new_kw.copy()
-
-        # Group-by expression  (eg. group_by=['node_uid'])
-        group_by = new_kw.pop('group_by_list', [])
-
-        # group by from stock table (eg. group_by_node=True)
-        # prepend table name to avoid ambiguities.
-        column_group_by = new_kw.pop('column_group_by', [])
-        if column_group_by:
-          group_by.extend(['%s.%s' % (table, x) for x in column_group_by])
-
-        # group by from related keys columns (eg. group_by_node_category=True)
-        related_key_group_by = new_kw.pop('related_key_group_by', [])
-        if related_key_group_by:
-          group_by.extend(['%s_%s' % (table, x) for x in related_key_group_by])
-
-        # group by involving a related key (eg. group_by=['product_line_uid'])
-        related_key_dict_passthrough_group_by = new_kw.get(
-                'related_key_dict_passthrough', {}).pop('group_by_list', [])
-        if isinstance(related_key_dict_passthrough_group_by, basestring):
-          related_key_dict_passthrough_group_by = (
-                related_key_dict_passthrough_group_by,)
-        group_by.extend(related_key_dict_passthrough_group_by)
-
-        if group_by:
-          new_kw['group_by_list'] = group_by
-
-        # select expression
-        select_dict = new_kw.setdefault('select_dict', {})
-        related_key_select_expression_list = new_kw.pop(
-                'related_key_select_expression_list', [])
-        for related_key_select in related_key_select_expression_list:
-          select_dict[related_key_select] = '%s_%s' % (table,
-                                                       related_key_select)
-
-        # Column values
-        column_value_dict = new_kw.pop('column_value_dict', {})
-        for key, value in column_value_dict.iteritems():
-          new_kw['%s.%s' % (table, key)] = value
-        # Related keys
-        # First, the passthrough (acts as default values)
-        for key, value in new_kw.pop('related_key_dict_passthrough', {})\
-            .iteritems():
-          new_kw[key] = value
-        # Second, calculated values
-        for key, value in new_kw.pop('related_key_dict', {}).iteritems():
-          new_kw['%s_%s' % (table, key)] = value
-        # Simulation states matched with input and output omission
-        def getSimulationQuery(simulation_dict, omit_dict):
-          simulation_query = self._buildSimulationStateQuery(
-            simulation_state_dict=simulation_dict,
-            table=table,
-          )
-          query_list = [
-            self._getIncreaseQuery(table, column, value)
-            for key, column, value in (
-              ('input',          'quantity',    False),
-              ('output',         'quantity',    True),
-              ('asset_increase', 'total_price', False),
-              ('asset_decrease', 'total_price', True),
-            )
-            if omit_dict.get(key)
-          ]
-          if query_list:
-            if simulation_query is not None:
-              query_list.append(simulation_query)
-            return ComplexQuery(
-              query_list,
-              logical_operator='AND',
-            )
-          return simulation_query
-        simulation_query = getSimulationQuery(
-          new_kw.pop('simulation_dict', {}),
-          new_kw.pop('omit_dict', {}),
-        )
-        reserved_kw = new_kw.pop('reserved_kw', None)
-        if reserved_kw is not None:
-          reserved_query = getSimulationQuery(
-            reserved_kw.pop('simulation_dict', {}),
-            reserved_kw.pop('omit_dict', {}),
-          )
-          if simulation_query is None:
-            simulation_query = reserved_query
-          elif reserved_query is not None:
-            simulation_query = ComplexQuery(
-              simulation_query,
-              reserved_query,
-              logical_operator='OR',
-            )
-        if simulation_query is not None:
-          new_kw['query'] = simulation_query
-
-        # Sort on
-        if 'sort_on' in new_kw:
-          table_column_list = ctool.getSQLCatalog().getTableColumnList(table)
-          sort_on = new_kw['sort_on']
-          new_sort_on = []
-          for column_id, sort_direction in sort_on:
-            if column_id in table_column_list:
-              column_id = '%s.%s' % (table, column_id)
-            new_sort_on.append((column_id, sort_direction))
-          new_kw['sort_on'] = tuple(new_sort_on)
-
-        # Remove some internal parameters that does not have any meaning for
-        # catalog
-        new_kw.pop('ignore_group_by', None)
-
-        catalog_sql_kw = ctool.buildSQLQuery(**new_kw)
-        from_table_dict = dict(sql_kw.pop('from_table_list', []))
-        for alias, table in catalog_sql_kw.pop('from_table_list', None) or []:
-          assert from_table_dict.get(alias) in (None, table), (
-            alias,
-            table,
-            from_table_dict[alias],
-          )
-          from_table_dict[alias] = table
-        sql_kw.update(catalog_sql_kw)
-        sql_kw['from_table_list'] = from_table_dict.items()
-        return sql_kw
-
-    def _generateKeywordDict(self,
-        # dates
-        from_date=None, to_date=None, at_date=None,
-        omit_mirror_date=1,
-        # instances
-        resource=None, node=None, payment=None,
-        section=None, mirror_section=None, item=None,
-        function=None, project=None, funding=None, payment_request=None,
-        transformed_resource=None, ledger=None,
-        # used for tracking
-        input=0, output=0,
-        # categories
-        resource_category=None, node_category=None, payment_category=None,
-        section_category=None, mirror_section_category=None,
-        function_category=None, project_category=None, funding_category=None,
-        ledger_category=None, payment_request_category=None,
-        # categories with strict membership
-        resource_category_strict_membership=None,
-        node_category_strict_membership=None,
-        payment_category_strict_membership=None,
-        section_category_strict_membership=None,
-        mirror_section_category_strict_membership=None,
-        function_category_strict_membership=None,
-        project_category_strict_membership=None,
-        funding_category_strict_membership=None,
-        ledger_category_strict_membership=None,
-        payment_request_category_strict_membership=None,
-        # simulation_state
-        strict_simulation_state=0,
-        simulation_state=None, transit_simulation_state = None, omit_transit=0,
-        input_simulation_state=None, output_simulation_state=None,
-        reserved_kw=None,
-        # variations
-        variation_text=None, sub_variation_text=None,
-        variation_category=None,
-        transformed_variation_text=None,
-        # uids
-        resource_uid=None, node_uid=None, section_uid=None, payment_uid=None,
-        mirror_node_uid=None, mirror_section_uid=None, function_uid=None,
-        project_uid=None, funding_uid=None, ledger_uid=None,
-        payment_request_uid=None,
-        # omit input and output
-        omit_input=0,
-        omit_output=0,
-        omit_asset_increase=0,
-        omit_asset_decrease=0,
-        # group by
-        group_by_node=0,
-        group_by_node_category=0,
-        group_by_node_category_strict_membership=0,
-        group_by_mirror_node=0,
-        group_by_mirror_node_category=0,
-        group_by_mirror_node_category_strict_membership=0,
-        group_by_section=0,
-        group_by_section_category=0,
-        group_by_section_category_strict_membership=0,
-        group_by_mirror_section=0,
-        group_by_mirror_section_category=0,
-        group_by_mirror_section_category_strict_membership=0,
-        group_by_payment=0,
-        group_by_payment_category=0,
-        group_by_payment_category_strict_membership=0,
-        group_by_sub_variation=0,
-        group_by_variation=0,
-        group_by_movement=0,
-        group_by_resource=0,
-        group_by_project=0,
-        group_by_project_category=0,
-        group_by_project_category_strict_membership=0,
-        group_by_funding=0,
-        group_by_funding_category=0,
-        group_by_funding_category_strict_membership=0,
-        group_by_ledger=0,
-        group_by_ledger_category=0,
-        group_by_ledger_category_strict_membership=0,
-        group_by_payment_request=0,
-        group_by_payment_request_category=0,
-        group_by_payment_request_category_strict_membership=0,
-        group_by_function=0,
-        group_by_function_category=0,
-        group_by_function_category_strict_membership=0,
-        group_by_date=0,
-        # sort_on
-        sort_on=None,
-        group_by=None,
-        # selection
-        selection_domain=None,
-        selection_report=None,
-        # keywords for related keys
-        **kw):
-      """
-      Generates keywords and calls buildSQLQuery
-
-      - omit_mirror_date: normally, date's parameters are only based on date
-        column. If 0, it also used the mirror_date column.
-      """
-      new_kw = {}
-      sql_kw = {
-        'from_table_list': [],
-        # Set of catalog aliases that must be joined in the ZSQLMethod ('foo'
-        # meaning something along the lines of 'foo.uid = stock.foo_uid')
-        'selection_domain_catalog_alias_set': [],
-        # input and output are used by getTrackingList
-        'input': input,
-        'output': output,
-        # BBB
-        'selection_domain': None,
-        'selection_report': None,
-      }
-
-      if selection_domain is None:
-        sql_kw['selection_domain_from_expression'] = None
-        sql_kw['selection_domain_where_expression'] = None
-      else:
-        # Pre-render selection_domain, as it is easier done here than in DTML.
-        if isinstance(selection_domain, dict):
-          selection_domain_dict = selection_domain
-        else:
-          selection_domain_dict = selection_domain.asDomainDict()
-        if 'ledger' in selection_domain_dict:
-          # XXX: what if both 'node' and 'ledger' are present ?
-          # Finer configuration may be needed here.
-          query_table_alias = 'ledger'
-        else:
-          query_table_alias = 'node'
-        selection_domain_sql_dict = self.getPortalObject().portal_catalog.buildSQLQuery(
-          selection_domain=selection_domain,
-          query_table_alias=query_table_alias,
-        )
-        sql_kw['selection_domain_from_expression'] = selection_domain_sql_dict['from_expression']
-        sql_kw['from_table_list'].extend(selection_domain_sql_dict['from_table_list'])
-        sql_kw['selection_domain_where_expression'] = selection_domain_sql_dict['where_expression']
-        sql_kw['selection_domain_catalog_alias_set'].append(query_table_alias)
-      if selection_report is not None:
-        new_kw['selection_report'] = selection_report
-
-      # Add sort_on parameter if defined
-      if sort_on is not None:
-        new_kw['sort_on'] = sort_on
-
-      class DictMixIn(dict):
-        def set(dictionary, key, value):
-          result = bool(value)
-          if result:
-            dictionary[key] = value
-          return result
-
-        def setUIDList(dictionary, key, value, as_text=0):
-          uid_list = self._generatePropertyUidList(value, as_text=as_text)
-          return dictionary.set(key, uid_list)
-
-      column_value_dict = DictMixIn()
-
-      if omit_mirror_date:
-        date_dict = {}
-        if from_date :
-          date_dict.setdefault('query', []).append(from_date)
-          date_dict['range'] = 'min'
-          if to_date :
-            date_dict.setdefault('query', []).append(to_date)
-            date_dict['range'] = 'minmax'
-          elif at_date :
-            date_dict.setdefault('query', []).append(at_date)
-            date_dict['range'] = 'minngt'
-        elif to_date :
-          date_dict.setdefault('query', []).append(to_date)
-          date_dict['range'] = 'max'
-        elif at_date :
-          date_dict.setdefault('query', []).append(at_date)
-          date_dict['range'] = 'ngt'
-        if date_dict:
-          column_value_dict['date'] = date_dict
-      else:
-        column_value_dict['date'] = {'query': [to_date], 'range': 'ngt'}
-        column_value_dict['mirror_date'] = {'query': [from_date], 'range': 'nlt'}
-
-      column_value_dict.set('resource_uid', resource_uid)
-      column_value_dict.set('payment_uid', payment_uid)
-      column_value_dict.set('project_uid', project_uid)
-      column_value_dict.set('funding_uid', funding_uid)
-      column_value_dict.set('ledger_uid', ledger_uid)
-      column_value_dict.set('payment_request_uid', payment_request_uid)
-      column_value_dict.set('function_uid', function_uid)
-      column_value_dict.set('section_uid', section_uid)
-      column_value_dict.set('node_uid', node_uid)
-      column_value_dict.set('mirror_node_uid', mirror_node_uid)
-      column_value_dict.set('mirror_section_uid', mirror_section_uid)
-      column_value_dict.setUIDList('resource_uid', resource)
-      column_value_dict.setUIDList('aggregate_uid', item)
-      column_value_dict.setUIDList('node_uid', node)
-      column_value_dict.setUIDList('payment_uid', payment)
-      column_value_dict.setUIDList('project_uid', project)
-      column_value_dict.setUIDList('funding_uid', funding)
-      column_value_dict.setUIDList('ledger_uid', ledger)
-      column_value_dict.setUIDList('payment_request_uid', payment_request)
-      column_value_dict.setUIDList('function_uid', function)
-
-      sql_kw['transformed_uid'] = self._generatePropertyUidList(transformed_resource)
-
-      column_value_dict.setUIDList('section_uid', section)
-      column_value_dict.setUIDList('mirror_section_uid', mirror_section)
-
-      # Handle variation_category as variation_text
-      if variation_category:
-        if variation_text:
-          raise ValueError(
-              "Passing both variation_category and variation_text is not supported")
-        warn("variation_category is deprecated, please use variation_text instead",
-             DeprecationWarning)
-        if isinstance(variation_category, basestring):
-          variation_category = (variation_category,)
-        # variation text is a \n separated list of variation categories, but without
-        # trailing nor leading \n
-        variation_text = [
-            "{}\n%".format(x) for x in variation_category] + [
-            "%\n{}\n%".format(x) for x in variation_category] + [
-            "%\n{}".format(x) for x in variation_category] + [
-            "{}".format(x) for x in variation_category]
-
-      column_value_dict.setUIDList('variation_text', variation_text,
-                                   as_text=1)
-      column_value_dict.setUIDList('sub_variation_text', sub_variation_text,
-                                   as_text=1)
-      new_kw['column_value_dict'] = column_value_dict.copy()
-
-      related_key_dict = DictMixIn()
-      # category membership
-      related_key_dict.setUIDList('resource_category_uid', resource_category)
-      related_key_dict.setUIDList('node_category_uid', node_category)
-      related_key_dict.setUIDList('project_category_uid', project_category)
-      related_key_dict.setUIDList('funding_category_uid', funding_category)
-      related_key_dict.setUIDList('ledger_category_uid', ledger_category)
-      related_key_dict.setUIDList('payment_request_category_uid', payment_request_category)
-      related_key_dict.setUIDList('function_category_uid', function_category)
-      related_key_dict.setUIDList('payment_category_uid', payment_category)
-      related_key_dict.setUIDList('section_category_uid', section_category)
-      related_key_dict.setUIDList('mirror_section_category_uid',
-                                  mirror_section_category)
-      # category strict membership
-      related_key_dict.setUIDList('resource_category_strict_membership_uid',
-                                  resource_category_strict_membership)
-      related_key_dict.setUIDList('node_category_strict_membership_uid',
-                                  node_category_strict_membership)
-      related_key_dict.setUIDList('project_category_strict_membership_uid',
-                                  project_category_strict_membership)
-      related_key_dict.setUIDList('funding_category_strict_membership_uid',
-                                  funding_category_strict_membership)
-      related_key_dict.setUIDList('ledger_category_strict_membership_uid',
-                                  ledger_category_strict_membership)
-      related_key_dict.setUIDList('payment_request_category_strict_membership_uid',
-                                  payment_request_category_strict_membership)
-      related_key_dict.setUIDList('function_category_strict_membership_uid',
-                                  function_category_strict_membership)
-      related_key_dict.setUIDList('payment_category_strict_membership_uid',
-                                  payment_category_strict_membership)
-      related_key_dict.setUIDList('section_category_strict_membership_uid',
-                                  section_category_strict_membership)
-      related_key_dict.setUIDList(
-        'mirror_section_category_strict_membership_uid',
-        mirror_section_category_strict_membership)
-
-      new_kw['related_key_dict'] = related_key_dict.copy()
-      new_kw['related_key_dict_passthrough'] = kw
-      # Check we do not get a known group_by
-      related_group_by = []
-      if group_by:
-        if isinstance(group_by, basestring):
-          group_by = (group_by,)
-        for value in group_by:
-          if value == "node_uid":
-            group_by_node = 1
-          elif value == 'mirror_node_uid':
-            group_by_mirror_node = 1
-          elif value ==  'section_uid':
-            group_by_section = 1
-          elif value == 'mirror_section_uid':
-            group_by_mirror_section = 1
-          elif value == 'payment_uid':
-            group_by_payment = 1
-          elif value == 'sub_variation_text':
-            group_by_sub_variation = 1
-          elif value == 'variation_text':
-            group_by_variation = 1
-          elif value == 'uid':
-            group_by_movement = 1
-          elif value == 'resource_uid':
-            group_by_resource = 1
-          elif value == 'project_uid':
-            group_by_project = 1
-          elif value == 'funding_uid':
-            group_by_funding = 1
-          elif value == 'ledger_uid':
-            group_by_ledger = 1
-          elif value == 'payment_request_uid':
-            group_by_payment_request = 1
-          elif value == "function_uid":
-            group_by_function = 1
-          elif value == 'date':
-            group_by_date = 1
-          else:
-            related_group_by.append(value)
-        if related_group_by:
-          new_kw['related_key_dict_passthrough']['group_by_list'] = related_group_by
-
-      new_kw['simulation_dict'] = self._getSimulationStateDict(
-        simulation_state=simulation_state,
-        omit_transit=omit_transit,
-        input_simulation_state=input_simulation_state,
-        output_simulation_state=output_simulation_state,
-        transit_simulation_state=transit_simulation_state,
-        strict_simulation_state=strict_simulation_state,
-      )
-      new_kw['omit_dict'] = {
-        'input': omit_input,
-        'output': omit_output,
-        'asset_increase': omit_asset_increase,
-        'asset_decrease': omit_asset_decrease,
-      }
-      if reserved_kw is not None:
-        if not isinstance(reserved_kw, dict):
-          # Not a dict when taken from URL, so, cast is needed
-          # to make pop method available
-          reserved_kw = dict(reserved_kw)
-        new_kw['reserved_kw'] = {
-          'omit_dict': {
-            'input': reserved_kw.pop('omit_input', False),
-            'output': reserved_kw.pop('omit_output', False),
-          },
-          'simulation_dict': self._getSimulationStateDict(**reserved_kw),
-        }
-
-      # build the group by expression
-      # if we group by a criterion, we also add this criterion to the select
-      # expression, unless it is already selected in Resource_zGetInventoryList
-      # the caller can also pass select_dict or select_list. select_expression,
-      # which is deprecated in ZSQLCatalog is not supported here.
-      select_dict = kw.get('select_dict', {})
-      select_dict.update(dict.fromkeys(list(kw.pop('select_list', [])) + related_group_by))
-      new_kw['select_dict'] = select_dict
-      related_key_select_expression_list = []
-
-      column_group_by_expression_list = []
-      related_key_group_by_expression_list = []
-      if group_by_node:
-        column_group_by_expression_list.append('node_uid')
-      if group_by_mirror_node:
-        column_group_by_expression_list.append('mirror_node_uid')
-      if group_by_section:
-        column_group_by_expression_list.append('section_uid')
-      if group_by_mirror_section:
-        column_group_by_expression_list.append('mirror_section_uid')
-      if group_by_payment:
-        column_group_by_expression_list.append('payment_uid')
-      if group_by_sub_variation:
-        column_group_by_expression_list.append('sub_variation_text')
-      if group_by_variation:
-        column_group_by_expression_list.append('variation_text')
-      if group_by_movement:
-        column_group_by_expression_list.append('uid')
-      if group_by_resource:
-        column_group_by_expression_list.append('resource_uid')
-      if group_by_project:
-        column_group_by_expression_list.append('project_uid')
-      if group_by_funding:
-        column_group_by_expression_list.append('funding_uid')
-      if group_by_ledger:
-        column_group_by_expression_list.append('ledger_uid')
-      if group_by_payment_request:
-        column_group_by_expression_list.append('payment_request_uid')
-      if group_by_function:
-        column_group_by_expression_list.append('function_uid')
-      if group_by_date:
-        column_group_by_expression_list.append('date')
-
-      if column_group_by_expression_list:
-        new_kw['column_group_by'] = column_group_by_expression_list
-
-      if group_by_section_category:
-        related_key_group_by_expression_list.append('section_category_uid')
-        related_key_select_expression_list.append('section_category_uid')
-      if group_by_section_category_strict_membership:
-        related_key_group_by_expression_list.append(
-            'section_category_strict_membership_uid')
-        related_key_select_expression_list.append(
-            'section_category_strict_membership_uid')
-      if group_by_mirror_section_category:
-        related_key_group_by_expression_list.append('mirror_section_category_uid')
-        related_key_select_expression_list.append('mirror_section_category_uid')
-      if group_by_mirror_section_category_strict_membership:
-        related_key_group_by_expression_list.append(
-            'mirror_section_category_strict_membership_uid')
-        related_key_select_expression_list.append(
-            'mirror_section_category_strict_membership_uid')
-      if group_by_node_category:
-        related_key_group_by_expression_list.append('node_category_uid')
-        related_key_select_expression_list.append('node_category_uid')
-      if group_by_node_category_strict_membership:
-        related_key_group_by_expression_list.append(
-            'node_category_strict_membership_uid')
-        related_key_select_expression_list.append(
-            'node_category_strict_membership_uid')
-      if group_by_mirror_node_category:
-        related_key_group_by_expression_list.append('mirror_node_category_uid')
-      if group_by_mirror_node_category_strict_membership:
-        related_key_group_by_expression_list.append(
-            'mirror_node_category_strict_membership_uid')
-        related_key_select_expression_list.append(
-            'mirror_node_category_strict_membership_uid')
-      if group_by_payment_category:
-        related_key_group_by_expression_list.append('payment_category_uid')
-        related_key_select_expression_list.append('payment_category_uid')
-      if group_by_payment_category_strict_membership:
-        related_key_group_by_expression_list.append(
-            'payment_category_strict_membership_uid')
-        related_key_select_expression_list.append(
-            'payment_category_strict_membership_uid')
-      if group_by_function_category:
-        related_key_group_by_expression_list.append('function_category_uid')
-        related_key_select_expression_list.append('function_category_uid')
-      if group_by_function_category_strict_membership:
-        related_key_group_by_expression_list.append(
-            'function_category_strict_membership_uid')
-        related_key_select_expression_list.append(
-            'function_category_strict_membership_uid')
-      if group_by_project_category:
-        related_key_group_by_expression_list.append('project_category_uid')
-        related_key_select_expression_list.append('project_category_uid')
-      if group_by_project_category_strict_membership:
-        related_key_group_by_expression_list.append(
-            'project_category_strict_membership_uid')
-        related_key_select_expression_list.append(
-            'project_category_strict_membership_uid')
-      if group_by_funding_category:
-        related_key_group_by_expression_list.append('funding_category_uid')
-        related_key_select_expression_list.append('funding_category_uid')
-      if group_by_funding_category_strict_membership:
-        related_key_group_by_expression_list.append(
-            'funding_category_strict_membership_uid')
-        related_key_select_expression_list.append(
-            'funding_category_strict_membership_uid')
-      if group_by_ledger_category:
-        related_key_group_by_expression_list.append('ledger_category_uid')
-        related_key_select_expression_list.append('ledger_category_uid')
-      if group_by_ledger_category_strict_membership:
-        related_key_group_by_expression_list.append(
-            'ledger_category_strict_membership_uid')
-        related_key_select_expression_list.append(
-            'ledger_category_strict_membership_uid')
-      if group_by_payment_category:
-        related_key_group_by_expression_list.append('payment_request_category_uid')
-        related_key_select_expression_list.append('payment_request_category_uid')
-      if group_by_payment_request_category_strict_membership:
-        related_key_group_by_expression_list.append(
-            'payment_request_category_strict_membership_uid')
-        related_key_select_expression_list.append(
-            'payment_request_category_strict_membership_uid')
-
-      if related_key_group_by_expression_list:
-        new_kw['related_key_group_by'] = related_key_group_by_expression_list
-      if related_key_select_expression_list:
-        new_kw['related_key_select_expression_list'] =\
-                related_key_select_expression_list
-
-      return sql_kw, new_kw
-
-    #######################################################
-    # Inventory management
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getInventory')
-    def getInventory(self, src__=0, simulation_period='', **kw):
-      """
-      Returns an inventory of a single or multiple resources on a single or
-      multiple nodes as a single float value
-
-      from_date (>=) - only take rows which date is >= from_date
-
-      to_date   (<)  - only take rows which date is < to_date
-
-      at_date   (<=) - only take rows which date is <= at_date
-
-      resource (only in generic API in simulation)
-
-      node           -  only take rows in stock table which node_uid is
-                        equivalent to node
-
-      payment        -  only take rows in stock table which payment_uid is
-                        equivalent to payment
-
-      section        -  only take rows in stock table which section_uid is
-                        equivalent to section
-
-      mirror_section -  only take rows in stock table which mirror_section_uid is
-                        mirror_section
-
-      resource_category  -  only take rows in stock table which
-                        resource_uid is member of resource_category
-
-      node_category   - only take rows in stock table which node_uid is
-                        member of section_category
-
-      payment_category   -  only take rows in stock table which payment_uid
-                            is in section_category
-
-      section_category -  only take rows in stock table which section_uid is
-                          member of section_category
-
-      mirror_section_category - only take rows in stock table which
-                                mirror_section_uid is member of
-                                mirror_section_category
-
-      node_filter     - only take rows in stock table which node_uid
-                        matches node_filter
-
-      payment_filter  - only take rows in stock table which payment_uid
-                        matches payment_filter
-
-      section_filter  - only take rows in stock table which section_uid
-                        matches section_filter
-
-      mirror_section_filter - only take rows in stock table which
-                              mirror_section_uid matches mirror_section_filter
-
-      variation_text -  only take rows in stock table with specified
-                        variation_text.
-                        XXX this way of implementing variation selection is far
-                        from perfect
-
-      sub_variation_text - only take rows in stock table with specified
-                        variation_text
-
-      variation_category - variation or list of possible variations (it is not
-                        a cross-search ; SQL query uses OR).
-                        Deprecated, use variation_text.
-
-      simulation_state - only take rows with specified simulation_state
-
-      transit_simulation_state - specifies which states are transit states
-
-      omit_transit   -  do not evaluate transit_simulation_state
-
-      input_simulation_state - only take rows with specified simulation_state
-                        and quantity > 0
-
-      output_simulation_state - only take rows with specified simulation_state
-                        and quantity < 0
-
-      ignore_variation -  do not take into account variation in inventory
-                        calculation (useless on getInventory, but useful on
-                        getInventoryList)
-
-      standardise    -  provide a standard quantity rather than an SKU (XXX
-                        not implemented yet)
-
-      omit_simulation - doesn't take into account simulation movements
-
-      only_accountable - Only take into account accountable movements. By
-                        default, only movements for which isAccountable() is
-                        true will be taken into account.
-
-      omit_input     -  doesn't take into account movement with quantity > 0
-
-      omit_output    -  doesn't take into account movement with quantity < 0
-
-      omit_asset_increase - doesn't take into account movement with asset_price > 0
-
-      omit_asset_decrease - doesn't take into account movement with asset_price < 0
-
-      selection_domain, selection_report - see ListBox
-
-      group_by_variation - (useless on getInventory, but useful on
-                        getInventoryList)
-
-      group_by_node  -  (useless on getInventory, but useful on
-                        getInventoryList)
-
-      group_by_mirror_node - (useless on getInventory, but useful on
-                        getInventoryList)
-
-      group_by_sub_variation - (useless on getInventory, but useful on
-                        getInventoryList)
-
-      group_by_movement - (useless on getInventory, but useful on
-                        getInventoryList)
-
-      precision - the precision used to round quantities and prices.
-
-      metric_type   - convert the results to a specific metric_type
-
-      quantity_unit - display results using this specific quantity unit
-
-      transformed_resource - one, or several resources. list resources that can
-                             be produced using those resources as input in a
-                             transformation.
-                             relative_resource_url for each returned line will
-                             point to the transformed resource, while the stock
-                             will be the stock of the produced resource,
-                             expressed in number of transformed resources.
-      transformed_variation_text - to be used with transformed_resource, to
-                                   to refine the transformation selection only
-                                   to those using variated resources as input.
-
-      **kw           -  if we want extended selection with more keywords (but
-                        bad performance) check what we can do with
-                        buildSQLQuery
-
-      NOTE: we may want to define a parameter so that we can select the kind of
-      inventory statistics we want to display (ex. sum, average, cost, etc.)
-      """
-      # JPS: this is a hint for implementation of xxx_filter arguments
-      # node_uid_count = portal_catalog.countResults(**node_filter)
-      # if node_uid_count not too big:
-      #   node_uid_list = cache(portal_catalog(**node_filter))
-      #   pass this list to ZSQL method
-      # else:
-      #   build a table in MySQL
-      #   and join that table with the stock table
-      method = getattr(self,'get%sInventoryList' % simulation_period)
-      kw['ignore_group_by'] = 1
-      result = method(inventory_list=0, src__=src__, **kw)
-      if src__:
-        return result
-
-      total_result = 0.0
-      if len(result) > 0:
-        if len(result) != 1:
-          raise ValueError, 'Sorry we must have only one'
-        result = result[0]
-
-        if hasattr(result, "converted_quantity"):
-          total_result = result.converted_quantity
-        else:
-          inventory = result.total_quantity
-          if inventory is not None:
-            total_result = inventory
-
-      return total_result
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getCurrentInventory')
-    def getCurrentInventory(self, **kw):
-      """
-      Returns current inventory
-      """
-      return self.getInventory(simulation_period='Current', **kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getAvailableInventory')
-    def getAvailableInventory(self, **kw):
-      """
-      Returns available inventory
-      (current inventory - reserved_inventory)
-      """
-      return self.getInventory(simulation_period='Available', **kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getFutureInventory')
-    def getFutureInventory(self, **kw):
-      """
-      Returns future inventory
-      """
-      return self.getInventory(simulation_period='Future', **kw)
-
-    def _getDefaultGroupByParameters(self, ignore_group_by=0,
-        group_by_node=0, group_by_mirror_node=0,
-        group_by_section=0, group_by_mirror_section=0,
-        group_by_payment=0, group_by_project=0, group_by_funding=0,
-        group_by_ledger=0, group_by_function=0,
-        group_by_variation=0, group_by_sub_variation=0,
-        group_by_movement=0, group_by_date=0,
-        group_by_section_category=0,
-        group_by_section_category_strict_membership=0,
-        group_by_resource=None,
-        group_by=None,
-        **ignored):
-      """
-      Set defaults group_by parameters
-
-      If ignore_group_by is true, this function returns an empty dict.
-
-      If any group-by is provided, automatically group by resource aswell
-      unless group_by_resource is explicitely set to false.
-      If no group by is provided, use the default group by: movement, node and
-      resource.
-      """
-      new_group_by_dict = {}
-      if not ignore_group_by and group_by is None:
-        if group_by_node or group_by_mirror_node or group_by_section or \
-           group_by_project or group_by_funding or group_by_ledger or \
-           group_by_function or group_by_mirror_section or group_by_payment or \
-           group_by_sub_variation or group_by_variation or \
-           group_by_movement or group_by_date or group_by_section_category or\
-           group_by_section_category_strict_membership:
-          if group_by_resource is None:
-            group_by_resource = 1
-          new_group_by_dict['group_by_resource'] = group_by_resource
-        elif group_by_resource is None:
-          new_group_by_dict['group_by_movement'] = 1
-          new_group_by_dict['group_by_node'] = 1
-          new_group_by_dict['group_by_resource'] = 1
-      return new_group_by_dict
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getInventoryList')
-    def getInventoryList(self, src__=0, optimisation__=True,
-                         ignore_variation=0, standardise=0,
-                         omit_simulation=0,
-                         only_accountable=True,
-                         default_stock_table='stock',
-                         statistic=0, inventory_list=1,
-                         precision=None, connection_id=None,
-                         **kw):
-      """
-        Returns a list of inventories for a single or multiple
-        resources on a single or multiple nodes, grouped by resource,
-        node, section, etc. Every line defines an inventory value for
-        a given group of resource, node, section.
-        NOTE: we may want to define a parameter so that we can select
-        the kind of inventory statistics we want to display (ex. sum,
-        average, cost, etc.)
-
-        Optimisation queries.
-        Optimisation of a stock lookup is done to avoid a table scan
-        of all lines corresponding to a given node, section or payment,
-        because they grow with time and query time should not.
-        First query: Fetch fitting full inventory dates.
-          For each node, section or payment, find the first anterior full
-          inventory.
-        Second query: Fetch full inventory amounts.
-          Fetch values of inventory identified in the first query.
-        Third query: Classic stock table read.
-          Fetch all rows in stock table which are posterior to the inventory.
-        Final result
-          Add results of the second and third queries, and return it.
-
-        Missing optimisations:
-         - In a getInventory case where everything is specified for the
-           resource, it's not required for the inventory to be full, it
-           just need to be done for the right resource.
-           If the resource isn't completely defined, we require inventory
-           to be full, which is implemented.
-         - Querying multiple nodes/categories/payments in one call prevents
-           from using optimisation, it should be equivalent to multiple calls
-           on individual nodes/categories/payments.
-         -
-      """
-      getCategory = self.getPortalObject().portal_categories.getCategoryUid
-
-      result_column_id_dict = {}
-
-      metric_type = kw.pop('metric_type', None)
-      quantity_unit = kw.pop('quantity_unit', None)
-      quantity_unit_uid = None
-
-      if quantity_unit is not None:
-
-        if isinstance(quantity_unit, str):
-          quantity_unit_uid = getCategory(quantity_unit, 'quantity_unit')
-          if quantity_unit_uid is not None:
-            result_column_id_dict['converted_quantity'] = None
-            if metric_type is None:
-              # use the default metric type
-              metric_type = quantity_unit.split("/", 1)[0]
-        elif isinstance(quantity_unit, (int, float)):
-          # quantity_unit used to be a numerical parameter..
-          raise ValueError('Numeric values for quantity_unit are not supported')
-
-
-      convert_quantity_result = False
-      if metric_type is not None:
-        metric_type_uid = getCategory(metric_type, 'metric_type')
-
-        if metric_type_uid is not None:
-          convert_quantity_result = True
-          kw['metric_type_uid'] = Query(
-                                    metric_type_uid=metric_type_uid,
-                                    table_alias_list=(("measure", "measure"),))
-
-      if src__:
-        sql_source_list = []
-      # If no group at all, give a default sort group by
-      kw.update(self._getDefaultGroupByParameters(**kw))
-      base_inventory_kw = {
-        'stock_table_id': default_stock_table,
-        'src__': src__,
-        'ignore_variation': ignore_variation,
-        'standardise': standardise,
-        'omit_simulation': omit_simulation,
-        'only_accountable': only_accountable,
-        'precision': precision,
-        'inventory_list': inventory_list,
-        'connection_id': connection_id,
-        'statistic': statistic,
-        'convert_quantity_result': convert_quantity_result,
-        'quantity_unit_uid': quantity_unit_uid,
-      }
-      # Get cached data
-      if getattr(self, "Resource_zGetInventoryCacheResult", None) is not None and \
-              optimisation__ and (not kw.get('from_date')) and \
-              'transformed_resource' not in kw:
-        # Here is the different kind of date
-        # from_date : >=
-        # to_date   : <
-        # at_date   : <=
-        # As we just have from_date, it means that we must use
-        # the to_date for the cache in order to avoid double computation
-        # of the same line
-        at_date = kw.pop("at_date", None)
-        if at_date is None:
-          to_date = kw.pop("to_date", None)
-        else:
-          # add one second so that we can use to_date
-          to_date = at_date + MYSQL_MIN_DATETIME_RESOLUTION
-        try:
-          cached_result, cached_date = self._getCachedInventoryList(
-              to_date=to_date,
-              sql_kw=kw,
-              **base_inventory_kw)
-        except StockOptimisationError:
-          cached_result = []
-          kw['to_date'] = to_date
-        else:
-          if src__:
-            sql_source_list.extend(cached_result)
-          # Now must generate query for date diff
-          kw['to_date'] = to_date
-          kw['from_date'] = cached_date
-      else:
-        cached_result = []
-      sql_kw, new_kw = self._generateKeywordDict(**kw)
-      # Copy kw content as _generateSQLKeywordDictFromKeywordDict
-      # remove some values from it
-      try:
-        new_kw_copy = deepcopy(new_kw)
-      except TypeError:
-        # new_kw contains wrong parameters
-        # as optimisation has already been disable we
-        # do not care about the deepcopy
-        new_kw_copy = new_kw
-      stock_sql_kw = self._generateSQLKeywordDictFromKeywordDict(
-          table=default_stock_table, sql_kw=sql_kw, new_kw=new_kw_copy)
-      stock_sql_kw.update(base_inventory_kw)
-      delta_result = self.Resource_zGetInventoryList(
-          **stock_sql_kw)
-      if src__:
-        sql_source_list.append(delta_result)
-        result = ';\n-- NEXT QUERY\n'.join(sql_source_list)
-      else:
-        if cached_result:
-            result = self._addBrainResults(delta_result, cached_result, new_kw)
-        else:
-            result = delta_result
-      return result
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getInventoryCacheLag')
-    def getInventoryCacheLag(self):
-      """
-      Returns a duration, in days, for stock cache management.
-      If data in stock cache is older than lag compared to query's date
-      (at_date or to_date), then it becomes a "soft miss": use found value,
-      but add a new entry to cache at query's date minus half the lag.
-      So this value should be:
-      - Small enough that few enough rows need to be table-scanned for
-        average queries (probably queries against current date).
-      - Large enough that few enough documents get modified past that date,
-        otherwise cache entries would be removed from cache all the time.
-      """
-      return self.SimulationTool_getInventoryCacheLag()
-
-    def _getCachedInventoryList(self, to_date, sql_kw, stock_table_id, src__=False, **kw):
-      """
-      Try to get a cached inventory list result
-      If not existing, fill the cache
-      """
-      Resource_zGetInventoryList = self.Resource_zGetInventoryList
-      # Generate the SQL source without date parameter
-      # This will be the cache key
-      try:
-          no_date_kw = deepcopy(sql_kw)
-      except TypeError:
-          LOG("SimulationTool._getCachedInventoryList", WARNING,
-              "Failed copying sql_kw, disabling stock cache",
-              error=True)
-          raise StockOptimisationError
-      no_date_sql_kw, no_date_new_kw = self._generateKeywordDict(**no_date_kw)
-      no_date_stock_sql_kw = self._generateSQLKeywordDictFromKeywordDict(
-        table=stock_table_id, sql_kw=no_date_sql_kw,
-        new_kw=no_date_new_kw)
-      kw.update(no_date_stock_sql_kw)
-      if src__:
-        sql_source_list = []
-      # Generate the cache key (md5 of query source)
-      sql_text_hash = md5(Resource_zGetInventoryList(
-        stock_table_id=stock_table_id,
-        src__=1,
-        **kw)).digest()
-      # Try to get result from cache
-      Resource_zGetInventoryCacheResult = self.Resource_zGetInventoryCacheResult
-      inventory_cache_kw = {'query': sql_text_hash}
-      if to_date is not None:
-        inventory_cache_kw['date'] = to_date
-      try:
-          cached_sql_result = Resource_zGetInventoryCacheResult(**inventory_cache_kw)
-      except ProgrammingError, (code, _):
-          if code != NO_SUCH_TABLE:
-            raise
-          # First use of the optimisation, we need to create the table
-          LOG("SimulationTool._getCachedInventoryList", INFO,
-              "Creating inventory cache stock")
-          if src__:
-              sql_source_list.append(self.SimulationTool_zCreateInventoryCache(src__=1))
-          else:
-              self.SimulationTool_zCreateInventoryCache()
-          cached_sql_result = None
-
-      if src__:
-        sql_source_list.append(Resource_zGetInventoryCacheResult(src__=1, **inventory_cache_kw))
-      if cached_sql_result:
-        brain_result = loads(cached_sql_result[0].result)
-        # Rebuild the brains
-        cached_result = Results(
-          (brain_result['items'], brain_result['data']),
-          brains=getBrain(
-            Resource_zGetInventoryList.class_file_,
-            Resource_zGetInventoryList.class_name_,
-          ),
-          parent=self,
-        )
-      else:
-        cached_result = []
-      cache_lag = self.getInventoryCacheLag()
-      if cached_sql_result and (to_date is None or (to_date - DateTime(cached_sql_result[0].date) < cache_lag)):
-        cached_date = DateTime(cached_sql_result[0].date)
-        result = cached_result
-      elif to_date is not None:
-        # Cache miss, or hit with old data: store a new entry in cache.
-        # Don't store it at to_date, as it risks being flushed soon (ie, when
-        # any document older than to_date gets reindexed in stock table).
-        # Don't store it at to_date - cache_lag, as it would risk expiring
-        # soon as we store it (except if to_date is fixed for many queries,
-        # which we cannot tell here).
-        # So store it at half the cache_lag before to_date.
-        cached_date = to_date - cache_lag / 2
-        new_cache_kw = deepcopy(sql_kw)
-        if cached_result:
-          # We can use cached result to generate new cache result
-          new_cache_kw['from_date'] = DateTime(cached_sql_result[0].date)
-        sql_kw, new_kw = self._generateKeywordDict(
-          to_date=cached_date,
-          **new_cache_kw)
-        kw.update(self._generateSQLKeywordDictFromKeywordDict(
-            table=stock_table_id,
-            sql_kw=sql_kw,
-            new_kw=new_kw,
-          )
-        )
-        new_result = Resource_zGetInventoryList(
-          stock_table_id=stock_table_id,
-          src__=src__,
-          **kw)
-        if src__:
-          sql_source_list.append(new_result)
-        else:
-          result = self._addBrainResults(new_result, cached_result, new_kw)
-          self.Resource_zInsertInventoryCacheResult(
-            query=sql_text_hash,
-            date=cached_date,
-            result=dumps({
-              'items': result.__items__,
-              'data': result._data,
-            }),
-          )
-      else:
-        # Cache miss and this getInventory() not specifying to_date,
-        # and other getInventory() have not created usable caches.
-        # In such case, do not create cache, do not use cache.
-        result = []
-        cached_date = None
-      if src__:
-        result = sql_source_list
-      return result, cached_date
-
-    def _addBrainResults(self, first_result, second_result, new_kw):
-      """
-      Build a Results which is the addition of two other result
-      """
-      # This part defined key to group lines from different Results
-      group_by_id_list = []
-      group_by_id_list_append = group_by_id_list.append
-
-      for group_by_id in new_kw.get('column_group_by', []):
-        if group_by_id == 'uid':
-          group_by_id_list_append('stock_uid')
-        else:
-          group_by_id_list_append(group_by_id)
-      # Add related key group by
-      related_key_dict_passthrough = new_kw.get("related_key_dict_passthrough", {})
-      group_by_list = related_key_dict_passthrough.get('group_by_list', [])
-      cannot_group_by = set(group_by_list).difference(
-        related_key_dict_passthrough.get('select_list', []),
-      )
-      if cannot_group_by:
-        # XXX-Aurel : to review & change, must prevent coming here before
-        raise ValueError("Impossible to group by %s" % (cannot_group_by, ))
-      group_by_id_list += group_by_list
-
-      if len(group_by_id_list):
-        def getInventoryListKey(line):
-          """
-          Generate a key based on values used in SQL group_by
-          """
-          return tuple([line[x] for x in group_by_id_list])
-
-      else:
-        def getInventoryListKey(line):
-          """
-          Return a dummy key, all line will be summed
-          """
-          return "dummy"
-      result_column_id_dict = {
-        'inventory': None,
-        'total_quantity': None,
-        'total_price': None
-      }
-      def addLineValues(line_a=None, line_b=None):
-        """
-        Add columns of 2 lines and return a line with same
-        schema. If one of the parameters is None, returns the
-        other parameters.
-
-        Arithmetic modifications on additions:
-        None + x = x
-        None + None = None
-        """
-        if line_a is None:
-          return line_b
-        if line_b is None:
-          return line_a
-        # Create a new Shared.DC.ZRDB.Results.Results.__class__
-        # instance to add the line values.
-        # the logic for the 5 lines below is taken from
-        # Shared.DC.ZRDB.Results.Results.__getitem__
-        Result = line_a.__class__
-        parent = line_a.aq_parent
-        result = Result((), parent)
-        try:
-          # We must copy the path so that getObject works
-          setattr(result, 'path', line_a.path)
-        except ValueError: # XXX: ValueError ? really ?
-          # getInventory return no object, so no path available
-          pass
-        if parent is not None:
-          result = result.__of__(parent)
-        for key in line_a.__record_schema__:
-          value = line_a[key]
-          if key in result_column_id_dict:
-            value_b = line_b[key]
-            if None not in (value, value_b):
-              result[key] = value + value_b
-            elif value is not None:
-              result[key] = value
-            else:
-              result[key] = value_b
-          elif line_a[key] == line_b[key]:
-            result[key] = line_a[key]
-          elif key not in ('date', 'stock_uid', 'path'):
-            # There are 2 possible reasons to end up here:
-            # - key corresponds to a projected column for which are neither
-            #   known aggregated columns (in which case they should be in
-            #   result_column_id_dict) nor part of grouping columns, and the
-            #   result happens to be unstable. There are cases in ERP5 where
-            #   such result is suposed to be stable, for example
-            #   group_by=('xxx_uid'), selection_list=('xxx_path') because the
-            #   relation is bijective (although the database doesn't know it).
-            #   These should result in stable results (but don't necessarily
-            #   do, ex: xxx_title when object title has been changed between
-            #   cache fill and cache lookup).
-            # - line_a and line_b are indeed mismatched, and code calling us
-            #   has a bug.
-            LOG('InventoryTool.getInventoryList.addLineValues',
-              PROBLEM,
-              'mismatch for %s column: %s and %s' % (
-                key, line_a[key], line_b[key]))
-        return result
-      # Add lines
-      inventory_list_dict = {}
-      for line_list in (first_result, second_result):
-        for line in line_list:
-          line_key = getInventoryListKey(line)
-          line_a = inventory_list_dict.get(line_key)
-          inventory_list_dict[line_key] = addLineValues(line_a, line)
-      sorted_inventory_list = inventory_list_dict.values()
-      # Sort results manually when required
-      sort_on = new_kw.get('sort_on')
-      if sort_on:
-        def cmp_inventory_line(line_a, line_b):
-          """
-            Compare 2 inventory lines and sort them according to
-            sort_on parameter.
-          """
-          result = 0
-          for key, sort_direction in sort_on:
-            try:
-              result = cmp(line_a[key], line_b[key])
-            except KeyError:
-              raise Exception('Impossible to sort result since columns sort '
-                'happens on are not available in result: %r' % (key, ))
-            if result:
-              if not sort_direction.upper().startswith('A'):
-                # Default sort is ascending, if a sort is given and
-                # it does not start with an 'A' then reverse sort.
-                # Tedious syntax checking is MySQL's job, and
-                # happened when queries were executed.
-                result *= -1
-              break
-          return result
-        sorted_inventory_list.sort(cmp_inventory_line)
-      # Brain is rebuild properly using tuple not r instance
-      column_list = first_result._searchable_result_columns()
-      column_name_list = [x['name'] for x in column_list]
-      # Rebuild a result object based on added results
-      Resource_zGetInventoryList = self.Resource_zGetInventoryList
-      return Results(
-        (column_list, tuple([tuple([getattr(y, x) for x in column_name_list]) \
-          for y in sorted_inventory_list])),
-        parent=self,
-        brains=getBrain(
-          Resource_zGetInventoryList.class_file_,
-          Resource_zGetInventoryList.class_name_,
-        ),
-      )
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getConvertedInventoryList')
-    def getConvertedInventoryList(self, simulation_period='', **kw):
-      """
-      Return list of inventory with a 'converted_quantity' additional column,
-      which contains the sum of measurements for the specified metric type,
-      expressed in the 'quantity_unit' unit.
-
-      metric_type   - category
-      quantity_unit - category
-      """
-
-      warn('getConvertedInventoryList is Deprecated, use ' \
-           'getInventory instead.', DeprecationWarning)
-
-      method = getattr(self,'get%sInventoryList' % simulation_period)
-
-      return method(**kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getAllInventoryList')
-    def getAllInventoryList(self, src__=0, **kw):
-      """
-      Returns list of inventory, for all periods.
-      Performs 1 SQL request for each simulation state, and merge the results.
-      Rename relevant columns with a '${simulation}_' prefix
-      (ex: 'total_price' -> 'current_total_price').
-      """
-      columns = ('total_quantity', 'total_price', 'converted_quantity')
-
-      # Guess the columns to use to identify each row, by looking at the GROUP
-      # clause. Note that the call to 'mergeZRDBResults' will crash if the GROUP
-      # clause contains a column not requested in the SELECT clause.
-      kw.update(self._getDefaultGroupByParameters(**kw), ignore_group_by=1)
-      group_by_list = self._generateKeywordDict(**kw)[1].get('group_by_list', ())
-
-      results = []
-      edit_result = {}
-      get_false_value = lambda row, column_name: row.get(column_name) or 0
-
-      for simulation in 'current', 'available', 'future':
-        method = getattr(self, 'get%sInventoryList' % simulation.capitalize())
-        rename = {'inventory': None} # inventory column is deprecated
-        for column in columns:
-          rename[column] = new_name = '%s_%s' % (simulation, column)
-          edit_result[new_name] = get_false_value
-        results += (method(src__=src__, **kw), rename),
-
-      if src__:
-        return ';\n-- NEXT QUERY\n'.join(r[0] for r in results)
-      return mergeZRDBResults(results, group_by_list, edit_result)
-
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getCurrentInventoryList')
-    def getCurrentInventoryList(self, omit_transit=1,
-                                transit_simulation_state=None, **kw):
-      """
-        Returns list of current inventory grouped by section or site
-      """
-      portal = self.getPortalObject()
-      kw['simulation_state'] = portal.getPortalCurrentInventoryStateList() + \
-                               portal.getPortalTransitInventoryStateList()
-      if transit_simulation_state is None:
-        transit_simulation_state = portal.getPortalTransitInventoryStateList()
-
-      return self.getInventoryList(
-                            omit_transit=omit_transit,
-                            transit_simulation_state=transit_simulation_state,
-                            **kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getAvailableInventoryList')
-    def getAvailableInventoryList(self, omit_transit=1, transit_simulation_state=None, **kw):
-      """
-        Returns list of current inventory grouped by section or site
-      """
-      portal = self.getPortalObject()
-      if transit_simulation_state is None:
-        transit_simulation_state = portal.getPortalTransitInventoryStateList()
-      kw['simulation_state'] = portal.getPortalCurrentInventoryStateList() + \
-                               portal.getPortalTransitInventoryStateList()
-      reserved_kw = {'simulation_state': portal.getPortalReservedInventoryStateList(),
-                     'transit_simulation_state': transit_simulation_state,
-                     'omit_input': 1}
-      return self.getInventoryList(reserved_kw=reserved_kw, omit_transit=omit_transit,
-                     transit_simulation_state=transit_simulation_state, **kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getFutureInventoryList')
-    def getFutureInventoryList(self, **kw):
-      """
-        Returns list of future inventory grouped by section or site
-      """
-      portal = self.getPortalObject()
-      kw['simulation_state'] = portal.getPortalFutureInventoryStateList() + \
-                               portal.getPortalTransitInventoryStateList() + \
-                               portal.getPortalReservedInventoryStateList() + \
-                               portal.getPortalCurrentInventoryStateList()
-      return self.getInventoryList(**kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getInventoryStat')
-    def getInventoryStat(self, simulation_period='', **kw):
-      """
-      getInventoryStat is the pending to getInventoryList in order to
-      provide statistics on getInventoryList lines in ListBox such as:
-      total of inventories, number of variations, number of different
-      nodes, etc.
-      """
-      kw['group_by_variation'] = 0
-      method = getattr(self,'get%sInventoryList' % simulation_period)
-      return method(statistic=1, inventory_list=0, optimisation__=False,
-                                   ignore_group_by=1, **kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getCurrentInventoryStat')
-    def getCurrentInventoryStat(self, **kw):
-      """
-      Returns statistics of current inventory grouped by section or site
-      """
-      return self.getInventoryStat(simulation_period='Current', **kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getAvailableInventoryStat')
-    def getAvailableInventoryStat(self, **kw):
-      """
-      Returns statistics of available inventory grouped by section or site
-      """
-      return self.getInventoryStat(simulation_period='Available', **kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getFutureInventoryStat')
-    def getFutureInventoryStat(self, **kw):
-      """
-      Returns statistics of future inventory grouped by section or site
-      """
-      return self.getInventoryStat(simulation_period='Future', **kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getInventoryChart')
-    def getInventoryChart(self, src__=0, **kw):
-      """
-      Returns a list of couples derived from getInventoryList in order
-      to feed a chart renderer. Each couple consist of a label
-      (node, section, payment, combination of node & section, etc.)
-      and an inventory value.
-
-      Mostly useful for charts in ERP5 forms.
-      """
-      result = self.getInventoryList(src__=src__, **kw)
-      if src__ :
-        return result
-
-      return map(lambda r: (r.node_title, r.total_quantity), result)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getCurrentInventoryChart')
-    def getCurrentInventoryChart(self, **kw):
-      """
-      Returns list of current inventory grouped by section or site
-      """
-      kw['simulation_state'] = self.getPortalObject()\
-        .getPortalCurrentInventoryStateList()
-      return self.getInventoryChart(**kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getFutureInventoryChart')
-    def getFutureInventoryChart(self, **kw):
-      """
-      Returns list of future inventory grouped by section or site
-      """
-      portal = self.getPortalObject()
-      kw['simulation_state'] = portal.getPortalFutureInventoryStateList() + \
-                               portal.getPortalTransitInventoryStateList() + \
-                               portal.getPortalReservedInventoryStateList() + \
-                               portal.getPortalCurrentInventoryStateList()
-      return self.getInventoryChart(**kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getInventoryAssetPrice')
-    def getInventoryAssetPrice(self, src__=0,
-                               simulation_period='',
-                               valuation_method=None,
-                               **kw):
-      """
-      Same thing as getInventory but returns an asset
-      price rather than an inventory.
-
-      If valuation method is None, returns the sum of total prices.
-
-      Else it should be a string, in:
-        Filo
-        Fifo
-        WeightedAverage
-        MonthlyWeightedAverage
-        MovingAverage
-      When using a specific valuation method, a resource_uid is expected
-      as well as one of (section_uid or node_uid).
-      """
-      if valuation_method is None:
-        method = getattr(self,'get%sInventoryList' % simulation_period)
-        kw['ignore_group_by'] = 1
-        result = method( src__=src__, inventory_list=0, **kw)
-        if src__ :
-          return result
-
-        if len(result) == 0:
-          return 0.0
-
-        total_result = 0.0
-        for result_line in result:
-          if result_line.total_price is not None:
-            total_result += result_line.total_price
-
-        return total_result
-
-      if valuation_method not in ('Fifo', 'Filo', 'WeightedAverage',
-        'MonthlyWeightedAverage', 'MovingAverage'):
-        raise ValueError("Invalid valuation method: %s" % valuation_method)
-
-      assert 'node_uid' in kw or 'section_uid' in kw
-      sql_kw = self._generateSQLKeywordDict(**kw)
-
-      if 'section_uid' in kw and 'node_uid' not in kw:
-        # ignore internal movements if ignore node
-        sql_kw['where_expression'] += ' AND ' \
-          'NOT(stock.section_uid<=>stock.mirror_section_uid)'
-
-      result = self.Resource_zGetAssetPrice(
-          valuation_method=valuation_method,
-          src__=src__,
-          **sql_kw)
-
-      if src__ :
-        return result
-
-      if len(result) > 0:
-        return result[-1].total_asset_price
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getCurrentInventoryAssetPrice')
-    def getCurrentInventoryAssetPrice(self, **kw):
-      """
-      Returns list of current inventory grouped by section or site
-      """
-      kw['simulation_state'] = self.getPortalCurrentInventoryStateList()
-      return self.getInventoryAssetPrice(simulation_period='Current',**kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getAvailableInventoryAssetPrice')
-    def getAvailableInventoryAssetPrice(self, **kw):
-      """
-      Returns list of available inventory grouped by section or site
-      (current inventory - deliverable)
-      """
-      portal = self.getPortalObject()
-      kw['simulation_state'] = portal.getPortalReservedInventoryStateList() + \
-                               portal.getPortalCurrentInventoryStateList()
-      return self.getInventoryAssetPrice(**kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getFutureInventoryAssetPrice')
-    def getFutureInventoryAssetPrice(self, **kw):
-      """
-      Returns list of future inventory grouped by section or site
-      """
-      portal = self.getPortalObject()
-      kw['simulation_state'] = portal.getPortalFutureInventoryStateList() + \
-                               portal.getPortalReservedInventoryStateList() + \
-                               portal.getPortalCurrentInventoryStateList()
-      return self.getInventoryAssetPrice(**kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getInventoryHistoryList')
-    def getInventoryHistoryList(self, src__=0, ignore_variation=0,
-                                standardise=0, omit_simulation=0,
-                                only_accountable=True, omit_input=0,
-                                omit_output=0, precision=None, **kw):
-      """
-      Returns a time based serie of inventory values
-      for a single or a group of resource, node, section, etc. This is useful
-      to list the evolution with time of inventory values (quantity, asset price).
-
-      TODO:
-        - make sure getInventoryHistoryList can return
-          cumulative values calculated by SQL (JPS)
-      """
-      sql_kw = self._generateSQLKeywordDict(**kw)
-      return self.Resource_getInventoryHistoryList(
-                      src__=src__, ignore_variation=ignore_variation,
-                      standardise=standardise, omit_simulation=omit_simulation,
-                      only_accountable=only_accountable,
-                      omit_input=omit_input, omit_output=omit_output,
-                      precision=precision,
-                      **sql_kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getInventoryHistoryChart')
-    def getInventoryHistoryChart(self, src__=0, ignore_variation=0,
-                                 standardise=0, omit_simulation=0,
-                                 only_accountable=True,
-                                 omit_input=0, omit_output=0,
-                                 precision=None, **kw):
-      """
-      getInventoryHistoryChart is the pensing to getInventoryHistoryList
-      to ease the rendering of time based graphs which show the evolution
-      of one ore more inventories. Each item in the serie consists of
-      time, value and "colour" (multiple graphs can be drawn for example
-      for each variation of a resource)
-      """
-      sql_kw = self._generateSQLKeywordDict(**kw)
-
-      return self.Resource_getInventoryHistoryChart(
-                    src__=src__, ignore_variation=ignore_variation,
-                    standardise=standardise, omit_simulation=omit_simulation,
-                    only_accountable=only_accountable,
-                    omit_input=omit_input, omit_output=omit_output,
-                    precision=precision,
-                    **sql_kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getMovementHistoryList')
-    def getMovementHistoryList(self, src__=0, ignore_variation=0,
-                               standardise=0, omit_simulation=0,
-                               omit_input=0, omit_output=0,
-                               only_accountable=True,
-                               omit_asset_increase=0, omit_asset_decrease=0,
-                               initial_running_total_quantity=0,
-                               initial_running_total_price=0, precision=None,
-                               **kw):
-      """Returns a list of movements which modify the inventory
-      for a single or a group of resource, node, section, etc.
-      A running total quantity and a running total price are available on
-      brains. The initial values can be passed, in case you want to have an
-      "initial summary line".
-      """
-      # Extend select_dict by order_by_list columns.
-      catalog = self.getPortalObject().portal_catalog.getSQLCatalog()
-      kw = catalog.getCannonicalArgumentDict(kw)
-      extra_column_set = {i[0] for i in kw.get('order_by_list', ())}
-      kw.setdefault('select_dict', {}).update(
-        (x.replace('.', '_') + '__ext__', x)
-        for x in extra_column_set if not x.endswith('__score__'))
-
-      sql_kw = self._generateSQLKeywordDict(**kw)
-
-      return self.Resource_zGetMovementHistoryList(
-                         src__=src__, ignore_variation=ignore_variation,
-                         standardise=standardise,
-                         omit_simulation=omit_simulation,
-                         only_accountable=only_accountable,
-                         omit_input=omit_input, omit_output=omit_output,
-                         omit_asset_increase=omit_asset_increase,
-                         omit_asset_decrease=omit_asset_decrease,
-                         initial_running_total_quantity=
-                                  initial_running_total_quantity,
-                         initial_running_total_price=
-                                  initial_running_total_price,
-                         precision=precision, **sql_kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getMovementHistoryStat')
-    def getMovementHistoryStat(self, src__=0, **kw):
-      """
-      getMovementHistoryStat is the pending to getMovementHistoryList
-      for ListBox stat
-
-      supported parameters are similar to ones accepted by getInventoryList
-      with the exception of group_by_*
-      """
-      sql_kw = self._generateSQLKeywordDict(**kw)
-      inventory_list = self.getInventoryList(ignore_group_by=1, **kw)
-      assert len(inventory_list) == 1
-      return inventory_list[0]
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getNextAlertInventoryDate')
-    def getNextAlertInventoryDate(self, reference_quantity=0, src__=0,
-                       simulation_period='Future', from_date=None,
-                       range='min',
-                       initial_inventory_kw=None,
-                       inventory_list_kw=None,
-                       **kw):
-      """
-      Give the next date where the quantity is lower than the
-      reference quantity. This is calculated by first looking if inventory
-      right now is good or not. If not, then look at inventory list until
-      a movement makes the inventory like expected.
-
-      range  - either 'min' (default) or 'nlt'. With 'nlt', returns
-               the next date where inventory is above reference_quantity
-
-      initial_inventory_kw - additional parameters for the initial inventory
-
-      inventory_list_kw - additional parameters for looking at next movements
-                          (exemple: use omit_output)
-      """
-      result = None
-      # First look at current inventory, we might have already an inventory
-      # lower than reference_quantity
-      def getCheckQuantityMethod():
-        if range == 'min':
-          return lambda x: x < reference_quantity
-        elif range == 'nlt':
-          return lambda x: x >= reference_quantity
-        else:
-          raise ValueError("Uknown range type : %s" % (range,))
-
-      checkQuantity = getCheckQuantityMethod()
-      if from_date is None:
-        from_date = DateTime()
-      def getAugmentedInventoryKeyword(additional_kw):
-        inventory_kw = kw
-        if additional_kw:
-          inventory_kw = kw.copy()
-          inventory_kw.update(additional_kw)
-        return inventory_kw
-      inventory_method = getattr(self, "get%sInventory" % simulation_period)
-      initial_inventory = inventory_method(at_date=from_date,
-                           **getAugmentedInventoryKeyword(initial_inventory_kw))
-      if checkQuantity(initial_inventory):
-        result = from_date
-      else:
-        inventory_list_method = getattr(self,
-          "get%sInventoryList" % simulation_period)
-        inventory_list = inventory_list_method(src__=src__, from_date=from_date,
-            sort_on = (('date', 'ascending'),), group_by_movement=1,
-            **getAugmentedInventoryKeyword(inventory_list_kw))
-        if src__ :
-          return inventory_list
-        total_inventory = initial_inventory
-        for inventory in inventory_list:
-          if inventory['inventory'] is not None:
-            total_inventory += inventory['inventory']
-            if checkQuantity(total_inventory):
-              result = inventory['date']
-              break
-      return result
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getNextNegativeInventoryDate')
-    def getNextNegativeInventoryDate(self, **kw):
-      """
-      Deficient Inventory with a reference_quantity of 0, so when the
-      stock will be negative
-      """
-      return self.getNextAlertInventoryDate(reference_quantity=0, **kw)
-
-    #######################################################
-    # Traceability management
-    security.declareProtected(Permissions.AccessContentsInformation, 'getTrackingList')
-    def getTrackingList(self, src__=0,
-        strict_simulation_state=1, history=0, **kw) :
-      """
-      Returns a list of items in the form
-
-        uid (of item)
-        date
-        node_uid
-        section_uid
-        resource_uid
-        variation_text
-        delivery_uid
-
-      If at_date is provided, returns the a list which answers
-      to the question "where are those items at this date" or
-      "which are those items which are there a this date"
-
-      If at_date is not provided, returns a history of positions
-      which answers the question "where have those items been
-      between this time and this time". This will be handled by
-      something like getTrackingHistoryList
-
-      This method is only suitable for singleton items (an item which can
-      only be at a single place at a given time). Such items include
-      containers, serial numbers (ex. for engine), rolls with subrolls,
-
-      This method is not suitable for batches (ex. a coloring batch).
-      For such items, standard getInventoryList method is appropriate
-
-      Parameters are the same as for getInventory.
-
-      Default sort orders is based on dates, reverse.
-
-
-      from_date (>=) -
-
-      to_date   (<)  -
-
-      at_date   (<=) - only take rows which date is <= at_date
-
-      history (boolean) - keep history variations
-
-      resource (only in generic API in simulation)
-
-      node        -  only take rows in stock table which node_uid is equivalent to node
-
-      section        -  only take rows in stock table which section_uid is equivalent to section
-
-      resource_category        -  only take rows in stock table which resource_uid is in resource_category
-
-      node_category        -  only take rows in stock table which node_uid is in section_category
-
-      section_category        -  only take rows in stock table which section_uid is in section_category
-
-      variation_text - only take rows in stock table with specified variation_text
-                       XXX this way of implementing variation selection is far from perfect
-
-      variation_category - variation or list of possible variations
-                           Deprecated, use variation_text
-
-      simulation_state - only take rows with specified simulation_state
-
-      selection_domain, selection_report - see ListBox
-
-      **kw  - if we want extended selection with more keywords (but bad performance)
-              check what we can do with buildSQLQuery
-
-      Extra parameters for getTrackingList :
-
-      item
-
-      input - if set, answers to the question "which are those items which have been
-              delivered for the first time after from_date". Cannot be used with output
-
-      output - if set, answers to the question "which are those items which have been
-               delivered for the last time before at_date or to_date". Cannot be used with input
-
-      """
-      next_item_simulation_state = kw.pop('next_item_simulation_state', None)
-      new_kw = self._generateSQLKeywordDict(table='item',strict_simulation_state=strict_simulation_state,**kw)
-      for key in ('at_date', 'to_date'):
-        value = kw.get(key, None)
-        if value is not None:
-          if isinstance(value, DateTime):
-            value = value.toZone('UTC').ISO()
-          value = '%s' % (value, )
-        # Do not remove dates in new_kw, they are required in
-        # order to do a "select item left join item on date"
-        new_kw[key] = value
-
-      # Extra parameters for the SQL Method
-      new_kw['join_on_item'] = not history and (new_kw.get('at_date') or \
-                               new_kw.get('to_date') or \
-                               new_kw.get('input') or \
-                               new_kw.get('output'))
-      new_kw['date_condition_in_join'] = not (new_kw.get('input') or new_kw.get('output'))
-
-      # Pass simulation state to request
-      if next_item_simulation_state:
-        new_kw['simulation_state_list'] = next_item_simulation_state
-      elif kw.has_key('item.simulation_state'):
-        new_kw['simulation_state_list'] = kw['item.simulation_state']
-      else:
-        new_kw['simulation_state_list'] =  None
-
-      return self.Resource_zGetTrackingList(src__=src__,
-                                            **new_kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation, 'getCurrentTrackingList')
-    def getCurrentTrackingList(self, **kw):
-      """
-      Returns list of current inventory grouped by section or site
-      """
-      portal = self.getPortalObject()
-      kw['item.simulation_state'] = portal\
-        .getPortalCurrentInventoryStateList()
-      kw['next_item_simulation_state'] = portal\
-        .getPortalCurrentInventoryStateList() + portal\
-        .getPortalTransitInventoryStateList()
-      return self.getTrackingList(**kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation, 'getCurrentTrackingHistoryList')
-    def getCurrentTrackingHistoryList(self, **kw):
-      """
-      Returns list of current inventory grouped by section or site
-      """
-      kw['item.simulation_state'] = self.getPortalObject()\
-        .getPortalCurrentInventoryStateList()
-      return self.getTrackingHistoryList(**kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation, 'getTrackingHistoryList')
-    def getTrackingHistoryList(self, **kw):
-      """
-      Returns history list of inventory grouped by section or site
-      """
-      kw['history'] = 1
-      return self.getTrackingList(**kw)
-
-    security.declareProtected(Permissions.AccessContentsInformation, 'getFutureTrackingList')
-    def getFutureTrackingList(self, **kw):
-      """
-      Returns list of future inventory grouped by section or site
-      """
-      portal = self.getPortalObject()
-      kw['item.simulation_state'] = portal.getPortalFutureInventoryStateList() + \
-                                    portal.getPortalTransitInventoryStateList() + \
-                                    portal.getPortalReservedInventoryStateList() + \
-                                    portal.getPortalCurrentInventoryStateList()
-      return self.getTrackingList(**kw)
-
-    #######################################################
-    # Capacity Management
-    security.declareProtected( Permissions.ModifyPortalContent, 'updateCapacity' )
-    def updateCapacity(self, node):
-      capacity_item_list = []
-      for o in node.contentValues():
-        if o.isCapacity():
-          # Do whatever is needed
-          capacity_item_list += o.asCapacityItemList()
-          pass
-      # Do whatever with capacity_item_list
-      # and store the resulting new capacity in node
-      node._capacity_item_list = capacity_item_list
-
-    security.declareProtected( Permissions.ModifyPortalContent, 'isMovementInsideCapacity' )
-    def isMovementInsideCapacity(self, movement):
-      """
-        Purpose: provide answer to customer for the question "can you do it ?"
-
-        movement:
-          date
-          source destination (2 nodes)
-          source_section ...
-      """
-      # Get nodes and dat
-      source_node = movement.getSourceValue()
-      destination_node = movement.getDestinationValue()
-      start_date = movement.getStartDate()
-      stop_date = movement.getStopDate()
-      # Return result
-      return self.isNodeInsideCapacity(source_node, start_date, additional_movement=movement, sign=1) and self.isNodeInsideCapacity(destination_node, stop_date, additional_movement=movement, sign=-1)
-
-    security.declareProtected( Permissions.ModifyPortalContent, 'isNodeInsideCapacity' )
-    def isNodeInsideCapacity(self, node, date, simulation_state=None, additional_movement=None, sign=1):
-      """
-        Purpose: decide if a node is consistent with its capacity definitions
-        at a certain date (ie. considreing the stock / production history
-      """
-      # First get the current inventory situation for this node
-      inventory_list = node.getInventoryList(XXXXX)
-      # Add additional movement
-      if additional_movement:
-          inventory_list = inventory_list + sign * additional_movement # needs to be implemented
-      # Return answer
-      return self.isAmountListInsideCapacity(node, inventory_list)
-
-    security.declareProtected( Permissions.ModifyPortalContent, 'isAmountListInsideCapacity' )
-    def isAmountListInsideCapacity(self, node, amount_list,
-         resource_aggregation_base_category=None, resource_aggregation_depth=None):
-      """
-        Purpose: decide if a list of amounts is consistent with the capacity of a node
-
-        If any resource in amount_list is missing in the capacity of the node, resource
-        aggregation is performed, based on resource_aggregation_base_category. If the
-        base category is not specified, it is an error (should guess instead?). The resource
-        aggregation is done at the level of resource_aggregation_depth in the tree
-        of categories. If resource_aggregation_depth is not specified, it's an error.
-
-        Assumptions: amount_list is an association list, like ((R1 V1) (R2 V2)).
-                     node has an attribute '_capacity_item_list' which is a list of association lists.
-                     resource_aggregation_base_category is a Base Category object or a list of Base
-                     Category objects or None.
-                     resource_aggregation_depth is a strictly positive integer or None.
-      """
-      # Make a copy of the attribute _capacity_item_list, because it may be necessary
-      # to modify it for resource aggregation.
-      capacity_item_list = node._capacity_item_list[:]
-
-      # Make a mapping between resources and its indices.
-      resource_map = {}
-      index = 0
-      for alist in capacity_item_list:
-        for pair in alist:
-          resource = pair[0]
-#          LOG('isAmountListInsideCapacity', 0,
-#              "resource is %s" % repr(resource))
-          if resource not in resource_map:
-            resource_map[resource] = index
-            index += 1
-
-      # Build a point from the amount list.
-      point = zeros(index, 'd') # Fill up zeros for safety.
-      mask_map = {}     # This is used to skip items in amount_list.
-      for amount in amount_list:
-        if amount[0] in mask_map:
-          continue
-        # This will fail, if amount_list has any different resource from the capacity.
-        # If it has any different point, then we should ......
-        #
-        # There would be two possible different solutions:
-        # 1) If a missing resource is a meta-resource of resources supported by the capacity,
-        #    it is possible to add the resource into the capacity by aggregation.
-        # 2) If a missing resource has a meta-resource as a parent and the capacity supports
-        #    the meta-resource directly or indirectly (`indirectly' means `by aggregation'),
-        #    it is possible to convert the missing resource into the meta-resource.
-        #
-        # However, another way has been implemented here. This does the following, if the resource
-        # is not present in the capacity:
-        # 1) If the value is zero, just ignore the resource, because zero is always acceptable.
-        # 2) Attempt to aggregate resources both of the capacity and of the amount list. This aggregation
-        #    is performed at the depth of 'resource_aggregation_depth' under the base category
-        #    'resource_aggregation_base_category'.
-        #
-        resource = amount[0]
-        if resource in resource_map:
-          point[resource_map[amount[0]]] = amount[1]
-        else:
-          if amount[1] == 0:
-            # If the value is zero, no need to consider.
-            pass
-          elif resource_aggregation_base_category is None or resource_aggregation_depth is None:
-            # XXX use an appropriate error class
-            # XXX should guess a base category instead of emitting an exception
-            raise RuntimeError, "The resource '%s' is not found in the capacity, and the argument 'resource_aggregation_base_category' or the argument 'resource_aggregation_depth' is not specified" % resource
-          else:
-            # It is necessary to aggregate resources, to guess the capacity of this resource.
-
-            def getAggregationResourceUrl(url, depth):
-              # Return a partial url of the argument 'url'.
-              # If 'url' is '/foo/bar/baz' and 'depth' is 2, return '/foo/bar'.
-              pos = 0
-              for i in range(resource_aggregation_depth):
-                pos = url.find('/', pos+1)
-                if pos < 0:
-                  break
-              if pos < 0:
-                return None
-              pos = url.find('/', pos+1)
-              if pos < 0:
-                pos = len(url)
-              return url[:pos]
-
-            def getAggregatedResourceList(aggregation_url, category, resource_list):
-              # Return a list of resources which should be aggregated. 'aggregation_url' is used
-              # for a top url of those resources. 'category' is a base category for the aggregation.
-              aggregated_resource_list = []
-              for resource in resource_list:
-                for url in resource.getCategoryMembershipList(category, base=1):
-                  if url.startswith(aggregation_url):
-                    aggregated_resource_list.append(resource)
-              return aggregated_resource_list
-
-            def getAggregatedItemList(item_list, resource_list, aggregation_resource):
-              # Return a list of association lists, which is a result of an aggregation.
-              # 'resource_list' is a list of resources which should be aggregated.
-              # 'aggregation_resource' is a category object which is a new resource created by
-              # this aggregation.
-              # 'item_list' is a list of association lists.
-              new_item_list = []
-              for alist in item_list:
-                new_val = 0
-                new_alist = []
-                # If a resource is not a aggregated, then add it to the new alist as it is.
-                # Otherwise, aggregate it to a single value.
-                for pair in alist:
-                  if pair[0] in resource_list:
-                    new_val += pair[1]
-                  else:
-                    new_alist.append(pair)
-                # If it is zero, ignore this alist, as it is nonsense.
-                if new_val != 0:
-                  new_alist.append([aggregation_resource, new_val])
-                  new_item_list.append(new_alist)
-              return new_item_list
-
-            # Convert this to a string if necessary, for convenience.
-            if not isinstance(resource_aggregation_base_category, (tuple, list)):
-              resource_aggregation_base_category = (resource_aggregation_base_category,)
-
-            done = 0
-#            LOG('isAmountListInsideCapacity', 0,
-#                "resource_aggregation_base_category is %s" % repr(resource_aggregation_base_category))
-            for category in resource_aggregation_base_category:
-              for resource_url in resource.getCategoryMembershipList(category, base=1):
-                aggregation_url = getAggregationResourceUrl(resource_url,
-                                                            resource_aggregation_depth)
-                if aggregation_url is None:
-                  continue
-                aggregated_resource_list = getAggregatedResourceList (aggregation_url,
-                                                                      category,
-                                                                      resource_map.keys())
-                # If any, do the aggregation.
-                if len(aggregated_resource_list) > 0:
-                  aggregation_resource = self.portal_categories.resolveCategory(aggregation_url)
-                  # Add the resource to the mapping.
- #                 LOG('aggregation_resource', 0, str(aggregation_resource))
-                  resource_map[aggregation_resource] = index
-                  index += 1
-                  # Add the resource to the point.
-                  point = resize(point, (index,))
-                  val = 0
-                  for aggregated_amount in amount_list:
-                    for url in aggregated_amount[0].getCategoryMembershipList(category, base=1):
-                      if url.startswith(aggregation_url):
-                        val += aggregated_amount[1]
-                        mask_map[aggregated_amount[0]] = None
-                        break
-                  point[index-1] = val
-                  # Add capacity definitions of the resource into the capacity.
-                  capacity_item_list += getAggregatedItemList(capacity_item_list,
-                                                              aggregated_resource_list,
-                                                              aggregation_resource)
-                  done = 1
-                  break
-              if done:
-                break
-            if not done:
-              raise RuntimeError, "Aggregation failed"
-
-      # Build a matrix from the capacity item list.
-#      LOG('resource_map', 0, str(resource_map))
-      matrix = zeros((len(capacity_item_list)+1, index), 'd')
-      for index in range(len(capacity_item_list)):
-        for pair in capacity_item_list[index]:
-          matrix[index,resource_map[pair[0]]] = pair[1]
-
-#      LOG('isAmountListInsideCapacity', 0,
-#          "matrix = %s, point = %s, capacity_item_list = %s" % (str(matrix), str(point), str(capacity_item_list)))
-      return solve(matrix, point)
-
-
-    # Asset Price Calculation
-    def updateAssetPrice(self, resource, variation_text, section_category, node_category,
-                         strict_membership=0, simulation_state=None):
-      if simulation_state is None:
-        simulation_state = self.getPortalCurrentInventoryStateList()
-      category_tool = getToolByName(self, 'portal_categories')
-      section_value = category_tool.resolveCategory(section_category)
-      node_value = category_tool.resolveCategory(node_category)
-      # Initialize price
-      current_asset_price = 0.0 # Missing: initial inventory price !!!
-      current_inventory = 0.0
-      # Parse each movement
-      brain_list = self.Resource_zGetMovementHistoryList(resource=[resource],
-                             variation_text=variation_text,
-                             section_category=section_category,
-                             node_category=node_category,
-                             strict_membership=strict_membership,
-                             simulation_state=simulation_state) # strict_membership not taken into account
-                             # We select movements related to certain nodes (ex. Stock) and sections (ex.Coramy Group)
-      result = []
-      for b in brain_list:
-        m = b.getObject()
-        if m is not None:
-          previous_inventory = current_inventory
-          inventory_quantity = b.quantity # We should use the aggregated quantity provided by Resource_zGetMovementHistoryList
-          quantity = m.getQuantity() # The movement quantity is important to determine the meaning of source and destination
-          # Maybe we should take care of target qty in delired deliveries
-          if quantity is None:
-            quantity = 0.0
-          if m.getSourceValue() is None:
-            # This is a production movement or an inventory movement
-            # Use Industrial Price
-            current_inventory += inventory_quantity # Update inventory
-            if m.getPortalType() in ('Inventory Line', 'Inventory Cell'): # XX should be replaced by isInventory ???
-              asset_price = m.getPrice()
-              if asset_price in (0.0, None):
-                asset_price = current_asset_price # Use current price if no price defined
-            else: # this is a production
-              asset_price = m.getIndustrialPrice()
-              if asset_price is None: asset_price = current_asset_price  # Use current price if no price defined
-            result.append((m.getRelativeUrl(), m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                          m.getQuantity(), 'Production or Inventory', 'Price: %s' % asset_price
-                        ))
-          elif m.getDestinationValue() is None:
-            # This is a consumption movement or an inventory movement
-            current_inventory += inventory_quantity # Update inventory
-            asset_price = current_asset_price
-            result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                          m.getQuantity(), 'Consumption or Inventory', 'Price: %s' % asset_price
-                        ))
-          elif m.getSourceValue().isAcquiredMemberOf(node_category) and m.getDestinationValue().isAcquiredMemberOf(node_category):
-            # This is an internal movement
-            current_inventory += inventory_quantity # Update inventory
-            asset_price = current_asset_price
-            result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                          m.getQuantity(), 'Internal', 'Price: %s' % asset_price
-                        ))
-          elif m.getSourceValue().isAcquiredMemberOf(node_category) and quantity < 0:
-            # This is a physically inbound movement - try to use commercial price
-            if m.getSourceSectionValue() is None:
-              # No meaning
-              current_inventory += inventory_quantity # Update inventory
-              asset_price = current_asset_price
-              result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                            m.getQuantity(), 'Error', 'Price: %s' % asset_price
-                          ))
-            elif m.getDestinationSectionValue() is None:
-              # No meaning
-              current_inventory += inventory_quantity # Update inventory
-              asset_price = current_asset_price
-              result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                            m.getQuantity(), 'Error', 'Price: %s' % asset_price
-                          ))
-            elif m.getDestinationSectionValue().isAcquiredMemberOf(section_category):
-              current_inventory += inventory_quantity # Update inventory
-              if m.getDestinationValue().isAcquiredMemberOf('site/Piquage'):
-                # Production
-                asset_price = m.getIndustrialPrice()
-                if asset_price is None: asset_price = current_asset_price  # Use current price if no price defined
-                result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                              m.getQuantity(), 'Production', 'Price: %s' % asset_price
-                            ))
-              else:
-                # Inbound from same section
-                asset_price = current_asset_price
-                result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                              m.getQuantity(), 'Inbound same section', 'Price: %s' % asset_price
-                            ))
-            else:
-              current_inventory += inventory_quantity # Update inventory
-              asset_price = m.getPrice()
-              result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                            m.getQuantity(), 'Inbound different section', 'Price: %s' % asset_price
-                          ))
-          elif m.getDestinationValue().isAcquiredMemberOf(node_category) and quantity > 0:
-            # This is a physically inbound movement - try to use commercial price
-            if m.getSourceSectionValue() is None:
-              # No meaning
-              current_inventory += inventory_quantity # Update inventory
-              asset_price = current_asset_price
-              result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                            m.getQuantity(), 'Error', 'Price: %s' % asset_price
-                          ))
-            elif m.getDestinationSectionValue() is None:
-              # No meaning
-              current_inventory += inventory_quantity # Update inventory
-              asset_price = current_asset_price
-              result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                            m.getQuantity(), 'Error', 'Price: %s' % asset_price
-                          ))
-            elif m.getSourceSectionValue().isAcquiredMemberOf(section_category):
-              current_inventory += inventory_quantity # Update inventory
-              if m.getSourceValue().isAcquiredMemberOf('site/Piquage'):
-                # Production
-                asset_price = m.getIndustrialPrice()
-                if asset_price is None: asset_price = current_asset_price  # Use current price if no price defined
-                result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                              m.getQuantity(), 'Production', 'Price: %s' % asset_price
-                            ))
-              else:
-                # Inbound from same section
-                asset_price = current_asset_price
-                result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                            m.getQuantity(), 'Inbound same section', 'Price: %s' % asset_price
-                          ))
-            else:
-              current_inventory += inventory_quantity # Update inventory
-              asset_price = m.getPrice()
-              result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                            m.getQuantity(), 'Inbound different section', 'Price: %s' % asset_price
-                          ))
-          else:
-            # Outbound movement
-            current_inventory += inventory_quantity # Update inventory
-            asset_price = current_asset_price
-            result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
-                            m.getQuantity(), 'Outbound', 'Price: %s' % asset_price
-                          ))
-
-          # Update asset_price
-          if current_inventory > 0:
-            if inventory_quantity is not None:
-              # Update price with an average of incoming goods and current goods
-              current_asset_price = ( current_asset_price * previous_inventory + asset_price * inventory_quantity ) / float(current_inventory)
-          else:
-            # New price is the price of incoming goods - negative stock has no meaning for asset calculation
-            current_asset_price = asset_price
-
-          result.append(('###New Asset Price', current_asset_price, 'New Inventory', current_inventory))
-
-          # Update Asset Price on the right side
-          if m.getSourceSectionValue() is not None and m.getSourceSectionValue().isAcquiredMemberOf(section_category):
-            # for each movement, source section is member of one and one only accounting category
-            # therefore there is only one and one only source asset price
-            m._setSourceAssetPrice(current_asset_price)
-            #quantity = m.getInventoriatedQuantity()
-            #if quantity:
-            #  #total_asset_price = - current_asset_price * quantity
-            #  #m.Movement_zSetSourceTotalAssetPrice(uid=m.getUid(), total_asset_price = total_asset_price)
-            #  m._setSourceAssetPrice(current_asset_price)
-          if m.getDestinationSectionValue() is not None and m.getDestinationSectionValue().isMemberOf(section_category):
-            # for each movement, destination section is member of one and one only accounting category
-            # therefore there is only one and one only destination asset price
-            m._setDestinationAssetPrice(current_asset_price)
-            #quantity = m.getInventoriatedQuantity()
-            #if quantity:
-            #  total_asset_price = current_asset_price * quantity
-            #  m.Movement_zSetDestinationTotalAssetPrice(uid=m.getUid(), total_asset_price = total_asset_price)
-          # Global reindexing required afterwards in any case: so let us do it now
-          # Until we get faster methods (->reindexObject())
-          m.reindexObject()
-
-      return result
-
-    def _findBuilderForDelivery(self, delivery, movement_portal_type):
-      """
-      Find out the builder corresponding to a delivery by looking at the business process
-      """
-      builder = None
-      portal_type = delivery.getPortalType()
-      for business_link in delivery.asComposedDocument().objectValues(portal_type="Business Link"):
-        for business_link_builder in business_link.getDeliveryBuilderValueList():
-          if business_link_builder.getDeliveryPortalType() == portal_type \
-          and business_link_builder.getDeliveryLinePortalType() == movement_portal_type:
-            builder = business_link_builder
-            break
-        if builder is not None:
-          break
-      return builder
-
-    security.declareProtected( Permissions.ModifyPortalContent, 'mergeDeliveryList' )
-    def mergeDeliveryList(self, delivery_list):
-      """
-        Merge multiple deliveries into one delivery.
-        All delivery lines are merged into the first one.
-        The first one is therefore called main_delivery here.
-        The others are cancelled.
-      """
-      # Sanity checks.
-      if not(len(delivery_list) >=2):
-        raise ValueError("Please select at least 2 deliveries")
-      portal= self.getPortalObject()
-      translateString = portal.Base_translateString
-      error_list = []
-      if len(delivery_list) > 1:
-        portal_type_set = set([x.getPortalType() for x in delivery_list])
-        if len(portal_type_set) != 1:
-          error_list.append(translateString("Please select only deliveries of same type"))
-        else:
-          allowed_state_set = set(portal.getPortalReservedInventoryStateList() + \
-                                 portal.getPortalFutureInventoryStateList())
-          found_state_set = set([x.getSimulationState() for x in delivery_list])
-          if found_state_set.difference(allowed_state_set):
-            error_list.append(translateString("Found delivery having unexpected status for merge"))
-          else:
-            movement_portal_type_set = set()
-            for delivery in delivery_list:
-              movement_portal_type_set.update([x.getPortalType() for x in delivery.getMovementList()])
-            if len(movement_portal_type_set) != 1:
-              error_list.append(translateString("Please Select only movement of same type"))
-            else:
-              # Allow to call a script to do custom checking conditions before merge
-              main_delivery = delivery_list[0]
-              check_merge_condition_method = main_delivery._getTypeBasedMethod("checkMergeConditionOnDeliveryList")
-              if check_merge_condition_method is not None:
-                error_list.extend(check_merge_condition_method(delivery_list=delivery_list))
-              if len(error_list) == 0:
-                # so far so good
-                # in delivery_list we have list of delivery to merge
-                simulation_movement_list = []
-                to_copy_delivery_line_list = [] # for lines not coming from upper simulation, thus
-                                                # created by hand should be manually added to main
-                                                # delivery since they are not coming from builder
-                for delivery in delivery_list:
-                  line_id_to_delete_list = []
-                  for movement in delivery.getMovementList():
-                    related_simulation_movement_list = movement.getDeliveryRelatedValueList()
-                    for simulation_movement in related_simulation_movement_list:
-                      # if we are on a root applied rule directly, so in the case of
-                      # a manually added line, we have to copy
-                      # the simulation movement into to main delivery
-                      if simulation_movement.getParentValue().getParentValue().getId() == "portal_simulation":
-                        # For manually added lines, make sure we have only one simulation movement
-                        assert len(related_simulation_movement_list) == 1
-                        if not(delivery is main_delivery):
-                          to_copy_delivery_line_list.append(movement)
-                      else:
-                        simulation_movement.setDeliveryValue(None)
-                        simulation_movement_list.append(simulation_movement)
-                        # Since we keep the main delivery, we remove existing lines already
-                        # coming from builder to let builder recreate them in the same time
-                        # as other ones (to possibly merge lines also)
-                        movement_id = movement.getId()
-                        if delivery is main_delivery and not(movement_id in line_id_to_delete_list):
-                          line_id_to_delete_list.append(movement.getId())
-                  if line_id_to_delete_list:
-                    delivery.manage_delObjects(ids=line_id_to_delete_list)
-                # It is required to expand again simulation movement, because
-                # we unlinked them from delivery, so it is possible that some
-                # properties will change on simulation movement (mostly categories).
-                # By expanding again, we will avoid having many deliveries instead
-                # of one when doing "merge"
-                for simulation_movement in simulation_movement_list:
-                  simulation_movement.expand(expand_policy='immediate')
-
-                # activate builder
-                movement_portal_type, = movement_portal_type_set
-                merged_builder = self._findBuilderForDelivery(main_delivery, movement_portal_type)
-                if merged_builder is None:
-                  error_list.append(translateString("Unable to find builder"))
-                else:
-                  merged_builder.build(movement_relative_url_list=[q.getRelativeUrl() for q in \
-                                       simulation_movement_list], merge_delivery=True,
-                                       delivery_relative_url_list=[main_delivery.getRelativeUrl()])
-                # Finally, copy all lines that were created manually on all deliveries except
-                # the main one
-                @UnrestrictedMethod
-                def setMainDeliveryModifiable(delivery):
-                  # set causality state in such way we can modify delivery
-                  delivery.diverge()
-                setMainDeliveryModifiable(main_delivery)
-                delivery_type_list = portal.getPortalDeliveryTypeList()
-                for delivery_line in to_copy_delivery_line_list:
-                  delivery = delivery_line.getParentValue()
-                  if not(delivery.getPortalType() in delivery_type_list):
-                    raise NotImplementedError("Merge of deliveries doe not yet handle case of cells")
-                  copy_data = delivery.manage_copyObjects(ids=[delivery_line.getId()])
-                  main_delivery.manage_pasteObjects(copy_data)
-                main_delivery.updateCausalityState()
-
-                # Finally do cleanup
-                for delivery in delivery_list[1:]:
-                  # cancel, delete - to disallow any user related operations on those deliveries
-                  after_merge_method = delivery._getTypeBasedMethod('cleanDeliveryAfterMerge')
-                  if after_merge_method is not None:
-                    after_merge_method()
-      else:
-        error_list.append(translateString("Please select at least two deliveries"))
-      return error_list
-
-    #######################################################
-    # Sequence
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getSequence')
-    def getSequence(self, **kw):
-      """
-      getSequence is take the same parameters as Sequence constructor,
-      and return a Sequence.
-      """
-      return Sequence(**kw)
-
-    #######################################################
-    # Time Management
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getAvailableTime')
-    def getAvailableTime(self, from_date=None, to_date=None,
-                         portal_type=[], node=[],
-                         resource=[], src__=0, **kw):
-      """
-      Calculate available time for a node
-      Returns an inventory of a single or multiple resources on a single
-      node as a single float value
-
-      from_date (>=) - only take rows which mirror_date is >= from_date
-
-      to_date   (<)  - only take rows which date is < to_date
-
-      node           - only take rows in stock table which node_uid is
-                       equivalent to node
-
-      resource       - only take rows in stock table which resource_uid is
-                       equivalent to resource
-
-      portal_type    - only take rows in stock table which portal_type
-                       is in portal_type parameter
-      """
-      # XXX For now, consider that from_date and to_date are required
-      if (from_date is None) or (to_date is None):
-        raise NotImplementedError, \
-              "getAvailableTime does not managed yet None values"
-      portal = self.getPortalObject()
-      # Calculate portal_type
-      if portal_type == []:
-        portal_type = portal.getPortalCalendarPeriodTypeList()
-
-      simulation_state = portal.getPortalCurrentInventoryStateList() + \
-                         portal.getPortalTransitInventoryStateList() + \
-                         portal.getPortalReservedInventoryStateList()
-
-      sql_result = portal.Node_zGetAvailableTime(
-                          from_date=from_date,
-                          to_date=to_date,
-                          portal_type=portal_type,
-                          node=node,
-                          resource=resource,
-                          simulation_state=simulation_state,
-                          src__=src__, **kw)
-      if not src__:
-        result = 0
-        if len(sql_result) == 1:
-          result = sql_result[0].total_quantity
-      else:
-        result = sql_result
-      return result
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getAvailableTimeSequence')
-    def getAvailableTimeSequence(self, from_date, to_date,
-                                 portal_type=[], node=[],
-                                 resource=[],
-                                 src__=0,
-                                 **kw):
-      """
-      Calculate available time for a node in multiple period of time.
-      Each row is the available time for a specific period
-
-      node           - only take rows in stock table which node_uid is
-                       equivalent to node
-
-      portal_type    - only take rows in stock table which portal_type
-                       is in portal_type parameter
-
-      resource       - only take rows in stock table which resource_uid is
-                       equivalent to resource
-
-      from_date (>=) - return period which start >= from_date
-
-      to_date   (<)  - return period which start < to_date
-
-      second, minute,
-      hour, day,
-      month, year   - duration of each time period (cumulative)
-      """
-      portal = self.getPortalObject()
-      # Calculate portal_type
-      if portal_type == []:
-        portal_type = portal.getPortalCalendarPeriodTypeList()
-
-      sequence = Sequence(from_date, to_date, **kw)
-      for sequence_item in sequence:
-        setattr(sequence_item, 'total_quantity',
-                self.getAvailableTime(
-                          from_date=sequence_item.from_date,
-                          to_date=sequence_item.to_date,
-                          portal_type=portal_type,
-                          node=node,
-                          resource=resource,
-                          src__=src__))
-      return sequence
-
-    security.declareProtected(Permissions.AccessContentsInformation,
-                              'getAvailableTimeMovementList')
-    def getAvailableTimeMovementList(self, from_date, to_date,
-                                 **kw):
-      """
-      Calculate available time movement list by taking into account
-      both available time and not available time.
-
-      Necessary parameter is at least node.
-
-      Parameters supported by getMovementHistoryList are supported here.
-
-      from_date (>=) - return period which start >= from_date
-
-      to_date   (<)  - return period which start < to_date
-      """
-      portal = self.getPortalObject()
-      if kw.get("simulation_state", None) is None:
-        kw["simulation_state"] = portal.getPortalCurrentInventoryStateList() + \
-                         portal.getPortalTransitInventoryStateList() + \
-                         portal.getPortalReservedInventoryStateList()
-      movement_list = self.getMovementHistoryList(from_date=from_date,
-                                 to_date=to_date, group_by_movement=1,
-                                 group_by_date=1, **kw)
-      # do import on top, but better to avoid breaking instances with older softwares
-      from interval import IntervalSet, Interval
-      # we look at all movements, and we build a set of intervals for available
-      # time, another for not available time, and we do substraction of both sets
-      assignment_interval_set = IntervalSet()
-      leave_interval_set = IntervalSet()
-      result_list = []
-
-      def getOrderedMovementDates(movement):
-        date_list = [movement.date, movement.mirror_date]
-        date_list.sort()
-        return date_list
-
-      movement_availability_dict = {} # to later map availability intervals with their movements
-      for movement in movement_list:
-        start_date, stop_date = getOrderedMovementDates(movement)
-        current_interval = Interval(start_date, stop_date)
-        # case of available time
-        if movement.total_quantity > 0:
-          assignment_interval_set.add(current_interval)
-          movement_availability_dict[current_interval] = movement
-        # case of not available time
-        else:
-          leave_interval_set.add(current_interval)
-      i = 0
-      # Parse all calculated availability_interval to find matching movements to
-      # be returned in the result. IntervalSet are already ordered
-      for availability_interval in (assignment_interval_set - leave_interval_set):
-        while True:
-          assignment_interval = assignment_interval_set[i]
-          if availability_interval in assignment_interval:
-            result_list.append(movement_availability_dict[assignment_interval].asContext(
-              start_date=availability_interval.lower_bound,
-              stop_date=availability_interval.upper_bound))
-            break
-          else:
-            i += 1
-      return result_list
-
-    def _checkExpandAll(self, activate_kw={}):
-      """Check all simulation trees using AppliedRule._checkExpand
-      """
-      portal = self.getPortalObject()
-      active_process = portal.portal_activities.newActiveProcess().getPath()
-      kw = dict(priority=3, tag='checkExpand')
-      kw.update(group_method_cost=1, max_retry=0,
-                active_process=active_process, **activate_kw)
-      self._recurseCallMethod('_checkExpand', min_depth=1, max_depth=1,
-                              activate_kw=kw)
-      return active_process
-
-from Products.ERP5Type.DateUtils import addToDate
-
-class SequenceItem:
-  """
-  SequenceItem define a time period.
-  period.
-  """
-  def __init__(self, from_date, to_date):
-    self.from_date = from_date
-    self.to_date = to_date
-
-class Sequence:
-  """
-  Sequence is a iterable object, which calculate a range of time
-  period.
-  """
-  def __init__(self, from_date, to_date,
-               second=None, minute=None, hour=None,
-               day=None, month=None, year=None):
-    """
-    Calculate a list of time period.
-    Time period is a 2-tuple of 2 DateTime, which represent the from date
-    and to date of the period.
-
-    The start date of a period is calculated with the rule
-        start_date of the previous + period duration
-
-    from_date (>=) - return period which start >= from_date
-
-    to_date   (<)  - return period which start < to_date
-
-    second, minute,
-    hour, day,
-    month, year   - duration of each time period (cumulative)
-                    at least one of those parameters must be specified.
-    """
-    if not (second or minute or hour or day or month or year):
-      raise ValueError('Period duration must be specified')
-
-    self.item_list = []
-    # Calculate all time period
-    current_from_date = from_date
-    while current_from_date < to_date:
-      current_to_date = addToDate(current_from_date,
-                                  second=second,
-                                  minute=minute,
-                                  hour=hour,
-                                  day=day,
-                                  month=month,
-                                  year=year)
-      self.item_list.append(SequenceItem(current_from_date,
-                                         current_to_date))
-      current_from_date = current_to_date
-
-  def __len__(self):
-    return len(self.item_list)
-
-  def __getitem__(self, key):
-    return self.item_list[key]
-
-  def __contains__(self, value):
-    return (value in self.item_list)
-
-  def __iter__(self):
-    for x in self.item_list:
-      yield x
-
-InitializeClass(Sequence)
-allow_class(Sequence)
-InitializeClass(SequenceItem)
-allow_class(SequenceItem)
-
-InitializeClass(SimulationTool)
diff --git a/product/ERP5/__init__.py b/product/ERP5/__init__.py
index 0c4a1fb4b6437861c701906d418d30c25d685d8f..0558a91f46990418faf37446f3412077cefc0234 100644
--- a/product/ERP5/__init__.py
+++ b/product/ERP5/__init__.py
@@ -35,17 +35,13 @@ from Products.ERP5Type.Utils import initializeProduct, updateGlobals
 import sys, Permissions
 this_module = sys.modules[ __name__ ]
 document_classes = updateGlobals( this_module, globals(), permissions_module = Permissions)
-from Products.PythonScripts.Utility import allow_class
 from AccessControl import ModuleSecurityInfo
 
-import MovementGroup
-allow_class(MovementGroup)
-
 from Products.ERP5Type.Globals import package_home
 product_path = package_home( globals() )
 
 # Define object classes and tools
-from Tool import CategoryTool, SimulationTool, RuleTool, IdTool, TemplateTool,\
+from Tool import CategoryTool, RuleTool, IdTool, TemplateTool,\
                  TestTool, DomainTool, AlarmTool, OrderTool, DeliveryTool,\
                  TrashTool, ContributionTool, NotificationTool, PasswordTool,\
                  GadgetTool, ContributionRegistryTool, IntrospectionTool,\
@@ -59,7 +55,6 @@ object_classes = ( ERP5Site.ERP5Site,
                    SQLMethod.SQLMethod,
                  )
 portal_tools = ( CategoryTool.CategoryTool,
-                 SimulationTool.SimulationTool,
                  RuleTool.RuleTool,
                  IdTool.IdTool,
                  TemplateTool.TemplateTool,
diff --git a/product/ERP5/Document/AppliedRule.py b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.AppliedRule.py
similarity index 95%
rename from product/ERP5/Document/AppliedRule.py
rename to product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.AppliedRule.py
index 598cc6013e1ebd84fb1344327792d9ceb6947983..06f61fca84456843d1c4f53e70ad281980405034 100644
--- a/product/ERP5/Document/AppliedRule.py
+++ b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.AppliedRule.py
@@ -34,15 +34,17 @@ import zope.interface
 from zExceptions import ExceptionFormatter
 from ZODB.POSException import ConflictError
 from AccessControl import ClassSecurityInfo
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
+from Products.ERP5Type import Permissions, PropertySheet
 from Products.ERP5Type.Accessor.Constant import PropertyGetter as ConstantGetter
 from Products.ERP5Type.Base import WorkflowMethod
 from Products.ERP5Type.Globals import PersistentMapping
 from Products.ERP5Type.XMLObject import XMLObject
 from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
-from Products.ERP5.ExpandPolicy import TREE_DELIVERED_CACHE_KEY
-from Products.ERP5.mixin.explainable import ExplainableMixin
-from Products.ERP5.mixin.rule import RuleMixin
+from erp5.component.module.ExpandPolicy import TREE_DELIVERED_CACHE_KEY
+from erp5.component.mixin.ExplainableMixin import ExplainableMixin
+from erp5.component.mixin.RuleMixin import RuleMixin
+from erp5.component.interface.IExpandable import IExpandable
+from erp5.component.interface.IMovementCollection import IMovementCollection
 
 class AppliedRule(XMLObject, ExplainableMixin):
   """
@@ -80,8 +82,8 @@ class AppliedRule(XMLObject, ExplainableMixin):
                     )
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IExpandable,
-                            interfaces.IMovementCollection)
+  zope.interface.implements(IExpandable,
+                            IMovementCollection)
 
   def tpValues(self) :
     """ show the content in the left pane of the ZMI """
@@ -170,6 +172,7 @@ class AppliedRule(XMLObject, ExplainableMixin):
     """
     return self.objectValues(portal_type=RuleMixin.movement_type)
 
+  # pylint: disable=cell-var-from-loop
   def _migrateSimulationTree(self,
                              get_matching_key,
                              get_original_property_dict,
@@ -370,8 +373,8 @@ class AppliedRule(XMLObject, ExplainableMixin):
       for delivery, sm_dict in deleted:
         if not sm_dict:
           del old_dict[delivery]
-      from Products.ERP5.Document.SimulationMovement import SimulationMovement
-      from Products.ERP5.mixin.movement_collection_updater import \
+      from erp5.component.document.SimulationMovement import SimulationMovement
+      from erp5.component.mixin.MovementCollectionUpdaterMixin import \
           MovementCollectionUpdaterMixin as mixin
       # Patch is already protected by WorkflowMethod.disable lock.
       orig_updateMovementCollection = mixin.__dict__['updateMovementCollection']
@@ -404,8 +407,8 @@ class AppliedRule(XMLObject, ExplainableMixin):
         document = object_list.popleft()
         portal_type = document.getPortalType()
         document_dict = {'portal_type': portal_type}
-        for property in property_dict[portal_type]:
-          document_dict[property] = document.getProperty(property)
+        for property_ in property_dict[portal_type]:
+          document_dict[property_] = document.getProperty(property_)
         rule_dict[document.getRelativeUrl()] = document_dict
         object_list += document.objectValues()
       return rule_dict
diff --git a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.AppliedRule.xml b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.AppliedRule.xml
new file mode 100644
index 0000000000000000000000000000000000000000..224bc81415c0bd8d5f662421b43b357c9dbe35ad
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.AppliedRule.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Document Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>AppliedRule</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.Document.AppliedRule</string> </value>
+        </item>
+        <item>
+            <key> <string>description</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>document.erp5.AppliedRule</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Document Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.Delivery.py b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.Delivery.py
index e5383137b5bbbe81247c53f7d8c1fad6e741395d..2f343590d30c85964de7d83dbf3dd28f7c80b6ee 100644
--- a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.Delivery.py
+++ b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.Delivery.py
@@ -44,6 +44,8 @@ from Products.ERP5.mixin.composition import CompositionMixin
 from erp5.component.mixin.SimulableMixin import SimulableMixin
 from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod, \
     unrestricted_apply
+from erp5.component.interface.IMovementCollection import IMovementCollection
+from erp5.component.interface.IDivergenceController import IDivergenceController
 
 class Delivery(XMLObject, ImmobilisationDelivery, SimulableMixin,
                CompositionMixin, AmountGeneratorMixin):
@@ -75,8 +77,8 @@ class Delivery(XMLObject, ImmobilisationDelivery, SimulableMixin,
 
   # Declarative interfaces
   zope.interface.implements(interfaces.IAmountGenerator,
-                            interfaces.IDivergenceController,
-                            interfaces.IMovementCollection)
+                            IDivergenceController,
+                            IMovementCollection)
 
   security.declareProtected(Permissions.AccessContentsInformation, 'isAccountable')
   def isAccountable(self):
diff --git a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.DeliveryCell.py b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.DeliveryCell.py
index 2d36072f5452932e663bf01c2af1e867e40a1161..5e1b6f034f0e67fbe1dc4eb769d6fc284f101bff 100644
--- a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.DeliveryCell.py
+++ b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.DeliveryCell.py
@@ -33,11 +33,12 @@ import zope.interface
 from AccessControl import ClassSecurityInfo
 from AccessControl.PermissionRole import PermissionRole
 
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
+from Products.ERP5Type import Permissions, PropertySheet
 
 from Products.ERP5.Document.Movement import Movement
 from Products.ERP5.Document.MappedValue import MappedValue
 from Products.ERP5.Document.ImmobilisationMovement import ImmobilisationMovement
+from erp5.component.interface.IDivergenceController import IDivergenceController
 
 class DeliveryCell(MappedValue, Movement, ImmobilisationMovement):
   """
@@ -66,7 +67,7 @@ class DeliveryCell(MappedValue, Movement, ImmobilisationMovement):
                     )
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IDivergenceController,)
+  zope.interface.implements(IDivergenceController,)
 
   security.declareProtected(Permissions.AccessContentsInformation, 'isPredicate')
   def isPredicate(self):
diff --git a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.DeliveryLine.py b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.DeliveryLine.py
index a834adfc67a760fc57106c1e0c8ba228eb75c1de..dde60f6ab2465e420a45d4975908b3a8f23d2c5e 100644
--- a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.DeliveryLine.py
+++ b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.DeliveryLine.py
@@ -30,7 +30,7 @@
 import zope.interface
 from AccessControl import ClassSecurityInfo
 
-from Products.ERP5Type import Permissions, PropertySheet, interfaces
+from Products.ERP5Type import Permissions, PropertySheet
 from Products.ERP5Type.XMLMatrix import XMLMatrix
 
 from Products.ERP5.Document.Movement import Movement
@@ -40,6 +40,8 @@ from inspect import getargspec
 from Products.ERP5Type.Base import Base
 edit_args_list = getargspec(Base._edit).args
 
+from erp5.component.interface.IDivergenceController import IDivergenceController
+
 class DeliveryLine(Movement, XMLMatrix, ImmobilisationMovement):
   """
   A DeliveryLine object allows to implement lines in
@@ -70,7 +72,7 @@ class DeliveryLine(Movement, XMLMatrix, ImmobilisationMovement):
                     )
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IDivergenceController,)
+  zope.interface.implements(IDivergenceController,)
 
   # Multiple inheritance definition
   updateRelatedContent = XMLMatrix.updateRelatedContent
diff --git a/product/ERP5/Document/SimulationMovement.py b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.SimulationMovement.py
similarity index 97%
rename from product/ERP5/Document/SimulationMovement.py
rename to product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.SimulationMovement.py
index 4ad4a8586a5c6a6438adc584c278003d2c737406..fd9248dc252505e83c1b29197d55c8840393e0b6 100644
--- a/product/ERP5/Document/SimulationMovement.py
+++ b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.SimulationMovement.py
@@ -33,12 +33,13 @@ from AccessControl import ClassSecurityInfo
 from Products.ERP5Type import Permissions, PropertySheet, interfaces
 from Products.ERP5Type.TransactionalVariable import getTransactionalVariable
 from Products.ERP5.Document.Movement import Movement
-from Products.ERP5.ExpandPolicy import policy_dict, TREE_DELIVERED_CACHE_KEY
+from erp5.component.module.ExpandPolicy import policy_dict, TREE_DELIVERED_CACHE_KEY
 
 from zLOG import LOG, WARNING
 
 from Products.ERP5.mixin.property_recordable import PropertyRecordableMixin
-from Products.ERP5.mixin.explainable import ExplainableMixin
+from erp5.component.mixin.ExplainableMixin import ExplainableMixin
+from erp5.component.interface.IExpandable import IExpandable
 
 # XXX Do we need to create groups ? (ie. confirm group include confirmed, getting_ready and ready
 
@@ -118,7 +119,7 @@ class SimulationMovement(PropertyRecordableMixin, Movement, ExplainableMixin):
                     )
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IExpandable,
+  zope.interface.implements(IExpandable,
                             interfaces.IPropertyRecordable)
 
   def tpValues(self) :
@@ -656,25 +657,25 @@ class SimulationMovement(PropertyRecordableMixin, Movement, ExplainableMixin):
       empty; a movement is only yielded if its causality value is in this set
       """
       object_id_list = document.objectIds()
-      for id in object_id_list:
-        if id not in tree_node.visited_movement_dict:
+      for id_ in object_id_list:
+        if id_ not in tree_node.visited_movement_dict:
           # we had not visited it in step #2
-          subdocument = document._getOb(id)
+          subdocument = document._getOb(id_)
           if subdocument.getPortalType() == "Simulation Movement":
             path = subdocument.getCausalityValue(portal_type='Business Link')
             t = (subdocument, path)
-            tree_node.visited_movement_dict[id] = t
+            tree_node.visited_movement_dict[id_] = t
             if path in path_set_to_check:
               yield t
           else:
             # it must be an Applied Rule
-            subtree = tree_node.get(id, treeNode())
+            subtree = tree_node.get(id_, treeNode())
             for d in descendantGenerator(subdocument,
                                          subtree,
                                          path_set_to_check):
               yield d
 
-      for id, t in tree_node.visited_movement_dict.iteritems():
+      for id_, t in tree_node.visited_movement_dict.iteritems():
         subdocument, path = t
         to_check = path_set_to_check
         # do we need to change/copy the set?
@@ -684,7 +685,7 @@ class SimulationMovement(PropertyRecordableMixin, Movement, ExplainableMixin):
             continue
           to_check = to_check.copy()
           to_check.remove(path)
-        subtree = tree_node.get(id, treeNode())
+        subtree = tree_node.get(id_, treeNode())
         for d in descendantGenerator(subdocument, subtree, to_check):
           yield d
 
diff --git a/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.SimulationMovement.xml b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.SimulationMovement.xml
new file mode 100644
index 0000000000000000000000000000000000000000..056aa8092b77f56f796f8c01c021835161b0447c
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/DocumentTemplateItem/portal_components/document.erp5.SimulationMovement.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Document Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>SimulationMovement</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.Document.SimulationMovement</string> </value>
+        </item>
+        <item>
+            <key> <string>description</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>document.erp5.SimulationMovement</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Document Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/interfaces/divergence_controller.py b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IDivergenceController.py
similarity index 100%
rename from product/ERP5/interfaces/divergence_controller.py
rename to product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IDivergenceController.py
diff --git a/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IDivergenceController.xml b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IDivergenceController.xml
new file mode 100644
index 0000000000000000000000000000000000000000..81a21a6c690332eb1f299267050aac71dbcc7789
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IDivergenceController.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Interface Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>IDivergenceController</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.interfaces.divergence_controller</string> </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>interface.erp5.IDivergenceController</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Interface Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/interfaces/expandable.py b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IExpandable.py
similarity index 98%
rename from product/ERP5/interfaces/expandable.py
rename to product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IExpandable.py
index d6f23e8c3c8d64910963a360c6947a75460db3cd..fd18b45064567091254b46ccbda4025bfab55bf2 100644
--- a/product/ERP5/interfaces/expandable.py
+++ b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IExpandable.py
@@ -26,7 +26,7 @@
 #
 ##############################################################################
 """
-Products.ERP5.interfaces.expandable
+erp5.component.interface.IExpandable
 """
 from zope.interface import Interface
 
diff --git a/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IExpandable.xml b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IExpandable.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2585d602562d12a2e6f08528ccfb514cae274366
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IExpandable.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Interface Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>IExpandable</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.interfaces.expandable</string> </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>interface.erp5.IExpandable</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Interface Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/interfaces/movement_collection.py b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollection.py
similarity index 100%
rename from product/ERP5/interfaces/movement_collection.py
rename to product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollection.py
diff --git a/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollection.xml b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollection.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4c066e9db513ace2597ceb8bcab63fac293085a5
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollection.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Interface Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>IMovementCollection</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.interfaces.movement_collection</string> </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>interface.erp5.IMovementCollection</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Interface Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/interfaces/movement_collection_diff.py b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollectionDiff.py
similarity index 100%
rename from product/ERP5/interfaces/movement_collection_diff.py
rename to product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollectionDiff.py
diff --git a/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollectionDiff.xml b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollectionDiff.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6463032e7561966a0a0c7b3d871c699bb8b43c60
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollectionDiff.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Interface Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>IMovementCollectionDiff</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.interfaces.movement_collection_diff</string> </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>interface.erp5.IMovementCollectionDiff</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Interface Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/interfaces/movement_collection_updater.py b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollectionUpdater.py
similarity index 100%
rename from product/ERP5/interfaces/movement_collection_updater.py
rename to product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollectionUpdater.py
diff --git a/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollectionUpdater.xml b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollectionUpdater.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5946b0658a92d90da13e014c48495ad4371d87b1
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IMovementCollectionUpdater.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Interface Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>IMovementCollectionUpdater</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.interfaces.movement_collection_updater</string> </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>interface.erp5.IMovementCollectionUpdater</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Interface Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/interfaces/rule.py b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IRule.py
similarity index 95%
rename from product/ERP5/interfaces/rule.py
rename to product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IRule.py
index 226a8a3ecfe143a410ac05ea1510d92e31d74db3..70e5c5e5bd48f3e523349395e7650b818564b323 100644
--- a/product/ERP5/interfaces/rule.py
+++ b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IRule.py
@@ -27,9 +27,9 @@
 #
 ##############################################################################
 """
-Products.ERP5.interfaces.rule
+erp5.component.interface.IRule
 """
-from Products.ERP5.interfaces.movement_collection_updater import IMovementCollectionUpdater
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class IRule(IMovementCollectionUpdater):
   """Rule interface specification
diff --git a/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IRule.xml b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IRule.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6612d62cdb881cc75f217ee91ee90cb211183cf8
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.IRule.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Interface Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>IRule</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.interfaces.rule</string> </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>interface.erp5.IRule</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Interface Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/interfaces/simulation_movement.py b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.ISimulationMovement.py
similarity index 97%
rename from product/ERP5/interfaces/simulation_movement.py
rename to product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.ISimulationMovement.py
index 0d8904d27cf6bf58559bb32a169a444075db7668..8e07a49050b96a28dfd84a3e070d4e9da78540f2 100644
--- a/product/ERP5/interfaces/simulation_movement.py
+++ b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.ISimulationMovement.py
@@ -27,14 +27,13 @@
 #
 ##############################################################################
 """
-Products.ERP5.interfaces.simulation_movement
+erp5.component.interface.ISimulationMovement
 """
 
 from Products.ERP5.interfaces.property_recordable import IPropertyRecordable
 from Products.ERP5.interfaces.movement import IMovement
-from Products.ERP5.interfaces.divergence_controller import IDivergenceController
+from erp5.component.interface.IDivergenceController import IDivergenceController
 from Products.ERP5.interfaces.explainable import IExplainable
-from zope.interface import Interface
 
 class ISimulationMovement(IMovement, IPropertyRecordable, IDivergenceController, IExplainable):
   """Simulation Movement interface specification
diff --git a/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.ISimulationMovement.xml b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.ISimulationMovement.xml
new file mode 100644
index 0000000000000000000000000000000000000000..17cc662b2112fb90e9c6fe5be762e228c48658d3
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/InterfaceTemplateItem/portal_components/interface.erp5.ISimulationMovement.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Interface Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>ISimulationMovement</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.interfaces.simulation_movement</string> </value>
+        </item>
+        <item>
+            <key> <string>description</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>interface.erp5.ISimulationMovement</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Interface Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/mixin/explainable.py b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.ExplainableMixin.py
similarity index 100%
rename from product/ERP5/mixin/explainable.py
rename to product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.ExplainableMixin.py
diff --git a/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.ExplainableMixin.xml b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.ExplainableMixin.xml
new file mode 100644
index 0000000000000000000000000000000000000000..758269a88369537b024c0947f0871ab83dabd6bb
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.ExplainableMixin.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Mixin Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>ExplainableMixin</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.mixin.explainable</string> </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>mixin.erp5.ExplainableMixin</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Mixin Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/mixin/movement_collection_updater.py b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.MovementCollectionUpdaterMixin.py
similarity index 96%
rename from product/ERP5/mixin/movement_collection_updater.py
rename to product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.MovementCollectionUpdaterMixin.py
index 47283b080489a15aa5d92e0557ce6af180c72d9a..83fe8d2a4b49ea8108978dd40821c020578e8156 100644
--- a/product/ERP5/mixin/movement_collection_updater.py
+++ b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.MovementCollectionUpdaterMixin.py
@@ -29,10 +29,11 @@
 import zope.interface
 from AccessControl import ClassSecurityInfo
 from Products.ERP5Type.Globals import InitializeClass
-from Products.ERP5Type import Permissions, interfaces
-from Products.ERP5.MovementCollectionDiff import (
+from Products.ERP5Type import Permissions
+from erp5.component.module.MovementCollectionDiff import (
   MovementCollectionDiff, _getPropertyAndCategoryList)
-from Products.ERP5.mixin.rule import _compare
+from erp5.component.mixin.RuleMixin import _compare
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 class MovementCollectionUpdaterMixin:
   """Movement Collection Updater.
@@ -49,7 +50,7 @@ class MovementCollectionUpdaterMixin:
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IMovementCollectionUpdater,)
+  zope.interface.implements(IMovementCollectionUpdater,)
 
   # Implementation of IMovementCollectionUpdater
   security.declareProtected(Permissions.AccessContentsInformation,
diff --git a/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.MovementCollectionUpdaterMixin.xml b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.MovementCollectionUpdaterMixin.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2aff2360d41c3f6f7a266048d3b121ea6777bf3d
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.MovementCollectionUpdaterMixin.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Mixin Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>MovementCollectionUpdaterMixin</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.mixin.movement_collection_updater</string> </value>
+        </item>
+        <item>
+            <key> <string>description</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>mixin.erp5.MovementCollectionUpdaterMixin</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Mixin Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/mixin/rule.py b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.RuleMixin.py
similarity index 90%
rename from product/ERP5/mixin/rule.py
rename to product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.RuleMixin.py
index 484cfab2050869c0f6080147bf58f03cf0f2ca5c..0ff0509fced87a21e214cb42b61f2aa8a3f71815 100644
--- a/product/ERP5/mixin/rule.py
+++ b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.RuleMixin.py
@@ -29,9 +29,12 @@
 import zope.interface
 from AccessControl import ClassSecurityInfo
 from Products.ERP5Type.Globals import InitializeClass
-from Products.ERP5Type import Permissions, interfaces
+from Products.ERP5Type import Permissions
 from Products.ERP5Type.Core.Predicate import Predicate
-from Products.ERP5.ExpandPolicy import policy_dict
+from erp5.component.module.ExpandPolicy import policy_dict
+from erp5.component.interface.IRule import IRule
+from erp5.component.interface.IDivergenceController import IDivergenceController
+from erp5.component.interface.IMovementCollectionUpdater import IMovementCollectionUpdater
 
 def _compare(tester_list, prevision_movement, decision_movement):
   for tester in tester_list:
@@ -49,9 +52,9 @@ class RuleMixin(Predicate):
   security.declareObjectProtected(Permissions.AccessContentsInformation)
 
   # Declarative interfaces
-  zope.interface.implements(interfaces.IRule,
-                            interfaces.IDivergenceController,
-                            interfaces.IMovementCollectionUpdater,)
+  zope.interface.implements(IRule,
+                            IDivergenceController,
+                            IMovementCollectionUpdater,)
 
   # Portal Type of created children
   movement_type = 'Simulation Movement'
@@ -83,13 +86,13 @@ class RuleMixin(Predicate):
         # Rules have a workflow - make sure applicable rule system works
         # if you wish, add a test here on workflow state to prevent using
         # rules which are no longer applicable
-   def test(self, *args, **kw):
-    """
-    If no test method is defined, return False, to prevent infinite loop
-    """
-    if not self.getTestMethodId():
-      return False
-    return super(RuleMixin, self).test(*args, **kw)
+    def test(self, *args, **kw):
+      """
+      If no test method is defined, return False, to prevent infinite loop
+      """
+      if not self.getTestMethodId():
+        return False
+      return super(RuleMixin, self).test(*args, **kw)
 
   security.declareProtected(Permissions.ModifyPortalContent,
                             'expand')
@@ -128,7 +131,7 @@ class RuleMixin(Predicate):
   # Implementation of IDivergenceController # XXX-JPS move to IDivergenceController only mixin for
   security.declareProtected( Permissions.AccessContentsInformation,
                             'isDivergent')
-  def isDivergent(self, movement, ignore_list=[]):
+  def isDivergent(self, movement, ignore_list=()):
     """
     Returns true if the Simulation Movement is divergent comparing to
     the delivery value
@@ -179,12 +182,16 @@ class RuleMixin(Predicate):
                          quantity divergence testers
     """
     if exclude_quantity:
-      return filter(lambda x:x.isDivergenceProvider() and \
-                    'quantity' not in x.getTestedPropertyList(), self.objectValues(
-        portal_type=self.getPortalDivergenceTesterTypeList()))
+      return [
+        x for x in self.objectValues(
+          portal_type=self.getPortalDivergenceTesterTypeList())
+        if (x.isDivergenceProvider() and
+            'quantity' not in x.getTestedPropertyList())]
     else:
-      return filter(lambda x:x.isDivergenceProvider(), self.objectValues(
-        portal_type=self.getPortalDivergenceTesterTypeList()))
+      return [
+        x for x in self.objectValues(
+          portal_type=self.getPortalDivergenceTesterTypeList())
+        if x.isDivergenceProvider()]
 
   def _getMatchingTesterList(self):
     """
@@ -192,8 +199,10 @@ class RuleMixin(Predicate):
     be used to match movements and build the diff (ie.
     not all divergence testers of the Rule)
     """
-    return filter(lambda x:x.isMatchingProvider(), self.objectValues(
-      portal_type=self.getPortalDivergenceTesterTypeList()))
+    return [
+      x for x in self.objectValues(
+        portal_type=self.getPortalDivergenceTesterTypeList())
+      if x.isMatchingProvider()]
 
   def _getUpdatingTesterList(self, exclude_quantity=False):
     """
@@ -204,12 +213,16 @@ class RuleMixin(Predicate):
                         quantity divergence testers
     """
     if exclude_quantity:
-      return filter(lambda x:x.isUpdatingProvider() and \
-                    'quantity' not in x.getTestedPropertyList(), self.objectValues(
-        portal_type=self.getPortalDivergenceTesterTypeList()))
+      return [
+        x for x in self.objectValues(
+          portal_type=self.getPortalDivergenceTesterTypeList())
+        if (x.isUpdatingProvider() and
+            'quantity' not in x.getTestedPropertyList())]
     else:
-      return filter(lambda x:x.isUpdatingProvider(), self.objectValues(
-        portal_type=self.getPortalDivergenceTesterTypeList()))
+      return [
+        x for x in self.objectValues(
+          portal_type=self.getPortalDivergenceTesterTypeList())
+        if x.isUpdatingProvider()]
 
   def _getQuantityTesterList(self):
     """
diff --git a/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.RuleMixin.xml b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.RuleMixin.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bcf0604fea2b2623b071a83a554083118e72b77e
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/MixinTemplateItem/portal_components/mixin.erp5.RuleMixin.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Mixin Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>RuleMixin</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.mixin.rule</string> </value>
+        </item>
+        <item>
+            <key> <string>description</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>mixin.erp5.RuleMixin</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Mixin Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/ExpandPolicy.py b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.ExpandPolicy.py
similarity index 98%
rename from product/ERP5/ExpandPolicy.py
rename to product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.ExpandPolicy.py
index 81b0c8076d1c8d403726fc73235355c5909a77aa..6c631ce97cc4a10b29a12d7977d75c955f12a191 100644
--- a/product/ERP5/ExpandPolicy.py
+++ b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.ExpandPolicy.py
@@ -130,7 +130,7 @@ class VerticalTimeBound(_Policy):
     super(VerticalTimeBound, self).__init__(**kw)
     self.stop = transaction.get().start_time + VERTICAL_EXPAND_TIMEOUT
 
-  def test(self, context):
+  def test(self, context): # pylint: disable=method-hidden
     if time() < self.stop:
       return True
     self.deferAll()
diff --git a/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.ExpandPolicy.xml b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.ExpandPolicy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b809340f01bdf4faeab0a2790afc0711eec6271a
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.ExpandPolicy.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Module Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>ExpandPolicy</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.ExpandPolicy</string> </value>
+        </item>
+        <item>
+            <key> <string>description</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>module.erp5.ExpandPolicy</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Module Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/MovementCollectionDiff.py b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementCollectionDiff.py
similarity index 97%
rename from product/ERP5/MovementCollectionDiff.py
rename to product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementCollectionDiff.py
index 7bf1c91b3ccee80e2f7d7844e33fb08b53c2e1da..872989fdbf08db97266ba7cd9f4eaea69509d139 100644
--- a/product/ERP5/MovementCollectionDiff.py
+++ b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementCollectionDiff.py
@@ -27,8 +27,8 @@
 ##############################################################################
 
 import zope.interface
-from Products.ERP5Type import interfaces
 from Products.ERP5Type.Accessor.TypeDefinition import list_types
+from erp5.component.interface.IMovementCollectionDiff import IMovementCollectionDiff
 
 class MovementCollectionDiff(object):
   """
@@ -39,7 +39,7 @@ class MovementCollectionDiff(object):
   IMovementCollectionUpdater.
   """
   # Declarative interfaces
-  zope.interface.implements(interfaces.IMovementCollectionDiff,)
+  zope.interface.implements(IMovementCollectionDiff,)
 
   def __init__(self):
     self._deletable_movement_list = []
diff --git a/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementCollectionDiff.xml b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementCollectionDiff.xml
new file mode 100644
index 0000000000000000000000000000000000000000..42dfc58fe4902f9d83e745ef246e70bc10198fc9
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementCollectionDiff.xml
@@ -0,0 +1,104 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Module Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>MovementCollectionDiff</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.MovementCollectionDiff</string> </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>module.erp5.MovementCollectionDiff</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Module Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/MovementGroup.py b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementGroup.py
similarity index 85%
rename from product/ERP5/MovementGroup.py
rename to product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementGroup.py
index 7e46c5e6b2426e571c935d13e2b7d66d951e693b..fda5ae9e62631576aa78509686f22f3d52e7e655 100644
--- a/product/ERP5/MovementGroup.py
+++ b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementGroup.py
@@ -41,7 +41,7 @@ class MovementGroupNode:
   # a separate method requests separating movements.
   def __init__(self, movement_group_list=None, movement_list=None,
                last_line_movement_group=None,
-               separate_method_name_list=[], movement_group=None,
+               separate_method_name_list=(), movement_group=None,
                merge_delivery=None):
     self._movement_list = []
     self._group_list = []
@@ -332,7 +332,7 @@ class FakeMovement:
         return True
     return False
 
-  def _setDelivery(self, object):
+  def _setDelivery(self, object): # pylint: disable=redefined-builtin
     """
       Set Delivery value for each movement
     """
@@ -507,86 +507,4 @@ class FakeMovement:
     repr_str = '<%s object at 0x%x for %r' % (self.__class__.__name__,
                                               id(self),
                                               self.getMovementList())
-    return repr_str
-
-# The following classes are not ported to Document/XxxxMovementGroup.py yet.
-
-class RootMovementGroup(MovementGroupNode):
-  pass
-
-class SplitResourceMovementGroup(RootMovementGroup):
-
-  def __init__(self, movement, **kw):
-    RootMovementGroup.__init__(self, movement=movement, **kw)
-    self.resource = movement.getResource()
-
-  def test(self, movement):
-    return movement.getResource() == self.resource
-
-allow_class(SplitResourceMovementGroup)
-
-class OptionMovementGroup(RootMovementGroup):
-
-  def __init__(self,movement,**kw):
-    RootMovementGroup.__init__(self, movement=movement, **kw)
-    option_base_category_list = movement.getPortalOptionBaseCategoryList()
-    self.option_category_list = movement.getVariationCategoryList(
-                                  base_category_list=option_base_category_list)
-    if self.option_category_list is None:
-      self.option_category_list = []
-    self.option_category_list.sort()
-    # XXX This is very bad, but no choice today.
-    self.setGroupEdit(industrial_phase_list = self.option_category_list)
-
-  def test(self,movement):
-    option_base_category_list = movement.getPortalOptionBaseCategoryList()
-    movement_option_category_list = movement.getVariationCategoryList(
-                              base_category_list=option_base_category_list)
-    if movement_option_category_list is None:
-      movement_option_category_list = []
-    movement_option_category_list.sort()
-    return movement_option_category_list == self.option_category_list
-
-allow_class(OptionMovementGroup)
-
-# XXX This should not be here
-# I (seb) have commited this because movement groups are not
-# yet configurable through the zope web interface
-class IntIndexMovementGroup(RootMovementGroup):
-
-  def getIntIndex(self,movement):
-    order_value = movement.getOrderValue()
-    int_index = 0
-    if order_value is not None:
-      if "Line" in order_value.getPortalType():
-        int_index = order_value.getIntIndex()
-      elif "Cell" in order_value.getPortalType():
-        int_index = order_value.getParentValue().getIntIndex()
-    return int_index
-
-  def __init__(self,movement,**kw):
-    RootMovementGroup.__init__(self, movement=movement, **kw)
-    int_index = self.getIntIndex(movement)
-    self.int_index = int_index
-    self.setGroupEdit(
-        int_index=int_index
-    )
-
-  def test(self,movement):
-    return self.getIntIndex(movement) == self.int_index
-
-allow_class(IntIndexMovementGroup)
-
-class ParentExplanationMovementGroup(RootMovementGroup): pass
-
-class ParentExplanationCausalityMovementGroup(ParentExplanationMovementGroup):
-  """
-  Like ParentExplanationMovementGroup, and set the causality.
-  """
-  def __init__(self, movement, **kw):
-    ParentExplanationMovementGroup.__init__(self, movement=movement, **kw)
-    self.updateGroupEdit(
-        causality_value = self.explanation_value
-    )
-
-allow_class(ParentExplanationCausalityMovementGroup)
+    return repr_str
\ No newline at end of file
diff --git a/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementGroup.xml b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementGroup.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4f232d16eacac950c94a487468b4333a7a3eb3c1
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/ModuleComponentTemplateItem/portal_components/module.erp5.MovementGroup.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Module Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>MovementGroup</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.MovementGroup</string> </value>
+        </item>
+        <item>
+            <key> <string>description</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>module.erp5.MovementGroup</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Module Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/bootstrap/erp5_core/ToolComponentTemplateItem/portal_components/tool.erp5.SimulationTool.py b/product/ERP5/bootstrap/erp5_core/ToolComponentTemplateItem/portal_components/tool.erp5.SimulationTool.py
new file mode 100644
index 0000000000000000000000000000000000000000..5de52acaa590d75aa189ca15fabcec959da45d12
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/ToolComponentTemplateItem/portal_components/tool.erp5.SimulationTool.py
@@ -0,0 +1,2998 @@
+##############################################################################
+#
+# 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 Products.CMFCore.utils import getToolByName
+
+from AccessControl import ClassSecurityInfo
+from Products.ERP5Type.Globals import InitializeClass
+from Products.ERP5Type import Permissions
+from Products.ERP5Type.Tool.BaseTool import BaseTool
+from Products.ERP5Type.UnrestrictedMethod import UnrestrictedMethod
+
+from zLOG import LOG, PROBLEM, WARNING, INFO
+
+from Products.ERP5.Capacity.GLPK import solve
+from numpy import zeros, resize
+from DateTime import DateTime
+
+from Products.PythonScripts.Utility import allow_class
+
+from Products.ZSQLCatalog.SQLCatalog import Query, ComplexQuery, SimpleQuery
+
+from Shared.DC.ZRDB.Results import Results
+from Products.ERP5Type.Utils import mergeZRDBResults
+from App.Extensions import getBrain
+from MySQLdb import ProgrammingError
+from MySQLdb.constants.ER import NO_SUCH_TABLE
+
+from hashlib import md5
+from warnings import warn
+from cPickle import loads, dumps
+from copy import deepcopy
+
+MYSQL_MIN_DATETIME_RESOLUTION = 1/86400.
+
+class StockOptimisationError(Exception):
+  pass
+
+class SimulationTool(BaseTool):
+  """
+  The SimulationTool implements the ERP5
+  simulation algorithmics.
+
+
+  Examples of applications:
+
+  -
+
+  -
+  ERP5 main purpose:
+
+  -
+
+  -
+
+  """
+  id = 'portal_simulation'
+  meta_type = 'ERP5 Simulation Tool'
+  portal_type = 'Simulation Tool'
+  allowed_types = ( 'ERP5 Applied Rule', )
+
+  # Declarative Security
+  security = ClassSecurityInfo()
+
+  #
+  #   ZMI methods
+  #
+
+  def filtered_meta_types(self, user=None):
+    # Filters the list of available meta types.
+    meta_types = []
+    for meta_type in SimulationTool.inheritedAttribute('filtered_meta_types')(self):
+      if meta_type['name'] in self.allowed_types:
+        meta_types.append(meta_type)
+    return meta_types
+
+  def tpValues(self) :
+    """ show the content in the left pane of the ZMI """
+    return self.objectValues()
+
+  security.declarePrivate('manage_afterAdd')
+  def manage_afterAdd(self, item, container) :
+    """Init permissions right after creation.
+
+    Permissions in simulation tool are simple:
+     o Each member can access and create some content.
+     o Only manager can view, because simulation can be seen as
+       sensitive information.
+    """
+    item.manage_permission(Permissions.AddPortalContent,
+          ['Member', 'Author', 'Manager'])
+    item.manage_permission(Permissions.AccessContentsInformation,
+          ['Member', 'Auditor', 'Manager'])
+    item.manage_permission(Permissions.View,
+          ['Manager',])
+    BaseTool.inheritedAttribute('manage_afterAdd')(self, item, container)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'solveDelivery')
+  def solveDelivery(self, delivery, delivery_solver_name, target_solver_name,
+                    additional_parameters=None, **kw):
+    """
+      XXX obsoleted API
+
+      Solves a delivery by calling first DeliverySolver, then TargetSolver
+    """
+    return self._solveMovementOrDelivery(delivery, delivery_solver_name,
+        target_solver_name, delivery=1,
+        additional_parameters=additional_parameters, **kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'solveMovement')
+  def solveMovement(self, movement, delivery_solver_name, target_solver_name,
+                    additional_parameters=None, **kw):
+    """
+      XXX obsoleted API
+
+      Solves a movement by calling first DeliverySolver, then TargetSolver
+    """
+    return self._solveMovementOrDelivery(movement, delivery_solver_name,
+        target_solver_name, movement=1,
+        additional_parameters=additional_parameters, **kw)
+
+  def _solveMovementOrDelivery(self, document, delivery_solver_name,
+                               target_solver_name, movement=0, delivery=0,
+                               additional_parameters=None,**kw):
+    """
+      Solves a document by calling first DeliverySolver, then TargetSolver
+    """
+    if movement == delivery:
+      raise ValueError('Parameters movement and delivery have to be'
+                       ' different')
+
+    from Products.ERP5 import DeliverySolver
+    from Products.ERP5 import TargetSolver
+    solve_result = []
+    for solver_name, solver_module in ((delivery_solver_name, DeliverySolver),
+                                       (target_solver_name, TargetSolver)):
+      result = None
+      if solver_name is not None:
+        solver_file_path = "%s.%s" % (solver_module.__name__,
+                                      solver_name)
+        __import__(solver_file_path)
+        solver_file = getattr(solver_module, solver_name)
+        solver_class = getattr(solver_file, solver_name)
+        solver = solver_class(additional_parameters=additional_parameters,
+            **kw)
+
+        if movement:
+          result = solver.solveMovement(document)
+        if delivery:
+          result = solver.solveDelivery(document)
+      solve_result.append(result)
+    return solve_result
+
+  #######################################################
+  # Stock Management
+
+  def _generatePropertyUidList(self, prop, as_text=0):
+    """
+    converts relative_url or text (single element or list or dict)
+      to an object usable by buildSQLQuery
+
+    as_text == 0: tries to lookup an uid from the relative_url
+    as_text == 1: directly passes the argument as text
+    """
+    if prop is None :
+      return []
+    category_tool = getToolByName(self, 'portal_categories')
+    property_uid_list = []
+    if isinstance(prop, str):
+      if not as_text:
+        prop_value = category_tool.getCategoryValue(prop)
+        if prop_value is None:
+          raise ValueError, 'Category %s does not exists' % prop
+        property_uid_list.append(prop_value.getUid())
+      else:
+        property_uid_list.append(prop)
+    elif isinstance(prop, (list, tuple)):
+      for property_item in prop :
+        if not as_text:
+          prop_value = category_tool.getCategoryValue(property_item)
+          if prop_value is None:
+            raise ValueError, 'Category %s does not exists' % property_item
+          property_uid_list.append(prop_value.getUid())
+        else:
+          property_uid_list.append(property_item)
+    elif isinstance(prop, dict):
+      tmp_uid_list = []
+      if isinstance(prop['query'], str):
+        prop['query'] = [prop['query']]
+      for property_item in prop['query'] :
+        if not as_text:
+          prop_value = category_tool.getCategoryValue(property_item)
+          if prop_value is None:
+            raise ValueError, 'Category %s does not exists' % property_item
+          tmp_uid_list.append(prop_value.getUid())
+        else:
+          tmp_uid_list.append(property_item)
+      if tmp_uid_list:
+        property_uid_list = {}
+        property_uid_list['operator'] = prop['operator']
+        property_uid_list['query'] = tmp_uid_list
+    return property_uid_list
+
+  def _getSimulationStateQuery(self, **kw):
+    simulation_state_dict = self._getSimulationStateDict(**kw)
+    return self._buildSimulationStateQuery(simulation_state_dict=simulation_state_dict)
+
+  def _buildSimulationStateQuery(self, simulation_state_dict, table='stock'):
+    simulation_state = simulation_state_dict.get('simulation_state')
+    if simulation_state is not None:
+      return SimpleQuery(**{table + '.simulation_state': simulation_state})
+    input_simulation_state = simulation_state_dict.get('input_simulation_state')
+    if input_simulation_state is not None:
+      simulation_query = ComplexQuery(
+        self._getIncreaseQuery(table, 'quantity', True),
+        SimpleQuery(**{table + '.simulation_state': input_simulation_state}),
+        logical_operator='AND',
+      )
+      output_simulation_state = simulation_state_dict.get('output_simulation_state')
+      if output_simulation_state is not None:
+        simulation_query = ComplexQuery(
+          simulation_query,
+          ComplexQuery(
+            self._getIncreaseQuery(table, 'quantity', False),
+            SimpleQuery(**{table + '.simulation_state': output_simulation_state}),
+            logical_operator='AND',
+          ),
+          logical_operator='OR'
+        )
+      return simulation_query
+
+  def _getSimulationStateDict(self, simulation_state=None, omit_transit=0,
+                              input_simulation_state=None,
+                              output_simulation_state=None,
+                              transit_simulation_state=None,
+                              strict_simulation_state=None):
+    """
+    This method is used in order to give what should be
+    the input_simulation_state or output_simulation_state
+    depending on many parameters
+    """
+    string_or_list = (str, list, tuple)
+    # Simulation States
+    # If strict_simulation_state is set, we directly put it into the dictionary
+    simulation_dict = {}
+    if strict_simulation_state:
+      if isinstance(simulation_state, string_or_list)\
+              and simulation_state:
+        simulation_dict['simulation_state'] = simulation_state
+    else:
+      # first, we evaluate simulation_state
+      sql_kw = {}
+      if simulation_state and isinstance(simulation_state, string_or_list):
+        if isinstance(simulation_state, str):
+          sql_kw['input_simulation_state'] = [simulation_state]
+          sql_kw['output_simulation_state'] = [simulation_state]
+        else:
+          sql_kw['input_simulation_state'] = simulation_state
+          sql_kw['output_simulation_state'] = simulation_state
+      # then, if omit_transit == 1, we evaluate (simulation_state -
+      # transit_simulation_state) for input_simulation_state
+      if omit_transit:
+        if isinstance(simulation_state, string_or_list)\
+              and simulation_state:
+          if isinstance(transit_simulation_state, string_or_list)\
+                and transit_simulation_state:
+            # when we know both are usable, we try to calculate
+            # (simulation_state - transit_simulation_state)
+            if isinstance(simulation_state, str):
+              simulation_state = [simulation_state]
+            if isinstance(transit_simulation_state, str) :
+              transit_simulation_state = [transit_simulation_state]
+            delivered_simulation_state_list = []
+            for state in simulation_state :
+              if state not in transit_simulation_state :
+                delivered_simulation_state_list.append(state)
+            sql_kw['input_simulation_state'] = delivered_simulation_state_list
+
+      # alternatively, the user can directly define input_simulation_state
+      # and output_simulation_state
+      if input_simulation_state and isinstance(input_simulation_state,
+                                                string_or_list):
+        if isinstance(input_simulation_state, str):
+          input_simulation_state = [input_simulation_state]
+        sql_kw['input_simulation_state'] = input_simulation_state
+      if output_simulation_state and isinstance(output_simulation_state,
+                                                string_or_list):
+        if isinstance(output_simulation_state, str):
+          output_simulation_state = [output_simulation_state]
+        sql_kw['output_simulation_state'] = output_simulation_state
+      # XXX In this case, we must not set sql_kw[input_simumlation_state] before
+      input_simulation_state = None
+      output_simulation_state = None
+      if sql_kw.has_key('input_simulation_state'):
+        input_simulation_state = sql_kw.get('input_simulation_state')
+      if sql_kw.has_key('output_simulation_state'):
+        output_simulation_state = sql_kw.get('output_simulation_state')
+      if input_simulation_state is not None \
+         or output_simulation_state is not None:
+        sql_kw.pop('input_simulation_state',None)
+        sql_kw.pop('output_simulation_state',None)
+      if input_simulation_state is not None:
+        if output_simulation_state is not None:
+          if input_simulation_state == output_simulation_state:
+            simulation_dict['simulation_state'] = input_simulation_state
+          else:
+            simulation_dict['input_simulation_state'] = input_simulation_state
+            simulation_dict['output_simulation_state'] = output_simulation_state
+        else:
+          simulation_dict['input_simulation_state'] = input_simulation_state
+      elif output_simulation_state is not None:
+        simulation_dict['simulation_state'] = output_simulation_state
+    return simulation_dict
+
+  def _getIncreaseQuery(self, table, column, increase, sql_catalog_id=None):
+    """
+    Returns a Query filtering rows depending on whether they represent an
+    increase or a decrease.
+    table (string)
+      Name of table to use as stock table.
+    column (string)
+      Name of interesting column. Supported values are:
+      - total_price for asset price increase/decrease
+      - quantity for quantity increase (aka input)/decrease (aka output)
+    increase (bool)
+      False: decreasing rows are kept
+      True: increasing rows are kept
+    sql_catalog_id (string or None)
+      Idenfitier of an SQLCatalog object relevant to table, or None for
+      default one.
+    """
+    if column == 'total_price':
+      dedicated_column = 'is_asset_increase'
+    elif column == 'quantity':
+      dedicated_column = 'is_input'
+    else:
+      raise ValueError('Unknown column %r' % (column, ))
+    if self.getPortalObject().portal_catalog.hasColumn(
+          dedicated_column,
+          sql_catalog_id,
+        ):
+      return SimpleQuery(**{dedicated_column: increase})
+    # Dedicated columns are not present, compute on the fly.
+    return ComplexQuery(
+      ComplexQuery(
+        SimpleQuery(comparison_operator='<', **{table + '.' + column: 0}),
+        SimpleQuery(**{table + '.is_cancellation': increase}),
+        logical_operator='AND',
+      ),
+      ComplexQuery(
+        SimpleQuery(comparison_operator='>=', **{table + '.' + column: 0}),
+        SimpleQuery(**{table + '.is_cancellation': not increase}),
+        logical_operator='AND',
+      ),
+      logical_operator='OR',
+    )
+
+  def _generateSQLKeywordDict(self, table='stock', **kw):
+    sql_kw, new_kw = self._generateKeywordDict(**kw)
+    return self._generateSQLKeywordDictFromKeywordDict(table=table,
+               sql_kw=sql_kw, new_kw=new_kw)
+
+  def _generateSQLKeywordDictFromKeywordDict(self, table='stock', sql_kw=None,
+                                             new_kw=None):
+    ctool = getToolByName(self, 'portal_catalog')
+    if sql_kw is None:
+      sql_kw = {}
+    else:
+      sql_kw = sql_kw.copy()
+    if new_kw is None:
+      new_kw = {}
+    else:
+      new_kw = new_kw.copy()
+
+    # Group-by expression  (eg. group_by=['node_uid'])
+    group_by = new_kw.pop('group_by_list', [])
+
+    # group by from stock table (eg. group_by_node=True)
+    # prepend table name to avoid ambiguities.
+    column_group_by = new_kw.pop('column_group_by', [])
+    if column_group_by:
+      group_by.extend(['%s.%s' % (table, x) for x in column_group_by])
+
+    # group by from related keys columns (eg. group_by_node_category=True)
+    related_key_group_by = new_kw.pop('related_key_group_by', [])
+    if related_key_group_by:
+      group_by.extend(['%s_%s' % (table, x) for x in related_key_group_by])
+
+    # group by involving a related key (eg. group_by=['product_line_uid'])
+    related_key_dict_passthrough_group_by = new_kw.get(
+            'related_key_dict_passthrough', {}).pop('group_by_list', [])
+    if isinstance(related_key_dict_passthrough_group_by, basestring):
+      related_key_dict_passthrough_group_by = (
+            related_key_dict_passthrough_group_by,)
+    group_by.extend(related_key_dict_passthrough_group_by)
+
+    if group_by:
+      new_kw['group_by_list'] = group_by
+
+    # select expression
+    select_dict = new_kw.setdefault('select_dict', {})
+    related_key_select_expression_list = new_kw.pop(
+              'related_key_select_expression_list', [])
+    for related_key_select in related_key_select_expression_list:
+      select_dict[related_key_select] = '%s_%s' % (table,
+                                                   related_key_select)
+
+    # Column values
+    column_value_dict = new_kw.pop('column_value_dict', {})
+    for key, value in column_value_dict.iteritems():
+      new_kw['%s.%s' % (table, key)] = value
+    # Related keys
+    # First, the passthrough (acts as default values)
+    for key, value in new_kw.pop('related_key_dict_passthrough', {})\
+        .iteritems():
+      new_kw[key] = value
+    # Second, calculated values
+    for key, value in new_kw.pop('related_key_dict', {}).iteritems():
+      new_kw['%s_%s' % (table, key)] = value
+    # Simulation states matched with input and output omission
+    def getSimulationQuery(simulation_dict, omit_dict):
+      simulation_query = self._buildSimulationStateQuery(
+        simulation_state_dict=simulation_dict,
+        table=table,
+      )
+      query_list = [
+        self._getIncreaseQuery(table, column, value)
+        for key, column, value in (
+          ('input',          'quantity',    False),
+          ('output',         'quantity',    True),
+          ('asset_increase', 'total_price', False),
+          ('asset_decrease', 'total_price', True),
+        )
+        if omit_dict.get(key)
+      ]
+      if query_list:
+        if simulation_query is not None:
+          query_list.append(simulation_query)
+        return ComplexQuery(
+          query_list,
+          logical_operator='AND',
+        )
+      return simulation_query
+    simulation_query = getSimulationQuery(
+      new_kw.pop('simulation_dict', {}),
+      new_kw.pop('omit_dict', {}),
+    )
+    reserved_kw = new_kw.pop('reserved_kw', None)
+    if reserved_kw is not None:
+      reserved_query = getSimulationQuery(
+        reserved_kw.pop('simulation_dict', {}),
+        reserved_kw.pop('omit_dict', {}),
+      )
+      if simulation_query is None:
+        simulation_query = reserved_query
+      elif reserved_query is not None:
+        simulation_query = ComplexQuery(
+          simulation_query,
+          reserved_query,
+          logical_operator='OR',
+        )
+    if simulation_query is not None:
+      new_kw['query'] = simulation_query
+
+    # Sort on
+    if 'sort_on' in new_kw:
+      table_column_list = ctool.getSQLCatalog().getTableColumnList(table)
+      sort_on = new_kw['sort_on']
+      new_sort_on = []
+      for column_id, sort_direction in sort_on:
+        if column_id in table_column_list:
+          column_id = '%s.%s' % (table, column_id)
+        new_sort_on.append((column_id, sort_direction))
+      new_kw['sort_on'] = tuple(new_sort_on)
+
+    # Remove some internal parameters that does not have any meaning for
+    # catalog
+    new_kw.pop('ignore_group_by', None)
+
+    catalog_sql_kw = ctool.buildSQLQuery(**new_kw)
+    from_table_dict = dict(sql_kw.pop('from_table_list', []))
+    for alias, table in catalog_sql_kw.pop('from_table_list', None) or []:
+      assert from_table_dict.get(alias) in (None, table), (
+        alias,
+        table,
+        from_table_dict[alias],
+      )
+      from_table_dict[alias] = table
+    sql_kw.update(catalog_sql_kw)
+    sql_kw['from_table_list'] = from_table_dict.items()
+    return sql_kw
+
+  def _generateKeywordDict(self,
+      # dates
+      from_date=None, to_date=None, at_date=None,
+      omit_mirror_date=1,
+      # instances
+      resource=None, node=None, payment=None,
+      section=None, mirror_section=None, item=None,
+      function=None, project=None, funding=None, payment_request=None,
+      transformed_resource=None, ledger=None,
+      # used for tracking
+      input=0, # pylint: disable=redefined-builtin
+      output=0,
+      # categories
+      resource_category=None, node_category=None, payment_category=None,
+      section_category=None, mirror_section_category=None,
+      function_category=None, project_category=None, funding_category=None,
+      ledger_category=None, payment_request_category=None,
+      # categories with strict membership
+      resource_category_strict_membership=None,
+      node_category_strict_membership=None,
+      payment_category_strict_membership=None,
+      section_category_strict_membership=None,
+      mirror_section_category_strict_membership=None,
+      function_category_strict_membership=None,
+      project_category_strict_membership=None,
+      funding_category_strict_membership=None,
+      ledger_category_strict_membership=None,
+      payment_request_category_strict_membership=None,
+      # simulation_state
+      strict_simulation_state=0,
+      simulation_state=None, transit_simulation_state = None, omit_transit=0,
+      input_simulation_state=None, output_simulation_state=None,
+      reserved_kw=None,
+      # variations
+      variation_text=None, sub_variation_text=None,
+      variation_category=None,
+      transformed_variation_text=None,
+      # uids
+      resource_uid=None, node_uid=None, section_uid=None, payment_uid=None,
+      mirror_node_uid=None, mirror_section_uid=None, function_uid=None,
+      project_uid=None, funding_uid=None, ledger_uid=None,
+      payment_request_uid=None,
+      # omit input and output
+      omit_input=0,
+      omit_output=0,
+      omit_asset_increase=0,
+      omit_asset_decrease=0,
+      # group by
+      group_by_node=0,
+      group_by_node_category=0,
+      group_by_node_category_strict_membership=0,
+      group_by_mirror_node=0,
+      group_by_mirror_node_category=0,
+      group_by_mirror_node_category_strict_membership=0,
+      group_by_section=0,
+      group_by_section_category=0,
+      group_by_section_category_strict_membership=0,
+      group_by_mirror_section=0,
+      group_by_mirror_section_category=0,
+      group_by_mirror_section_category_strict_membership=0,
+      group_by_payment=0,
+      group_by_payment_category=0,
+      group_by_payment_category_strict_membership=0,
+      group_by_sub_variation=0,
+      group_by_variation=0,
+      group_by_movement=0,
+      group_by_resource=0,
+      group_by_project=0,
+      group_by_project_category=0,
+      group_by_project_category_strict_membership=0,
+      group_by_funding=0,
+      group_by_funding_category=0,
+      group_by_funding_category_strict_membership=0,
+      group_by_ledger=0,
+      group_by_ledger_category=0,
+      group_by_ledger_category_strict_membership=0,
+      group_by_payment_request=0,
+      group_by_payment_request_category=0,
+      group_by_payment_request_category_strict_membership=0,
+      group_by_function=0,
+      group_by_function_category=0,
+      group_by_function_category_strict_membership=0,
+      group_by_date=0,
+      # sort_on
+      sort_on=None,
+      group_by=None,
+      # selection
+      selection_domain=None,
+      selection_report=None,
+      # keywords for related keys
+      **kw):
+    """
+    Generates keywords and calls buildSQLQuery
+
+    - omit_mirror_date: normally, date's parameters are only based on date
+      column. If 0, it also used the mirror_date column.
+    """
+    new_kw = {}
+    sql_kw = {
+      'from_table_list': [],
+      # Set of catalog aliases that must be joined in the ZSQLMethod ('foo'
+      # meaning something along the lines of 'foo.uid = stock.foo_uid')
+      'selection_domain_catalog_alias_set': [],
+      # input and output are used by getTrackingList
+      'input': input,
+      'output': output,
+      # BBB
+      'selection_domain': None,
+      'selection_report': None,
+    }
+
+    if selection_domain is None:
+      sql_kw['selection_domain_from_expression'] = None
+      sql_kw['selection_domain_where_expression'] = None
+    else:
+      # Pre-render selection_domain, as it is easier done here than in DTML.
+      if isinstance(selection_domain, dict):
+        selection_domain_dict = selection_domain
+      else:
+        selection_domain_dict = selection_domain.asDomainDict()
+      if 'ledger' in selection_domain_dict:
+        # XXX: what if both 'node' and 'ledger' are present ?
+        # Finer configuration may be needed here.
+        query_table_alias = 'ledger'
+      else:
+        query_table_alias = 'node'
+      selection_domain_sql_dict = self.getPortalObject().portal_catalog.buildSQLQuery(
+        selection_domain=selection_domain,
+        query_table_alias=query_table_alias,
+      )
+      sql_kw['selection_domain_from_expression'] = selection_domain_sql_dict['from_expression']
+      sql_kw['from_table_list'].extend(selection_domain_sql_dict['from_table_list'])
+      sql_kw['selection_domain_where_expression'] = selection_domain_sql_dict['where_expression']
+      sql_kw['selection_domain_catalog_alias_set'].append(query_table_alias)
+    if selection_report is not None:
+      new_kw['selection_report'] = selection_report
+
+    # Add sort_on parameter if defined
+    if sort_on is not None:
+      new_kw['sort_on'] = sort_on
+
+    class DictMixIn(dict):
+      def set(dictionary, key, value): # pylint: disable=no-self-argument
+        result = bool(value)
+        if result:
+          dictionary[key] = value
+        return result
+
+      def setUIDList(dictionary, key, value, as_text=0): # pylint: disable=no-self-argument
+        uid_list = self._generatePropertyUidList(value, as_text=as_text)
+        return dictionary.set(key, uid_list)
+
+    column_value_dict = DictMixIn()
+
+    if omit_mirror_date:
+      date_dict = {}
+      if from_date :
+        date_dict.setdefault('query', []).append(from_date)
+        date_dict['range'] = 'min'
+        if to_date :
+          date_dict.setdefault('query', []).append(to_date)
+          date_dict['range'] = 'minmax'
+        elif at_date :
+          date_dict.setdefault('query', []).append(at_date)
+          date_dict['range'] = 'minngt'
+      elif to_date :
+        date_dict.setdefault('query', []).append(to_date)
+        date_dict['range'] = 'max'
+      elif at_date :
+        date_dict.setdefault('query', []).append(at_date)
+        date_dict['range'] = 'ngt'
+      if date_dict:
+        column_value_dict['date'] = date_dict
+    else:
+      column_value_dict['date'] = {'query': [to_date], 'range': 'ngt'}
+      column_value_dict['mirror_date'] = {'query': [from_date], 'range': 'nlt'}
+
+    column_value_dict.set('resource_uid', resource_uid)
+    column_value_dict.set('payment_uid', payment_uid)
+    column_value_dict.set('project_uid', project_uid)
+    column_value_dict.set('funding_uid', funding_uid)
+    column_value_dict.set('ledger_uid', ledger_uid)
+    column_value_dict.set('payment_request_uid', payment_request_uid)
+    column_value_dict.set('function_uid', function_uid)
+    column_value_dict.set('section_uid', section_uid)
+    column_value_dict.set('node_uid', node_uid)
+    column_value_dict.set('mirror_node_uid', mirror_node_uid)
+    column_value_dict.set('mirror_section_uid', mirror_section_uid)
+    column_value_dict.setUIDList('resource_uid', resource)
+    column_value_dict.setUIDList('aggregate_uid', item)
+    column_value_dict.setUIDList('node_uid', node)
+    column_value_dict.setUIDList('payment_uid', payment)
+    column_value_dict.setUIDList('project_uid', project)
+    column_value_dict.setUIDList('funding_uid', funding)
+    column_value_dict.setUIDList('ledger_uid', ledger)
+    column_value_dict.setUIDList('payment_request_uid', payment_request)
+    column_value_dict.setUIDList('function_uid', function)
+
+    sql_kw['transformed_uid'] = self._generatePropertyUidList(transformed_resource)
+
+    column_value_dict.setUIDList('section_uid', section)
+    column_value_dict.setUIDList('mirror_section_uid', mirror_section)
+
+    # Handle variation_category as variation_text
+    if variation_category:
+      if variation_text:
+        raise ValueError(
+            "Passing both variation_category and variation_text is not supported")
+      warn("variation_category is deprecated, please use variation_text instead",
+           DeprecationWarning)
+      if isinstance(variation_category, basestring):
+        variation_category = (variation_category,)
+      # variation text is a \n separated list of variation categories, but without
+      # trailing nor leading \n
+      variation_text = [
+          "{}\n%".format(x) for x in variation_category] + [
+          "%\n{}\n%".format(x) for x in variation_category] + [
+          "%\n{}".format(x) for x in variation_category] + [
+          "{}".format(x) for x in variation_category]
+
+    column_value_dict.setUIDList('variation_text', variation_text,
+                                 as_text=1)
+    column_value_dict.setUIDList('sub_variation_text', sub_variation_text,
+                                 as_text=1)
+    new_kw['column_value_dict'] = column_value_dict.copy()
+
+    related_key_dict = DictMixIn()
+    # category membership
+    related_key_dict.setUIDList('resource_category_uid', resource_category)
+    related_key_dict.setUIDList('node_category_uid', node_category)
+    related_key_dict.setUIDList('project_category_uid', project_category)
+    related_key_dict.setUIDList('funding_category_uid', funding_category)
+    related_key_dict.setUIDList('ledger_category_uid', ledger_category)
+    related_key_dict.setUIDList('payment_request_category_uid', payment_request_category)
+    related_key_dict.setUIDList('function_category_uid', function_category)
+    related_key_dict.setUIDList('payment_category_uid', payment_category)
+    related_key_dict.setUIDList('section_category_uid', section_category)
+    related_key_dict.setUIDList('mirror_section_category_uid',
+                                mirror_section_category)
+    # category strict membership
+    related_key_dict.setUIDList('resource_category_strict_membership_uid',
+                                resource_category_strict_membership)
+    related_key_dict.setUIDList('node_category_strict_membership_uid',
+                                node_category_strict_membership)
+    related_key_dict.setUIDList('project_category_strict_membership_uid',
+                                project_category_strict_membership)
+    related_key_dict.setUIDList('funding_category_strict_membership_uid',
+                                funding_category_strict_membership)
+    related_key_dict.setUIDList('ledger_category_strict_membership_uid',
+                                ledger_category_strict_membership)
+    related_key_dict.setUIDList('payment_request_category_strict_membership_uid',
+                                payment_request_category_strict_membership)
+    related_key_dict.setUIDList('function_category_strict_membership_uid',
+                                function_category_strict_membership)
+    related_key_dict.setUIDList('payment_category_strict_membership_uid',
+                                payment_category_strict_membership)
+    related_key_dict.setUIDList('section_category_strict_membership_uid',
+                                section_category_strict_membership)
+    related_key_dict.setUIDList(
+      'mirror_section_category_strict_membership_uid',
+      mirror_section_category_strict_membership)
+
+    new_kw['related_key_dict'] = related_key_dict.copy()
+    new_kw['related_key_dict_passthrough'] = kw
+    # Check we do not get a known group_by
+    related_group_by = []
+    if group_by:
+      if isinstance(group_by, basestring):
+        group_by = (group_by,)
+      for value in group_by:
+        if value == "node_uid":
+          group_by_node = 1
+        elif value == 'mirror_node_uid':
+          group_by_mirror_node = 1
+        elif value ==  'section_uid':
+          group_by_section = 1
+        elif value == 'mirror_section_uid':
+          group_by_mirror_section = 1
+        elif value == 'payment_uid':
+          group_by_payment = 1
+        elif value == 'sub_variation_text':
+          group_by_sub_variation = 1
+        elif value == 'variation_text':
+          group_by_variation = 1
+        elif value == 'uid':
+          group_by_movement = 1
+        elif value == 'resource_uid':
+          group_by_resource = 1
+        elif value == 'project_uid':
+          group_by_project = 1
+        elif value == 'funding_uid':
+          group_by_funding = 1
+        elif value == 'ledger_uid':
+          group_by_ledger = 1
+        elif value == 'payment_request_uid':
+          group_by_payment_request = 1
+        elif value == "function_uid":
+          group_by_function = 1
+        elif value == 'date':
+          group_by_date = 1
+        else:
+          related_group_by.append(value)
+      if related_group_by:
+        new_kw['related_key_dict_passthrough']['group_by_list'] = related_group_by
+
+    new_kw['simulation_dict'] = self._getSimulationStateDict(
+      simulation_state=simulation_state,
+      omit_transit=omit_transit,
+      input_simulation_state=input_simulation_state,
+      output_simulation_state=output_simulation_state,
+      transit_simulation_state=transit_simulation_state,
+      strict_simulation_state=strict_simulation_state,
+    )
+    new_kw['omit_dict'] = {
+      'input': omit_input,
+      'output': omit_output,
+      'asset_increase': omit_asset_increase,
+      'asset_decrease': omit_asset_decrease,
+    }
+    if reserved_kw is not None:
+      if not isinstance(reserved_kw, dict):
+        # Not a dict when taken from URL, so, cast is needed
+        # to make pop method available
+        reserved_kw = dict(reserved_kw)
+      new_kw['reserved_kw'] = {
+        'omit_dict': {
+          'input': reserved_kw.pop('omit_input', False),
+          'output': reserved_kw.pop('omit_output', False),
+        },
+        'simulation_dict': self._getSimulationStateDict(**reserved_kw),
+      }
+
+    # build the group by expression
+    # if we group by a criterion, we also add this criterion to the select
+    # expression, unless it is already selected in Resource_zGetInventoryList
+    # the caller can also pass select_dict or select_list. select_expression,
+    # which is deprecated in ZSQLCatalog is not supported here.
+    select_dict = kw.get('select_dict', {})
+    select_dict.update(dict.fromkeys(list(kw.pop('select_list', [])) + related_group_by))
+    new_kw['select_dict'] = select_dict
+    related_key_select_expression_list = []
+
+    column_group_by_expression_list = []
+    related_key_group_by_expression_list = []
+    if group_by_node:
+      column_group_by_expression_list.append('node_uid')
+    if group_by_mirror_node:
+      column_group_by_expression_list.append('mirror_node_uid')
+    if group_by_section:
+      column_group_by_expression_list.append('section_uid')
+    if group_by_mirror_section:
+      column_group_by_expression_list.append('mirror_section_uid')
+    if group_by_payment:
+      column_group_by_expression_list.append('payment_uid')
+    if group_by_sub_variation:
+      column_group_by_expression_list.append('sub_variation_text')
+    if group_by_variation:
+      column_group_by_expression_list.append('variation_text')
+    if group_by_movement:
+      column_group_by_expression_list.append('uid')
+    if group_by_resource:
+      column_group_by_expression_list.append('resource_uid')
+    if group_by_project:
+      column_group_by_expression_list.append('project_uid')
+    if group_by_funding:
+      column_group_by_expression_list.append('funding_uid')
+    if group_by_ledger:
+      column_group_by_expression_list.append('ledger_uid')
+    if group_by_payment_request:
+      column_group_by_expression_list.append('payment_request_uid')
+    if group_by_function:
+      column_group_by_expression_list.append('function_uid')
+    if group_by_date:
+      column_group_by_expression_list.append('date')
+
+    if column_group_by_expression_list:
+      new_kw['column_group_by'] = column_group_by_expression_list
+
+    if group_by_section_category:
+      related_key_group_by_expression_list.append('section_category_uid')
+      related_key_select_expression_list.append('section_category_uid')
+    if group_by_section_category_strict_membership:
+      related_key_group_by_expression_list.append(
+          'section_category_strict_membership_uid')
+      related_key_select_expression_list.append(
+          'section_category_strict_membership_uid')
+    if group_by_mirror_section_category:
+      related_key_group_by_expression_list.append('mirror_section_category_uid')
+      related_key_select_expression_list.append('mirror_section_category_uid')
+    if group_by_mirror_section_category_strict_membership:
+      related_key_group_by_expression_list.append(
+          'mirror_section_category_strict_membership_uid')
+      related_key_select_expression_list.append(
+          'mirror_section_category_strict_membership_uid')
+    if group_by_node_category:
+      related_key_group_by_expression_list.append('node_category_uid')
+      related_key_select_expression_list.append('node_category_uid')
+    if group_by_node_category_strict_membership:
+      related_key_group_by_expression_list.append(
+          'node_category_strict_membership_uid')
+      related_key_select_expression_list.append(
+          'node_category_strict_membership_uid')
+    if group_by_mirror_node_category:
+      related_key_group_by_expression_list.append('mirror_node_category_uid')
+    if group_by_mirror_node_category_strict_membership:
+      related_key_group_by_expression_list.append(
+          'mirror_node_category_strict_membership_uid')
+      related_key_select_expression_list.append(
+          'mirror_node_category_strict_membership_uid')
+    if group_by_payment_category:
+      related_key_group_by_expression_list.append('payment_category_uid')
+      related_key_select_expression_list.append('payment_category_uid')
+    if group_by_payment_category_strict_membership:
+      related_key_group_by_expression_list.append(
+          'payment_category_strict_membership_uid')
+      related_key_select_expression_list.append(
+          'payment_category_strict_membership_uid')
+    if group_by_function_category:
+      related_key_group_by_expression_list.append('function_category_uid')
+      related_key_select_expression_list.append('function_category_uid')
+    if group_by_function_category_strict_membership:
+      related_key_group_by_expression_list.append(
+          'function_category_strict_membership_uid')
+      related_key_select_expression_list.append(
+          'function_category_strict_membership_uid')
+    if group_by_project_category:
+      related_key_group_by_expression_list.append('project_category_uid')
+      related_key_select_expression_list.append('project_category_uid')
+    if group_by_project_category_strict_membership:
+      related_key_group_by_expression_list.append(
+          'project_category_strict_membership_uid')
+      related_key_select_expression_list.append(
+          'project_category_strict_membership_uid')
+    if group_by_funding_category:
+      related_key_group_by_expression_list.append('funding_category_uid')
+      related_key_select_expression_list.append('funding_category_uid')
+    if group_by_funding_category_strict_membership:
+      related_key_group_by_expression_list.append(
+          'funding_category_strict_membership_uid')
+      related_key_select_expression_list.append(
+          'funding_category_strict_membership_uid')
+    if group_by_ledger_category:
+      related_key_group_by_expression_list.append('ledger_category_uid')
+      related_key_select_expression_list.append('ledger_category_uid')
+    if group_by_ledger_category_strict_membership:
+      related_key_group_by_expression_list.append(
+          'ledger_category_strict_membership_uid')
+      related_key_select_expression_list.append(
+          'ledger_category_strict_membership_uid')
+    if group_by_payment_category:
+      related_key_group_by_expression_list.append('payment_request_category_uid')
+      related_key_select_expression_list.append('payment_request_category_uid')
+    if group_by_payment_request_category_strict_membership:
+      related_key_group_by_expression_list.append(
+          'payment_request_category_strict_membership_uid')
+      related_key_select_expression_list.append(
+          'payment_request_category_strict_membership_uid')
+
+    if related_key_group_by_expression_list:
+      new_kw['related_key_group_by'] = related_key_group_by_expression_list
+    if related_key_select_expression_list:
+      new_kw['related_key_select_expression_list'] =\
+              related_key_select_expression_list
+
+    return sql_kw, new_kw
+
+  #######################################################
+  # Inventory management
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getInventory')
+  def getInventory(self, src__=0, simulation_period='', **kw):
+    """
+    Returns an inventory of a single or multiple resources on a single or
+    multiple nodes as a single float value
+
+    from_date (>=) - only take rows which date is >= from_date
+
+    to_date   (<)  - only take rows which date is < to_date
+
+    at_date   (<=) - only take rows which date is <= at_date
+
+    resource (only in generic API in simulation)
+
+    node           -  only take rows in stock table which node_uid is
+                      equivalent to node
+
+    payment        -  only take rows in stock table which payment_uid is
+                      equivalent to payment
+
+    section        -  only take rows in stock table which section_uid is
+                      equivalent to section
+
+    mirror_section -  only take rows in stock table which mirror_section_uid is
+                      mirror_section
+
+    resource_category  -  only take rows in stock table which
+                      resource_uid is member of resource_category
+
+    node_category   - only take rows in stock table which node_uid is
+                      member of section_category
+
+    payment_category   -  only take rows in stock table which payment_uid
+                          is in section_category
+
+    section_category -  only take rows in stock table which section_uid is
+                        member of section_category
+
+    mirror_section_category - only take rows in stock table which
+                              mirror_section_uid is member of
+                              mirror_section_category
+
+    node_filter     - only take rows in stock table which node_uid
+                      matches node_filter
+
+    payment_filter  - only take rows in stock table which payment_uid
+                      matches payment_filter
+
+    section_filter  - only take rows in stock table which section_uid
+                      matches section_filter
+
+    mirror_section_filter - only take rows in stock table which
+                            mirror_section_uid matches mirror_section_filter
+
+    variation_text -  only take rows in stock table with specified
+                      variation_text.
+                      XXX this way of implementing variation selection is far
+                      from perfect
+
+    sub_variation_text - only take rows in stock table with specified
+                      variation_text
+
+    variation_category - variation or list of possible variations (it is not
+                      a cross-search ; SQL query uses OR).
+                      Deprecated, use variation_text.
+
+    simulation_state - only take rows with specified simulation_state
+
+    transit_simulation_state - specifies which states are transit states
+
+    omit_transit   -  do not evaluate transit_simulation_state
+
+    input_simulation_state - only take rows with specified simulation_state
+                      and quantity > 0
+
+    output_simulation_state - only take rows with specified simulation_state
+                      and quantity < 0
+
+    ignore_variation -  do not take into account variation in inventory
+                      calculation (useless on getInventory, but useful on
+                      getInventoryList)
+
+    standardise    -  provide a standard quantity rather than an SKU (XXX
+                      not implemented yet)
+
+    omit_simulation - doesn't take into account simulation movements
+
+    only_accountable - Only take into account accountable movements. By
+                      default, only movements for which isAccountable() is
+                      true will be taken into account.
+
+    omit_input     -  doesn't take into account movement with quantity > 0
+
+    omit_output    -  doesn't take into account movement with quantity < 0
+
+    omit_asset_increase - doesn't take into account movement with asset_price > 0
+
+    omit_asset_decrease - doesn't take into account movement with asset_price < 0
+
+    selection_domain, selection_report - see ListBox
+
+    group_by_variation - (useless on getInventory, but useful on
+                      getInventoryList)
+
+    group_by_node  -  (useless on getInventory, but useful on
+                      getInventoryList)
+
+    group_by_mirror_node - (useless on getInventory, but useful on
+                      getInventoryList)
+
+    group_by_sub_variation - (useless on getInventory, but useful on
+                      getInventoryList)
+
+    group_by_movement - (useless on getInventory, but useful on
+                      getInventoryList)
+
+    precision - the precision used to round quantities and prices.
+
+    metric_type   - convert the results to a specific metric_type
+
+    quantity_unit - display results using this specific quantity unit
+
+    transformed_resource - one, or several resources. list resources that can
+                           be produced using those resources as input in a
+                           transformation.
+                           relative_resource_url for each returned line will
+                           point to the transformed resource, while the stock
+                           will be the stock of the produced resource,
+                           expressed in number of transformed resources.
+    transformed_variation_text - to be used with transformed_resource, to
+                                 to refine the transformation selection only
+                                 to those using variated resources as input.
+
+    **kw           -  if we want extended selection with more keywords (but
+                      bad performance) check what we can do with
+                      buildSQLQuery
+
+    NOTE: we may want to define a parameter so that we can select the kind of
+    inventory statistics we want to display (ex. sum, average, cost, etc.)
+    """
+    # JPS: this is a hint for implementation of xxx_filter arguments
+    # node_uid_count = portal_catalog.countResults(**node_filter)
+    # if node_uid_count not too big:
+    #   node_uid_list = cache(portal_catalog(**node_filter))
+    #   pass this list to ZSQL method
+    # else:
+    #   build a table in MySQL
+    #   and join that table with the stock table
+    method = getattr(self,'get%sInventoryList' % simulation_period)
+    kw['ignore_group_by'] = 1
+    result = method(inventory_list=0, src__=src__, **kw)
+    if src__:
+      return result
+
+    total_result = 0.0
+    if len(result) > 0:
+      if len(result) != 1:
+        raise ValueError, 'Sorry we must have only one'
+      result = result[0]
+
+      if hasattr(result, "converted_quantity"):
+        total_result = result.converted_quantity
+      else:
+        inventory = result.total_quantity
+        if inventory is not None:
+          total_result = inventory
+
+    return total_result
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getCurrentInventory')
+  def getCurrentInventory(self, **kw):
+    """
+    Returns current inventory
+    """
+    return self.getInventory(simulation_period='Current', **kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getAvailableInventory')
+  def getAvailableInventory(self, **kw):
+    """
+    Returns available inventory
+    (current inventory - reserved_inventory)
+    """
+    return self.getInventory(simulation_period='Available', **kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getFutureInventory')
+  def getFutureInventory(self, **kw):
+    """
+    Returns future inventory
+    """
+    return self.getInventory(simulation_period='Future', **kw)
+
+  def _getDefaultGroupByParameters(self, ignore_group_by=0,
+      group_by_node=0, group_by_mirror_node=0,
+      group_by_section=0, group_by_mirror_section=0,
+      group_by_payment=0, group_by_project=0, group_by_funding=0,
+      group_by_ledger=0, group_by_function=0,
+      group_by_variation=0, group_by_sub_variation=0,
+      group_by_movement=0, group_by_date=0,
+      group_by_section_category=0,
+      group_by_section_category_strict_membership=0,
+      group_by_resource=None,
+      group_by=None,
+      **ignored):
+    """
+    Set defaults group_by parameters
+
+    If ignore_group_by is true, this function returns an empty dict.
+
+    If any group-by is provided, automatically group by resource aswell
+    unless group_by_resource is explicitely set to false.
+    If no group by is provided, use the default group by: movement, node and
+    resource.
+    """
+    new_group_by_dict = {}
+    if not ignore_group_by and group_by is None:
+      if group_by_node or group_by_mirror_node or group_by_section or \
+         group_by_project or group_by_funding or group_by_ledger or \
+         group_by_function or group_by_mirror_section or group_by_payment or \
+         group_by_sub_variation or group_by_variation or \
+         group_by_movement or group_by_date or group_by_section_category or\
+         group_by_section_category_strict_membership:
+        if group_by_resource is None:
+          group_by_resource = 1
+        new_group_by_dict['group_by_resource'] = group_by_resource
+      elif group_by_resource is None:
+        new_group_by_dict['group_by_movement'] = 1
+        new_group_by_dict['group_by_node'] = 1
+        new_group_by_dict['group_by_resource'] = 1
+    return new_group_by_dict
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getInventoryList')
+  def getInventoryList(self, src__=0, optimisation__=True,
+                       ignore_variation=0, standardise=0,
+                       omit_simulation=0,
+                       only_accountable=True,
+                       default_stock_table='stock',
+                       statistic=0, inventory_list=1,
+                       precision=None, connection_id=None,
+                       **kw):
+    """
+      Returns a list of inventories for a single or multiple
+      resources on a single or multiple nodes, grouped by resource,
+      node, section, etc. Every line defines an inventory value for
+      a given group of resource, node, section.
+      NOTE: we may want to define a parameter so that we can select
+      the kind of inventory statistics we want to display (ex. sum,
+      average, cost, etc.)
+
+      Optimisation queries.
+      Optimisation of a stock lookup is done to avoid a table scan
+      of all lines corresponding to a given node, section or payment,
+      because they grow with time and query time should not.
+      First query: Fetch fitting full inventory dates.
+        For each node, section or payment, find the first anterior full
+        inventory.
+      Second query: Fetch full inventory amounts.
+        Fetch values of inventory identified in the first query.
+      Third query: Classic stock table read.
+        Fetch all rows in stock table which are posterior to the inventory.
+      Final result
+        Add results of the second and third queries, and return it.
+
+      Missing optimisations:
+       - In a getInventory case where everything is specified for the
+         resource, it's not required for the inventory to be full, it
+         just need to be done for the right resource.
+         If the resource isn't completely defined, we require inventory
+         to be full, which is implemented.
+       - Querying multiple nodes/categories/payments in one call prevents
+         from using optimisation, it should be equivalent to multiple calls
+         on individual nodes/categories/payments.
+       -
+    """
+    getCategory = self.getPortalObject().portal_categories.getCategoryUid
+
+    result_column_id_dict = {}
+
+    metric_type = kw.pop('metric_type', None)
+    quantity_unit = kw.pop('quantity_unit', None)
+    quantity_unit_uid = None
+
+    if quantity_unit is not None:
+
+      if isinstance(quantity_unit, str):
+        quantity_unit_uid = getCategory(quantity_unit, 'quantity_unit')
+        if quantity_unit_uid is not None:
+          result_column_id_dict['converted_quantity'] = None
+          if metric_type is None:
+            # use the default metric type
+            metric_type = quantity_unit.split("/", 1)[0]
+      elif isinstance(quantity_unit, (int, float)):
+        # quantity_unit used to be a numerical parameter..
+        raise ValueError('Numeric values for quantity_unit are not supported')
+
+
+    convert_quantity_result = False
+    if metric_type is not None:
+      metric_type_uid = getCategory(metric_type, 'metric_type')
+
+      if metric_type_uid is not None:
+        convert_quantity_result = True
+        kw['metric_type_uid'] = Query(
+                                  metric_type_uid=metric_type_uid,
+                                  table_alias_list=(("measure", "measure"),))
+
+    if src__:
+      sql_source_list = []
+    # If no group at all, give a default sort group by
+    kw.update(self._getDefaultGroupByParameters(**kw))
+    base_inventory_kw = {
+      'stock_table_id': default_stock_table,
+      'src__': src__,
+      'ignore_variation': ignore_variation,
+      'standardise': standardise,
+      'omit_simulation': omit_simulation,
+      'only_accountable': only_accountable,
+      'precision': precision,
+      'inventory_list': inventory_list,
+      'connection_id': connection_id,
+      'statistic': statistic,
+      'convert_quantity_result': convert_quantity_result,
+      'quantity_unit_uid': quantity_unit_uid,
+    }
+    # Get cached data
+    if getattr(self, "Resource_zGetInventoryCacheResult", None) is not None and \
+            optimisation__ and (not kw.get('from_date')) and \
+            'transformed_resource' not in kw:
+      # Here is the different kind of date
+      # from_date : >=
+      # to_date   : <
+      # at_date   : <=
+      # As we just have from_date, it means that we must use
+      # the to_date for the cache in order to avoid double computation
+      # of the same line
+      at_date = kw.pop("at_date", None)
+      if at_date is None:
+        to_date = kw.pop("to_date", None)
+      else:
+        # add one second so that we can use to_date
+        to_date = at_date + MYSQL_MIN_DATETIME_RESOLUTION
+      try:
+        cached_result, cached_date = self._getCachedInventoryList(
+            to_date=to_date,
+            sql_kw=kw,
+            **base_inventory_kw)
+      except StockOptimisationError:
+        cached_result = []
+        kw['to_date'] = to_date
+      else:
+        if src__:
+          sql_source_list.extend(cached_result)
+        # Now must generate query for date diff
+        kw['to_date'] = to_date
+        kw['from_date'] = cached_date
+    else:
+      cached_result = []
+    sql_kw, new_kw = self._generateKeywordDict(**kw)
+    # Copy kw content as _generateSQLKeywordDictFromKeywordDict
+    # remove some values from it
+    try:
+      new_kw_copy = deepcopy(new_kw)
+    except TypeError:
+      # new_kw contains wrong parameters
+      # as optimisation has already been disable we
+      # do not care about the deepcopy
+      new_kw_copy = new_kw
+    stock_sql_kw = self._generateSQLKeywordDictFromKeywordDict(
+        table=default_stock_table, sql_kw=sql_kw, new_kw=new_kw_copy)
+    stock_sql_kw.update(base_inventory_kw)
+    delta_result = self.Resource_zGetInventoryList(
+        **stock_sql_kw)
+    if src__:
+      sql_source_list.append(delta_result)
+      result = ';\n-- NEXT QUERY\n'.join(sql_source_list)
+    else:
+      if cached_result:
+        result = self._addBrainResults(delta_result, cached_result, new_kw)
+      else:
+        result = delta_result
+    return result
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getInventoryCacheLag')
+  def getInventoryCacheLag(self):
+    """
+    Returns a duration, in days, for stock cache management.
+    If data in stock cache is older than lag compared to query's date
+    (at_date or to_date), then it becomes a "soft miss": use found value,
+    but add a new entry to cache at query's date minus half the lag.
+    So this value should be:
+    - Small enough that few enough rows need to be table-scanned for
+      average queries (probably queries against current date).
+    - Large enough that few enough documents get modified past that date,
+      otherwise cache entries would be removed from cache all the time.
+    """
+    return self.SimulationTool_getInventoryCacheLag()
+
+  def _getCachedInventoryList(self, to_date, sql_kw, stock_table_id, src__=False, **kw):
+    """
+    Try to get a cached inventory list result
+    If not existing, fill the cache
+    """
+    Resource_zGetInventoryList = self.Resource_zGetInventoryList
+    # Generate the SQL source without date parameter
+    # This will be the cache key
+    try:
+      no_date_kw = deepcopy(sql_kw)
+    except TypeError:
+      LOG("SimulationTool._getCachedInventoryList", WARNING,
+          "Failed copying sql_kw, disabling stock cache",
+          error=True)
+      raise StockOptimisationError
+    no_date_sql_kw, no_date_new_kw = self._generateKeywordDict(**no_date_kw)
+    no_date_stock_sql_kw = self._generateSQLKeywordDictFromKeywordDict(
+      table=stock_table_id, sql_kw=no_date_sql_kw,
+      new_kw=no_date_new_kw)
+    kw.update(no_date_stock_sql_kw)
+    if src__:
+      sql_source_list = []
+    # Generate the cache key (md5 of query source)
+    sql_text_hash = md5(Resource_zGetInventoryList(
+      stock_table_id=stock_table_id,
+      src__=1,
+      **kw)).digest()
+    # Try to get result from cache
+    Resource_zGetInventoryCacheResult = self.Resource_zGetInventoryCacheResult
+    inventory_cache_kw = {'query': sql_text_hash}
+    if to_date is not None:
+      inventory_cache_kw['date'] = to_date
+    try:
+      cached_sql_result = Resource_zGetInventoryCacheResult(**inventory_cache_kw)
+    except ProgrammingError, (code, _):
+      if code != NO_SUCH_TABLE:
+        raise
+      # First use of the optimisation, we need to create the table
+      LOG("SimulationTool._getCachedInventoryList", INFO,
+          "Creating inventory cache stock")
+      if src__:
+        sql_source_list.append(self.SimulationTool_zCreateInventoryCache(src__=1))
+      else:
+        self.SimulationTool_zCreateInventoryCache()
+      cached_sql_result = None
+
+    if src__:
+      sql_source_list.append(Resource_zGetInventoryCacheResult(src__=1, **inventory_cache_kw))
+    if cached_sql_result:
+      brain_result = loads(cached_sql_result[0].result)
+      # Rebuild the brains
+      cached_result = Results(
+        (brain_result['items'], brain_result['data']),
+        brains=getBrain(
+          Resource_zGetInventoryList.class_file_,
+          Resource_zGetInventoryList.class_name_,
+        ),
+        parent=self,
+      )
+    else:
+      cached_result = []
+    cache_lag = self.getInventoryCacheLag()
+    if cached_sql_result and (to_date is None or (to_date - DateTime(cached_sql_result[0].date) < cache_lag)):
+      cached_date = DateTime(cached_sql_result[0].date)
+      result = cached_result
+    elif to_date is not None:
+      # Cache miss, or hit with old data: store a new entry in cache.
+      # Don't store it at to_date, as it risks being flushed soon (ie, when
+      # any document older than to_date gets reindexed in stock table).
+      # Don't store it at to_date - cache_lag, as it would risk expiring
+      # soon as we store it (except if to_date is fixed for many queries,
+      # which we cannot tell here).
+      # So store it at half the cache_lag before to_date.
+      cached_date = to_date - cache_lag / 2
+      new_cache_kw = deepcopy(sql_kw)
+      if cached_result:
+        # We can use cached result to generate new cache result
+        new_cache_kw['from_date'] = DateTime(cached_sql_result[0].date)
+      sql_kw, new_kw = self._generateKeywordDict(
+        to_date=cached_date,
+        **new_cache_kw)
+      kw.update(self._generateSQLKeywordDictFromKeywordDict(
+          table=stock_table_id,
+          sql_kw=sql_kw,
+          new_kw=new_kw,
+        )
+      )
+      new_result = Resource_zGetInventoryList(
+        stock_table_id=stock_table_id,
+        src__=src__,
+        **kw)
+      if src__:
+        sql_source_list.append(new_result)
+      else:
+        result = self._addBrainResults(new_result, cached_result, new_kw)
+        self.Resource_zInsertInventoryCacheResult(
+          query=sql_text_hash,
+          date=cached_date,
+          result=dumps({
+            'items': result.__items__,
+            'data': result._data,
+          }),
+        )
+    else:
+      # Cache miss and this getInventory() not specifying to_date,
+      # and other getInventory() have not created usable caches.
+      # In such case, do not create cache, do not use cache.
+      result = []
+      cached_date = None
+    if src__:
+      result = sql_source_list
+    return result, cached_date
+
+  def _addBrainResults(self, first_result, second_result, new_kw):
+    """
+    Build a Results which is the addition of two other result
+    """
+    # This part defined key to group lines from different Results
+    group_by_id_list = []
+    group_by_id_list_append = group_by_id_list.append
+
+    for group_by_id in new_kw.get('column_group_by', []):
+      if group_by_id == 'uid':
+        group_by_id_list_append('stock_uid')
+      else:
+        group_by_id_list_append(group_by_id)
+    # Add related key group by
+    related_key_dict_passthrough = new_kw.get("related_key_dict_passthrough", {})
+    group_by_list = related_key_dict_passthrough.get('group_by_list', [])
+    cannot_group_by = set(group_by_list).difference(
+      related_key_dict_passthrough.get('select_list', []),
+    )
+    if cannot_group_by:
+      # XXX-Aurel : to review & change, must prevent coming here before
+      raise ValueError("Impossible to group by %s" % (cannot_group_by, ))
+    group_by_id_list += group_by_list
+
+    if len(group_by_id_list):
+      def getInventoryListKey(line):
+        """
+        Generate a key based on values used in SQL group_by
+        """
+        return tuple([line[x] for x in group_by_id_list])
+
+    else:
+      def getInventoryListKey(line):
+        """
+        Return a dummy key, all line will be summed
+        """
+        return "dummy"
+    result_column_id_dict = {
+      'inventory': None,
+      'total_quantity': None,
+      'total_price': None
+    }
+    def addLineValues(line_a=None, line_b=None):
+      """
+      Add columns of 2 lines and return a line with same
+      schema. If one of the parameters is None, returns the
+      other parameters.
+
+      Arithmetic modifications on additions:
+      None + x = x
+      None + None = None
+      """
+      if line_a is None:
+        return line_b
+      if line_b is None:
+        return line_a
+      # Create a new Shared.DC.ZRDB.Results.Results.__class__
+      # instance to add the line values.
+      # the logic for the 5 lines below is taken from
+      # Shared.DC.ZRDB.Results.Results.__getitem__
+      Result = line_a.__class__
+      parent = line_a.aq_parent
+      result = Result((), parent)
+      try:
+        # We must copy the path so that getObject works
+        setattr(result, 'path', line_a.path)
+      except ValueError: # XXX: ValueError ? really ?
+        # getInventory return no object, so no path available
+        pass
+      if parent is not None:
+        result = result.__of__(parent)
+      for key in line_a.__record_schema__:
+        value = line_a[key]
+        if key in result_column_id_dict:
+          value_b = line_b[key]
+          if None not in (value, value_b):
+            result[key] = value + value_b
+          elif value is not None:
+            result[key] = value
+          else:
+            result[key] = value_b
+        elif line_a[key] == line_b[key]:
+          result[key] = line_a[key]
+        elif key not in ('date', 'stock_uid', 'path'):
+          # There are 2 possible reasons to end up here:
+          # - key corresponds to a projected column for which are neither
+          #   known aggregated columns (in which case they should be in
+          #   result_column_id_dict) nor part of grouping columns, and the
+          #   result happens to be unstable. There are cases in ERP5 where
+          #   such result is suposed to be stable, for example
+          #   group_by=('xxx_uid'), selection_list=('xxx_path') because the
+          #   relation is bijective (although the database doesn't know it).
+          #   These should result in stable results (but don't necessarily
+          #   do, ex: xxx_title when object title has been changed between
+          #   cache fill and cache lookup).
+          # - line_a and line_b are indeed mismatched, and code calling us
+          #   has a bug.
+          LOG('InventoryTool.getInventoryList.addLineValues',
+            PROBLEM,
+            'mismatch for %s column: %s and %s' % (
+              key, line_a[key], line_b[key]))
+      return result
+    # Add lines
+    inventory_list_dict = {}
+    for line_list in (first_result, second_result):
+      for line in line_list:
+        line_key = getInventoryListKey(line)
+        line_a = inventory_list_dict.get(line_key)
+        inventory_list_dict[line_key] = addLineValues(line_a, line)
+    sorted_inventory_list = inventory_list_dict.values()
+    # Sort results manually when required
+    sort_on = new_kw.get('sort_on')
+    if sort_on:
+      def cmp_inventory_line(line_a, line_b):
+        """
+          Compare 2 inventory lines and sort them according to
+          sort_on parameter.
+        """
+        result = 0
+        for key, sort_direction in sort_on:
+          try:
+            result = cmp(line_a[key], line_b[key])
+          except KeyError:
+            raise Exception('Impossible to sort result since columns sort '
+              'happens on are not available in result: %r' % (key, ))
+          if result:
+            if not sort_direction.upper().startswith('A'):
+              # Default sort is ascending, if a sort is given and
+              # it does not start with an 'A' then reverse sort.
+              # Tedious syntax checking is MySQL's job, and
+              # happened when queries were executed.
+              result *= -1
+            break
+        return result
+      sorted_inventory_list.sort(cmp_inventory_line)
+    # Brain is rebuild properly using tuple not r instance
+    column_list = first_result._searchable_result_columns()
+    column_name_list = [x['name'] for x in column_list]
+    # Rebuild a result object based on added results
+    Resource_zGetInventoryList = self.Resource_zGetInventoryList
+    return Results(
+      (column_list, tuple([tuple([getattr(y, x) for x in column_name_list]) \
+        for y in sorted_inventory_list])),
+      parent=self,
+      brains=getBrain(
+        Resource_zGetInventoryList.class_file_,
+        Resource_zGetInventoryList.class_name_,
+      ),
+    )
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getConvertedInventoryList')
+  def getConvertedInventoryList(self, simulation_period='', **kw):
+    """
+    Return list of inventory with a 'converted_quantity' additional column,
+    which contains the sum of measurements for the specified metric type,
+    expressed in the 'quantity_unit' unit.
+
+    metric_type   - category
+    quantity_unit - category
+    """
+
+    warn('getConvertedInventoryList is Deprecated, use ' \
+         'getInventory instead.', DeprecationWarning)
+
+    method = getattr(self,'get%sInventoryList' % simulation_period)
+
+    return method(**kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getAllInventoryList')
+  def getAllInventoryList(self, src__=0, **kw):
+    """
+    Returns list of inventory, for all periods.
+    Performs 1 SQL request for each simulation state, and merge the results.
+    Rename relevant columns with a '${simulation}_' prefix
+    (ex: 'total_price' -> 'current_total_price').
+    """
+    columns = ('total_quantity', 'total_price', 'converted_quantity')
+
+    # Guess the columns to use to identify each row, by looking at the GROUP
+    # clause. Note that the call to 'mergeZRDBResults' will crash if the GROUP
+    # clause contains a column not requested in the SELECT clause.
+    kw.update(self._getDefaultGroupByParameters(**kw), ignore_group_by=1)
+    group_by_list = self._generateKeywordDict(**kw)[1].get('group_by_list', ())
+
+    results = []
+    edit_result = {}
+    get_false_value = lambda row, column_name: row.get(column_name) or 0
+
+    for simulation in 'current', 'available', 'future':
+      method = getattr(self, 'get%sInventoryList' % simulation.capitalize())
+      rename = {'inventory': None} # inventory column is deprecated
+      for column in columns:
+        rename[column] = new_name = '%s_%s' % (simulation, column)
+        edit_result[new_name] = get_false_value
+      results += (method(src__=src__, **kw), rename),
+
+    if src__:
+      return ';\n-- NEXT QUERY\n'.join(r[0] for r in results)
+    return mergeZRDBResults(results, group_by_list, edit_result)
+
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getCurrentInventoryList')
+  def getCurrentInventoryList(self, omit_transit=1,
+                              transit_simulation_state=None, **kw):
+    """
+      Returns list of current inventory grouped by section or site
+    """
+    portal = self.getPortalObject()
+    kw['simulation_state'] = portal.getPortalCurrentInventoryStateList() + \
+                             portal.getPortalTransitInventoryStateList()
+    if transit_simulation_state is None:
+      transit_simulation_state = portal.getPortalTransitInventoryStateList()
+
+    return self.getInventoryList(
+                          omit_transit=omit_transit,
+                          transit_simulation_state=transit_simulation_state,
+                          **kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getAvailableInventoryList')
+  def getAvailableInventoryList(self, omit_transit=1, transit_simulation_state=None, **kw):
+    """
+      Returns list of current inventory grouped by section or site
+    """
+    portal = self.getPortalObject()
+    if transit_simulation_state is None:
+      transit_simulation_state = portal.getPortalTransitInventoryStateList()
+    kw['simulation_state'] = portal.getPortalCurrentInventoryStateList() + \
+                             portal.getPortalTransitInventoryStateList()
+    reserved_kw = {'simulation_state': portal.getPortalReservedInventoryStateList(),
+                   'transit_simulation_state': transit_simulation_state,
+                   'omit_input': 1}
+    return self.getInventoryList(reserved_kw=reserved_kw, omit_transit=omit_transit,
+                   transit_simulation_state=transit_simulation_state, **kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getFutureInventoryList')
+  def getFutureInventoryList(self, **kw):
+    """
+      Returns list of future inventory grouped by section or site
+    """
+    portal = self.getPortalObject()
+    kw['simulation_state'] = portal.getPortalFutureInventoryStateList() + \
+                             portal.getPortalTransitInventoryStateList() + \
+                             portal.getPortalReservedInventoryStateList() + \
+                             portal.getPortalCurrentInventoryStateList()
+    return self.getInventoryList(**kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getInventoryStat')
+  def getInventoryStat(self, simulation_period='', **kw):
+    """
+    getInventoryStat is the pending to getInventoryList in order to
+    provide statistics on getInventoryList lines in ListBox such as:
+    total of inventories, number of variations, number of different
+    nodes, etc.
+    """
+    kw['group_by_variation'] = 0
+    method = getattr(self,'get%sInventoryList' % simulation_period)
+    return method(statistic=1, inventory_list=0, optimisation__=False,
+                                 ignore_group_by=1, **kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getCurrentInventoryStat')
+  def getCurrentInventoryStat(self, **kw):
+    """
+    Returns statistics of current inventory grouped by section or site
+    """
+    return self.getInventoryStat(simulation_period='Current', **kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getAvailableInventoryStat')
+  def getAvailableInventoryStat(self, **kw):
+    """
+    Returns statistics of available inventory grouped by section or site
+    """
+    return self.getInventoryStat(simulation_period='Available', **kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getFutureInventoryStat')
+  def getFutureInventoryStat(self, **kw):
+    """
+    Returns statistics of future inventory grouped by section or site
+    """
+    return self.getInventoryStat(simulation_period='Future', **kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getInventoryChart')
+  def getInventoryChart(self, src__=0, **kw):
+    """
+    Returns a list of couples derived from getInventoryList in order
+    to feed a chart renderer. Each couple consist of a label
+    (node, section, payment, combination of node & section, etc.)
+    and an inventory value.
+
+    Mostly useful for charts in ERP5 forms.
+    """
+    result = self.getInventoryList(src__=src__, **kw)
+    if src__ :
+      return result
+
+    return [(r.node_title, r.total_quantity) for r in result]
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getCurrentInventoryChart')
+  def getCurrentInventoryChart(self, **kw):
+    """
+    Returns list of current inventory grouped by section or site
+    """
+    kw['simulation_state'] = self.getPortalObject()\
+      .getPortalCurrentInventoryStateList()
+    return self.getInventoryChart(**kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getFutureInventoryChart')
+  def getFutureInventoryChart(self, **kw):
+    """
+    Returns list of future inventory grouped by section or site
+    """
+    portal = self.getPortalObject()
+    kw['simulation_state'] = portal.getPortalFutureInventoryStateList() + \
+                             portal.getPortalTransitInventoryStateList() + \
+                             portal.getPortalReservedInventoryStateList() + \
+                             portal.getPortalCurrentInventoryStateList()
+    return self.getInventoryChart(**kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getInventoryAssetPrice')
+  def getInventoryAssetPrice(self, src__=0,
+                             simulation_period='',
+                             valuation_method=None,
+                             **kw):
+    """
+    Same thing as getInventory but returns an asset
+    price rather than an inventory.
+
+    If valuation method is None, returns the sum of total prices.
+
+    Else it should be a string, in:
+      Filo
+      Fifo
+      WeightedAverage
+      MonthlyWeightedAverage
+      MovingAverage
+    When using a specific valuation method, a resource_uid is expected
+    as well as one of (section_uid or node_uid).
+    """
+    if valuation_method is None:
+      method = getattr(self,'get%sInventoryList' % simulation_period)
+      kw['ignore_group_by'] = 1
+      result = method( src__=src__, inventory_list=0, **kw)
+      if src__ :
+        return result
+
+      if len(result) == 0:
+        return 0.0
+
+      total_result = 0.0
+      for result_line in result:
+        if result_line.total_price is not None:
+          total_result += result_line.total_price
+
+      return total_result
+
+    if valuation_method not in ('Fifo', 'Filo', 'WeightedAverage',
+      'MonthlyWeightedAverage', 'MovingAverage'):
+      raise ValueError("Invalid valuation method: %s" % valuation_method)
+
+    assert 'node_uid' in kw or 'section_uid' in kw
+    sql_kw = self._generateSQLKeywordDict(**kw)
+
+    if 'section_uid' in kw and 'node_uid' not in kw:
+      # ignore internal movements if ignore node
+      sql_kw['where_expression'] += ' AND ' \
+        'NOT(stock.section_uid<=>stock.mirror_section_uid)'
+
+    result = self.Resource_zGetAssetPrice(
+        valuation_method=valuation_method,
+        src__=src__,
+        **sql_kw)
+
+    if src__ :
+      return result
+
+    if len(result) > 0:
+      return result[-1].total_asset_price
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getCurrentInventoryAssetPrice')
+  def getCurrentInventoryAssetPrice(self, **kw):
+    """
+    Returns list of current inventory grouped by section or site
+    """
+    kw['simulation_state'] = self.getPortalCurrentInventoryStateList()
+    return self.getInventoryAssetPrice(simulation_period='Current',**kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getAvailableInventoryAssetPrice')
+  def getAvailableInventoryAssetPrice(self, **kw):
+    """
+    Returns list of available inventory grouped by section or site
+    (current inventory - deliverable)
+    """
+    portal = self.getPortalObject()
+    kw['simulation_state'] = portal.getPortalReservedInventoryStateList() + \
+                             portal.getPortalCurrentInventoryStateList()
+    return self.getInventoryAssetPrice(**kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getFutureInventoryAssetPrice')
+  def getFutureInventoryAssetPrice(self, **kw):
+    """
+    Returns list of future inventory grouped by section or site
+    """
+    portal = self.getPortalObject()
+    kw['simulation_state'] = portal.getPortalFutureInventoryStateList() + \
+                             portal.getPortalReservedInventoryStateList() + \
+                             portal.getPortalCurrentInventoryStateList()
+    return self.getInventoryAssetPrice(**kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getInventoryHistoryList')
+  def getInventoryHistoryList(self, src__=0, ignore_variation=0,
+                              standardise=0, omit_simulation=0,
+                              only_accountable=True, omit_input=0,
+                              omit_output=0, precision=None, **kw):
+    """
+    Returns a time based serie of inventory values
+    for a single or a group of resource, node, section, etc. This is useful
+    to list the evolution with time of inventory values (quantity, asset price).
+
+    TODO:
+      - make sure getInventoryHistoryList can return
+        cumulative values calculated by SQL (JPS)
+    """
+    sql_kw = self._generateSQLKeywordDict(**kw)
+    return self.Resource_getInventoryHistoryList(
+                    src__=src__, ignore_variation=ignore_variation,
+                    standardise=standardise, omit_simulation=omit_simulation,
+                    only_accountable=only_accountable,
+                    omit_input=omit_input, omit_output=omit_output,
+                    precision=precision,
+                    **sql_kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getInventoryHistoryChart')
+  def getInventoryHistoryChart(self, src__=0, ignore_variation=0,
+                               standardise=0, omit_simulation=0,
+                               only_accountable=True,
+                               omit_input=0, omit_output=0,
+                               precision=None, **kw):
+    """
+    getInventoryHistoryChart is the pensing to getInventoryHistoryList
+    to ease the rendering of time based graphs which show the evolution
+    of one ore more inventories. Each item in the serie consists of
+    time, value and "colour" (multiple graphs can be drawn for example
+    for each variation of a resource)
+    """
+    sql_kw = self._generateSQLKeywordDict(**kw)
+
+    return self.Resource_getInventoryHistoryChart(
+                  src__=src__, ignore_variation=ignore_variation,
+                  standardise=standardise, omit_simulation=omit_simulation,
+                  only_accountable=only_accountable,
+                  omit_input=omit_input, omit_output=omit_output,
+                  precision=precision,
+                  **sql_kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getMovementHistoryList')
+  def getMovementHistoryList(self, src__=0, ignore_variation=0,
+                             standardise=0, omit_simulation=0,
+                             omit_input=0, omit_output=0,
+                             only_accountable=True,
+                             omit_asset_increase=0, omit_asset_decrease=0,
+                             initial_running_total_quantity=0,
+                             initial_running_total_price=0, precision=None,
+                             **kw):
+    """Returns a list of movements which modify the inventory
+    for a single or a group of resource, node, section, etc.
+    A running total quantity and a running total price are available on
+    brains. The initial values can be passed, in case you want to have an
+    "initial summary line".
+    """
+    # Extend select_dict by order_by_list columns.
+    catalog = self.getPortalObject().portal_catalog.getSQLCatalog()
+    kw = catalog.getCannonicalArgumentDict(kw)
+    extra_column_set = {i[0] for i in kw.get('order_by_list', ())}
+    kw.setdefault('select_dict', {}).update(
+      (x.replace('.', '_') + '__ext__', x)
+      for x in extra_column_set if not x.endswith('__score__'))
+
+    sql_kw = self._generateSQLKeywordDict(**kw)
+
+    return self.Resource_zGetMovementHistoryList(
+                       src__=src__, ignore_variation=ignore_variation,
+                       standardise=standardise,
+                       omit_simulation=omit_simulation,
+                       only_accountable=only_accountable,
+                       omit_input=omit_input, omit_output=omit_output,
+                       omit_asset_increase=omit_asset_increase,
+                       omit_asset_decrease=omit_asset_decrease,
+                       initial_running_total_quantity=
+                                initial_running_total_quantity,
+                       initial_running_total_price=
+                                initial_running_total_price,
+                       precision=precision, **sql_kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getMovementHistoryStat')
+  def getMovementHistoryStat(self, src__=0, **kw):
+    """
+    getMovementHistoryStat is the pending to getMovementHistoryList
+    for ListBox stat
+
+    supported parameters are similar to ones accepted by getInventoryList
+    with the exception of group_by_*
+    """
+    inventory_list = self.getInventoryList(ignore_group_by=1, **kw)
+    assert len(inventory_list) == 1
+    return inventory_list[0]
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getNextAlertInventoryDate')
+  def getNextAlertInventoryDate(self, reference_quantity=0, src__=0,
+                     simulation_period='Future', from_date=None,
+                     range='min', # pylint: disable=redefined-builtin
+                     initial_inventory_kw=None,
+                     inventory_list_kw=None,
+                     **kw):
+    """
+    Give the next date where the quantity is lower than the
+    reference quantity. This is calculated by first looking if inventory
+    right now is good or not. If not, then look at inventory list until
+    a movement makes the inventory like expected.
+
+    range  - either 'min' (default) or 'nlt'. With 'nlt', returns
+             the next date where inventory is above reference_quantity
+
+    initial_inventory_kw - additional parameters for the initial inventory
+
+    inventory_list_kw - additional parameters for looking at next movements
+                        (exemple: use omit_output)
+    """
+    result = None
+    # First look at current inventory, we might have already an inventory
+    # lower than reference_quantity
+    def getCheckQuantityMethod():
+      if range == 'min':
+        return lambda x: x < reference_quantity
+      elif range == 'nlt':
+        return lambda x: x >= reference_quantity
+      else:
+        raise ValueError("Uknown range type : %s" % (range,))
+
+    checkQuantity = getCheckQuantityMethod()
+    if from_date is None:
+      from_date = DateTime()
+    def getAugmentedInventoryKeyword(additional_kw):
+      inventory_kw = kw
+      if additional_kw:
+        inventory_kw = kw.copy()
+        inventory_kw.update(additional_kw)
+      return inventory_kw
+    inventory_method = getattr(self, "get%sInventory" % simulation_period)
+    initial_inventory = inventory_method(at_date=from_date,
+                         **getAugmentedInventoryKeyword(initial_inventory_kw))
+    if checkQuantity(initial_inventory):
+      result = from_date
+    else:
+      inventory_list_method = getattr(self,
+        "get%sInventoryList" % simulation_period)
+      inventory_list = inventory_list_method(src__=src__, from_date=from_date,
+          sort_on = (('date', 'ascending'),), group_by_movement=1,
+          **getAugmentedInventoryKeyword(inventory_list_kw))
+      if src__ :
+        return inventory_list
+      total_inventory = initial_inventory
+      for inventory in inventory_list:
+        if inventory['inventory'] is not None:
+          total_inventory += inventory['inventory']
+          if checkQuantity(total_inventory):
+            result = inventory['date']
+            break
+    return result
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getNextNegativeInventoryDate')
+  def getNextNegativeInventoryDate(self, **kw):
+    """
+    Deficient Inventory with a reference_quantity of 0, so when the
+    stock will be negative
+    """
+    return self.getNextAlertInventoryDate(reference_quantity=0, **kw)
+
+  #######################################################
+  # Traceability management
+  security.declareProtected(Permissions.AccessContentsInformation, 'getTrackingList')
+  def getTrackingList(self, src__=0,
+      strict_simulation_state=1, history=0, **kw) :
+    """
+    Returns a list of items in the form
+
+      uid (of item)
+      date
+      node_uid
+      section_uid
+      resource_uid
+      variation_text
+      delivery_uid
+
+    If at_date is provided, returns the a list which answers
+    to the question "where are those items at this date" or
+    "which are those items which are there a this date"
+
+    If at_date is not provided, returns a history of positions
+    which answers the question "where have those items been
+    between this time and this time". This will be handled by
+    something like getTrackingHistoryList
+
+    This method is only suitable for singleton items (an item which can
+    only be at a single place at a given time). Such items include
+    containers, serial numbers (ex. for engine), rolls with subrolls,
+
+    This method is not suitable for batches (ex. a coloring batch).
+    For such items, standard getInventoryList method is appropriate
+
+    Parameters are the same as for getInventory.
+
+    Default sort orders is based on dates, reverse.
+
+
+    from_date (>=) -
+
+    to_date   (<)  -
+
+    at_date   (<=) - only take rows which date is <= at_date
+
+    history (boolean) - keep history variations
+
+    resource (only in generic API in simulation)
+
+    node        -  only take rows in stock table which node_uid is equivalent to node
+
+    section        -  only take rows in stock table which section_uid is equivalent to section
+
+    resource_category        -  only take rows in stock table which resource_uid is in resource_category
+
+    node_category        -  only take rows in stock table which node_uid is in section_category
+
+    section_category        -  only take rows in stock table which section_uid is in section_category
+
+    variation_text - only take rows in stock table with specified variation_text
+                     XXX this way of implementing variation selection is far from perfect
+
+    variation_category - variation or list of possible variations
+                         Deprecated, use variation_text
+
+    simulation_state - only take rows with specified simulation_state
+
+    selection_domain, selection_report - see ListBox
+
+    **kw  - if we want extended selection with more keywords (but bad performance)
+            check what we can do with buildSQLQuery
+
+    Extra parameters for getTrackingList :
+
+    item
+
+    input - if set, answers to the question "which are those items which have been
+            delivered for the first time after from_date". Cannot be used with output
+
+    output - if set, answers to the question "which are those items which have been
+             delivered for the last time before at_date or to_date". Cannot be used with input
+
+    """
+    next_item_simulation_state = kw.pop('next_item_simulation_state', None)
+    new_kw = self._generateSQLKeywordDict(table='item',strict_simulation_state=strict_simulation_state,**kw)
+    for key in ('at_date', 'to_date'):
+      value = kw.get(key, None)
+      if value is not None:
+        if isinstance(value, DateTime):
+          value = value.toZone('UTC').ISO()
+        value = '%s' % (value, )
+      # Do not remove dates in new_kw, they are required in
+      # order to do a "select item left join item on date"
+      new_kw[key] = value
+
+    # Extra parameters for the SQL Method
+    new_kw['join_on_item'] = not history and (new_kw.get('at_date') or \
+                             new_kw.get('to_date') or \
+                             new_kw.get('input') or \
+                             new_kw.get('output'))
+    new_kw['date_condition_in_join'] = not (new_kw.get('input') or new_kw.get('output'))
+
+    # Pass simulation state to request
+    if next_item_simulation_state:
+      new_kw['simulation_state_list'] = next_item_simulation_state
+    elif kw.has_key('item.simulation_state'):
+      new_kw['simulation_state_list'] = kw['item.simulation_state']
+    else:
+      new_kw['simulation_state_list'] =  None
+
+    return self.Resource_zGetTrackingList(src__=src__,
+                                          **new_kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getCurrentTrackingList')
+  def getCurrentTrackingList(self, **kw):
+    """
+    Returns list of current inventory grouped by section or site
+    """
+    portal = self.getPortalObject()
+    kw['item.simulation_state'] = portal\
+      .getPortalCurrentInventoryStateList()
+    kw['next_item_simulation_state'] = portal\
+      .getPortalCurrentInventoryStateList() + portal\
+      .getPortalTransitInventoryStateList()
+    return self.getTrackingList(**kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getCurrentTrackingHistoryList')
+  def getCurrentTrackingHistoryList(self, **kw):
+    """
+    Returns list of current inventory grouped by section or site
+    """
+    kw['item.simulation_state'] = self.getPortalObject()\
+      .getPortalCurrentInventoryStateList()
+    return self.getTrackingHistoryList(**kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getTrackingHistoryList')
+  def getTrackingHistoryList(self, **kw):
+    """
+    Returns history list of inventory grouped by section or site
+    """
+    kw['history'] = 1
+    return self.getTrackingList(**kw)
+
+  security.declareProtected(Permissions.AccessContentsInformation, 'getFutureTrackingList')
+  def getFutureTrackingList(self, **kw):
+    """
+    Returns list of future inventory grouped by section or site
+    """
+    portal = self.getPortalObject()
+    kw['item.simulation_state'] = portal.getPortalFutureInventoryStateList() + \
+                                  portal.getPortalTransitInventoryStateList() + \
+                                  portal.getPortalReservedInventoryStateList() + \
+                                  portal.getPortalCurrentInventoryStateList()
+    return self.getTrackingList(**kw)
+
+  #######################################################
+  # Capacity Management
+  security.declareProtected( Permissions.ModifyPortalContent, 'updateCapacity' )
+  def updateCapacity(self, node):
+    capacity_item_list = []
+    for o in node.contentValues():
+      if o.isCapacity():
+        # Do whatever is needed
+        capacity_item_list += o.asCapacityItemList()
+    # Do whatever with capacity_item_list
+    # and store the resulting new capacity in node
+    node._capacity_item_list = capacity_item_list
+
+  security.declareProtected( Permissions.ModifyPortalContent, 'isAmountListInsideCapacity' )
+  def isAmountListInsideCapacity(self, node, amount_list,
+       resource_aggregation_base_category=None, resource_aggregation_depth=None):
+    """
+      Purpose: decide if a list of amounts is consistent with the capacity of a node
+
+      If any resource in amount_list is missing in the capacity of the node, resource
+      aggregation is performed, based on resource_aggregation_base_category. If the
+      base category is not specified, it is an error (should guess instead?). The resource
+      aggregation is done at the level of resource_aggregation_depth in the tree
+      of categories. If resource_aggregation_depth is not specified, it's an error.
+
+      Assumptions: amount_list is an association list, like ((R1 V1) (R2 V2)).
+                   node has an attribute '_capacity_item_list' which is a list of association lists.
+                   resource_aggregation_base_category is a Base Category object or a list of Base
+                   Category objects or None.
+                   resource_aggregation_depth is a strictly positive integer or None.
+    """
+    # Make a copy of the attribute _capacity_item_list, because it may be necessary
+    # to modify it for resource aggregation.
+    capacity_item_list = node._capacity_item_list[:]
+
+    # Make a mapping between resources and its indices.
+    resource_map = {}
+    index = 0
+    for alist in capacity_item_list:
+      for pair in alist:
+        resource = pair[0]
+#          LOG('isAmountListInsideCapacity', 0,
+#              "resource is %s" % repr(resource))
+        if resource not in resource_map:
+          resource_map[resource] = index
+          index += 1
+
+    # Build a point from the amount list.
+    point = zeros(index, 'd') # Fill up zeros for safety.
+    mask_map = {}     # This is used to skip items in amount_list.
+    for amount in amount_list:
+      if amount[0] in mask_map:
+        continue
+      # This will fail, if amount_list has any different resource from the capacity.
+      # If it has any different point, then we should ......
+      #
+      # There would be two possible different solutions:
+      # 1) If a missing resource is a meta-resource of resources supported by the capacity,
+      #    it is possible to add the resource into the capacity by aggregation.
+      # 2) If a missing resource has a meta-resource as a parent and the capacity supports
+      #    the meta-resource directly or indirectly (`indirectly' means `by aggregation'),
+      #    it is possible to convert the missing resource into the meta-resource.
+      #
+      # However, another way has been implemented here. This does the following, if the resource
+      # is not present in the capacity:
+      # 1) If the value is zero, just ignore the resource, because zero is always acceptable.
+      # 2) Attempt to aggregate resources both of the capacity and of the amount list. This aggregation
+      #    is performed at the depth of 'resource_aggregation_depth' under the base category
+      #    'resource_aggregation_base_category'.
+      #
+      resource = amount[0]
+      if resource in resource_map:
+        point[resource_map[amount[0]]] = amount[1]
+      else:
+        if amount[1] == 0:
+          # If the value is zero, no need to consider.
+          pass
+        elif resource_aggregation_base_category is None or resource_aggregation_depth is None:
+          # XXX use an appropriate error class
+          # XXX should guess a base category instead of emitting an exception
+          raise RuntimeError, "The resource '%s' is not found in the capacity, and the argument 'resource_aggregation_base_category' or the argument 'resource_aggregation_depth' is not specified" % resource
+        else:
+          # It is necessary to aggregate resources, to guess the capacity of this resource.
+
+          def getAggregationResourceUrl(url, depth):
+            # Return a partial url of the argument 'url'.
+            # If 'url' is '/foo/bar/baz' and 'depth' is 2, return '/foo/bar'.
+            pos = 0
+            for _ in range(resource_aggregation_depth):
+              pos = url.find('/', pos+1)
+              if pos < 0:
+                break
+            if pos < 0:
+              return None
+            pos = url.find('/', pos+1)
+            if pos < 0:
+              pos = len(url)
+            return url[:pos]
+
+          def getAggregatedResourceList(aggregation_url, category, resource_list):
+            # Return a list of resources which should be aggregated. 'aggregation_url' is used
+            # for a top url of those resources. 'category' is a base category for the aggregation.
+            aggregated_resource_list = []
+            for resource in resource_list:
+              for url in resource.getCategoryMembershipList(category, base=1):
+                if url.startswith(aggregation_url):
+                  aggregated_resource_list.append(resource)
+            return aggregated_resource_list
+
+          def getAggregatedItemList(item_list, resource_list, aggregation_resource):
+            # Return a list of association lists, which is a result of an aggregation.
+            # 'resource_list' is a list of resources which should be aggregated.
+            # 'aggregation_resource' is a category object which is a new resource created by
+            # this aggregation.
+            # 'item_list' is a list of association lists.
+            new_item_list = []
+            for alist in item_list:
+              new_val = 0
+              new_alist = []
+              # If a resource is not a aggregated, then add it to the new alist as it is.
+              # Otherwise, aggregate it to a single value.
+              for pair in alist:
+                if pair[0] in resource_list:
+                  new_val += pair[1]
+                else:
+                  new_alist.append(pair)
+              # If it is zero, ignore this alist, as it is nonsense.
+              if new_val != 0:
+                new_alist.append([aggregation_resource, new_val])
+                new_item_list.append(new_alist)
+            return new_item_list
+
+          # Convert this to a string if necessary, for convenience.
+          if not isinstance(resource_aggregation_base_category, (tuple, list)):
+            resource_aggregation_base_category = (resource_aggregation_base_category,)
+
+          done = 0
+#            LOG('isAmountListInsideCapacity', 0,
+#                "resource_aggregation_base_category is %s" % repr(resource_aggregation_base_category))
+          for category in resource_aggregation_base_category:
+            for resource_url in resource.getCategoryMembershipList(category, base=1):
+              aggregation_url = getAggregationResourceUrl(resource_url,
+                                                          resource_aggregation_depth)
+              if aggregation_url is None:
+                continue
+              aggregated_resource_list = getAggregatedResourceList (aggregation_url,
+                                                                    category,
+                                                                    resource_map.keys())
+              # If any, do the aggregation.
+              if len(aggregated_resource_list) > 0:
+                aggregation_resource = self.portal_categories.resolveCategory(aggregation_url)
+                # Add the resource to the mapping.
+ #                 LOG('aggregation_resource', 0, str(aggregation_resource))
+                resource_map[aggregation_resource] = index
+                index += 1
+                # Add the resource to the point.
+                point = resize(point, (index,))
+                val = 0
+                for aggregated_amount in amount_list:
+                  for url in aggregated_amount[0].getCategoryMembershipList(category, base=1):
+                    if url.startswith(aggregation_url):
+                      val += aggregated_amount[1]
+                      mask_map[aggregated_amount[0]] = None
+                      break
+                point[index-1] = val
+                # Add capacity definitions of the resource into the capacity.
+                capacity_item_list += getAggregatedItemList(capacity_item_list,
+                                                            aggregated_resource_list,
+                                                            aggregation_resource)
+                done = 1
+                break
+            if done:
+              break
+          if not done:
+            raise RuntimeError, "Aggregation failed"
+
+    # Build a matrix from the capacity item list.
+#      LOG('resource_map', 0, str(resource_map))
+    matrix = zeros((len(capacity_item_list)+1, index), 'd')
+    for index in range(len(capacity_item_list)):
+      for pair in capacity_item_list[index]:
+        matrix[index,resource_map[pair[0]]] = pair[1]
+
+#      LOG('isAmountListInsideCapacity', 0,
+#          "matrix = %s, point = %s, capacity_item_list = %s" % (str(matrix), str(point), str(capacity_item_list)))
+    return solve(matrix, point)
+
+
+  # Asset Price Calculation
+  def updateAssetPrice(self, resource, variation_text, section_category, node_category,
+                       strict_membership=0, simulation_state=None):
+    if simulation_state is None:
+      simulation_state = self.getPortalCurrentInventoryStateList()
+    # Initialize price
+    current_asset_price = 0.0 # Missing: initial inventory price !!!
+    current_inventory = 0.0
+    # Parse each movement
+    brain_list = self.Resource_zGetMovementHistoryList(resource=[resource],
+                           variation_text=variation_text,
+                           section_category=section_category,
+                           node_category=node_category,
+                           strict_membership=strict_membership,
+                           simulation_state=simulation_state) # strict_membership not taken into account
+                           # We select movements related to certain nodes (ex. Stock) and sections (ex.Coramy Group)
+    result = []
+    for b in brain_list:
+      m = b.getObject()
+      if m is not None:
+        previous_inventory = current_inventory
+        inventory_quantity = b.quantity # We should use the aggregated quantity provided by Resource_zGetMovementHistoryList
+        quantity = m.getQuantity() # The movement quantity is important to determine the meaning of source and destination
+        # Maybe we should take care of target qty in delired deliveries
+        if quantity is None:
+          quantity = 0.0
+        if m.getSourceValue() is None:
+          # This is a production movement or an inventory movement
+          # Use Industrial Price
+          current_inventory += inventory_quantity # Update inventory
+          if m.getPortalType() in ('Inventory Line', 'Inventory Cell'): # XX should be replaced by isInventory ???
+            asset_price = m.getPrice()
+            if asset_price in (0.0, None):
+              asset_price = current_asset_price # Use current price if no price defined
+          else: # this is a production
+            asset_price = m.getIndustrialPrice()
+            if asset_price is None: asset_price = current_asset_price  # Use current price if no price defined
+          result.append((m.getRelativeUrl(), m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                        m.getQuantity(), 'Production or Inventory', 'Price: %s' % asset_price
+                      ))
+        elif m.getDestinationValue() is None:
+          # This is a consumption movement or an inventory movement
+          current_inventory += inventory_quantity # Update inventory
+          asset_price = current_asset_price
+          result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                        m.getQuantity(), 'Consumption or Inventory', 'Price: %s' % asset_price
+                      ))
+        elif m.getSourceValue().isAcquiredMemberOf(node_category) and m.getDestinationValue().isAcquiredMemberOf(node_category):
+          # This is an internal movement
+          current_inventory += inventory_quantity # Update inventory
+          asset_price = current_asset_price
+          result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                        m.getQuantity(), 'Internal', 'Price: %s' % asset_price
+                      ))
+        elif m.getSourceValue().isAcquiredMemberOf(node_category) and quantity < 0:
+          # This is a physically inbound movement - try to use commercial price
+          if m.getSourceSectionValue() is None:
+            # No meaning
+            current_inventory += inventory_quantity # Update inventory
+            asset_price = current_asset_price
+            result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                          m.getQuantity(), 'Error', 'Price: %s' % asset_price
+                        ))
+          elif m.getDestinationSectionValue() is None:
+            # No meaning
+            current_inventory += inventory_quantity # Update inventory
+            asset_price = current_asset_price
+            result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                          m.getQuantity(), 'Error', 'Price: %s' % asset_price
+                        ))
+          elif m.getDestinationSectionValue().isAcquiredMemberOf(section_category):
+            current_inventory += inventory_quantity # Update inventory
+            if m.getDestinationValue().isAcquiredMemberOf('site/Piquage'):
+              # Production
+              asset_price = m.getIndustrialPrice()
+              if asset_price is None: asset_price = current_asset_price  # Use current price if no price defined
+              result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                            m.getQuantity(), 'Production', 'Price: %s' % asset_price
+                          ))
+            else:
+              # Inbound from same section
+              asset_price = current_asset_price
+              result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                            m.getQuantity(), 'Inbound same section', 'Price: %s' % asset_price
+                          ))
+          else:
+            current_inventory += inventory_quantity # Update inventory
+            asset_price = m.getPrice()
+            result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                          m.getQuantity(), 'Inbound different section', 'Price: %s' % asset_price
+                        ))
+        elif m.getDestinationValue().isAcquiredMemberOf(node_category) and quantity > 0:
+          # This is a physically inbound movement - try to use commercial price
+          if m.getSourceSectionValue() is None:
+            # No meaning
+            current_inventory += inventory_quantity # Update inventory
+            asset_price = current_asset_price
+            result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                          m.getQuantity(), 'Error', 'Price: %s' % asset_price
+                        ))
+          elif m.getDestinationSectionValue() is None:
+            # No meaning
+            current_inventory += inventory_quantity # Update inventory
+            asset_price = current_asset_price
+            result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                          m.getQuantity(), 'Error', 'Price: %s' % asset_price
+                        ))
+          elif m.getSourceSectionValue().isAcquiredMemberOf(section_category):
+            current_inventory += inventory_quantity # Update inventory
+            if m.getSourceValue().isAcquiredMemberOf('site/Piquage'):
+              # Production
+              asset_price = m.getIndustrialPrice()
+              if asset_price is None: asset_price = current_asset_price  # Use current price if no price defined
+              result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                            m.getQuantity(), 'Production', 'Price: %s' % asset_price
+                          ))
+            else:
+              # Inbound from same section
+              asset_price = current_asset_price
+              result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                          m.getQuantity(), 'Inbound same section', 'Price: %s' % asset_price
+                        ))
+          else:
+            current_inventory += inventory_quantity # Update inventory
+            asset_price = m.getPrice()
+            result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                          m.getQuantity(), 'Inbound different section', 'Price: %s' % asset_price
+                        ))
+        else:
+          # Outbound movement
+          current_inventory += inventory_quantity # Update inventory
+          asset_price = current_asset_price
+          result.append((m.getRelativeUrl(),m.getStartDate(), m.getSource(), m.getSourceSection(), m.getDestination(), m.getDestinationSection(),
+                          m.getQuantity(), 'Outbound', 'Price: %s' % asset_price
+                        ))
+
+        # Update asset_price
+        if current_inventory > 0:
+          if inventory_quantity is not None:
+            # Update price with an average of incoming goods and current goods
+            current_asset_price = ( current_asset_price * previous_inventory + asset_price * inventory_quantity ) / float(current_inventory)
+        else:
+          # New price is the price of incoming goods - negative stock has no meaning for asset calculation
+          current_asset_price = asset_price
+
+        result.append(('###New Asset Price', current_asset_price, 'New Inventory', current_inventory))
+
+        # Update Asset Price on the right side
+        if m.getSourceSectionValue() is not None and m.getSourceSectionValue().isAcquiredMemberOf(section_category):
+          # for each movement, source section is member of one and one only accounting category
+          # therefore there is only one and one only source asset price
+          m._setSourceAssetPrice(current_asset_price)
+          #quantity = m.getInventoriatedQuantity()
+          #if quantity:
+          #  #total_asset_price = - current_asset_price * quantity
+          #  #m.Movement_zSetSourceTotalAssetPrice(uid=m.getUid(), total_asset_price = total_asset_price)
+          #  m._setSourceAssetPrice(current_asset_price)
+        if m.getDestinationSectionValue() is not None and m.getDestinationSectionValue().isMemberOf(section_category):
+          # for each movement, destination section is member of one and one only accounting category
+          # therefore there is only one and one only destination asset price
+          m._setDestinationAssetPrice(current_asset_price)
+          #quantity = m.getInventoriatedQuantity()
+          #if quantity:
+          #  total_asset_price = current_asset_price * quantity
+          #  m.Movement_zSetDestinationTotalAssetPrice(uid=m.getUid(), total_asset_price = total_asset_price)
+        # Global reindexing required afterwards in any case: so let us do it now
+        # Until we get faster methods (->reindexObject())
+        m.reindexObject()
+
+    return result
+
+  def _findBuilderForDelivery(self, delivery, movement_portal_type):
+    """
+    Find out the builder corresponding to a delivery by looking at the business process
+    """
+    builder = None
+    portal_type = delivery.getPortalType()
+    for business_link in delivery.asComposedDocument().objectValues(portal_type="Business Link"):
+      for business_link_builder in business_link.getDeliveryBuilderValueList():
+        if business_link_builder.getDeliveryPortalType() == portal_type \
+        and business_link_builder.getDeliveryLinePortalType() == movement_portal_type:
+          builder = business_link_builder
+          break
+      if builder is not None:
+        break
+    return builder
+
+  security.declareProtected( Permissions.ModifyPortalContent, 'mergeDeliveryList' )
+  def mergeDeliveryList(self, delivery_list):
+    """
+      Merge multiple deliveries into one delivery.
+      All delivery lines are merged into the first one.
+      The first one is therefore called main_delivery here.
+      The others are cancelled.
+    """
+    # Sanity checks.
+    if not(len(delivery_list) >=2):
+      raise ValueError("Please select at least 2 deliveries")
+    portal= self.getPortalObject()
+    translateString = portal.Base_translateString
+    error_list = []
+    if len(delivery_list) > 1:
+      portal_type_set = set([x.getPortalType() for x in delivery_list])
+      if len(portal_type_set) != 1:
+        error_list.append(translateString("Please select only deliveries of same type"))
+      else:
+        allowed_state_set = set(portal.getPortalReservedInventoryStateList() + \
+                               portal.getPortalFutureInventoryStateList())
+        found_state_set = set([x.getSimulationState() for x in delivery_list])
+        if found_state_set.difference(allowed_state_set):
+          error_list.append(translateString("Found delivery having unexpected status for merge"))
+        else:
+          movement_portal_type_set = set()
+          for delivery in delivery_list:
+            movement_portal_type_set.update([x.getPortalType() for x in delivery.getMovementList()])
+          if len(movement_portal_type_set) != 1:
+            error_list.append(translateString("Please Select only movement of same type"))
+          else:
+            # Allow to call a script to do custom checking conditions before merge
+            main_delivery = delivery_list[0]
+            check_merge_condition_method = main_delivery._getTypeBasedMethod("checkMergeConditionOnDeliveryList")
+            if check_merge_condition_method is not None:
+              error_list.extend(check_merge_condition_method(delivery_list=delivery_list))
+            if len(error_list) == 0:
+              # so far so good
+              # in delivery_list we have list of delivery to merge
+              simulation_movement_list = []
+              to_copy_delivery_line_list = [] # for lines not coming from upper simulation, thus
+                                              # created by hand should be manually added to main
+                                              # delivery since they are not coming from builder
+              for delivery in delivery_list:
+                line_id_to_delete_list = []
+                for movement in delivery.getMovementList():
+                  related_simulation_movement_list = movement.getDeliveryRelatedValueList()
+                  for simulation_movement in related_simulation_movement_list:
+                    # if we are on a root applied rule directly, so in the case of
+                    # a manually added line, we have to copy
+                    # the simulation movement into to main delivery
+                    if simulation_movement.getParentValue().getParentValue().getId() == "portal_simulation":
+                      # For manually added lines, make sure we have only one simulation movement
+                      assert len(related_simulation_movement_list) == 1
+                      if not(delivery is main_delivery):
+                        to_copy_delivery_line_list.append(movement)
+                    else:
+                      simulation_movement.setDeliveryValue(None)
+                      simulation_movement_list.append(simulation_movement)
+                      # Since we keep the main delivery, we remove existing lines already
+                      # coming from builder to let builder recreate them in the same time
+                      # as other ones (to possibly merge lines also)
+                      movement_id = movement.getId()
+                      if delivery is main_delivery and not(movement_id in line_id_to_delete_list):
+                        line_id_to_delete_list.append(movement.getId())
+                if line_id_to_delete_list:
+                  delivery.manage_delObjects(ids=line_id_to_delete_list)
+              # It is required to expand again simulation movement, because
+              # we unlinked them from delivery, so it is possible that some
+              # properties will change on simulation movement (mostly categories).
+              # By expanding again, we will avoid having many deliveries instead
+              # of one when doing "merge"
+              for simulation_movement in simulation_movement_list:
+                simulation_movement.expand(expand_policy='immediate')
+
+              # activate builder
+              movement_portal_type, = movement_portal_type_set
+              merged_builder = self._findBuilderForDelivery(main_delivery, movement_portal_type)
+              if merged_builder is None:
+                error_list.append(translateString("Unable to find builder"))
+              else:
+                merged_builder.build(movement_relative_url_list=[q.getRelativeUrl() for q in \
+                                     simulation_movement_list], merge_delivery=True,
+                                     delivery_relative_url_list=[main_delivery.getRelativeUrl()])
+              # Finally, copy all lines that were created manually on all deliveries except
+              # the main one
+              @UnrestrictedMethod
+              def setMainDeliveryModifiable(delivery):
+                # set causality state in such way we can modify delivery
+                delivery.diverge()
+              setMainDeliveryModifiable(main_delivery)
+              delivery_type_list = portal.getPortalDeliveryTypeList()
+              for delivery_line in to_copy_delivery_line_list:
+                delivery = delivery_line.getParentValue()
+                if not(delivery.getPortalType() in delivery_type_list):
+                  raise NotImplementedError("Merge of deliveries doe not yet handle case of cells")
+                copy_data = delivery.manage_copyObjects(ids=[delivery_line.getId()])
+                main_delivery.manage_pasteObjects(copy_data)
+              main_delivery.updateCausalityState()
+
+              # Finally do cleanup
+              for delivery in delivery_list[1:]:
+                # cancel, delete - to disallow any user related operations on those deliveries
+                after_merge_method = delivery._getTypeBasedMethod('cleanDeliveryAfterMerge')
+                if after_merge_method is not None:
+                  after_merge_method()
+    else:
+      error_list.append(translateString("Please select at least two deliveries"))
+    return error_list
+
+  #######################################################
+  # Sequence
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getSequence')
+  def getSequence(self, **kw):
+    """
+    getSequence is take the same parameters as Sequence constructor,
+    and return a Sequence.
+    """
+    return Sequence(**kw)
+
+  #######################################################
+  # Time Management
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getAvailableTime')
+  def getAvailableTime(self, from_date=None, to_date=None,
+                       portal_type=(), node=(),
+                       resource=(), src__=0, **kw):
+    """
+    Calculate available time for a node
+    Returns an inventory of a single or multiple resources on a single
+    node as a single float value
+
+    from_date (>=) - only take rows which mirror_date is >= from_date
+
+    to_date   (<)  - only take rows which date is < to_date
+
+    node           - only take rows in stock table which node_uid is
+                     equivalent to node
+
+    resource       - only take rows in stock table which resource_uid is
+                     equivalent to resource
+
+    portal_type    - only take rows in stock table which portal_type
+                     is in portal_type parameter
+    """
+    # XXX For now, consider that from_date and to_date are required
+    if (from_date is None) or (to_date is None):
+      raise NotImplementedError, \
+            "getAvailableTime does not managed yet None values"
+    portal = self.getPortalObject()
+    # Calculate portal_type
+    if not portal_type:
+      portal_type = portal.getPortalCalendarPeriodTypeList()
+
+    simulation_state = portal.getPortalCurrentInventoryStateList() + \
+                       portal.getPortalTransitInventoryStateList() + \
+                       portal.getPortalReservedInventoryStateList()
+
+    sql_result = portal.Node_zGetAvailableTime(
+                        from_date=from_date,
+                        to_date=to_date,
+                        portal_type=portal_type,
+                        node=node,
+                        resource=resource,
+                        simulation_state=simulation_state,
+                        src__=src__, **kw)
+    if not src__:
+      result = 0
+      if len(sql_result) == 1:
+        result = sql_result[0].total_quantity
+    else:
+      result = sql_result
+    return result
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getAvailableTimeSequence')
+  def getAvailableTimeSequence(self, from_date, to_date,
+                               portal_type=(), node=(),
+                               resource=(),
+                               src__=0,
+                               **kw):
+    """
+    Calculate available time for a node in multiple period of time.
+    Each row is the available time for a specific period
+
+    node           - only take rows in stock table which node_uid is
+                     equivalent to node
+
+    portal_type    - only take rows in stock table which portal_type
+                     is in portal_type parameter
+
+    resource       - only take rows in stock table which resource_uid is
+                     equivalent to resource
+
+    from_date (>=) - return period which start >= from_date
+
+    to_date   (<)  - return period which start < to_date
+
+    second, minute,
+    hour, day,
+    month, year   - duration of each time period (cumulative)
+    """
+    portal = self.getPortalObject()
+    # Calculate portal_type
+    if not portal_type:
+      portal_type = portal.getPortalCalendarPeriodTypeList()
+
+    sequence = Sequence(from_date, to_date, **kw)
+    for sequence_item in sequence:
+      setattr(sequence_item, 'total_quantity',
+              self.getAvailableTime(
+                        from_date=sequence_item.from_date,
+                        to_date=sequence_item.to_date,
+                        portal_type=portal_type,
+                        node=node,
+                        resource=resource,
+                        src__=src__))
+    return sequence
+
+  security.declareProtected(Permissions.AccessContentsInformation,
+                            'getAvailableTimeMovementList')
+  def getAvailableTimeMovementList(self, from_date, to_date,
+                               **kw):
+    """
+    Calculate available time movement list by taking into account
+    both available time and not available time.
+
+    Necessary parameter is at least node.
+
+    Parameters supported by getMovementHistoryList are supported here.
+
+    from_date (>=) - return period which start >= from_date
+
+    to_date   (<)  - return period which start < to_date
+    """
+    portal = self.getPortalObject()
+    if kw.get("simulation_state", None) is None:
+      kw["simulation_state"] = portal.getPortalCurrentInventoryStateList() + \
+                       portal.getPortalTransitInventoryStateList() + \
+                       portal.getPortalReservedInventoryStateList()
+    movement_list = self.getMovementHistoryList(from_date=from_date,
+                               to_date=to_date, group_by_movement=1,
+                               group_by_date=1, **kw)
+    # do import on top, but better to avoid breaking instances with older softwares
+    from interval import IntervalSet, Interval
+    # we look at all movements, and we build a set of intervals for available
+    # time, another for not available time, and we do substraction of both sets
+    assignment_interval_set = IntervalSet()
+    leave_interval_set = IntervalSet()
+    result_list = []
+
+    def getOrderedMovementDates(movement):
+      date_list = [movement.date, movement.mirror_date]
+      date_list.sort()
+      return date_list
+
+    movement_availability_dict = {} # to later map availability intervals with their movements
+    for movement in movement_list:
+      start_date, stop_date = getOrderedMovementDates(movement)
+      current_interval = Interval(start_date, stop_date)
+      # case of available time
+      if movement.total_quantity > 0:
+        assignment_interval_set.add(current_interval)
+        movement_availability_dict[current_interval] = movement
+      # case of not available time
+      else:
+        leave_interval_set.add(current_interval)
+    i = 0
+    # Parse all calculated availability_interval to find matching movements to
+    # be returned in the result. IntervalSet are already ordered
+    for availability_interval in (assignment_interval_set - leave_interval_set):
+      while True:
+        assignment_interval = assignment_interval_set[i]
+        if availability_interval in assignment_interval:
+          result_list.append(movement_availability_dict[assignment_interval].asContext(
+            start_date=availability_interval.lower_bound,
+            stop_date=availability_interval.upper_bound))
+          break
+        else:
+          i += 1
+    return result_list
+
+  def _checkExpandAll(self, activate_kw=None):
+    """Check all simulation trees using AppliedRule._checkExpand
+    """
+    if activate_kw is None:
+      activate_kw = {}
+    portal = self.getPortalObject()
+    active_process = portal.portal_activities.newActiveProcess().getPath()
+    kw = dict(priority=3, tag='checkExpand')
+    kw.update(group_method_cost=1, max_retry=0,
+              active_process=active_process, **activate_kw)
+    self._recurseCallMethod('_checkExpand', min_depth=1, max_depth=1,
+                            activate_kw=kw)
+    return active_process
+
+from Products.ERP5Type.DateUtils import addToDate
+
+class SequenceItem:
+  """
+  SequenceItem define a time period.
+  period.
+  """
+  def __init__(self, from_date, to_date):
+    self.from_date = from_date
+    self.to_date = to_date
+
+class Sequence:
+  """
+  Sequence is a iterable object, which calculate a range of time
+  period.
+  """
+  def __init__(self, from_date, to_date,
+               second=None, minute=None, hour=None,
+               day=None, month=None, year=None):
+    """
+    Calculate a list of time period.
+    Time period is a 2-tuple of 2 DateTime, which represent the from date
+    and to date of the period.
+
+    The start date of a period is calculated with the rule
+        start_date of the previous + period duration
+
+    from_date (>=) - return period which start >= from_date
+
+    to_date   (<)  - return period which start < to_date
+
+    second, minute,
+    hour, day,
+    month, year   - duration of each time period (cumulative)
+                    at least one of those parameters must be specified.
+    """
+    if not (second or minute or hour or day or month or year):
+      raise ValueError('Period duration must be specified')
+
+    self.item_list = []
+    # Calculate all time period
+    current_from_date = from_date
+    while current_from_date < to_date:
+      current_to_date = addToDate(current_from_date,
+                                  second=second,
+                                  minute=minute,
+                                  hour=hour,
+                                  day=day,
+                                  month=month,
+                                  year=year)
+      self.item_list.append(SequenceItem(current_from_date,
+                                         current_to_date))
+      current_from_date = current_to_date
+
+  def __len__(self):
+    return len(self.item_list)
+
+  def __getitem__(self, key):
+    return self.item_list[key]
+
+  def __contains__(self, value):
+    return (value in self.item_list)
+
+  def __iter__(self):
+    for x in self.item_list:
+      yield x
+
+InitializeClass(Sequence)
+allow_class(Sequence)
+InitializeClass(SequenceItem)
+allow_class(SequenceItem)
+
+InitializeClass(SimulationTool)
diff --git a/product/ERP5/bootstrap/erp5_core/ToolComponentTemplateItem/portal_components/tool.erp5.SimulationTool.xml b/product/ERP5/bootstrap/erp5_core/ToolComponentTemplateItem/portal_components/tool.erp5.SimulationTool.xml
new file mode 100644
index 0000000000000000000000000000000000000000..33a359547764fe17d63e3fee83d01279ed0a1807
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/ToolComponentTemplateItem/portal_components/tool.erp5.SimulationTool.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+<ZopeData>
+  <record id="1" aka="AAAAAAAAAAE=">
+    <pickle>
+      <global name="Tool Component" module="erp5.portal_type"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>default_reference</string> </key>
+            <value> <string>SimulationTool</string> </value>
+        </item>
+        <item>
+            <key> <string>default_source_reference</string> </key>
+            <value> <string>Products.ERP5.Tool.SimulationTool</string> </value>
+        </item>
+        <item>
+            <key> <string>description</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>id</string> </key>
+            <value> <string>tool.erp5.SimulationTool</string> </value>
+        </item>
+        <item>
+            <key> <string>portal_type</string> </key>
+            <value> <string>Tool Component</string> </value>
+        </item>
+        <item>
+            <key> <string>sid</string> </key>
+            <value>
+              <none/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_error_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>text_content_warning_message</string> </key>
+            <value>
+              <tuple/>
+            </value>
+        </item>
+        <item>
+            <key> <string>version</string> </key>
+            <value> <string>erp5</string> </value>
+        </item>
+        <item>
+            <key> <string>workflow_history</string> </key>
+            <value>
+              <persistent> <string encoding="base64">AAAAAAAAAAI=</string> </persistent>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="2" aka="AAAAAAAAAAI=">
+    <pickle>
+      <global name="PersistentMapping" module="Persistence.mapping"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>data</string> </key>
+            <value>
+              <dictionary>
+                <item>
+                    <key> <string>component_validation_workflow</string> </key>
+                    <value>
+                      <persistent> <string encoding="base64">AAAAAAAAAAM=</string> </persistent>
+                    </value>
+                </item>
+              </dictionary>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+  <record id="3" aka="AAAAAAAAAAM=">
+    <pickle>
+      <global name="WorkflowHistoryList" module="Products.ERP5Type.Workflow"/>
+    </pickle>
+    <pickle>
+      <dictionary>
+        <item>
+            <key> <string>_log</string> </key>
+            <value>
+              <list>
+                <dictionary>
+                  <item>
+                      <key> <string>action</string> </key>
+                      <value> <string>validate</string> </value>
+                  </item>
+                  <item>
+                      <key> <string>validation_state</string> </key>
+                      <value> <string>validated</string> </value>
+                  </item>
+                </dictionary>
+              </list>
+            </value>
+        </item>
+      </dictionary>
+    </pickle>
+  </record>
+</ZopeData>
diff --git a/product/ERP5/bootstrap/erp5_core/bt/template_document_id_list b/product/ERP5/bootstrap/erp5_core/bt/template_document_id_list
index 70f76cd5bf27b9a8e072ff4a2f50db9e2a8b69cf..c3e8c5ac1c4bc3b9c99f15d8e96f59e999482ea1 100644
--- a/product/ERP5/bootstrap/erp5_core/bt/template_document_id_list
+++ b/product/ERP5/bootstrap/erp5_core/bt/template_document_id_list
@@ -1,3 +1,4 @@
+document.erp5.AppliedRule
 document.erp5.Delivery
 document.erp5.DeliveryCell
 document.erp5.DeliveryLine
@@ -9,4 +10,5 @@ document.erp5.InvoiceLine
 document.erp5.Item
 document.erp5.Order
 document.erp5.PackingList
-document.erp5.ScriptConstraint
\ No newline at end of file
+document.erp5.ScriptConstraint
+document.erp5.SimulationMovement
\ No newline at end of file
diff --git a/product/ERP5/bootstrap/erp5_core/bt/template_interface_id_list b/product/ERP5/bootstrap/erp5_core/bt/template_interface_id_list
new file mode 100644
index 0000000000000000000000000000000000000000..ee918611f0170678de6b885cf3d489edc75051f4
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/bt/template_interface_id_list
@@ -0,0 +1,7 @@
+interface.erp5.IDivergenceController
+interface.erp5.IExpandable
+interface.erp5.IMovementCollection
+interface.erp5.IMovementCollectionDiff
+interface.erp5.IMovementCollectionUpdater
+interface.erp5.IRule
+interface.erp5.ISimulationMovement
\ No newline at end of file
diff --git a/product/ERP5/bootstrap/erp5_core/bt/template_mixin_id_list b/product/ERP5/bootstrap/erp5_core/bt/template_mixin_id_list
index 3f7d83cdf8dd05615e0cf6be49892a93240bebd0..d8a18da1dbff5e6b75403600b55019d1476c10fb 100644
--- a/product/ERP5/bootstrap/erp5_core/bt/template_mixin_id_list
+++ b/product/ERP5/bootstrap/erp5_core/bt/template_mixin_id_list
@@ -1 +1,4 @@
+mixin.erp5.ExplainableMixin
+mixin.erp5.MovementCollectionUpdaterMixin
+mixin.erp5.RuleMixin
 mixin.erp5.SimulableMixin
\ No newline at end of file
diff --git a/product/ERP5/bootstrap/erp5_core/bt/template_module_component_id_list b/product/ERP5/bootstrap/erp5_core/bt/template_module_component_id_list
new file mode 100644
index 0000000000000000000000000000000000000000..179c1047c6843f93d0ea5f283083130478b3851a
--- /dev/null
+++ b/product/ERP5/bootstrap/erp5_core/bt/template_module_component_id_list
@@ -0,0 +1,3 @@
+module.erp5.ExpandPolicy
+module.erp5.MovementCollectionDiff
+module.erp5.MovementGroup
\ No newline at end of file
diff --git a/product/ERP5/bootstrap/erp5_core/bt/template_tool_component_id_list b/product/ERP5/bootstrap/erp5_core/bt/template_tool_component_id_list
index d8e627b7b183ac8bbd5233a81279fbb7acd23a6c..9816bb52f551741fc3a68482861f4268139be08e 100644
--- a/product/ERP5/bootstrap/erp5_core/bt/template_tool_component_id_list
+++ b/product/ERP5/bootstrap/erp5_core/bt/template_tool_component_id_list
@@ -1,2 +1,3 @@
 tool.erp5.CallableTool
-tool.erp5.DiffTool
\ No newline at end of file
+tool.erp5.DiffTool
+tool.erp5.SimulationTool
\ No newline at end of file
diff --git a/product/ERP5/dtml/explainSimulationTool.dtml b/product/ERP5/dtml/explainSimulationTool.dtml
deleted file mode 100644
index 35b6e7108faacf6ffa4b86db3f82ff6e7808c69e..0000000000000000000000000000000000000000
--- a/product/ERP5/dtml/explainSimulationTool.dtml
+++ /dev/null
@@ -1,4 +0,0 @@
-<dtml-var manage_page_header>
-<dtml-var manage_tabs>
-
-<dtml-var manage_page_footer>
diff --git a/product/ERP5/tests/testInventoryAPI.py b/product/ERP5/tests/testInventoryAPI.py
index 91230a771150273f463e8aa1d41897df7f74f9b1..aab2a61d6b04a7851bc10545fdb03aca08a1a99e 100644
--- a/product/ERP5/tests/testInventoryAPI.py
+++ b/product/ERP5/tests/testInventoryAPI.py
@@ -43,8 +43,6 @@ from MySQLdb import ProgrammingError
 
 from Products.ERP5Type.tests.ERP5TypeTestCase import ERP5TypeTestCase
 from Products.ERP5Type.tests.utils import reindex
-from Products.ERP5.Tool.SimulationTool import MYSQL_MIN_DATETIME_RESOLUTION
-
 
 class InventoryAPITestCase(ERP5TypeTestCase):
   """Base class for Inventory API Tests {{{
@@ -2913,6 +2911,7 @@ class TestInventoryCacheTable(InventoryAPITestCase):
     min_lag = cache_lag / 2
     self.NOW = now = DateTime(DateTime().strftime("%Y-%m-%d %H:%M:%S UTC"))
     self.CACHE_DATE = cache_date = now - min_lag
+    from erp5.component.tool.SimulationTool import MYSQL_MIN_DATETIME_RESOLUTION
     self.LAST_CACHED_MOVEMENT_DATE = last_cached_movement_date = \
       cache_date - MYSQL_MIN_DATETIME_RESOLUTION
     # First movement, won't be into cache
diff --git a/product/ERP5Type/interfaces/divergence_message.py b/product/ERP5Type/interfaces/divergence_message.py
index ffb8ccaf616710353e762e2987ebebdbfadbed04..8b4550aef500ef4fd340a3129ac238c7607426bc 100644
--- a/product/ERP5Type/interfaces/divergence_message.py
+++ b/product/ERP5Type/interfaces/divergence_message.py
@@ -27,7 +27,7 @@
 #
 ##############################################################################
 """
-Products.ERP5Type.interfaces.divergence_message
+erp5.component.interface.IDivergenceMessage
 """
 from zope.interface import Interface